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