Fix preferences dialog.
[dhcpcd-ui] / src / libdhcpcd / dhcpcd.c
1 /*
2  * libdhcpcd
3  * Copyright 2009-2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 // For strverscmp(3)
28 #define _GNU_SOURCE
29
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/un.h>
33
34 #include <arpa/inet.h>
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <libintl.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45
46 #define IN_LIBDHCPCD
47
48 #include "config.h"
49 #include "dhcpcd.h"
50
51 #ifdef HAS_GETTEXT
52 #include <libintl.h>
53 #define _ gettext
54 #else
55 #define _(a) (a)
56 #endif
57
58 #ifndef SUN_LEN
59 #define SUN_LEN(su) \
60         (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
61 #endif
62
63 static const char * const dhcpcd_types[] =
64     { "link", "ipv4", "ra", "dhcp6", NULL };
65
66 static ssize_t
67 dhcpcd_command_fd(DHCPCD_CONNECTION *con,
68     int fd, const char *cmd, char **buffer)
69 {
70         size_t len;
71         ssize_t bytes;
72         char buf[1024], *p;
73         char *nbuf;
74
75         /* Each command is \n terminated.
76          * Each argument is NULL seperated.
77          * We may need to send a space one day, so the API
78          * in this function may need to be improved */
79         len = strlen(cmd) + 1;
80         if (con->terminate_commands)
81                 len++;
82         if (len > sizeof(buf)) {
83                 errno = ENOBUFS;
84                 return -1;
85         }
86         strlcpy(buf, cmd, sizeof(buf));
87         p = buf;
88         while ((p = strchr(p, ' ')) != NULL)
89                 *p++ = '\0';
90         if (con->terminate_commands) {
91                 buf[len - 2] = '\n';
92                 buf[len - 1] = '\0';
93         } else
94                 buf[len - 1] = '\0';
95         if (write(fd, buf, len) == -1)
96                 return -1;
97         if (buffer == NULL)
98                 return 0;
99
100         bytes = read(fd, buf, sizeof(size_t));
101         if (bytes == 0 || bytes == -1)
102                 return bytes;
103         memcpy(&len, buf, sizeof(size_t));
104         nbuf = realloc(*buffer, len + 1);
105         if (nbuf == NULL)
106                 return -1;
107         *buffer = nbuf;
108         bytes = read(fd, *buffer, len);
109         if (bytes != -1 && (size_t)bytes < len)
110                 *buffer[bytes] = '\0';
111         return bytes;
112 }
113
114 ssize_t
115 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
116 {
117
118         return dhcpcd_command_fd(con, con->command_fd, cmd, buffer);
119 }
120
121 bool
122 dhcpcd_realloc(DHCPCD_CONNECTION *con, size_t len)
123 {
124
125         if (con->buflen < len) {
126                 char *nbuf;
127
128                 nbuf = realloc(con->buf, len);
129                 if (nbuf == NULL)
130                         return false;
131                 con->buf = nbuf;
132                 con->buflen = len;
133         }
134         return true;
135 }
136
137 ssize_t
138 dhcpcd_command_arg(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
139     char **buffer)
140 {
141         size_t cmdlen, len;
142
143         cmdlen = strlen(cmd);
144         if (arg)
145                 len = cmdlen + strlen(arg) + 2;
146         else
147                 len = cmdlen + 1;
148         if (!dhcpcd_realloc(con, len))
149                 return -1;
150         strlcpy(con->buf, cmd, con->buflen);
151         if (arg) {
152                 con->buf[cmdlen] = ' ';
153                 strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
154         }
155
156         return dhcpcd_command_fd(con, con->command_fd, con->buf, buffer);
157 }
158
159
160 static int
161 dhcpcd_connect(const char *path, int opts)
162 {
163         int fd;
164         socklen_t len;
165         struct sockaddr_un sun;
166
167         fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | opts, 0);
168         if (fd == -1)
169                 return -1;
170
171         memset(&sun, 0, sizeof(sun));
172         sun.sun_family = AF_UNIX;
173         strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
174         len = (socklen_t)SUN_LEN(&sun);
175         if (connect(fd, (struct sockaddr *)&sun, len) == 0)
176                 return fd;
177         close(fd);
178         return -1;
179 }
180
181 static const char *
182 get_value(const char *data, size_t len, const char *var)
183 {
184         const char *end, *p;
185         size_t vlen;
186
187         assert(var);
188         end = data + len;
189         vlen = strlen(var);
190         p = NULL;
191         while (data + vlen + 1 < end) {
192                 if (strncmp(data, var, vlen) == 0) {
193                         p = data + vlen;
194                         break;
195                 }
196                 data += strlen(data) + 1;
197         }
198         if (p != NULL && *p != '\0')
199                 return p;
200         return NULL;
201 }
202
203 const char *
204 dhcpcd_get_value(const DHCPCD_IF *i, const char *var)
205 {
206
207         assert(i);
208         return get_value(i->data, i->data_len, var);
209 }
210
211 const char *
212 dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
213 {
214         char pvar[128], *p;
215         size_t plen, l;
216
217         p = pvar;
218         plen = sizeof(pvar);
219         l = strlcpy(p, prefix, plen);
220         if (l >= sizeof(pvar)) {
221                 errno = ENOBUFS;
222                 return NULL;
223         }
224         p += l;
225         plen -= l;
226         if (strlcpy(p, var, plen) >= plen) {
227                 errno = ENOBUFS;
228                 return NULL;
229         }
230         return dhcpcd_get_value(i, pvar);
231 }
232
233 static bool
234 strtobool(const char *var)
235 {
236
237         if (var == NULL)
238                 return false;
239
240          return (*var == '0' || *var == '\0' ||
241             strcmp(var, "false") == 0 ||
242             strcmp(var, "no") == 0) ? false : true;
243 }
244
245 static const char *
246 get_status(DHCPCD_CONNECTION *con)
247 {
248         DHCPCD_IF *i;
249         const char *status;
250
251         assert(con);
252         if (con->command_fd == -1)
253                 return "down";
254
255         if (con->listen_fd == -1)
256                 return "opened";
257
258         if (con->interfaces == NULL)
259                 return "initialised";
260
261         status = "disconnected";
262         for (i = con->interfaces; i; i = i->next) {
263                 if (i->up) {
264                         if (strcmp(i->type, "link")) {
265                                 status = "connected";
266                                 break;
267                         } else
268                                 status = "connecting";
269                 }
270         }
271         return status;
272 }
273
274 static void
275 update_status(DHCPCD_CONNECTION *con, const char *nstatus)
276 {
277
278         assert(con);
279         if (nstatus == NULL)
280                 nstatus = get_status(con);
281         if (con->status == NULL || strcmp(nstatus, con->status)) {
282                 con->status = nstatus;
283                 if (con->status_cb)
284                         con->status_cb(con, con->status, con->status_context);
285         }
286 }
287
288 DHCPCD_IF *
289 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
290 {
291
292         assert(con);
293         return con->interfaces;
294 }
295
296 DHCPCD_IF *
297 dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
298 {
299         DHCPCD_IF *i;
300
301         assert(con);
302         for (i = con->interfaces; i; i = i->next)
303                 if (strcmp(i->ifname, ifname) == 0 &&
304                     strcmp(i->type, type) == 0)
305                         return i;
306         return NULL;
307 }
308
309 static DHCPCD_IF *
310 dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
311 {
312         const char *ifname, *ifclass, *reason, *type, *order, *flags;
313         char *orderdup, *o, *p;
314         DHCPCD_IF *e, *i, *l, *n, *nl;
315         int ti;
316         bool addedi;
317
318         ifname = get_value(data, len, "interface=");
319         if (ifname == NULL || *ifname == '\0') {
320                 errno = ESRCH;
321                 return NULL;
322         }
323         reason = get_value(data, len, "reason=");
324         if (reason == NULL || *reason == '\0') {
325                 errno = ESRCH;
326                 return NULL;
327         }
328         ifclass = get_value(data, len, "ifclass=");
329         /* Skip pseudo interfaces */
330         if (ifclass && *ifclass != '\0') {
331                 errno = ENOTSUP;
332                 return NULL;
333         }
334         if (strcmp(reason, "RECONFIGURE") == 0) {
335                 errno = ENOTSUP;
336                 return NULL;
337         }
338         order = get_value(data, len, "interface_order=");
339         if (order == NULL || *order == '\0') {
340                 errno = ESRCH;
341                 return NULL;
342         }
343
344         if (strcmp(reason, "PREINIT") == 0 ||
345             strcmp(reason, "UNKNOWN") == 0 ||
346             strcmp(reason, "CARRIER") == 0 ||
347             strcmp(reason, "NOCARRIER") == 0 ||
348             strcmp(reason, "DEPARTED") == 0 ||
349             strcmp(reason, "STOPPED") == 0)
350                 type = "link";
351         else if (strcmp(reason, "ROUTERADVERT") == 0)
352                 type = "ra";
353         else if (reason[strlen(reason) - 1] == '6')
354                 type = "dhcp6";
355         else
356                 type = "ipv4";
357
358         i = NULL;
359        /* Remove all instances on carrier drop */
360         if (strcmp(reason, "NOCARRIER") == 0 ||
361             strcmp(reason, "DEPARTED") == 0 ||
362             strcmp(reason, "STOPPED") == 0)
363         {
364                 l = NULL;
365                 for (e = con->interfaces; e; e = n) {
366                         n = e->next;
367                         if (strcmp(e->ifname, ifname) == 0) {
368                                 if (strcmp(e->type, type) == 0)
369                                         l = i = e;
370                                 else {
371                                         if (l)
372                                                 l->next = e->next;
373                                         else
374                                                 con->interfaces = e->next;
375                                         free(e);
376                                 }
377                         } else
378                                 l = e;
379                 }
380         } else if (strcmp(type, "link")) {
381                 /* If link is down, ignore it */
382                 e = dhcpcd_get_if(con, ifname, "link");
383                 if (e && !e->up)
384                         return NULL;
385         }
386
387         orderdup = strdup(order);
388         if (orderdup == NULL)
389                 return NULL;
390
391         /* Find our pointer */
392         if (i == NULL) {
393                 l = NULL;
394                 for (e = con->interfaces; e; e = e->next) {
395                         if (strcmp(e->ifname, ifname) == 0 &&
396                             strcmp(e->type, type) == 0)
397                         {
398                                 i = e;
399                                 break;
400                         }
401                         l = e;
402                 }
403         }
404         if (i == NULL) {
405                 i = malloc(sizeof(*i));
406                 if (i == NULL) {
407                         free(orderdup);
408                         return NULL;
409                 }
410                 if (l)
411                         l->next = i;
412                 else
413                         con->interfaces = i;
414                 i->next = NULL;
415                 i->last_message = NULL;
416         } else
417                 free(i->data);
418
419         /* Now fill out our interface structure */
420         i->con = con;
421         i->data = data;
422         i->data_len = len;
423         i->ifname = ifname;
424         i->type = type;
425         i->reason = reason;
426         flags = dhcpcd_get_value(i, "ifflags=");
427         if (flags)
428                 i->flags = (unsigned int)strtoul(flags, NULL, 0);
429         else
430                 i->flags = 0;
431         if (strcmp(reason, "CARRIER") == 0)
432                 i->up = true;
433         else
434                 i->up = strtobool(dhcpcd_get_value(i, "if_up="));
435         i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless="));
436         i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid=" : "old_ssid=");
437
438        /* Sort! */
439         n = nl = NULL;
440         p = orderdup;
441         addedi = false;
442         while ((o = strsep(&p, " ")) != NULL) {
443                 for (ti = 0; dhcpcd_types[ti]; ti++) {
444                         l = NULL;
445                         for (e = con->interfaces; e; e = e->next) {
446                                 if (strcmp(e->ifname, o) == 0 &&
447                                     strcmp(e->type, dhcpcd_types[ti]) == 0)
448                                         break;
449                                 l = e;
450                         }
451                         if (e == NULL)
452                                 continue;
453                         if (i == e)
454                                 addedi = true;
455                         if (l)
456                                 l->next = e->next;
457                         else
458                                 con->interfaces = e->next;
459                         e->next = NULL;
460                         if (nl == NULL)
461                                 n = nl = e;
462                         else {
463                                 nl->next = e;
464                                 nl = e;
465                         }
466                 }
467         }
468         free(orderdup);
469         /* Free any stragglers */
470         while (con->interfaces) {
471                 e = con->interfaces->next;
472                 free(con->interfaces->data);
473                 free(con->interfaces->last_message);
474                 free(con->interfaces);
475                 con->interfaces = e;
476         }
477         con->interfaces = n;
478
479         return addedi ? i : NULL;
480 }
481
482 static DHCPCD_IF *
483 dhcpcd_read_if(DHCPCD_CONNECTION *con, int fd)
484 {
485         char sbuf[sizeof(size_t)], *rbuf;
486         size_t len;
487         ssize_t bytes;
488         DHCPCD_IF *i;
489
490         bytes = read(fd, sbuf, sizeof(sbuf));
491         if (bytes == 0 || bytes == -1) {
492                 dhcpcd_close(con);
493                 return NULL;
494         }
495         memcpy(&len, sbuf, sizeof(len));
496         rbuf = malloc(len + 1);
497         if (rbuf == NULL)
498                 return NULL;
499         bytes = read(fd, rbuf, len);
500         if (bytes == 0 || bytes == -1) {
501                 free(rbuf);
502                 dhcpcd_close(con);
503                 return NULL;
504         }
505         if ((size_t)bytes != len) {
506                 free(rbuf);
507                 errno = EINVAL;
508                 return NULL;
509         }
510         rbuf[bytes] = '\0';
511
512         i = dhcpcd_new_if(con, rbuf, len);
513         if (i == NULL)
514                 free(rbuf);
515         return i;
516 }
517
518 static void
519 dhcpcd_dispatchif(DHCPCD_IF *i)
520 {
521
522         assert(i);
523         if (i->con->if_cb)
524                 i->con->if_cb(i, i->con->if_context);
525         dhcpcd_wpa_if_event(i);
526 }
527
528 void
529 dhcpcd_dispatch(DHCPCD_CONNECTION *con)
530 {
531         DHCPCD_IF *i;
532
533         assert(con);
534         i = dhcpcd_read_if(con, con->listen_fd);
535         if (i)
536                 dhcpcd_dispatchif(i);
537
538         /* Have to call update_status last as it could
539          * cause the interface to be destroyed. */
540         update_status(con, NULL);
541 }
542
543 DHCPCD_CONNECTION *
544 dhcpcd_new(void)
545 {
546         DHCPCD_CONNECTION *con;
547
548         con = calloc(1, sizeof(*con));
549         con->command_fd = con->listen_fd = -1;
550         con->open = false;
551         return con;
552 }
553
554 #ifndef __GLIBC__
555 /* Good enough for our needs */
556 static int
557 strverscmp(const char *s1, const char *s2)
558 {
559         int s1maj, s1min, s1part;
560         int s2maj, s2min, s2part;
561         int r;
562
563         s1min = s1part = 0;
564         if (sscanf(s1, "%d.%d.%d", &s1maj, &s1min, &s1part) < 1)
565                 return -1;
566         s2min = s2part = 0;
567         if (sscanf(s2, "%d.%d.%d", &s2maj, &s2min, &s2part) < 1)
568                 return -1;
569         r = s1maj - s2maj;
570         if (r != 0)
571                 return r;
572         r = s1min - s2min;
573         if (r != 0)
574                 return r;
575         return s1part - s2part;
576 }
577 #endif
578
579 int
580 dhcpcd_open(DHCPCD_CONNECTION *con, bool privileged)
581 {
582         const char *path = privileged ? DHCPCD_SOCKET : DHCPCD_UNPRIV_SOCKET;
583         char cmd[128];
584         ssize_t bytes;
585         size_t nifs, n;
586
587         assert(con);
588         if (con->open) {
589                 if (con->listen_fd != -1)
590                         return con->listen_fd;
591                 errno = EISCONN;
592                 return -1;
593         }
594         /* We need to block the command fd */
595         con->command_fd = dhcpcd_connect(path, 0);
596         if (con->command_fd == -1)
597                 goto err_exit;
598
599         con->terminate_commands = false;
600         if (dhcpcd_command(con, "--version", &con->version) <= 0)
601                 goto err_exit;
602         con->terminate_commands =
603             strverscmp(con->version, "6.4.1") >= 0 ? true : false;
604
605         if (dhcpcd_command(con, "--getconfigfile", &con->cffile) <= 0)
606                 goto err_exit;
607
608         con->open = true;
609         con->privileged = privileged;
610         update_status(con, NULL);
611
612         con->listen_fd = dhcpcd_connect(path, SOCK_NONBLOCK);
613         if (con->listen_fd == -1)
614                 goto err_exit;
615
616         dhcpcd_command_fd(con, con->listen_fd, "--listen", NULL);
617         dhcpcd_command_fd(con, con->command_fd, "--getinterfaces", NULL);
618         bytes = read(con->command_fd, cmd, sizeof(nifs));
619         if (bytes != sizeof(nifs))
620                 goto err_exit;
621         memcpy(&nifs, cmd, sizeof(nifs));
622         /* We don't dispatch each interface here as that
623          * causes too much notification spam when the GUI starts */
624         for (n = 0; n < nifs; n++)
625                 dhcpcd_read_if(con, con->command_fd);
626
627         update_status(con, NULL);
628
629         return con->listen_fd;
630
631 err_exit:
632         dhcpcd_close(con);
633         return -1;
634 }
635
636 int
637 dhcpcd_get_fd(DHCPCD_CONNECTION *con)
638 {
639
640         assert(con);
641         return con->listen_fd;
642 }
643
644 bool
645 dhcpcd_privileged(DHCPCD_CONNECTION *con)
646 {
647
648         assert(con);
649         return con->privileged;
650 }
651
652 const char *
653 dhcpcd_status(DHCPCD_CONNECTION *con)
654 {
655
656         assert(con);
657         return con->status;
658 }
659
660 const char *
661 dhcpcd_version(DHCPCD_CONNECTION *con)
662 {
663
664         assert(con);
665         return con->version;
666 }
667
668 const char *
669 dhcpcd_cffile(DHCPCD_CONNECTION *con)
670 {
671
672         assert(con);
673         return con->cffile;
674 }
675
676 void
677 dhcpcd_set_if_callback(DHCPCD_CONNECTION *con,
678     void (*cb)(DHCPCD_IF *, void *), void *ctx)
679 {
680
681         assert(con);
682         con->if_cb = cb;
683         con->if_context = ctx;
684 }
685
686 void
687 dhcpcd_set_status_callback(DHCPCD_CONNECTION *con,
688     void (*cb)(DHCPCD_CONNECTION *, const char *, void *), void *ctx)
689 {
690
691         assert(con);
692         con->status_cb = cb;
693         con->status_context = ctx;
694 }
695
696 void
697 dhcpcd_close(DHCPCD_CONNECTION *con)
698 {
699         DHCPCD_IF *nif;
700         DHCPCD_WPA *nwpa;
701         DHCPCD_WI_HIST *nh;
702
703         assert(con);
704
705         con->open = false;
706
707         /* Shut down WPA listeners as they aren't much good without dhcpcd.
708          * They'll be restarted anyway when dhcpcd comes back up. */
709         while (con->wpa) {
710                 nwpa = con->wpa->next;
711                 dhcpcd_wpa_close(con->wpa);
712                 free(con->wpa);
713                 con->wpa = nwpa;
714         }
715         while (con->wi_history) {
716                 nh = con->wi_history->next;
717                 free(con->wi_history);
718                 con->wi_history = nh;
719         }
720         while (con->interfaces) {
721                 nif = con->interfaces->next;
722                 free(con->interfaces->data);
723                 free(con->interfaces->last_message);
724                 free(con->interfaces);
725                 con->interfaces = nif;
726         }
727
728         if (con->command_fd != -1)
729                 shutdown(con->command_fd, SHUT_RDWR);
730         if (con->listen_fd != -1)
731                 shutdown(con->listen_fd, SHUT_RDWR);
732
733         update_status(con, "down");
734
735         if (con->command_fd != -1) {
736                 close(con->command_fd);
737                 con->command_fd = -1;
738         }
739         if (con->listen_fd != -1) {
740                 close(con->listen_fd);
741                 con->listen_fd = -1;
742         }
743
744         if (con->cffile) {
745                 free(con->cffile);
746                 con->cffile = NULL;
747         }
748         if (con->version) {
749                 free(con->version);
750                 con->version = NULL;
751         }
752         if (con->buf) {
753                 free(con->buf);
754                 con->buf = NULL;
755                 con->buflen = 0;
756         }
757 }
758
759 void
760 dhcpcd_free(DHCPCD_CONNECTION *con)
761 {
762
763         assert(con);
764         free(con);
765 }
766
767 DHCPCD_CONNECTION *
768 dhcpcd_if_connection(DHCPCD_IF *i)
769 {
770
771         assert(i);
772         return i->con;
773 }
774
775 char *
776 dhcpcd_if_message(DHCPCD_IF *i, bool *new_msg)
777 {
778         const char *ip, *iplen, *pfx;
779         char *msg, *p;
780         const char *reason = NULL;
781         size_t len;
782         bool showssid;
783
784         assert(i);
785         /* Don't report non SLAAC configurations */
786         if (strcmp(i->type, "ra") == 0 && i->up &&
787             dhcpcd_get_value(i, "ra1_prefix=") == NULL)
788                 return NULL;
789
790         showssid = false;
791         if (strcmp(i->reason, "EXPIRE") == 0)
792                 reason = _("Expired");
793         else if (strcmp(i->reason, "CARRIER") == 0) {
794                 if (i->wireless) {
795                         showssid = true;
796                         reason = _("Associated with");
797                 } else
798                         reason = _("Cable plugged in");
799         } else if (strcmp(i->reason, "NOCARRIER") == 0) {
800                 if (i->wireless) {
801                         if (i->ssid) {
802                                 reason = _("Disassociated from");
803                                 showssid = true;
804                         } else
805                                 reason = _("Not associated");
806                 } else
807                         reason = _("Cable unplugged");
808         } else if (strcmp(i->reason, "DEPARTED") == 0)
809                 reason = _("Departed");
810         else if (strcmp(i->reason, "UNKNOWN") == 0)
811                 reason = _("Unknown link state");
812         else if (strcmp(i->reason, "FAIL") == 0)
813                 reason = _("Automatic configuration not possible");
814         else if (strcmp(i->reason, "3RDPARTY") == 0)
815                 reason = _("Waiting for 3rd Party configuration");
816
817         if (reason == NULL) {
818                 if (i->up)
819                         reason = _("Configured");
820                 else if (strcmp(i->type, "ra") == 0)
821                         reason = "Expired RA";
822                 else
823                         reason = i->reason;
824         }
825
826         pfx = i->up ? "new_" : "old_";
827         if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address=")))
828                 iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr=");
829         else if ((ip = dhcpcd_get_value(i, "ra1_prefix=")))
830                 iplen = NULL;
831         else if ((ip = dhcpcd_get_prefix_value(i, pfx,
832             "dhcp6_ia_na1_ia_addr1=")))
833                 iplen = "128";
834         else {
835                 ip = NULL;
836                 iplen = NULL;
837         }
838
839         len = strlen(i->ifname) + strlen(reason) + 3;
840         if (showssid && i->ssid)
841                 len += strlen(i->ssid) + 1;
842         if (ip)
843                 len += strlen(ip) + 1;
844         if (iplen)
845                 len += strlen(iplen) + 1;
846         msg = p = malloc(len);
847         if (msg == NULL)
848                 return NULL;
849         p += snprintf(msg, len, "%s: %s", i->ifname, reason);
850         if (showssid)
851                 p += snprintf(p, len - (size_t)(p - msg), " %s", i->ssid);
852         if (iplen)
853                 snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
854         else if (ip)
855                 snprintf(p, len - (size_t)(p - msg), " %s", ip);
856
857         if (new_msg) {
858                 if (i->last_message == NULL || strcmp(i->last_message, msg))
859                         *new_msg = true;
860                 else
861                         *new_msg = false;
862         }
863         free(i->last_message);
864         i->last_message = strdup(msg);
865
866         return msg;
867 }