Update copyrights
[dhcpcd-ui] / src / libdhcpcd / dhcpcd.c
index 3876d0ff7e54cb8948a052f14631cd2fe96588eb..c8bdd14fbfd8cb48cd54b610fbc3dc06c77a55ab 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * libdhcpcd
- * Copyright 2009-2014 Roy Marples <roy@marples.name>
+ * Copyright 2009-2015 Roy Marples <roy@marples.name>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -34,9 +34,9 @@
 #include <arpa/inet.h>
 
 #include <assert.h>
+#include <ctype.h>
 #include <errno.h>
 #include <fcntl.h>
-#include <libintl.h>
 #include <limits.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include "config.h"
 #include "dhcpcd.h"
 
+#ifdef HAS_GETTEXT
+#include <libintl.h>
 #define _ gettext
+#else
+#define _(a) (a)
+#endif
+
+#ifdef HAVE_VIS_H
+#include <vis.h>
+#endif
 
 #ifndef SUN_LEN
 #define SUN_LEN(su) \
        (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
 #endif
 
+#ifndef iswhite
+#define iswhite(c)     (c == ' ' || c == '\t' || c == '\n')
+#endif
+
 static const char * const dhcpcd_types[] =
     { "link", "ipv4", "ra", "dhcp6", NULL };
 
 static ssize_t
 dhcpcd_command_fd(DHCPCD_CONNECTION *con,
-    int fd, const char *cmd, char **buffer)
+    int fd, bool progname, const char *cmd, char **buffer)
 {
-       size_t len;
+       size_t pl, cl, len;
        ssize_t bytes;
        char buf[1024], *p;
        char *nbuf;
 
+       assert(con);
+       assert(cmd);
+
        /* Each command is \n terminated.
         * Each argument is NULL seperated.
         * We may need to send a space one day, so the API
         * in this function may need to be improved */
-       len = strlen(cmd) + 1;
+       cl = strlen(cmd);
+       if (progname) {
+               pl = strlen(con->progname);
+               len = pl + 1 + cl + 1;
+       } else {
+               pl = 0;
+               len = cl + 1;
+       }
        if (con->terminate_commands)
                len++;
        if (len > sizeof(buf)) {
                errno = ENOBUFS;
                return -1;
        }
-       strlcpy(buf, cmd, sizeof(buf));
        p = buf;
+       if (progname) {
+               memcpy(buf, con->progname, pl);
+               buf[pl] = '\0';
+               p = buf + pl + 1;
+       }
+       memcpy(p, cmd, cl);
+       p[cl] = '\0';
        while ((p = strchr(p, ' ')) != NULL)
                *p++ = '\0';
        if (con->terminate_commands) {
@@ -110,13 +139,26 @@ ssize_t
 dhcpcd_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
 {
 
-       return dhcpcd_command_fd(con, con->command_fd, cmd, buffer);
+       assert(con);
+       if (!con->privileged) {
+               errno = EACCES;
+               return -1;
+       }
+       return dhcpcd_command_fd(con, con->command_fd, true, cmd, buffer);
+}
+
+static ssize_t
+dhcpcd_ctrl_command(DHCPCD_CONNECTION *con, const char *cmd, char **buffer)
+{
+
+       return dhcpcd_command_fd(con, con->command_fd, false, cmd, buffer);
 }
 
 bool
 dhcpcd_realloc(DHCPCD_CONNECTION *con, size_t len)
 {
 
+       assert(con);
        if (con->buflen < len) {
                char *nbuf;
 
@@ -135,32 +177,41 @@ dhcpcd_command_arg(DHCPCD_CONNECTION *con, const char *cmd, const char *arg,
 {
        size_t cmdlen, len;
 
+       assert(con);
+       assert(cmd);
+
        cmdlen = strlen(cmd);
-       len = cmdlen + strlen(arg) + 2;
+       if (arg)
+               len = cmdlen + strlen(arg) + 2;
+       else
+               len = cmdlen + 1;
        if (!dhcpcd_realloc(con, len))
                return -1;
        strlcpy(con->buf, cmd, con->buflen);
-       con->buf[cmdlen] = ' ';
-       strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
+       if (arg) {
+               con->buf[cmdlen] = ' ';
+               strlcpy(con->buf + cmdlen + 1, arg, con->buflen - 1 - cmdlen);
+       }
 
-       return dhcpcd_command_fd(con, con->command_fd, con->buf, buffer);
+       return dhcpcd_command_fd(con, con->command_fd, true, con->buf, buffer);
 }
 
 
 static int
-dhcpcd_connect(void)
+dhcpcd_connect(const char *path, int opts)
 {
        int fd;
        socklen_t len;
        struct sockaddr_un sun;
 
-       fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+       assert(path);
+       fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | opts, 0);
        if (fd == -1)
                return -1;
 
        memset(&sun, 0, sizeof(sun));
        sun.sun_family = AF_UNIX;
-       strlcpy(sun.sun_path, DHCPCD_SOCKET, sizeof(sun.sun_path));
+       strlcpy(sun.sun_path, path, sizeof(sun.sun_path));
        len = (socklen_t)SUN_LEN(&sun);
        if (connect(fd, (struct sockaddr *)&sun, len) == 0)
                return fd;
@@ -179,8 +230,13 @@ get_value(const char *data, size_t len, const char *var)
        vlen = strlen(var);
        p = NULL;
        while (data + vlen + 1 < end) {
-               if (strncmp(data, var, vlen) == 0) {
-                       p = data + vlen;
+               /* Skip past NUL padding */
+               if (*data == '\0') {
+                       data++;
+                       continue;
+               }
+               if (strncmp(data, var, vlen) == 0 && data[vlen] == '=') {
+                       p = data + vlen + 1;
                        break;
                }
                data += strlen(data) + 1;
@@ -195,15 +251,186 @@ dhcpcd_get_value(const DHCPCD_IF *i, const char *var)
 {
 
        assert(i);
+       assert(var);
        return get_value(i->data, i->data_len, var);
 }
 
+ssize_t
+dhcpcd_encode_string_escape(char *dst, size_t len, const char *src, size_t slen)
+{
+       const char *end;
+       size_t bytes;
+       unsigned char c;
+
+       end = src + slen;
+       bytes = 0;
+       while (src < end) {
+               c = (unsigned char)*src++;
+               if ((c == '\\' || !isascii(c) || !isprint(c))) {
+                       if (c == '\\') {
+                               if (dst) {
+                                       if (len  == 0 || len == 1) {
+                                               errno = ENOSPC;
+                                               return -1;
+                                       }
+                                       *dst++ = '\\'; *dst++ = '\\';
+                                       len -= 2;
+                               }
+                               bytes += 2;
+                               continue;
+                       }
+                       if (dst) {
+                               if (len < 5) {
+                                       errno = ENOSPC;
+                                       return -1;
+                               }
+                               *dst++ = '\\';
+                               *dst++ = (char)(((c >> 6) & 03) + '0');
+                               *dst++ = (char)(((c >> 3) & 07) + '0');
+                               *dst++ = (char)(( c       & 07) + '0');
+                               len -= 4;
+                       }
+                       bytes += 4;
+               } else {
+                       if (dst) {
+                               if (len == 0) {
+                                       errno = ENOSPC;
+                                       return -1;
+                               }
+                               *dst++ = (char)c;
+                               len--;
+                       }
+                       bytes++;
+               }
+       }
+
+       if (dst) {
+               if (len == 0) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               *dst = '\0';
+       }
+
+       return (ssize_t)bytes;
+}
+
+ssize_t
+dhcpcd_decode_string_escape(char *dst, size_t dlen, const char *src)
+{
+       char c, esc;
+       int oct;
+       ssize_t bytes;
+
+       bytes = 0;
+       for (;;) {
+               c = *src++;
+               if (c == '\0')
+                       break;
+               if (dst && --dlen == 0) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               switch (c) {
+               case '\\':
+                       if (*src == '\0') {
+                               errno = EINVAL;
+                               return -1;
+                       }
+                       esc = *src++;
+                       switch (esc) {
+                       case '\\':
+                       case '0':
+                       case '1':
+                       case '3':
+                       case '4':
+                       case '5':
+                       case '6':
+                       case '7':
+                               oct = esc - '0';
+                               if (*src >= '0' && *src <='7')
+                                       oct = oct * 8 + (*src++ - '0');
+                               else {
+                                       errno = EINVAL;
+                                       return -1;
+                               }
+                               if (*src >= '0' && *src <='7')
+                                       oct = oct * 8 + (*src++ - '0');
+                               else {
+                                       errno = EINVAL;
+                                       return -1;
+                               }
+                               if (dst)
+                                       *dst++ = (char)oct;
+                       default:
+                               errno = EINVAL;
+                               return -1;
+                       }
+                       break;
+               default:
+                       if (dst)
+                               *dst++ = c;
+               }
+               bytes++;
+       }
+
+       if (dst) {
+               if (--dlen == 0) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               *dst = '\0';
+       }
+       return bytes;
+}
+
+ssize_t
+dhcpcd_decode_hex(char *dst, size_t dlen, const char *src)
+{
+       size_t bytes, i;
+       char c;
+       int val, n;
+
+       bytes = 0;
+       while (*src) {
+               if (dlen == 0 || dlen == 1) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               val = 0;
+               for (i = 0; i < 2; i++) {
+                       c = *src++;
+                       if (c >= '0' && c <= '9')
+                               n = c - '0';
+                       else if (c >= 'a' && c <= 'f')
+                               n = 10 + c - 'a';
+                       else if (c >= 'A' && c <= 'F')
+                               n = 10 + c - 'A';
+                       else {
+                               errno = EINVAL;
+                               return -1;
+                       }
+                       val = val * 16 + n;
+               }
+               *dst++ = (char)val;
+               bytes += 2;
+               dlen -= 2;
+               if (*src == ':')
+                       src++;
+       }
+       return (ssize_t)bytes;
+}
+
 const char *
 dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
 {
        char pvar[128], *p;
        size_t plen, l;
 
+       assert(i);
+       assert(prefix);
+       assert(var);
+
        p = pvar;
        plen = sizeof(pvar);
        l = strlcpy(p, prefix, plen);
@@ -220,7 +447,8 @@ dhcpcd_get_prefix_value(const DHCPCD_IF *i, const char *prefix, const char *var)
        return dhcpcd_get_value(i, pvar);
 }
 
-static bool strtobool(const char *var)
+static bool
+strtobool(const char *var)
 {
 
        if (var == NULL)
@@ -238,9 +466,15 @@ get_status(DHCPCD_CONNECTION *con)
        const char *status;
 
        assert(con);
-       if (con->command_fd == -1 || con->listen_fd == -1)
+       if (con->command_fd == -1)
                return "down";
 
+       if (con->listen_fd == -1)
+               return "opened";
+
+       if (con->interfaces == NULL)
+               return "initialised";
+
        status = "disconnected";
        for (i = con->interfaces; i; i = i->next) {
                if (i->up) {
@@ -255,12 +489,12 @@ get_status(DHCPCD_CONNECTION *con)
 }
 
 static void
-update_status(DHCPCD_CONNECTION *con)
+update_status(DHCPCD_CONNECTION *con, const char *nstatus)
 {
-       const char *nstatus;
 
        assert(con);
-       nstatus = get_status(con);
+       if (nstatus == NULL)
+               nstatus = get_status(con);
        if (con->status == NULL || strcmp(nstatus, con->status)) {
                con->status = nstatus;
                if (con->status_cb)
@@ -276,12 +510,89 @@ dhcpcd_interfaces(DHCPCD_CONNECTION *con)
        return con->interfaces;
 }
 
+char **
+dhcpcd_interface_names(DHCPCD_CONNECTION *con, size_t *nnames)
+{
+       char **names;
+       size_t n;
+       DHCPCD_IF *i;
+
+       assert(con);
+       if (con->interfaces == NULL)
+               return NULL;
+
+       n = 0;
+       for (i = con->interfaces; i; i = i->next) {
+               if (strcmp(i->type, "link") == 0)
+                       n++;
+       }
+       names = malloc(sizeof(char *) * (n + 1));
+       if (names == NULL)
+               return NULL;
+       n = 0;
+       for (i = con->interfaces; i; i = i->next) {
+               if (strcmp(i->type, "link") == 0) {
+                       names[n] = strdup(i->ifname);
+                       if (names[n] == NULL) {
+                               dhcpcd_freev(names);
+                               return NULL;
+                       }
+                       n++;
+               }
+       }
+       names[n] = NULL;
+       if (nnames)
+               *nnames = n;
+
+       return names;
+}
+
+void
+dhcpcd_freev(char **argv)
+{
+       char **v;
+
+       if (argv) {
+               for (v = argv; *v; v++)
+                       free(*v);
+               free(argv);
+       }
+}
+
+static int
+dhcpcd_cmpstring(const void *p1, const void *p2)
+{
+       const char *s1, *s2;
+       int cmp;
+
+       s1 = *(char * const *)p1;
+       s2 = *(char * const *)p2;
+       if ((cmp = strcasecmp(s1, s2)) == 0)
+               cmp = strcmp(s1, s2);
+       return cmp;
+}
+
+char **
+dhcpcd_interface_names_sorted(DHCPCD_CONNECTION *con)
+{
+       char **names;
+       size_t nnames;
+
+       names = dhcpcd_interface_names(con, &nnames);
+       if (names)
+               qsort(names, nnames, sizeof(char *), dhcpcd_cmpstring);
+       return names;
+}
+
 DHCPCD_IF *
 dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
 {
        DHCPCD_IF *i;
 
        assert(con);
+       assert(ifname);
+       assert(type);
+
        for (i = con->interfaces; i; i = i->next)
                if (strcmp(i->ifname, ifname) == 0 &&
                    strcmp(i->type, type) == 0)
@@ -292,26 +603,43 @@ dhcpcd_get_if(DHCPCD_CONNECTION *con, const char *ifname, const char *type)
 static DHCPCD_IF *
 dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
 {
-       const char *ifname, *reason, *type, *order, *flags;
+       const char *ifname, *ifclass, *reason, *type, *order, *flags;
        char *orderdup, *o, *p;
        DHCPCD_IF *e, *i, *l, *n, *nl;
        int ti;
+       bool addedi;
 
-       ifname = get_value(data, len, "interface=");
+#if 0
+       char *dp = data, *de = data + len;
+       while (dp < de) {
+               printf ("XX: %s\n", dp);
+               dp += strlen(dp) + 1;
+       }
+#endif
+
+       ifname = get_value(data, len, "interface");
        if (ifname == NULL || *ifname == '\0') {
                errno = ESRCH;
                return NULL;
        }
-       reason = get_value(data, len, "reason=");
+       reason = get_value(data, len, "reason");
        if (reason == NULL || *reason == '\0') {
                errno = ESRCH;
                return NULL;
        }
-       if (strcmp(reason, "RECONFIGURE") == 0) {
+       ifclass = get_value(data, len, "ifclass");
+       /* Skip pseudo interfaces */
+       if (ifclass && *ifclass != '\0') {
+               errno = ENOTSUP;
+               return NULL;
+       }
+       if (strcmp(reason, "RECONFIGURE") == 0 ||
+           strcmp(reason, "INFORM") == 0 || strcmp(reason, "INFORM6") == 0)
+       {
                errno = ENOTSUP;
                return NULL;
        }
-       order = get_value(data, len, "interface_order=");
+       order = get_value(data, len, "interface_order");
        if (order == NULL || *order == '\0') {
                errno = ESRCH;
                return NULL;
@@ -356,7 +684,7 @@ dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
         } else if (strcmp(type, "link")) {
                /* If link is down, ignore it */
                e = dhcpcd_get_if(con, ifname, "link");
-               if (e && strcmp(e->reason, "NOCARRIER") == 0)
+               if (e && !e->up)
                        return NULL;
        }
 
@@ -388,6 +716,7 @@ dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
                else
                        con->interfaces = i;
                i->next = NULL;
+               i->last_message = NULL;
        } else
                free(i->data);
 
@@ -398,21 +727,25 @@ dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
        i->ifname = ifname;
        i->type = type;
        i->reason = reason;
-       flags = dhcpcd_get_value(i, "ifflags=");
+       flags = dhcpcd_get_value(i, "ifflags");
        if (flags)
                i->flags = (unsigned int)strtoul(flags, NULL, 0);
        else
                i->flags = 0;
-       if (strcmp(reason, "CARRIER") == 0)
+       if (strcmp(reason, "CARRIER") == 0 ||
+           strcmp(reason, "DELEGATED6") == 0)
                i->up = true;
        else
-               i->up = strtobool(dhcpcd_get_value(i, "if_up="));
-       i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless="));
-       i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid=" : "old_ssid=");
+               i->up = strtobool(dhcpcd_get_value(i, "if_up"));
+       i->wireless = strtobool(dhcpcd_get_value(i, "ifwireless"));
+       i->ssid = dhcpcd_get_value(i, "ifssid");
+       if (i->ssid == NULL && i->wireless)
+               i->ssid = dhcpcd_get_value(i, i->up ? "new_ssid" : "old_ssid");
 
        /* Sort! */
        n = nl = NULL;
        p = orderdup;
+       addedi = false;
         while ((o = strsep(&p, " ")) != NULL) {
                 for (ti = 0; dhcpcd_types[ti]; ti++) {
                         l = NULL;
@@ -424,6 +757,8 @@ dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
                         }
                         if (e == NULL)
                                 continue;
+                       if (i == e)
+                               addedi = true;
                         if (l)
                                 l->next = e->next;
                         else
@@ -442,12 +777,13 @@ dhcpcd_new_if(DHCPCD_CONNECTION *con, char *data, size_t len)
         while (con->interfaces) {
                 e = con->interfaces->next;
                free(con->interfaces->data);
+               free(con->interfaces->last_message);
                 free(con->interfaces);
                 con->interfaces = e;
         }
         con->interfaces = n;
 
-       return i;
+       return addedi ? i : NULL;
 }
 
 static DHCPCD_IF *
@@ -508,7 +844,7 @@ dhcpcd_dispatch(DHCPCD_CONNECTION *con)
 
        /* Have to call update_status last as it could
         * cause the interface to be destroyed. */
-       update_status(con);
+       update_status(con, NULL);
 }
 
 DHCPCD_CONNECTION *
@@ -518,10 +854,28 @@ dhcpcd_new(void)
 
        con = calloc(1, sizeof(*con));
        con->command_fd = con->listen_fd = -1;
+       con->open = false;
+       con->progname = "libdhcpcd";
        return con;
 }
 
-#ifndef __GLIBC__
+void
+dhcpcd_set_progname(DHCPCD_CONNECTION *con, const char *progname)
+{
+
+       assert(con);
+       con->progname = progname;
+}
+
+const char *
+dhcpcd_get_progname(const DHCPCD_CONNECTION *con)
+{
+
+       assert(con);
+       return con->progname;
+}
+
+#ifndef HAVE_STRVERSCMP
 /* Good enough for our needs */
 static int
 strverscmp(const char *s1, const char *s2)
@@ -547,60 +901,60 @@ strverscmp(const char *s1, const char *s2)
 #endif
 
 int
-dhcpcd_open(DHCPCD_CONNECTION *con)
+dhcpcd_open(DHCPCD_CONNECTION *con, bool privileged)
 {
+       const char *path = privileged ? DHCPCD_SOCKET : DHCPCD_UNPRIV_SOCKET;
        char cmd[128];
        ssize_t bytes;
        size_t nifs, n;
-       DHCPCD_IF *i;
 
        assert(con);
-       if (con->listen_fd != -1)
-               return con->listen_fd;
-       con->command_fd = dhcpcd_connect();
-       if (con->command_fd == -1) {
-               update_status(con);
+       if (con->open) {
+               if (con->listen_fd != -1)
+                       return con->listen_fd;
+               errno = EISCONN;
                return -1;
        }
+       /* We need to block the command fd */
+       con->command_fd = dhcpcd_connect(path, 0);
+       if (con->command_fd == -1)
+               goto err_exit;
 
        con->terminate_commands = false;
-       if (dhcpcd_command(con, "--version", &con->version) <= 0)
-               return -1;
+       if (dhcpcd_ctrl_command(con, "--version", &con->version) <= 0)
+               goto err_exit;
        con->terminate_commands =
            strverscmp(con->version, "6.4.1") >= 0 ? true : false;
 
-       if (dhcpcd_command(con, "--getconfigfile", &con->cffile) <= 0)
-               return -1;
+       if (dhcpcd_ctrl_command(con, "--getconfigfile", &con->cffile) <= 0)
+               goto err_exit;
 
-       con->listen_fd = dhcpcd_connect();
-       if (con->listen_fd == -1) {
-               close(con->command_fd);
-               con->command_fd = -1;
-               update_status(con);
-               return -1;
-       }
-       dhcpcd_command_fd(con, con->listen_fd, "--listen", NULL);
+       con->open = true;
+       con->privileged = privileged;
+       update_status(con, NULL);
+
+       con->listen_fd = dhcpcd_connect(path, SOCK_NONBLOCK);
+       if (con->listen_fd == -1)
+               goto err_exit;
 
-       dhcpcd_command_fd(con, con->command_fd, "--getinterfaces", NULL);
+       dhcpcd_command_fd(con, con->listen_fd, false, "--listen", NULL);
+       dhcpcd_command_fd(con, con->command_fd, false, "--getinterfaces", NULL);
        bytes = read(con->command_fd, cmd, sizeof(nifs));
-       if (bytes != sizeof(nifs)) {
-               close(con->command_fd);
-               con->command_fd = -1;
-               close(con->listen_fd);
-               con->listen_fd = -1;
-       } else {
-               memcpy(&nifs, cmd, sizeof(nifs));
-               /* We don't dispatch each interface here as that
-                * causes too much notification spam when the GUI starts */
-               for (n = 0; n < nifs; n++) {
-                       i = dhcpcd_read_if(con, con->command_fd);
-                       if (i)
-                               dhcpcd_wpa_if_event(i);
-               }
-       }
-       update_status(con);
+       if (bytes != sizeof(nifs))
+               goto err_exit;
+       memcpy(&nifs, cmd, sizeof(nifs));
+       /* We don't dispatch each interface here as that
+        * causes too much notification spam when the GUI starts */
+       for (n = 0; n < nifs; n++)
+               dhcpcd_read_if(con, con->command_fd);
+
+       update_status(con, NULL);
 
        return con->listen_fd;
+
+err_exit:
+       dhcpcd_close(con);
+       return -1;
 }
 
 int
@@ -611,6 +965,14 @@ dhcpcd_get_fd(DHCPCD_CONNECTION *con)
        return con->listen_fd;
 }
 
+bool
+dhcpcd_privileged(DHCPCD_CONNECTION *con)
+{
+
+       assert(con);
+       return con->privileged;
+}
+
 const char *
 dhcpcd_status(DHCPCD_CONNECTION *con)
 {
@@ -658,21 +1020,48 @@ dhcpcd_set_status_callback(DHCPCD_CONNECTION *con,
 void
 dhcpcd_close(DHCPCD_CONNECTION *con)
 {
-       DHCPCD_WPA *wpa;
+       DHCPCD_IF *nif;
+       DHCPCD_WPA *nwpa;
+       DHCPCD_WI_HIST *nh;
 
        assert(con);
 
+       con->open = false;
+
        /* Shut down WPA listeners as they aren't much good without dhcpcd.
         * They'll be restarted anyway when dhcpcd comes back up. */
-       for (wpa = con->wpa; wpa; wpa = wpa->next)
+       while (con->wpa) {
+               nwpa = con->wpa->next;
                dhcpcd_wpa_close(con->wpa);
+               free(con->wpa);
+               con->wpa = nwpa;
+       }
+       while (con->wi_history) {
+               nh = con->wi_history->next;
+               free(con->wi_history);
+               con->wi_history = nh;
+       }
+       while (con->interfaces) {
+               nif = con->interfaces->next;
+               free(con->interfaces->data);
+               free(con->interfaces->last_message);
+               free(con->interfaces);
+               con->interfaces = nif;
+       }
 
-       if (con->command_fd != -1) {
+       if (con->command_fd != -1)
                shutdown(con->command_fd, SHUT_RDWR);
+       if (con->listen_fd != -1)
+               shutdown(con->listen_fd, SHUT_RDWR);
+
+       update_status(con, "down");
+
+       if (con->command_fd != -1) {
+               close(con->command_fd);
                con->command_fd = -1;
        }
        if (con->listen_fd != -1) {
-               shutdown(con->listen_fd, SHUT_RDWR);
+               close(con->listen_fd);
                con->listen_fd = -1;
        }
 
@@ -691,6 +1080,14 @@ dhcpcd_close(DHCPCD_CONNECTION *con)
        }
 }
 
+void
+dhcpcd_free(DHCPCD_CONNECTION *con)
+{
+
+       assert(con);
+       free(con);
+}
+
 DHCPCD_CONNECTION *
 dhcpcd_if_connection(DHCPCD_IF *i)
 {
@@ -700,7 +1097,7 @@ dhcpcd_if_connection(DHCPCD_IF *i)
 }
 
 char *
-dhcpcd_if_message(const DHCPCD_IF *i)
+dhcpcd_if_message(DHCPCD_IF *i, bool *new_msg)
 {
        const char *ip, *iplen, *pfx;
        char *msg, *p;
@@ -711,7 +1108,7 @@ dhcpcd_if_message(const DHCPCD_IF *i)
        assert(i);
        /* Don't report non SLAAC configurations */
        if (strcmp(i->type, "ra") == 0 && i->up &&
-           dhcpcd_get_value(i, "ra1_prefix=") == NULL)
+           dhcpcd_get_value(i, "ra1_prefix") == NULL)
                return NULL;
 
        showssid = false;
@@ -721,8 +1118,20 @@ dhcpcd_if_message(const DHCPCD_IF *i)
                if (i->wireless) {
                        showssid = true;
                        reason = _("Associated with");
-               } else
-                       reason = _("Cable plugged in");
+               } else {
+                       /* Don't report able in if we have addresses */
+                       const DHCPCD_IF *ci;
+
+                       for (ci = i->con->interfaces; ci; ci = ci->next) {
+                               if (ci != i &&
+                                   strcmp(i->ifname, ci->ifname) == 0 &&
+                                   ci->up)
+                                       break;
+                       }
+                       if (ci)
+                               return NULL;
+                       reason = _("Link is up, configuring");
+               }
        } else if (strcmp(i->reason, "NOCARRIER") == 0) {
                if (i->wireless) {
                        if (i->ssid) {
@@ -731,8 +1140,10 @@ dhcpcd_if_message(const DHCPCD_IF *i)
                        } else
                                reason = _("Not associated");
                } else
-                       reason = _("Cable unplugged");
-       } else if (strcmp(i->reason, "UNKNOWN") == 0)
+                       reason = _("Link is down");
+       } else if (strcmp(i->reason, "DEPARTED") == 0)
+               reason = _("Departed");
+       else if (strcmp(i->reason, "UNKNOWN") == 0)
                reason = _("Unknown link state");
        else if (strcmp(i->reason, "FAIL") == 0)
                reason = _("Automatic configuration not possible");
@@ -740,22 +1151,30 @@ dhcpcd_if_message(const DHCPCD_IF *i)
                reason = _("Waiting for 3rd Party configuration");
 
        if (reason == NULL) {
-               if (i->up)
-                       reason = _("Configured");
-               else if (strcmp(i->type, "ra") == 0)
+               if (i->up) {
+                       if (strcmp(i->reason, "DELEGATED6") == 0)
+                               reason = _("Delegated");
+                       else
+                               reason = _("Configured");
+               } else if (strcmp(i->type, "ra") == 0)
                        reason = "Expired RA";
                else
                        reason = i->reason;
        }
 
        pfx = i->up ? "new_" : "old_";
-       if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address=")))
-               iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr=");
-       else if ((ip = dhcpcd_get_value(i, "ra1_prefix=")))
+       if ((ip = dhcpcd_get_prefix_value(i, pfx, "ip_address")))
+               iplen = dhcpcd_get_prefix_value(i, pfx, "subnet_cidr");
+       else if ((ip = dhcpcd_get_value(i, "ra1_addr")))
+               iplen = NULL;
+       else if ((ip = dhcpcd_get_value(i, "ra1_prefix")))
                iplen = NULL;
        else if ((ip = dhcpcd_get_prefix_value(i, pfx,
-           "dhcp6_ia_na1_ia_addr1=")))
+           "dhcp6_ia_na1_ia_addr1")))
                iplen = "128";
+       else if ((ip = dhcpcd_get_prefix_value(i, pfx,
+           "delegated_dhcp6_prefix")))
+               iplen = NULL;
        else {
                ip = NULL;
                iplen = NULL;
@@ -775,30 +1194,18 @@ dhcpcd_if_message(const DHCPCD_IF *i)
        if (showssid)
                p += snprintf(p, len - (size_t)(p - msg), " %s", i->ssid);
        if (iplen)
-               p += snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
+               snprintf(p, len - (size_t)(p - msg), " %s/%s", ip, iplen);
        else if (ip)
-               p += snprintf(p, len - (size_t)(p - msg), " %s", ip);
-       return msg;
-}
+               snprintf(p, len - (size_t)(p - msg), " %s", ip);
 
-void
-dhcpcd_free(DHCPCD_CONNECTION *con)
-{
-       DHCPCD_IF *nif;
-       DHCPCD_WPA *nwpa;
-
-       assert(con);
-       while (con->interfaces) {
-               nif = con->interfaces->next;
-               free(con->interfaces->data);
-               free(con->interfaces);
-               con->interfaces = nif;
-       }
-       while (con->wpa) {
-               nwpa = con->wpa->next;
-               dhcpcd_wpa_close(con->wpa);
-               free(con->wpa);
-               con->wpa = nwpa;
+       if (new_msg) {
+               if (i->last_message == NULL || strcmp(i->last_message, msg))
+                       *new_msg = true;
+               else
+                       *new_msg = false;
        }
-       free(con);
+       free(i->last_message);
+       i->last_message = strdup(msg);
+
+       return msg;
 }