Fix a crash by clearing our reference to the menuwidget when it is deleted
[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 <ctype.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <libintl.h>
41 #include <limits.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
46
47 #define IN_LIBDHCPCD
48
49 #include "config.h"
50 #include "dhcpcd.h"
51
52 #ifdef HAS_GETTEXT
53 #include <libintl.h>
54 #define _ gettext
55 #else
56 #define _(a) (a)
57 #endif
58
59 #ifdef HAVE_VIS_H
60 #include <vis.h>
61 #endif
62
63 #ifndef SUN_LEN
64 #define SUN_LEN(su) \
65         (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
66 #endif
67
68 #ifndef iswhite
69 #define iswhite(c)      (c == ' ' || c == '\t' || c == '\n')
70 #endif
71
72 static const char * const dhcpcd_types[] =
73     { "link", "ipv4", "ra", "dhcp6", NULL };
74
75 static ssize_t
76 dhcpcd_command_fd(DHCPCD_CONNECTION *con,
77     int fd, bool progname, const char *cmd, char **buffer)
78 {
79         size_t pl, cl, len;
80         ssize_t bytes;
81         char buf[1024], *p;
82         char *nbuf;
83
84         assert(con);
85         assert(cmd);
86
87         /* Each command is \n terminated.
88          * Each argument is NULL seperated.
89          * We may need to send a space one day, so the API
90          * in this function may need to be improved */
91         cl = strlen(cmd);
92         if (progname) {
93                 pl = strlen(con->progname);
94                 len = pl + 1 + cl + 1;
95         } else {
96                 pl = 0;
97                 len = cl + 1;
98         }
99         if (con->terminate_commands)
100                 len++;
101         if (len > sizeof(buf)) {
102                 errno = ENOBUFS;
103                 return -1;
104         }
105         p = buf;
106         if (progname) {
107                 memcpy(buf, con->progname, pl);
108                 buf[pl] = '\0';
109                 p = buf + pl + 1;
110         }
111         memcpy(p, cmd, cl);
112         p[cl] = '\0';
113         while ((p = strchr(p, ' ')) != NULL)
114                 *p++ = '\0';
115         if (con->terminate_commands) {
116                 buf[len - 2] = '\n';
117                 buf[len - 1] = '\0';
118         } else
119                 buf[len - 1] = '\0';
120         if (write(fd, buf, len) == -1)
121                 return -1;
122         if (buffer == NULL)
123                 return 0;
124
125         bytes = read(fd, buf, sizeof(size_t));
126         if (bytes == 0 || bytes == -1)
127                 return bytes;
128         memcpy(&len, buf, sizeof(size_t));
129         nbuf = realloc(*buffer, len + 1);
130         if (nbuf == NULL)
131                 return -1;
132         *buffer = nbuf;
133         bytes = read(fd, *buffer, len);
134         if (bytes != -1 && (size_t)bytes < len)
135                 *buffer[bytes] = '\0';
136         return bytes;
137 }
138
139 ssize_t
140 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
141 {
142
143         assert(con);
144         if (!con->privileged) {
145                 errno = EACCES;
146                 return -1;
147         }
148         return dhcpcd_command_fd(con, con->command_fd, true, cmd, buffer);
149 }
150
151 static ssize_t
152 dhcpcd_ctrl_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
153 {
154
155         return dhcpcd_command_fd(con, con->command_fd, false, cmd, buffer);
156 }
157
158 bool
159 dhcpcd_realloc(DHCPCD_CONNECTION *con, size_t len)
160 {
161
162         assert(con);
163         if (con->buflen < len) {
164                 char *nbuf;
165
166                 nbuf = realloc(con->buf, len);
167                 if (nbuf == NULL)
168                         return false;
169                 con->buf = nbuf;
170                 con->buflen = len;
171         }
172         return true;
173 }
174
175 ssize_t
176 dhcpcd_command_arg(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
177     char **buffer)
178 {
179         size_t cmdlen, len;
180
181         assert(con);
182         assert(cmd);
183
184         cmdlen = strlen(cmd);
185         if (arg)
186                 len = cmdlen + strlen(arg) + 2;
187         else
188                 len = cmdlen + 1;
189         if (!dhcpcd_realloc(con, len))
190                 return -1;
191         strlcpy(con->buf, cmd, con->buflen);
192         if (arg) {
193                 con->buf[cmdlen] = ' ';
194                 strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
195         }
196
197         return dhcpcd_command_fd(con, con->command_fd, true, con->buf, buffer);
198 }
199
200
201 static int
202 dhcpcd_connect(const char *path, int opts)
203 {
204         int fd;
205         socklen_t len;
206         struct sockaddr_un sun;
207
208         assert(path);
209         fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | opts, 0);
210         if (fd == -1)
211                 return -1;
212
213         memset(&sun, 0, sizeof(sun));
214         sun.sun_family = AF_UNIX;
215         strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
216         len = (socklen_t)SUN_LEN(&sun);
217         if (connect(fd, (struct sockaddr *)&sun, len) == 0)
218                 return fd;
219         close(fd);
220         return -1;
221 }
222
223 static const char *
224 get_value(const char *data, size_t len, const char *var)
225 {
226         const char *end, *p;
227         size_t vlen;
228
229         assert(var);
230         end = data + len;
231         vlen = strlen(var);
232         p = NULL;
233         while (data + vlen + 1 < end) {
234                 /* Skip past NUL padding */
235                 if (*data == '\0') {
236                         data++;
237                         continue;
238                 }
239                 if (strncmp(data, var, vlen) == 0 && data[vlen] == '=') {
240                         p = data + vlen + 1;
241                         break;
242                 }
243                 data += strlen(data) + 1;
244         }
245         if (p != NULL && *p != '\0')
246                 return p;
247         return NULL;
248 }
249
250 const char *
251 dhcpcd_get_value(const DHCPCD_IF *i, const char *var)
252 {
253
254         assert(i);
255         assert(var);
256         return get_value(i->data, i->data_len, var);
257 }
258
259 ssize_t
260 dhcpcd_encode_string_escape(char *dst, size_t len, const char *src, size_t slen)
261 {
262         const char *end;
263         size_t bytes;
264         int c;
265
266         end = src + slen;
267         bytes = 0;
268         while (src < end) {
269                 c = *src++;
270                 if ((c == '\\' || !isascii(c) || !isprint(c))) {
271                         if (c == '\\') {
272                                 if (dst) {
273                                         if (len  == 0 || len == 1) {
274                                                 errno = ENOSPC;
275                                                 return -1;
276                                         }
277                                         *dst++ = '\\'; *dst++ = '\\';
278                                         len -= 2;
279                                 }
280                                 bytes += 2;
281                                 continue;
282                         }
283                         if (dst) {
284                                 if (len < 5) {
285                                         errno = ENOSPC;
286                                         return -1;
287                                 }
288                                 *dst++ = '\\';
289                                 *dst++ = (((unsigned char)c >> 6) & 03) + '0';
290                                 *dst++ = (((unsigned char)c >> 3) & 07) + '0';
291                                 *dst++ = ( (unsigned char)c       & 07) + '0';
292                                 len -= 4;
293                         }
294                         bytes += 4;
295                 } else {
296                         if (dst) {
297                                 if (len == 0) {
298                                         errno = ENOSPC;
299                                         return -1;
300                                 }
301                                 *dst++ = (char)c;
302                                 len--;
303                         }
304                         bytes++;
305                 }
306         }
307
308         if (dst) {
309                 if (len == 0) {
310                         errno = ENOSPC;
311                         return -1;
312                 }
313                 *dst = '\0';
314         }
315
316         return (ssize_t)bytes;
317 }
318
319 ssize_t
320 dhcpcd_decode_string_escape(char *dst, size_t dlen, const char *src)
321 {
322         char c, esc;
323         int oct;
324         ssize_t bytes;
325
326         bytes = 0;
327         for (;;) {
328                 c = *src++;
329                 if (c == '\0')
330                         break;
331                 if (dst && --dlen == 0) {
332                         errno = ENOSPC;
333                         return -1;
334                 }
335                 switch (c) {
336                 case '\\':
337                         if (*src == '\0') {
338                                 errno = EINVAL;
339                                 return -1;
340                         }
341                         esc = *src++;
342                         switch (esc) {
343                         case '\\':
344                         case '0':
345                         case '1':
346                         case '3':
347                         case '4':
348                         case '5':
349                         case '6':
350                         case '7':
351                                 oct = esc - '0';
352                                 if (*src >= '0' && *src <='7')
353                                         oct = oct * 8 + (*src++ - '0');
354                                 else {
355                                         errno = EINVAL;
356                                         return -1;
357                                 }
358                                 if (*src >= '0' && *src <='7')
359                                         oct = oct * 8 + (*src++ - '0');
360                                 else {
361                                         errno = EINVAL;
362                                         return -1;
363                                 }
364                                 if (dst)
365                                         *dst++ = (char)oct;
366                         default:
367                                 errno = EINVAL;
368                                 return -1;
369                         }
370                         break;
371                 default:
372                         if (dst)
373                                 *dst++ = c;
374                 }
375                 bytes++;
376         }
377
378         if (dst) {
379                 if (--dlen == 0) {
380                         errno = ENOSPC;
381                         return -1;
382                 }
383                 *dst = '\0';
384         }
385         return bytes;
386 }
387
388 ssize_t
389 dhcpcd_decode_hex(char *dst, size_t dlen, const char *src)
390 {
391         size_t bytes, i;
392         char c;
393         int val, n;
394
395         bytes = 0;
396         while (*src) {
397                 if (dlen == 0 || dlen == 1) {
398                         errno = ENOSPC;
399                         return -1;
400                 }
401                 val = 0;
402                 for (i = 0; i < 2; i++) {
403                         c = *src++;
404                         if (c >= '0' && c <= '9')
405                                 n = c - '0';
406                         else if (c >= 'a' && c <= 'f')
407                                 n = 10 + c - 'a';
408                         else if (c >= 'A' && c <= 'F')
409                                 n = 10 + c - 'A';
410                         else {
411                                 errno = EINVAL;
412                                 return -1;
413                         }
414                         val = val * 16 + n;
415                 }
416                 *dst++ = (char)val;
417                 bytes += 2;
418                 dlen -= 2;
419                 if (*src == ':')
420                         src++;
421         }
422         return (ssize_t)bytes;
423 }
424
425 const char *
426 dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
427 {
428         char pvar[128], *p;
429         size_t plen, l;
430
431         assert(i);
432         assert(prefix);
433         assert(var);
434
435         p = pvar;
436         plen = sizeof(pvar);
437         l = strlcpy(p, prefix, plen);
438         if (l >= sizeof(pvar)) {
439                 errno = ENOBUFS;
440                 return NULL;
441         }
442         p += l;
443         plen -= l;
444         if (strlcpy(p, var, plen) >= plen) {
445                 errno = ENOBUFS;
446                 return NULL;
447         }
448         return dhcpcd_get_value(i, pvar);
449 }
450
451 static bool
452 strtobool(const char *var)
453 {
454
455         if (var == NULL)
456                 return false;
457
458          return (*var == '0' || *var == '\0' ||
459             strcmp(var, "false") == 0 ||
460             strcmp(var, "no") == 0) ? false : true;
461 }
462
463 static const char *
464 get_status(DHCPCD_CONNECTION *con)
465 {
466         DHCPCD_IF *i;
467         const char *status;
468
469         assert(con);
470         if (con->command_fd == -1)
471                 return "down";
472
473         if (con->listen_fd == -1)
474                 return "opened";
475
476         if (con->interfaces == NULL)
477                 return "initialised";
478
479         status = "disconnected";
480         for (i = con->interfaces; i; i = i->next) {
481                 if (i->up) {
482                         if (strcmp(i->type, "link")) {
483                                 status = "connected";
484                                 break;
485                         } else
486                                 status = "connecting";
487                 }
488         }
489         return status;
490 }
491
492 static void
493 update_status(DHCPCD_CONNECTION *con, const char *nstatus)
494 {
495
496         assert(con);
497         if (nstatus == NULL)
498                 nstatus = get_status(con);
499         if (con->status == NULL || strcmp(nstatus, con->status)) {
500                 con->status = nstatus;
501                 if (con->status_cb)
502                         con->status_cb(con, con->status, con->status_context);
503         }
504 }
505
506 DHCPCD_IF *
507 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
508 {
509
510         assert(con);
511         return con->interfaces;
512 }
513
514 char **
515 dhcpcd_interface_names(DHCPCD_CONNECTION *con, size_t *nnames)
516 {
517         char **names;
518         size_t n;
519         DHCPCD_IF *i;
520
521         assert(con);
522         if (con->interfaces == NULL)
523                 return NULL;
524
525         n = 0;
526         for (i = con->interfaces; i; i = i->next) {
527                 if (strcmp(i->type, "link") == 0)
528                         n++;
529         }
530         names = malloc(sizeof(char *) * (n + 1));
531         if (names == NULL)
532                 return NULL;
533         n = 0;
534         for (i = con->interfaces; i; i = i->next) {
535                 if (strcmp(i->type, "link") == 0) {
536                         names[n] = strdup(i->ifname);
537                         if (names[n] == NULL) {
538                                 dhcpcd_freev(names);
539                                 return NULL;
540                         }
541                         n++;
542                 }
543         }
544         names[n] = NULL;
545         if (nnames)
546                 *nnames = n;
547
548         return names;
549 }
550
551 void
552 dhcpcd_freev(char **argv)
553 {
554         char **v;
555
556         if (argv) {
557                 for (v = argv; *v; v++)
558                         free(*v);
559                 free(argv);
560         }
561 }
562
563 static int
564 dhcpcd_cmpstring(const void *p1, const void *p2)
565 {
566         const char *s1, *s2;
567         int cmp;
568
569         s1 = *(char * const *)p1;
570         s2 = *(char * const *)p2;
571         if ((cmp = strcasecmp(s1, s2)) == 0)
572                 cmp = strcmp(s1, s2);
573         return cmp;
574 }
575
576 char **
577 dhcpcd_interface_names_sorted(DHCPCD_CONNECTION *con)
578 {
579         char **names;
580         size_t nnames;
581
582         names = dhcpcd_interface_names(con, &nnames);
583         if (names)
584                 qsort(names, nnames, sizeof(char *), dhcpcd_cmpstring);
585         return names;
586 }
587
588 DHCPCD_IF *
589 dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
590 {
591         DHCPCD_IF *i;
592
593         assert(con);
594         assert(ifname);
595         assert(type);
596
597         for (i = con->interfaces; i; i = i->next)
598                 if (strcmp(i->ifname, ifname) == 0 &&
599                     strcmp(i->type, type) == 0)
600                         return i;
601         return NULL;
602 }
603
604 static DHCPCD_IF *
605 dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
606 {
607         const char *ifname, *ifclass, *reason, *type, *order, *flags;
608         char *orderdup, *o, *p;
609         DHCPCD_IF *e, *i, *l, *n, *nl;
610         int ti;
611         bool addedi;
612
613 #if 0
614         char *dp = data, *de = data + len;
615         while (dp < de) {
616                 printf ("XX: %s\n", dp);
617                 dp += strlen(dp) + 1;
618         }
619 #endif
620
621         ifname = get_value(data, len, "interface");
622         if (ifname == NULL || *ifname == '\0') {
623                 errno = ESRCH;
624                 return NULL;
625         }
626         reason = get_value(data, len, "reason");
627         if (reason == NULL || *reason == '\0') {
628                 errno = ESRCH;
629                 return NULL;
630         }
631         ifclass = get_value(data, len, "ifclass");
632         /* Skip pseudo interfaces */
633         if (ifclass && *ifclass != '\0') {
634                 errno = ENOTSUP;
635                 return NULL;
636         }
637         if (strcmp(reason, "RECONFIGURE") == 0 ||
638             strcmp(reason, "INFORM") == 0 || strcmp(reason, "INFORM6") == 0)
639         {
640                 errno = ENOTSUP;
641                 return NULL;
642         }
643         order = get_value(data, len, "interface_order");
644         if (order == NULL || *order == '\0') {
645                 errno = ESRCH;
646                 return NULL;
647         }
648
649         if (strcmp(reason, "PREINIT") == 0 ||
650             strcmp(reason, "UNKNOWN") == 0 ||
651             strcmp(reason, "CARRIER") == 0 ||
652             strcmp(reason, "NOCARRIER") == 0 ||
653             strcmp(reason, "DEPARTED") == 0 ||
654             strcmp(reason, "STOPPED") == 0)
655                 type = "link";
656         else if (strcmp(reason, "ROUTERADVERT") == 0)
657                 type = "ra";
658         else if (reason[strlen(reason) - 1] == '6')
659                 type = "dhcp6";
660         else
661                 type = "ipv4";
662
663         i = NULL;
664        /* Remove all instances on carrier drop */
665         if (strcmp(reason, "NOCARRIER") == 0 ||
666             strcmp(reason, "DEPARTED") == 0 ||
667             strcmp(reason, "STOPPED") == 0)
668         {
669                 l = NULL;
670                 for (e = con->interfaces; e; e = n) {
671                         n = e->next;
672                         if (strcmp(e->ifname, ifname) == 0) {
673                                 if (strcmp(e->type, type) == 0)
674                                         l = i = e;
675                                 else {
676                                         if (l)
677                                                 l->next = e->next;
678                                         else
679                                                 con->interfaces = e->next;
680                                         free(e);
681                                 }
682                         } else
683                                 l = e;
684                 }
685         } else if (strcmp(type, "link")) {
686                 /* If link is down, ignore it */
687                 e = dhcpcd_get_if(con, ifname, "link");
688                 if (e && !e->up)
689                         return NULL;
690         }
691
692         orderdup = strdup(order);
693         if (orderdup == NULL)
694                 return NULL;
695
696         /* Find our pointer */
697         if (i == NULL) {
698                 l = NULL;
699                 for (e = con->interfaces; e; e = e->next) {
700                         if (strcmp(e->ifname, ifname) == 0 &&
701                             strcmp(e->type, type) == 0)
702                         {
703                                 i = e;
704                                 break;
705                         }
706                         l = e;
707                 }
708         }
709         if (i == NULL) {
710                 i = malloc(sizeof(*i));
711                 if (i == NULL) {
712                         free(orderdup);
713                         return NULL;
714                 }
715                 if (l)
716                         l->next = i;
717                 else
718                         con->interfaces = i;
719                 i->next = NULL;
720                 i->last_message = NULL;
721         } else
722                 free(i->data);
723
724         /* Now fill out our interface structure */
725         i->con = con;
726         i->data = data;
727         i->data_len = len;
728         i->ifname = ifname;
729         i->type = type;
730         i->reason = reason;
731         flags = dhcpcd_get_value(i, "ifflags");
732         if (flags)
733                 i->flags = (unsigned int)strtoul(flags, NULL, 0);
734         else
735                 i->flags = 0;
736         if (strcmp(reason, "CARRIER") == 0 ||
737             strcmp(reason, "DELEGATED6") == 0)
738                 i->up = true;
739         else
740                 i->up = strtobool(dhcpcd_get_value(i, "if_up"));
741         i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless"));
742         i->ssid = dhcpcd_get_value(i, "ifssid");
743         if (i->ssid == NULL && i->wireless)
744                 i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid" : "old_ssid");
745
746        /* Sort! */
747         n = nl = NULL;
748         p = orderdup;
749         addedi = false;
750         while ((o = strsep(&p, " ")) != NULL) {
751                 for (ti = 0; dhcpcd_types[ti]; ti++) {
752                         l = NULL;
753                         for (e = con->interfaces; e; e = e->next) {
754                                 if (strcmp(e->ifname, o) == 0 &&
755                                     strcmp(e->type, dhcpcd_types[ti]) == 0)
756                                         break;
757                                 l = e;
758                         }
759                         if (e == NULL)
760                                 continue;
761                         if (i == e)
762                                 addedi = true;
763                         if (l)
764                                 l->next = e->next;
765                         else
766                                 con->interfaces = e->next;
767                         e->next = NULL;
768                         if (nl == NULL)
769                                 n = nl = e;
770                         else {
771                                 nl->next = e;
772                                 nl = e;
773                         }
774                 }
775         }
776         free(orderdup);
777         /* Free any stragglers */
778         while (con->interfaces) {
779                 e = con->interfaces->next;
780                 free(con->interfaces->data);
781                 free(con->interfaces->last_message);
782                 free(con->interfaces);
783                 con->interfaces = e;
784         }
785         con->interfaces = n;
786
787         return addedi ? i : NULL;
788 }
789
790 static DHCPCD_IF *
791 dhcpcd_read_if(DHCPCD_CONNECTION *con, int fd)
792 {
793         char sbuf[sizeof(size_t)], *rbuf;
794         size_t len;
795         ssize_t bytes;
796         DHCPCD_IF *i;
797
798         bytes = read(fd, sbuf, sizeof(sbuf));
799         if (bytes == 0 || bytes == -1) {
800                 dhcpcd_close(con);
801                 return NULL;
802         }
803         memcpy(&len, sbuf, sizeof(len));
804         rbuf = malloc(len + 1);
805         if (rbuf == NULL)
806                 return NULL;
807         bytes = read(fd, rbuf, len);
808         if (bytes == 0 || bytes == -1) {
809                 free(rbuf);
810                 dhcpcd_close(con);
811                 return NULL;
812         }
813         if ((size_t)bytes != len) {
814                 free(rbuf);
815                 errno = EINVAL;
816                 return NULL;
817         }
818         rbuf[bytes] = '\0';
819
820         i = dhcpcd_new_if(con, rbuf, len);
821         if (i == NULL)
822                 free(rbuf);
823         return i;
824 }
825
826 static void
827 dhcpcd_dispatchif(DHCPCD_IF *i)
828 {
829
830         assert(i);
831         if (i->con->if_cb)
832                 i->con->if_cb(i, i->con->if_context);
833         dhcpcd_wpa_if_event(i);
834 }
835
836 void
837 dhcpcd_dispatch(DHCPCD_CONNECTION *con)
838 {
839         DHCPCD_IF *i;
840
841         assert(con);
842         i = dhcpcd_read_if(con, con->listen_fd);
843         if (i)
844                 dhcpcd_dispatchif(i);
845
846         /* Have to call update_status last as it could
847          * cause the interface to be destroyed. */
848         update_status(con, NULL);
849 }
850
851 DHCPCD_CONNECTION *
852 dhcpcd_new(void)
853 {
854         DHCPCD_CONNECTION *con;
855
856         con = calloc(1, sizeof(*con));
857         con->command_fd = con->listen_fd = -1;
858         con->open = false;
859         con->progname = "libdhcpcd";
860         return con;
861 }
862
863 void
864 dhcpcd_set_progname(DHCPCD_CONNECTION *con, const char *progname)
865 {
866
867         assert(con);
868         con->progname = progname;
869 }
870
871 const char *
872 dhcpcd_get_progname(const DHCPCD_CONNECTION *con)
873 {
874
875         assert(con);
876         return con->progname;
877 }
878
879 #ifndef HAVE_STRVERSCMP
880 /* Good enough for our needs */
881 static int
882 strverscmp(const char *s1, const char *s2)
883 {
884         int s1maj, s1min, s1part;
885         int s2maj, s2min, s2part;
886         int r;
887
888         s1min = s1part = 0;
889         if (sscanf(s1, "%d.%d.%d", &s1maj, &s1min, &s1part) < 1)
890                 return -1;
891         s2min = s2part = 0;
892         if (sscanf(s2, "%d.%d.%d", &s2maj, &s2min, &s2part) < 1)
893                 return -1;
894         r = s1maj - s2maj;
895         if (r != 0)
896                 return r;
897         r = s1min - s2min;
898         if (r != 0)
899                 return r;
900         return s1part - s2part;
901 }
902 #endif
903
904 int
905 dhcpcd_open(DHCPCD_CONNECTION *con, bool privileged)
906 {
907         const char *path = privileged ? DHCPCD_SOCKET : DHCPCD_UNPRIV_SOCKET;
908         char cmd[128];
909         ssize_t bytes;
910         size_t nifs, n;
911
912         assert(con);
913         if (con->open) {
914                 if (con->listen_fd != -1)
915                         return con->listen_fd;
916                 errno = EISCONN;
917                 return -1;
918         }
919         /* We need to block the command fd */
920         con->command_fd = dhcpcd_connect(path, 0);
921         if (con->command_fd == -1)
922                 goto err_exit;
923
924         con->terminate_commands = false;
925         if (dhcpcd_ctrl_command(con, "--version", &con->version) <= 0)
926                 goto err_exit;
927         con->terminate_commands =
928             strverscmp(con->version, "6.4.1") >= 0 ? true : false;
929
930         if (dhcpcd_ctrl_command(con, "--getconfigfile", &con->cffile) <= 0)
931                 goto err_exit;
932
933         con->open = true;
934         con->privileged = privileged;
935         update_status(con, NULL);
936
937         con->listen_fd = dhcpcd_connect(path, SOCK_NONBLOCK);
938         if (con->listen_fd == -1)
939                 goto err_exit;
940
941         dhcpcd_command_fd(con, con->listen_fd, false, "--listen", NULL);
942         dhcpcd_command_fd(con, con->command_fd, false, "--getinterfaces", NULL);
943         bytes = read(con->command_fd, cmd, sizeof(nifs));
944         if (bytes != sizeof(nifs))
945                 goto err_exit;
946         memcpy(&nifs, cmd, sizeof(nifs));
947         /* We don't dispatch each interface here as that
948          * causes too much notification spam when the GUI starts */
949         for (n = 0; n < nifs; n++)
950                 dhcpcd_read_if(con, con->command_fd);
951
952         update_status(con, NULL);
953
954         return con->listen_fd;
955
956 err_exit:
957         dhcpcd_close(con);
958         return -1;
959 }
960
961 int
962 dhcpcd_get_fd(DHCPCD_CONNECTION *con)
963 {
964
965         assert(con);
966         return con->listen_fd;
967 }
968
969 bool
970 dhcpcd_privileged(DHCPCD_CONNECTION *con)
971 {
972
973         assert(con);
974         return con->privileged;
975 }
976
977 const char *
978 dhcpcd_status(DHCPCD_CONNECTION *con)
979 {
980
981         assert(con);
982         return con->status;
983 }
984
985 const char *
986 dhcpcd_version(DHCPCD_CONNECTION *con)
987 {
988
989         assert(con);
990         return con->version;
991 }
992
993 const char *
994 dhcpcd_cffile(DHCPCD_CONNECTION *con)
995 {
996
997         assert(con);
998         return con->cffile;
999 }
1000
1001 void
1002 dhcpcd_set_if_callback(DHCPCD_CONNECTION *con,
1003     void (*cb)(DHCPCD_IF *, void *), void *ctx)
1004 {
1005
1006         assert(con);
1007         con->if_cb = cb;
1008         con->if_context = ctx;
1009 }
1010
1011 void
1012 dhcpcd_set_status_callback(DHCPCD_CONNECTION *con,
1013     void (*cb)(DHCPCD_CONNECTION *, const char *, void *), void *ctx)
1014 {
1015
1016         assert(con);
1017         con->status_cb = cb;
1018         con->status_context = ctx;
1019 }
1020
1021 void
1022 dhcpcd_close(DHCPCD_CONNECTION *con)
1023 {
1024         DHCPCD_IF *nif;
1025         DHCPCD_WPA *nwpa;
1026         DHCPCD_WI_HIST *nh;
1027
1028         assert(con);
1029
1030         con->open = false;
1031
1032         /* Shut down WPA listeners as they aren't much good without dhcpcd.
1033          * They'll be restarted anyway when dhcpcd comes back up. */
1034         while (con->wpa) {
1035                 nwpa = con->wpa->next;
1036                 dhcpcd_wpa_close(con->wpa);
1037                 free(con->wpa);
1038                 con->wpa = nwpa;
1039         }
1040         while (con->wi_history) {
1041                 nh = con->wi_history->next;
1042                 free(con->wi_history);
1043                 con->wi_history = nh;
1044         }
1045         while (con->interfaces) {
1046                 nif = con->interfaces->next;
1047                 free(con->interfaces->data);
1048                 free(con->interfaces->last_message);
1049                 free(con->interfaces);
1050                 con->interfaces = nif;
1051         }
1052
1053         if (con->command_fd != -1)
1054                 shutdown(con->command_fd, SHUT_RDWR);
1055         if (con->listen_fd != -1)
1056                 shutdown(con->listen_fd, SHUT_RDWR);
1057
1058         update_status(con, "down");
1059
1060         if (con->command_fd != -1) {
1061                 close(con->command_fd);
1062                 con->command_fd = -1;
1063         }
1064         if (con->listen_fd != -1) {
1065                 close(con->listen_fd);
1066                 con->listen_fd = -1;
1067         }
1068
1069         if (con->cffile) {
1070                 free(con->cffile);
1071                 con->cffile = NULL;
1072         }
1073         if (con->version) {
1074                 free(con->version);
1075                 con->version = NULL;
1076         }
1077         if (con->buf) {
1078                 free(con->buf);
1079                 con->buf = NULL;
1080                 con->buflen = 0;
1081         }
1082 }
1083
1084 void
1085 dhcpcd_free(DHCPCD_CONNECTION *con)
1086 {
1087
1088         assert(con);
1089         free(con);
1090 }
1091
1092 DHCPCD_CONNECTION *
1093 dhcpcd_if_connection(DHCPCD_IF *i)
1094 {
1095
1096         assert(i);
1097         return i->con;
1098 }
1099
1100 char *
1101 dhcpcd_if_message(DHCPCD_IF *i, bool *new_msg)
1102 {
1103         const char *ip, *iplen, *pfx;
1104         char *msg, *p;
1105         const char *reason = NULL;
1106         size_t len;
1107         bool showssid;
1108
1109         assert(i);
1110         /* Don't report non SLAAC configurations */
1111         if (strcmp(i->type, "ra") == 0 && i->up &&
1112             dhcpcd_get_value(i, "ra1_prefix") == NULL)
1113                 return NULL;
1114
1115         showssid = false;
1116         if (strcmp(i->reason, "EXPIRE") == 0)
1117                 reason = _("Expired");
1118         else if (strcmp(i->reason, "CARRIER") == 0) {
1119                 if (i->wireless) {
1120                         showssid = true;
1121                         reason = _("Associated with");
1122                 } else {
1123                         /* Don't report able in if we have addresses */
1124                         const DHCPCD_IF *ci;
1125
1126                         for (ci = i->con->interfaces; ci; ci = ci->next) {
1127                                 if (ci != i &&
1128                                     strcmp(i->ifname, ci->ifname) == 0 &&
1129                                     ci->up)
1130                                         break;
1131                         }
1132                         if (ci)
1133                                 return NULL;
1134                         reason = _("Link is up, configuring");
1135                 }
1136         } else if (strcmp(i->reason, "NOCARRIER") == 0) {
1137                 if (i->wireless) {
1138                         if (i->ssid) {
1139                                 reason = _("Disassociated from");
1140                                 showssid = true;
1141                         } else
1142                                 reason = _("Not associated");
1143                 } else
1144                         reason = _("Link is down");
1145         } else if (strcmp(i->reason, "DEPARTED") == 0)
1146                 reason = _("Departed");
1147         else if (strcmp(i->reason, "UNKNOWN") == 0)
1148                 reason = _("Unknown link state");
1149         else if (strcmp(i->reason, "FAIL") == 0)
1150                 reason = _("Automatic configuration not possible");
1151         else if (strcmp(i->reason, "3RDPARTY") == 0)
1152                 reason = _("Waiting for 3rd Party configuration");
1153
1154         if (reason == NULL) {
1155                 if (i->up) {
1156                         if (strcmp(i->reason, "DELEGATED6") == 0)
1157                                 reason = _("Delegated");
1158                         else
1159                                 reason = _("Configured");
1160                 } else if (strcmp(i->type, "ra") == 0)
1161                         reason = "Expired RA";
1162                 else
1163                         reason = i->reason;
1164         }
1165
1166         pfx = i->up ? "new_" : "old_";
1167         if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address")))
1168                 iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr");
1169         else if ((ip = dhcpcd_get_value(i, "ra1_addr")))
1170                 iplen = NULL;
1171         else if ((ip = dhcpcd_get_value(i, "ra1_prefix")))
1172                 iplen = NULL;
1173         else if ((ip = dhcpcd_get_prefix_value(i, pfx,
1174             "dhcp6_ia_na1_ia_addr1")))
1175                 iplen = "128";
1176         else if ((ip = dhcpcd_get_prefix_value(i, pfx,
1177             "delegated_dhcp6_prefix")))
1178                 iplen = NULL;
1179         else {
1180                 ip = NULL;
1181                 iplen = NULL;
1182         }
1183
1184         len = strlen(i->ifname) + strlen(reason) + 3;
1185         if (showssid && i->ssid)
1186                 len += strlen(i->ssid) + 1;
1187         if (ip)
1188                 len += strlen(ip) + 1;
1189         if (iplen)
1190                 len += strlen(iplen) + 1;
1191         msg = p = malloc(len);
1192         if (msg == NULL)
1193                 return NULL;
1194         p += snprintf(msg, len, "%s: %s", i->ifname, reason);
1195         if (showssid)
1196                 p += snprintf(p, len - (size_t)(p - msg), " %s", i->ssid);
1197         if (iplen)
1198                 snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
1199         else if (ip)
1200                 snprintf(p, len - (size_t)(p - msg), " %s", ip);
1201
1202         if (new_msg) {
1203                 if (i->last_message == NULL || strcmp(i->last_message, msg))
1204                         *new_msg = true;
1205                 else
1206                         *new_msg = false;
1207         }
1208         free(i->last_message);
1209         i->last_message = strdup(msg);
1210
1211         return msg;
1212 }