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