3bfbb56f3b162a022041ae2e4792fb06982726cb
[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         len = cmdlen + strlen(arg) + 2;
145         if (!dhcpcd_realloc(con, len))
146                 return -1;
147         strlcpy(con->buf, cmd, con->buflen);
148         con->buf[cmdlen] = ' ';
149         strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
150
151         return dhcpcd_command_fd(con, con->command_fd, con->buf, buffer);
152 }
153
154
155 static int
156 dhcpcd_connect(const char *path, int opts)
157 {
158         int fd;
159         socklen_t len;
160         struct sockaddr_un sun;
161
162         fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | opts, 0);
163         if (fd == -1)
164                 return -1;
165
166         memset(&sun, 0, sizeof(sun));
167         sun.sun_family = AF_UNIX;
168         strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
169         len = (socklen_t)SUN_LEN(&sun);
170         if (connect(fd, (struct sockaddr *)&sun, len) == 0)
171                 return fd;
172         close(fd);
173         return -1;
174 }
175
176 static const char *
177 get_value(const char *data, size_t len, const char *var)
178 {
179         const char *end, *p;
180         size_t vlen;
181
182         assert(var);
183         end = data + len;
184         vlen = strlen(var);
185         p = NULL;
186         while (data + vlen + 1 < end) {
187                 if (strncmp(data, var, vlen) == 0) {
188                         p = data + vlen;
189                         break;
190                 }
191                 data += strlen(data) + 1;
192         }
193         if (p != NULL && *p != '\0')
194                 return p;
195         return NULL;
196 }
197
198 const char *
199 dhcpcd_get_value(const DHCPCD_IF *i, const char *var)
200 {
201
202         assert(i);
203         return get_value(i->data, i->data_len, var);
204 }
205
206 const char *
207 dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
208 {
209         char pvar[128], *p;
210         size_t plen, l;
211
212         p = pvar;
213         plen = sizeof(pvar);
214         l = strlcpy(p, prefix, plen);
215         if (l >= sizeof(pvar)) {
216                 errno = ENOBUFS;
217                 return NULL;
218         }
219         p += l;
220         plen -= l;
221         if (strlcpy(p, var, plen) >= plen) {
222                 errno = ENOBUFS;
223                 return NULL;
224         }
225         return dhcpcd_get_value(i, pvar);
226 }
227
228 static bool
229 strtobool(const char *var)
230 {
231
232         if (var == NULL)
233                 return false;
234
235          return (*var == '0' || *var == '\0' ||
236             strcmp(var, "false") == 0 ||
237             strcmp(var, "no") == 0) ? false : true;
238 }
239
240 static const char *
241 get_status(DHCPCD_CONNECTION *con)
242 {
243         DHCPCD_IF *i;
244         const char *status;
245
246         assert(con);
247         if (con->command_fd == -1)
248                 return "down";
249
250         if (con->listen_fd == -1)
251                 return "opened";
252
253         if (con->interfaces == NULL)
254                 return "initialised";
255
256         status = "disconnected";
257         for (i = con->interfaces; i; i = i->next) {
258                 if (i->up) {
259                         if (strcmp(i->type, "link")) {
260                                 status = "connected";
261                                 break;
262                         } else
263                                 status = "connecting";
264                 }
265         }
266         return status;
267 }
268
269 static void
270 update_status(DHCPCD_CONNECTION *con, const char *nstatus)
271 {
272
273         assert(con);
274         if (nstatus == NULL)
275                 nstatus = get_status(con);
276         if (con->status == NULL || strcmp(nstatus, con->status)) {
277                 con->status = nstatus;
278                 if (con->status_cb)
279                         con->status_cb(con, con->status, con->status_context);
280         }
281 }
282
283 DHCPCD_IF *
284 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
285 {
286
287         assert(con);
288         return con->interfaces;
289 }
290
291 DHCPCD_IF *
292 dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
293 {
294         DHCPCD_IF *i;
295
296         assert(con);
297         for (i = con->interfaces; i; i = i->next)
298                 if (strcmp(i->ifname, ifname) == 0 &&
299                     strcmp(i->type, type) == 0)
300                         return i;
301         return NULL;
302 }
303
304 static DHCPCD_IF *
305 dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
306 {
307         const char *ifname, *ifclass, *reason, *type, *order, *flags;
308         char *orderdup, *o, *p;
309         DHCPCD_IF *e, *i, *l, *n, *nl;
310         int ti;
311         bool addedi;
312
313         ifname = get_value(data, len, "interface=");
314         if (ifname == NULL || *ifname == '\0') {
315                 errno = ESRCH;
316                 return NULL;
317         }
318         reason = get_value(data, len, "reason=");
319         if (reason == NULL || *reason == '\0') {
320                 errno = ESRCH;
321                 return NULL;
322         }
323         ifclass = get_value(data, len, "ifclass=");
324         /* Skip pseudo interfaces */
325         if (ifclass && *ifclass != '\0') {
326                 errno = ENOTSUP;
327                 return NULL;
328         }
329         if (strcmp(reason, "RECONFIGURE") == 0) {
330                 errno = ENOTSUP;
331                 return NULL;
332         }
333         order = get_value(data, len, "interface_order=");
334         if (order == NULL || *order == '\0') {
335                 errno = ESRCH;
336                 return NULL;
337         }
338
339         if (strcmp(reason, "PREINIT") == 0 ||
340             strcmp(reason, "UNKNOWN") == 0 ||
341             strcmp(reason, "CARRIER") == 0 ||
342             strcmp(reason, "NOCARRIER") == 0 ||
343             strcmp(reason, "DEPARTED") == 0 ||
344             strcmp(reason, "STOPPED") == 0)
345                 type = "link";
346         else if (strcmp(reason, "ROUTERADVERT") == 0)
347                 type = "ra";
348         else if (reason[strlen(reason) - 1] == '6')
349                 type = "dhcp6";
350         else
351                 type = "ipv4";
352
353         i = NULL;
354        /* Remove all instances on carrier drop */
355         if (strcmp(reason, "NOCARRIER") == 0 ||
356             strcmp(reason, "DEPARTED") == 0 ||
357             strcmp(reason, "STOPPED") == 0)
358         {
359                 l = NULL;
360                 for (e = con->interfaces; e; e = n) {
361                         n = e->next;
362                         if (strcmp(e->ifname, ifname) == 0) {
363                                 if (strcmp(e->type, type) == 0)
364                                         l = i = e;
365                                 else {
366                                         if (l)
367                                                 l->next = e->next;
368                                         else
369                                                 con->interfaces = e->next;
370                                         free(e);
371                                 }
372                         } else
373                                 l = e;
374                 }
375         } else if (strcmp(type, "link")) {
376                 /* If link is down, ignore it */
377                 e = dhcpcd_get_if(con, ifname, "link");
378                 if (e && !e->up)
379                         return NULL;
380         }
381
382         orderdup = strdup(order);
383         if (orderdup == NULL)
384                 return NULL;
385
386         /* Find our pointer */
387         if (i == NULL) {
388                 l = NULL;
389                 for (e = con->interfaces; e; e = e->next) {
390                         if (strcmp(e->ifname, ifname) == 0 &&
391                             strcmp(e->type, type) == 0)
392                         {
393                                 i = e;
394                                 break;
395                         }
396                         l = e;
397                 }
398         }
399         if (i == NULL) {
400                 i = malloc(sizeof(*i));
401                 if (i == NULL) {
402                         free(orderdup);
403                         return NULL;
404                 }
405                 if (l)
406                         l->next = i;
407                 else
408                         con->interfaces = i;
409                 i->next = NULL;
410                 i->last_message = NULL;
411         } else
412                 free(i->data);
413
414         /* Now fill out our interface structure */
415         i->con = con;
416         i->data = data;
417         i->data_len = len;
418         i->ifname = ifname;
419         i->type = type;
420         i->reason = reason;
421         flags = dhcpcd_get_value(i, "ifflags=");
422         if (flags)
423                 i->flags = (unsigned int)strtoul(flags, NULL, 0);
424         else
425                 i->flags = 0;
426         if (strcmp(reason, "CARRIER") == 0)
427                 i->up = true;
428         else
429                 i->up = strtobool(dhcpcd_get_value(i, "if_up="));
430         i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless="));
431         i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid=" : "old_ssid=");
432
433        /* Sort! */
434         n = nl = NULL;
435         p = orderdup;
436         addedi = false;
437         while ((o = strsep(&p, " ")) != NULL) {
438                 for (ti = 0; dhcpcd_types[ti]; ti++) {
439                         l = NULL;
440                         for (e = con->interfaces; e; e = e->next) {
441                                 if (strcmp(e->ifname, o) == 0 &&
442                                     strcmp(e->type, dhcpcd_types[ti]) == 0)
443                                         break;
444                                 l = e;
445                         }
446                         if (e == NULL)
447                                 continue;
448                         if (i == e)
449                                 addedi = true;
450                         if (l)
451                                 l->next = e->next;
452                         else
453                                 con->interfaces = e->next;
454                         e->next = NULL;
455                         if (nl == NULL)
456                                 n = nl = e;
457                         else {
458                                 nl->next = e;
459                                 nl = e;
460                         }
461                 }
462         }
463         free(orderdup);
464         /* Free any stragglers */
465         while (con->interfaces) {
466                 e = con->interfaces->next;
467                 free(con->interfaces->data);
468                 free(con->interfaces->last_message);
469                 free(con->interfaces);
470                 con->interfaces = e;
471         }
472         con->interfaces = n;
473
474         return addedi ? i : NULL;
475 }
476
477 static DHCPCD_IF *
478 dhcpcd_read_if(DHCPCD_CONNECTION *con, int fd)
479 {
480         char sbuf[sizeof(size_t)], *rbuf;
481         size_t len;
482         ssize_t bytes;
483         DHCPCD_IF *i;
484
485         bytes = read(fd, sbuf, sizeof(sbuf));
486         if (bytes == 0 || bytes == -1) {
487                 dhcpcd_close(con);
488                 return NULL;
489         }
490         memcpy(&len, sbuf, sizeof(len));
491         rbuf = malloc(len + 1);
492         if (rbuf == NULL)
493                 return NULL;
494         bytes = read(fd, rbuf, len);
495         if (bytes == 0 || bytes == -1) {
496                 free(rbuf);
497                 dhcpcd_close(con);
498                 return NULL;
499         }
500         if ((size_t)bytes != len) {
501                 free(rbuf);
502                 errno = EINVAL;
503                 return NULL;
504         }
505         rbuf[bytes] = '\0';
506
507         i = dhcpcd_new_if(con, rbuf, len);
508         if (i == NULL)
509                 free(rbuf);
510         return i;
511 }
512
513 static void
514 dhcpcd_dispatchif(DHCPCD_IF *i)
515 {
516
517         assert(i);
518         if (i->con->if_cb)
519                 i->con->if_cb(i, i->con->if_context);
520         dhcpcd_wpa_if_event(i);
521 }
522
523 void
524 dhcpcd_dispatch(DHCPCD_CONNECTION *con)
525 {
526         DHCPCD_IF *i;
527
528         assert(con);
529         i = dhcpcd_read_if(con, con->listen_fd);
530         if (i)
531                 dhcpcd_dispatchif(i);
532
533         /* Have to call update_status last as it could
534          * cause the interface to be destroyed. */
535         update_status(con, NULL);
536 }
537
538 DHCPCD_CONNECTION *
539 dhcpcd_new(void)
540 {
541         DHCPCD_CONNECTION *con;
542
543         con = calloc(1, sizeof(*con));
544         con->command_fd = con->listen_fd = -1;
545         con->open = false;
546         return con;
547 }
548
549 #ifndef __GLIBC__
550 /* Good enough for our needs */
551 static int
552 strverscmp(const char *s1, const char *s2)
553 {
554         int s1maj, s1min, s1part;
555         int s2maj, s2min, s2part;
556         int r;
557
558         s1min = s1part = 0;
559         if (sscanf(s1, "%d.%d.%d", &s1maj, &s1min, &s1part) < 1)
560                 return -1;
561         s2min = s2part = 0;
562         if (sscanf(s2, "%d.%d.%d", &s2maj, &s2min, &s2part) < 1)
563                 return -1;
564         r = s1maj - s2maj;
565         if (r != 0)
566                 return r;
567         r = s1min - s2min;
568         if (r != 0)
569                 return r;
570         return s1part - s2part;
571 }
572 #endif
573
574 int
575 dhcpcd_open(DHCPCD_CONNECTION *con, bool privileged)
576 {
577         const char *path = privileged ? DHCPCD_SOCKET : DHCPCD_UNPRIV_SOCKET;
578         char cmd[128];
579         ssize_t bytes;
580         size_t nifs, n;
581
582         assert(con);
583         if (con->open) {
584                 if (con->listen_fd != -1)
585                         return con->listen_fd;
586                 errno = EISCONN;
587                 return -1;
588         }
589         /* We need to block the command fd */
590         con->command_fd = dhcpcd_connect(path, 0);
591         if (con->command_fd == -1)
592                 goto err_exit;
593
594         con->terminate_commands = false;
595         if (dhcpcd_command(con, "--version", &con->version) <= 0)
596                 goto err_exit;
597         con->terminate_commands =
598             strverscmp(con->version, "6.4.1") >= 0 ? true : false;
599
600         if (dhcpcd_command(con, "--getconfigfile", &con->cffile) <= 0)
601                 goto err_exit;
602
603         con->open = true;
604         con->privileged = privileged;
605         update_status(con, NULL);
606
607         con->listen_fd = dhcpcd_connect(path, SOCK_NONBLOCK);
608         if (con->listen_fd == -1)
609                 goto err_exit;
610
611         dhcpcd_command_fd(con, con->listen_fd, "--listen", NULL);
612         dhcpcd_command_fd(con, con->command_fd, "--getinterfaces", NULL);
613         bytes = read(con->command_fd, cmd, sizeof(nifs));
614         if (bytes != sizeof(nifs))
615                 goto err_exit;
616         memcpy(&nifs, cmd, sizeof(nifs));
617         /* We don't dispatch each interface here as that
618          * causes too much notification spam when the GUI starts */
619         for (n = 0; n < nifs; n++)
620                 dhcpcd_read_if(con, con->command_fd);
621
622         update_status(con, NULL);
623
624         return con->listen_fd;
625
626 err_exit:
627         dhcpcd_close(con);
628         return -1;
629 }
630
631 int
632 dhcpcd_get_fd(DHCPCD_CONNECTION *con)
633 {
634
635         assert(con);
636         return con->listen_fd;
637 }
638
639 bool
640 dhcpcd_privileged(DHCPCD_CONNECTION *con)
641 {
642
643         assert(con);
644         return con->privileged;
645 }
646
647 const char *
648 dhcpcd_status(DHCPCD_CONNECTION *con)
649 {
650
651         assert(con);
652         return con->status;
653 }
654
655 const char *
656 dhcpcd_version(DHCPCD_CONNECTION *con)
657 {
658
659         assert(con);
660         return con->version;
661 }
662
663 const char *
664 dhcpcd_cffile(DHCPCD_CONNECTION *con)
665 {
666
667         assert(con);
668         return con->cffile;
669 }
670
671 void
672 dhcpcd_set_if_callback(DHCPCD_CONNECTION *con,
673     void (*cb)(DHCPCD_IF *, void *), void *ctx)
674 {
675
676         assert(con);
677         con->if_cb = cb;
678         con->if_context = ctx;
679 }
680
681 void
682 dhcpcd_set_status_callback(DHCPCD_CONNECTION *con,
683     void (*cb)(DHCPCD_CONNECTION *, const char *, void *), void *ctx)
684 {
685
686         assert(con);
687         con->status_cb = cb;
688         con->status_context = ctx;
689 }
690
691 void
692 dhcpcd_close(DHCPCD_CONNECTION *con)
693 {
694         DHCPCD_IF *nif;
695         DHCPCD_WPA *nwpa;
696         DHCPCD_WI_HIST *nh;
697
698         assert(con);
699
700         con->open = false;
701
702         /* Shut down WPA listeners as they aren't much good without dhcpcd.
703          * They'll be restarted anyway when dhcpcd comes back up. */
704         while (con->wpa) {
705                 nwpa = con->wpa->next;
706                 dhcpcd_wpa_close(con->wpa);
707                 free(con->wpa);
708                 con->wpa = nwpa;
709         }
710         while (con->wi_history) {
711                 nh = con->wi_history->next;
712                 free(con->wi_history);
713                 con->wi_history = nh;
714         }
715         while (con->interfaces) {
716                 nif = con->interfaces->next;
717                 free(con->interfaces->data);
718                 free(con->interfaces->last_message);
719                 free(con->interfaces);
720                 con->interfaces = nif;
721         }
722
723         if (con->command_fd != -1)
724                 shutdown(con->command_fd, SHUT_RDWR);
725         if (con->listen_fd != -1)
726                 shutdown(con->listen_fd, SHUT_RDWR);
727
728         update_status(con, "down");
729
730         if (con->command_fd != -1) {
731                 close(con->command_fd);
732                 con->command_fd = -1;
733         }
734         if (con->listen_fd != -1) {
735                 close(con->listen_fd);
736                 con->listen_fd = -1;
737         }
738
739         if (con->cffile) {
740                 free(con->cffile);
741                 con->cffile = NULL;
742         }
743         if (con->version) {
744                 free(con->version);
745                 con->version = NULL;
746         }
747         if (con->buf) {
748                 free(con->buf);
749                 con->buf = NULL;
750                 con->buflen = 0;
751         }
752 }
753
754 void
755 dhcpcd_free(DHCPCD_CONNECTION *con)
756 {
757
758         assert(con);
759         free(con);
760 }
761
762 DHCPCD_CONNECTION *
763 dhcpcd_if_connection(DHCPCD_IF *i)
764 {
765
766         assert(i);
767         return i->con;
768 }
769
770 char *
771 dhcpcd_if_message(DHCPCD_IF *i, bool *new_msg)
772 {
773         const char *ip, *iplen, *pfx;
774         char *msg, *p;
775         const char *reason = NULL;
776         size_t len;
777         bool showssid;
778
779         assert(i);
780         /* Don't report non SLAAC configurations */
781         if (strcmp(i->type, "ra") == 0 && i->up &&
782             dhcpcd_get_value(i, "ra1_prefix=") == NULL)
783                 return NULL;
784
785         showssid = false;
786         if (strcmp(i->reason, "EXPIRE") == 0)
787                 reason = _("Expired");
788         else if (strcmp(i->reason, "CARRIER") == 0) {
789                 if (i->wireless) {
790                         showssid = true;
791                         reason = _("Associated with");
792                 } else
793                         reason = _("Cable plugged in");
794         } else if (strcmp(i->reason, "NOCARRIER") == 0) {
795                 if (i->wireless) {
796                         if (i->ssid) {
797                                 reason = _("Disassociated from");
798                                 showssid = true;
799                         } else
800                                 reason = _("Not associated");
801                 } else
802                         reason = _("Cable unplugged");
803         } else if (strcmp(i->reason, "DEPARTED") == 0)
804                 reason = _("Departed");
805         else if (strcmp(i->reason, "UNKNOWN") == 0)
806                 reason = _("Unknown link state");
807         else if (strcmp(i->reason, "FAIL") == 0)
808                 reason = _("Automatic configuration not possible");
809         else if (strcmp(i->reason, "3RDPARTY") == 0)
810                 reason = _("Waiting for 3rd Party configuration");
811
812         if (reason == NULL) {
813                 if (i->up)
814                         reason = _("Configured");
815                 else if (strcmp(i->type, "ra") == 0)
816                         reason = "Expired RA";
817                 else
818                         reason = i->reason;
819         }
820
821         pfx = i->up ? "new_" : "old_";
822         if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address=")))
823                 iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr=");
824         else if ((ip = dhcpcd_get_value(i, "ra1_prefix=")))
825                 iplen = NULL;
826         else if ((ip = dhcpcd_get_prefix_value(i, pfx,
827             "dhcp6_ia_na1_ia_addr1=")))
828                 iplen = "128";
829         else {
830                 ip = NULL;
831                 iplen = NULL;
832         }
833
834         len = strlen(i->ifname) + strlen(reason) + 3;
835         if (showssid && i->ssid)
836                 len += strlen(i->ssid) + 1;
837         if (ip)
838                 len += strlen(ip) + 1;
839         if (iplen)
840                 len += strlen(iplen) + 1;
841         msg = p = malloc(len);
842         if (msg == NULL)
843                 return NULL;
844         p += snprintf(msg, len, "%s: %s", i->ifname, reason);
845         if (showssid)
846                 p += snprintf(p, len - (size_t)(p - msg), " %s", i->ssid);
847         if (iplen)
848                 snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
849         else if (ip)
850                 snprintf(p, len - (size_t)(p - msg), " %s", ip);
851
852         if (new_msg) {
853                 if (i->last_message == NULL || strcmp(i->last_message, msg))
854                         *new_msg = true;
855                 else
856                         *new_msg = false;
857         }
858         free(i->last_message);
859         i->last_message = strdup(msg);
860
861         return msg;
862 }