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