Re-write libdhcpcd to talk to dhcpcd directly and not use DBus.
[dhcpcd-ui] / src / libdhcpcd / dhcpcd.c
1 /*
2  * libdhcpcd
3  * Copyright 2009-2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26
27 // For strverscmp(3)
28 #define _GNU_SOURCE
29
30 #include <sys/socket.h>
31 #include <sys/stat.h>
32 #include <sys/un.h>
33
34 #include <arpa/inet.h>
35
36 #include <assert.h>
37 #include <errno.h>
38 #include <fcntl.h>
39 #include <libintl.h>
40 #include <limits.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45
46 #define IN_LIBDHCPCD
47
48 #include "config.h"
49 #include "dhcpcd.h"
50
51 #define _ gettext
52
53 #ifndef SUN_LEN
54 #define SUN_LEN(su) \
55         (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
56 #endif
57
58 static const char * const dhcpcd_types[] =
59     { "link", "ipv4", "ra", "dhcp6", NULL };
60
61 static ssize_t
62 dhcpcd_command_fd(DHCPCD_CONNECTION *con,
63     int fd, const char *cmd, char **buffer)
64 {
65         size_t len;
66         ssize_t bytes;
67         char buf[1024], *p;
68         char *nbuf;
69
70         /* Each command is \n terminated.
71          * Each argument is NULL seperated.
72          * We may need to send a space one day, so the API
73          * in this function may need to be improved */
74         len = strlen(cmd) + 1;
75         if (con->terminate_commands)
76                 len++;
77         if (len > sizeof(buf)) {
78                 errno = ENOBUFS;
79                 return -1;
80         }
81         strlcpy(buf, cmd, sizeof(buf));
82         p = buf;
83         while ((p = strchr(p, ' ')) != NULL)
84                 *p++ = '\0';
85         if (con->terminate_commands) {
86                 buf[len - 2] = '\n';
87                 buf[len - 1] = '\0';
88         } else
89                 buf[len - 1] = '\0';
90         if (write(fd, buf, len) == -1)
91                 return -1;
92         if (buffer == NULL)
93                 return 0;
94
95         bytes = read(fd, buf, sizeof(size_t));
96         if (bytes == 0 || bytes == -1)
97                 return bytes;
98         memcpy(&len, buf, sizeof(size_t));
99         nbuf = realloc(*buffer, len + 1);
100         if (nbuf == NULL)
101                 return -1;
102         *buffer = nbuf;
103         bytes = read(fd, *buffer, len);
104         if (bytes != -1 && (size_t)bytes < len)
105                 *buffer[bytes] = '\0';
106         return bytes;
107 }
108
109 ssize_t
110 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
111 {
112
113         return dhcpcd_command_fd(con, con->command_fd, cmd, buffer);
114 }
115
116 bool
117 dhcpcd_realloc(DHCPCD_CONNECTION *con, size_t len)
118 {
119
120         if (con->buflen < len) {
121                 char *nbuf;
122
123                 nbuf = realloc(con->buf, len);
124                 if (nbuf == NULL)
125                         return false;
126                 con->buf = nbuf;
127                 con->buflen = len;
128         }
129         return true;
130 }
131
132 ssize_t
133 dhcpcd_command_arg(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
134     char **buffer)
135 {
136         size_t cmdlen, len;
137
138         cmdlen = strlen(cmd);
139         len = cmdlen + strlen(arg) + 2;
140         if (!dhcpcd_realloc(con, len))
141                 return -1;
142         strlcpy(con->buf, cmd, con->buflen);
143         con->buf[cmdlen] = ' ';
144         strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
145
146         return dhcpcd_command_fd(con, con->command_fd, con->buf, buffer);
147 }
148
149
150 static int
151 dhcpcd_connect(void)
152 {
153         int fd;
154         socklen_t len;
155         struct sockaddr_un sun;
156
157         fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
158         if (fd == -1)
159                 return -1;
160
161         memset(&sun, 0, sizeof(sun));
162         sun.sun_family = AF_UNIX;
163         strlcpy(sun.sun_path, DHCPCD_SOCKET, sizeof(sun.sun_path));
164         len = (socklen_t)SUN_LEN(&sun);
165         if (connect(fd, (struct sockaddr *)&sun, len) == 0)
166                 return fd;
167         close(fd);
168         return -1;
169 }
170
171 static const char *
172 get_value(const char *data, size_t len, const char *var)
173 {
174         const char *end, *p;
175         size_t vlen;
176
177         assert(var);
178         end = data + len;
179         vlen = strlen(var);
180         p = NULL;
181         while (data + vlen + 1 < end) {
182                 if (strncmp(data, var, vlen) == 0) {
183                         p = data + vlen;
184                         break;
185                 }
186                 data += strlen(data) + 1;
187         }
188         if (p != NULL && *p != '\0')
189                 return p;
190         return NULL;
191 }
192
193 const char *
194 dhcpcd_get_value(const DHCPCD_IF *i, const char *var)
195 {
196
197         assert(i);
198         return get_value(i->data, i->data_len, var);
199 }
200
201 const char *
202 dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
203 {
204         char pvar[128], *p;
205         size_t plen, l;
206
207         p = pvar;
208         plen = sizeof(pvar);
209         l = strlcpy(p, prefix, plen);
210         if (l >= sizeof(pvar)) {
211                 errno = ENOBUFS;
212                 return NULL;
213         }
214         p += l;
215         plen -= l;
216         if (strlcpy(p, var, plen) >= plen) {
217                 errno = ENOBUFS;
218                 return NULL;
219         }
220         return dhcpcd_get_value(i, pvar);
221 }
222
223 static bool strtobool(const char *var)
224 {
225
226         if (var == NULL)
227                 return false;
228
229          return (*var == '0' || *var == '\0' ||
230             strcmp(var, "false") == 0 ||
231             strcmp(var, "no") == 0) ? false : true;
232 }
233
234 static const char *
235 get_status(DHCPCD_CONNECTION *con)
236 {
237         DHCPCD_IF *i;
238         const char *status;
239
240         assert(con);
241         if (con->command_fd == -1 || con->listen_fd == -1)
242                 return "down";
243
244         status = "disconnected";
245         for (i = con->interfaces; i; i = i->next) {
246                 if (i->up) {
247                         if (strcmp(i->type, "link")) {
248                                 status = "connected";
249                                 break;
250                         } else
251                                 status = "connecting";
252                 }
253         }
254         return status;
255 }
256
257 static void
258 update_status(DHCPCD_CONNECTION *con)
259 {
260         const char *nstatus;
261
262         assert(con);
263         nstatus = get_status(con);
264         if (con->status == NULL || strcmp(nstatus, con->status)) {
265                 con->status = nstatus;
266                 if (con->status_cb)
267                         con->status_cb(con, con->status, con->status_context);
268         }
269 }
270
271 DHCPCD_IF *
272 dhcpcd_interfaces(DHCPCD_CONNECTION *con)
273 {
274
275         assert(con);
276         return con->interfaces;
277 }
278
279 DHCPCD_IF *
280 dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
281 {
282         DHCPCD_IF *i;
283
284         assert(con);
285         for (i = con->interfaces; i; i = i->next)
286                 if (strcmp(i->ifname, ifname) == 0 &&
287                     strcmp(i->type, type) == 0)
288                         return i;
289         return NULL;
290 }
291
292 static DHCPCD_IF *
293 dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
294 {
295         const char *ifname, *reason, *type, *order, *flags;
296         char *orderdup, *o, *p;
297         DHCPCD_IF *e, *i, *l, *n, *nl;
298         int ti;
299
300         ifname = get_value(data, len, "interface=");
301         if (ifname == NULL || *ifname == '\0') {
302                 errno = ESRCH;
303                 return NULL;
304         }
305         reason = get_value(data, len, "reason=");
306         if (reason == NULL || *reason == '\0') {
307                 errno = ESRCH;
308                 return NULL;
309         }
310         if (strcmp(reason, "RECONFIGURE") == 0) {
311                 errno = ENOTSUP;
312                 return NULL;
313         }
314         order = get_value(data, len, "interface_order=");
315         if (order == NULL || *order == '\0') {
316                 errno = ESRCH;
317                 return NULL;
318         }
319
320         if (strcmp(reason, "PREINIT") == 0 ||
321             strcmp(reason, "UNKNOWN") == 0 ||
322             strcmp(reason, "CARRIER") == 0 ||
323             strcmp(reason, "NOCARRIER") == 0 ||
324             strcmp(reason, "DEPARTED") == 0 ||
325             strcmp(reason, "STOPPED") == 0)
326                 type = "link";
327         else if (strcmp(reason, "ROUTERADVERT") == 0)
328                 type = "ra";
329         else if (reason[strlen(reason) - 1] == '6')
330                 type = "dhcp6";
331         else
332                 type = "ipv4";
333
334         i = NULL;
335        /* Remove all instances on carrier drop */
336         if (strcmp(reason, "NOCARRIER") == 0 ||
337             strcmp(reason, "DEPARTED") == 0 ||
338             strcmp(reason, "STOPPED") == 0)
339         {
340                 l = NULL;
341                 for (e = con->interfaces; e; e = n) {
342                         n = e->next;
343                         if (strcmp(e->ifname, ifname) == 0) {
344                                 if (strcmp(e->type, type) == 0)
345                                         l = i = e;
346                                 else {
347                                         if (l)
348                                                 l->next = e->next;
349                                         else
350                                                 con->interfaces = e->next;
351                                         free(e);
352                                 }
353                         } else
354                                 l = e;
355                 }
356         } else if (strcmp(type, "link")) {
357                 /* If link is down, ignore it */
358                 e = dhcpcd_get_if(con, ifname, "link");
359                 if (e && strcmp(e->reason, "NOCARRIER") == 0)
360                         return NULL;
361         }
362
363         orderdup = strdup(order);
364         if (orderdup == NULL)
365                 return NULL;
366
367         /* Find our pointer */
368         if (i == NULL) {
369                 l = NULL;
370                 for (e = con->interfaces; e; e = e->next) {
371                         if (strcmp(e->ifname, ifname) == 0 &&
372                             strcmp(e->type, type) == 0)
373                         {
374                                 i = e;
375                                 break;
376                         }
377                         l = e;
378                 }
379         }
380         if (i == NULL) {
381                 i = malloc(sizeof(*i));
382                 if (i == NULL) {
383                         free(orderdup);
384                         return NULL;
385                 }
386                 if (l)
387                         l->next = i;
388                 else
389                         con->interfaces = i;
390                 i->next = NULL;
391         } else
392                 free(i->data);
393
394         /* Now fill out our interface structure */
395         i->con = con;
396         i->data = data;
397         i->data_len = len;
398         i->ifname = ifname;
399         i->type = type;
400         i->reason = reason;
401         flags = dhcpcd_get_value(i, "ifflags=");
402         if (flags)
403                 i->flags = (unsigned int)strtoul(flags, NULL, 0);
404         else
405                 i->flags = 0;
406         if (strcmp(reason, "CARRIER") == 0)
407                 i->up = true;
408         else
409                 i->up = strtobool(dhcpcd_get_value(i, "if_up="));
410         i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless="));
411         i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid=" : "old_ssid=");
412
413        /* Sort! */
414         n = nl = NULL;
415         p = orderdup;
416         while ((o = strsep(&p, " ")) != NULL) {
417                 for (ti = 0; dhcpcd_types[ti]; ti++) {
418                         l = NULL;
419                         for (e = con->interfaces; e; e = e->next) {
420                                 if (strcmp(e->ifname, o) == 0 &&
421                                     strcmp(e->type, dhcpcd_types[ti]) == 0)
422                                         break;
423                                 l = e;
424                         }
425                         if (e == NULL)
426                                 continue;
427                         if (l)
428                                 l->next = e->next;
429                         else
430                                 con->interfaces = e->next;
431                         e->next = NULL;
432                         if (nl == NULL)
433                                 n = nl = e;
434                         else {
435                                 nl->next = e;
436                                 nl = e;
437                         }
438                 }
439         }
440         free(orderdup);
441         /* Free any stragglers */
442         while (con->interfaces) {
443                 e = con->interfaces->next;
444                 free(con->interfaces->data);
445                 free(con->interfaces);
446                 con->interfaces = e;
447         }
448         con->interfaces = n;
449
450         return i;
451 }
452
453 static DHCPCD_IF *
454 dhcpcd_read_if(DHCPCD_CONNECTION *con, int fd)
455 {
456         char sbuf[sizeof(size_t)], *rbuf;
457         size_t len;
458         ssize_t bytes;
459         DHCPCD_IF *i;
460
461         bytes = read(fd, sbuf, sizeof(sbuf));
462         if (bytes == 0 || bytes == -1) {
463                 dhcpcd_close(con);
464                 return NULL;
465         }
466         memcpy(&len, sbuf, sizeof(len));
467         rbuf = malloc(len + 1);
468         if (rbuf == NULL)
469                 return NULL;
470         bytes = read(fd, rbuf, len);
471         if (bytes == 0 || bytes == -1) {
472                 free(rbuf);
473                 dhcpcd_close(con);
474                 return NULL;
475         }
476         if ((size_t)bytes != len) {
477                 free(rbuf);
478                 errno = EINVAL;
479                 return NULL;
480         }
481         rbuf[bytes] = '\0';
482
483         i = dhcpcd_new_if(con, rbuf, len);
484         if (i == NULL)
485                 free(rbuf);
486         return i;
487 }
488
489 static void
490 dhcpcd_dispatchif(DHCPCD_IF *i)
491 {
492
493         assert(i);
494         if (i->con->if_cb)
495                 i->con->if_cb(i, i->con->if_context);
496         dhcpcd_wpa_if_event(i);
497 }
498
499 void
500 dhcpcd_dispatch(DHCPCD_CONNECTION *con)
501 {
502         DHCPCD_IF *i;
503
504         assert(con);
505         i = dhcpcd_read_if(con, con->listen_fd);
506         if (i)
507                 dhcpcd_dispatchif(i);
508
509         /* Have to call update_status last as it could
510          * cause the interface to be destroyed. */
511         update_status(con);
512 }
513
514 DHCPCD_CONNECTION *
515 dhcpcd_new(void)
516 {
517         DHCPCD_CONNECTION *con;
518
519         con = calloc(1, sizeof(*con));
520         con->command_fd = con->listen_fd = -1;
521         return con;
522 }
523
524 #ifndef __GLIBC__
525 /* Good enough for our needs */
526 static int
527 strverscmp(const char *s1, const char *s2)
528 {
529         int s1maj, s1min, s1part;
530         int s2maj, s2min, s2part;
531         int r;
532
533         s1min = s1part = 0;
534         if (sscanf(s1, "%d.%d.%d", &s1maj, &s1min, &s1part) < 1)
535                 return -1;
536         s2min = s2part = 0;
537         if (sscanf(s2, "%d.%d.%d", &s2maj, &s2min, &s2part) < 1)
538                 return -1;
539         r = s1maj - s2maj;
540         if (r != 0)
541                 return r;
542         r = s1min - s2min;
543         if (r != 0)
544                 return r;
545         return s1part - s2part;
546 }
547 #endif
548
549 int
550 dhcpcd_open(DHCPCD_CONNECTION *con)
551 {
552         char cmd[128];
553         ssize_t bytes;
554         size_t nifs, n;
555         DHCPCD_IF *i;
556
557         assert(con);
558         if (con->listen_fd != -1)
559                 return con->listen_fd;
560         con->command_fd = dhcpcd_connect();
561         if (con->command_fd == -1) {
562                 update_status(con);
563                 return -1;
564         }
565
566         con->terminate_commands = false;
567         if (dhcpcd_command(con, "--version", &con->version) <= 0)
568                 return -1;
569         con->terminate_commands =
570             strverscmp(con->version, "6.4.1") >= 0 ? true : false;
571
572         if (dhcpcd_command(con, "--getconfigfile", &con->cffile) <= 0)
573                 return -1;
574
575         con->listen_fd = dhcpcd_connect();
576         if (con->listen_fd == -1) {
577                 close(con->command_fd);
578                 con->command_fd = -1;
579                 update_status(con);
580                 return -1;
581         }
582         dhcpcd_command_fd(con, con->listen_fd, "--listen", NULL);
583
584         dhcpcd_command_fd(con, con->command_fd, "--getinterfaces", NULL);
585         bytes = read(con->command_fd, cmd, sizeof(nifs));
586         if (bytes != sizeof(nifs)) {
587                 close(con->command_fd);
588                 con->command_fd = -1;
589                 close(con->listen_fd);
590                 con->listen_fd = -1;
591         } else {
592                 memcpy(&nifs, cmd, sizeof(nifs));
593                 /* We don't dispatch each interface here as that
594                  * causes too much notification spam when the GUI starts */
595                 for (n = 0; n < nifs; n++) {
596                         i = dhcpcd_read_if(con, con->command_fd);
597                         if (i)
598                                 dhcpcd_wpa_if_event(i);
599                 }
600         }
601         update_status(con);
602
603         return con->listen_fd;
604 }
605
606 int
607 dhcpcd_get_fd(DHCPCD_CONNECTION *con)
608 {
609
610         assert(con);
611         return con->listen_fd;
612 }
613
614 const char *
615 dhcpcd_status(DHCPCD_CONNECTION *con)
616 {
617
618         assert(con);
619         return con->status;
620 }
621
622 const char *
623 dhcpcd_version(DHCPCD_CONNECTION *con)
624 {
625
626         assert(con);
627         return con->version;
628 }
629
630 const char *
631 dhcpcd_cffile(DHCPCD_CONNECTION *con)
632 {
633
634         assert(con);
635         return con->cffile;
636 }
637
638 void
639 dhcpcd_set_if_callback(DHCPCD_CONNECTION *con,
640     void (*cb)(DHCPCD_IF *, void *), void *ctx)
641 {
642
643         assert(con);
644         con->if_cb = cb;
645         con->if_context = ctx;
646 }
647
648 void
649 dhcpcd_set_status_callback(DHCPCD_CONNECTION *con,
650     void (*cb)(DHCPCD_CONNECTION *, const char *, void *), void *ctx)
651 {
652
653         assert(con);
654         con->status_cb = cb;
655         con->status_context = ctx;
656 }
657
658 void
659 dhcpcd_close(DHCPCD_CONNECTION *con)
660 {
661         DHCPCD_WPA *wpa;
662
663         assert(con);
664
665         /* Shut down WPA listeners as they aren't much good without dhcpcd.
666          * They'll be restarted anyway when dhcpcd comes back up. */
667         for (wpa = con->wpa; wpa; wpa = wpa->next)
668                 dhcpcd_wpa_close(con->wpa);
669
670         if (con->command_fd != -1) {
671                 shutdown(con->command_fd, SHUT_RDWR);
672                 con->command_fd = -1;
673         }
674         if (con->listen_fd != -1) {
675                 shutdown(con->listen_fd, SHUT_RDWR);
676                 con->listen_fd = -1;
677         }
678
679         if (con->cffile) {
680                 free(con->cffile);
681                 con->cffile = NULL;
682         }
683         if (con->version) {
684                 free(con->version);
685                 con->version = NULL;
686         }
687         if (con->buf) {
688                 free(con->buf);
689                 con->buf = NULL;
690                 con->buflen = 0;
691         }
692 }
693
694 DHCPCD_CONNECTION *
695 dhcpcd_if_connection(DHCPCD_IF *i)
696 {
697
698         assert(i);
699         return i->con;
700 }
701
702 char *
703 dhcpcd_if_message(const DHCPCD_IF *i)
704 {
705         const char *ip, *iplen, *pfx;
706         char *msg, *p;
707         const char *reason = NULL;
708         size_t len;
709         bool showssid;
710
711         assert(i);
712         /* Don't report non SLAAC configurations */
713         if (strcmp(i->type, "ra") == 0 && i->up &&
714             dhcpcd_get_value(i, "ra1_prefix=") == NULL)
715                 return NULL;
716
717         showssid = false;
718         if (strcmp(i->reason, "EXPIRE") == 0)
719                 reason = _("Expired");
720         else if (strcmp(i->reason, "CARRIER") == 0) {
721                 if (i->wireless) {
722                         showssid = true;
723                         reason = _("Associated with");
724                 } else
725                         reason = _("Cable plugged in");
726         } else if (strcmp(i->reason, "NOCARRIER") == 0) {
727                 if (i->wireless) {
728                         if (i->ssid) {
729                                 reason = _("Disassociated from");
730                                 showssid = true;
731                         } else
732                                 reason = _("Not associated");
733                 } else
734                         reason = _("Cable unplugged");
735         } else if (strcmp(i->reason, "UNKNOWN") == 0)
736                 reason = _("Unknown link state");
737         else if (strcmp(i->reason, "FAIL") == 0)
738                 reason = _("Automatic configuration not possible");
739         else if (strcmp(i->reason, "3RDPARTY") == 0)
740                 reason = _("Waiting for 3rd Party configuration");
741
742         if (reason == NULL) {
743                 if (i->up)
744                         reason = _("Configured");
745                 else if (strcmp(i->type, "ra") == 0)
746                         reason = "Expired RA";
747                 else
748                         reason = i->reason;
749         }
750
751         pfx = i->up ? "new_" : "old_";
752         if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address=")))
753                 iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr=");
754         else if ((ip = dhcpcd_get_value(i, "ra1_prefix=")))
755                 iplen = NULL;
756         else if ((ip = dhcpcd_get_prefix_value(i, pfx,
757             "dhcp6_ia_na1_ia_addr1=")))
758                 iplen = "128";
759         else {
760                 ip = NULL;
761                 iplen = NULL;
762         }
763
764         len = strlen(i->ifname) + strlen(reason) + 3;
765         if (showssid && i->ssid)
766                 len += strlen(i->ssid) + 1;
767         if (ip)
768                 len += strlen(ip) + 1;
769         if (iplen)
770                 len += strlen(iplen) + 1;
771         msg = p = malloc(len);
772         if (msg == NULL)
773                 return NULL;
774         p += snprintf(msg, len, "%s: %s", i->ifname, reason);
775         if (showssid)
776                 p += snprintf(p, len - (size_t)(p - msg), " %s", i->ssid);
777         if (iplen)
778                 p += snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
779         else if (ip)
780                 p += snprintf(p, len - (size_t)(p - msg), " %s", ip);
781         return msg;
782 }
783
784 void
785 dhcpcd_free(DHCPCD_CONNECTION *con)
786 {
787         DHCPCD_IF *nif;
788         DHCPCD_WPA *nwpa;
789
790         assert(con);
791         while (con->interfaces) {
792                 nif = con->interfaces->next;
793                 free(con->interfaces->data);
794                 free(con->interfaces);
795                 con->interfaces = nif;
796         }
797         while (con->wpa) {
798                 nwpa = con->wpa->next;
799                 dhcpcd_wpa_close(con->wpa);
800                 free(con->wpa);
801                 con->wpa = nwpa;
802         }
803         free(con);
804 }