Allow a blank psk to just select the network.
[dhcpcd-ui] / src / libdhcpcd / wpa.c
index 68c56db7073d31345d492933936ac71d0e8e7312..358b37333d653a69ba9fbef6012bedc57dbbf98c 100644 (file)
  * SUCH DAMAGE.
  */
 
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+
+#include <assert.h>
+#include <ctype.h>
 #include <errno.h>
+#include <limits.h>
+#include <poll.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <unistd.h>
 
 #define IN_LIBDHCPCD
-#include "libdhcpcd.h"
 #include "config.h"
+#include "dhcpcd.h"
 
-#define HIST_MAX 10 /* Max history per ssid/bssid */
+#ifndef SUN_LEN
+#define SUN_LEN(su) \
+       (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
+#endif
 
-void
-dhcpcd_wi_history_clear(DHCPCD_CONNECTION *con)
+#define CLAMP(x, low, high) \
+       (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
+
+static int
+wpa_open(const char *ifname, char **path)
 {
-       DHCPCD_WI_HIST *h;
+       static int counter;
+       int fd;
+       socklen_t len;
+       struct sockaddr_un sun;
 
-       while (con->wi_history) {
-               h = con->wi_history->next;
-               free(con->wi_history);
-               con->wi_history = h;
+       if ((fd = socket(AF_UNIX,
+           SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1)
+               return -1;
+       memset(&sun, 0, sizeof(sun));
+       sun.sun_family = AF_UNIX;
+       snprintf(sun.sun_path, sizeof(sun.sun_path),
+           "/tmp/libdhcpcd-wpa-%d.%d", getpid(), counter++);
+       *path = strdup(sun.sun_path);
+       len = (socklen_t)SUN_LEN(&sun);
+       if (bind(fd, (struct sockaddr *)&sun, len) == -1) {
+               close(fd);
+               unlink(*path);
+               free(*path);
+               *path = NULL;
+               return -1;
        }
+       snprintf(sun.sun_path, sizeof(sun.sun_path),
+           WPA_CTRL_DIR "/%s", ifname);
+       len = (socklen_t)SUN_LEN(&sun);
+       if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
+               close(fd);
+               unlink(*path);
+               free(*path);
+               *path = NULL;
+               return -1;
+       }
+
+       return fd;
+}
+
+static ssize_t
+wpa_cmd(int fd, const char *cmd, char *buffer, size_t len)
+{
+       int retval;
+       ssize_t bytes;
+       struct pollfd pfd;
+
+       if (buffer)
+               *buffer = '\0';
+       bytes = write(fd, cmd, strlen(cmd));
+       if (bytes == -1 || bytes == 0)
+               return -1;
+       if (buffer == NULL || len == 0)
+               return 0;
+       pfd.fd = fd;
+       pfd.events = POLLIN | POLLHUP;
+       pfd.revents = 0;
+       retval = poll(&pfd, 1, 2000);
+       if (retval == -1)
+               return -1;
+       if (retval == 0 || !(pfd.revents & (POLLIN | POLLHUP)))
+               return -1;
+
+       bytes = read(fd, buffer, len == 1 ? 1 : len - 1);
+       if (bytes != -1)
+               buffer[bytes] = '\0';
+       return bytes;
+}
+
+bool
+dhcpcd_wpa_command(DHCPCD_WPA *wpa, const char *cmd)
+{
+       char buf[10];
+       ssize_t bytes;
+
+       bytes = wpa_cmd(wpa->command_fd, cmd, buf, sizeof(buf));
+       return (bytes == -1 || bytes == 0 ||
+           strcmp(buf, "OK\n")) ? false : true;
+}
+
+bool
+dhcpcd_wpa_command_arg(DHCPCD_WPA *wpa, const char *cmd, const char *arg)
+{
+       size_t cmdlen, nlen;
+
+       cmdlen = strlen(cmd);
+       nlen = cmdlen + strlen(arg) + 2;
+       if (!dhcpcd_realloc(wpa->con, nlen))
+               return -1;
+       strlcpy(wpa->con->buf, cmd, wpa->con->buflen);
+       wpa->con->buf[cmdlen] = ' ';
+       strlcpy(wpa->con->buf + cmdlen + 1, arg, wpa->con->buflen - 1 - cmdlen);
+       return dhcpcd_wpa_command(wpa, wpa->con->buf);
+}
+
+static bool
+dhcpcd_attach_detach(DHCPCD_WPA *wpa, bool attach)
+{
+       char buf[10];
+       ssize_t bytes;
+
+       if (wpa->attached == attach)
+               return true;
+
+       bytes = wpa_cmd(wpa->listen_fd, attach > 0 ? "ATTACH" : "DETACH",
+           buf, sizeof(buf));
+       if (bytes == -1 || bytes == 0 || strcmp(buf, "OK\n"))
+               return false;
+
+       wpa->attached = attach;
+       return true;
+}
+
+bool
+dhcpcd_wpa_scan(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_wpa_command(wpa, "SCAN");
 }
 
 void
@@ -59,114 +180,371 @@ dhcpcd_wi_scans_free(DHCPCD_WI_SCAN *wis)
        }
 }
 
-static DHCPCD_WI_SCAN *
-dhcpcd_scanresult_new(DHCPCD_CONNECTION *con, DBusMessageIter *array)
+static void
+dhcpcd_strtoi(int *val, const char *s)
 {
-       DBusMessageIter dict, entry, var;
-       DHCPCD_WI_SCAN *wis;
-       char *s;
-       int32_t i32;
-       int errors;
+       long l;
 
-       wis = calloc(1, sizeof(*wis));
-       if (wis == NULL) {
-               dhcpcd_error_set(con, NULL, errno);
-               return NULL;
-       }
-       errors = con->errors;
-       dbus_message_iter_recurse(array, &dict);
-       for (;
-            dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY;
-            dbus_message_iter_next(&dict))
-       {
-               dbus_message_iter_recurse(&dict, &entry);
-               if (!dhcpcd_iter_get(con, &entry, DBUS_TYPE_STRING, &s))
-                   break;
-               if (dbus_message_iter_get_arg_type(&entry) !=
-                   DBUS_TYPE_VARIANT)
+       l = strtol(s, NULL, 0);
+       if (l >= INT_MIN && l <= INT_MAX)
+               *val = (int)l;
+       else
+               errno = ERANGE;
+}
+
+static int
+dhcpcd_wpa_hex2num(char c)
+{
+
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       return -1;
+}
+
+static int
+dhcpcd_wpa_hex2byte(const char *src)
+{
+       int h, l;
+
+       if ((h = dhcpcd_wpa_hex2num(*src++)) == -1 ||
+           (l = dhcpcd_wpa_hex2num(*src)) == -1)
+               return -1;
+       return (h << 4) | l;
+}
+
+static ssize_t
+dhcpcd_wpa_decode_ssid(char *dst, size_t dlen, const char *src)
+{
+       const char *start;
+       char c, esc;
+       int xb;
+
+       start = dst;
+       for (;;) {
+               if (*src == '\0')
                        break;
-               dbus_message_iter_recurse(&entry, &var);
-               if (strcmp(s, "BSSID") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
-                               break;
-                       strlcpy(wis->bssid, s, sizeof(wis->bssid));
-               } else if (strcmp(s, "Frequency") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_INT32, &i32))
-                               break;
-                       wis->frequency = i32;
-               } else if (strcmp(s, "Quality") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_INT32, &i32))
-                               break;
-                       wis->quality.value = i32;
-               } else if (strcmp(s, "Noise") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_INT32, &i32))
-                               break;
-                       wis->noise.value = i32;
-               } else if (strcmp(s, "Level") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_INT32, &i32))
-                               break;
-                       wis->level.value = i32;
-               } else if (strcmp(s, "Flags") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
-                               break;
-                       strlcpy(wis->flags, s, sizeof(wis->flags));
-               } else if (strcmp(s, "SSID") == 0) {
-                       if (!dhcpcd_iter_get(con, &var, DBUS_TYPE_STRING, &s))
+               if (--dlen == 0) {
+                       errno = ENOSPC;
+                       return -1;
+               }
+               c = *src++;
+               switch (c) {
+               case '\\':
+                       if (*src == '\0') {
+                               errno = EINVAL;
+                               return -1;
+                       }
+                       esc = *src++;
+                       switch (esc) {
+                       case '\\':
+                       case '"': *dst++ = esc; break;
+                       case 'n': *dst++ = '\n'; break;
+                       case 'r': *dst++ = '\r'; break;
+                       case 't': *dst++ = '\t'; break;
+                       case 'e': *dst++ = '\033'; break;
+                       case 'x':
+                               if (src[0] == '\0' || src[1] == '\0') {
+                                       errno = EINVAL;
+                                       return -1;
+                               }
+                               if ((xb = dhcpcd_wpa_hex2byte(src)) == -1)
+                                       return -1;
+                               *dst++ = (char)xb;
+                               src += 2;
                                break;
-                       strlcpy(wis->ssid, s, sizeof(wis->ssid));
+                       default: errno = EINVAL; return -1;
+                       }
+               default: *dst++ = c; break;
                }
        }
-       if (dbus_message_iter_get_arg_type(&dict) != DBUS_TYPE_INVALID) {
-               if (con->errors == errors)
-                       dhcpcd_error_set(con, NULL, EINVAL);
-               free(wis);
+       if (dlen == 0) {
+               errno = ENOSPC;
+               return -1;
+       }
+       *dst = '\0';
+       return dst - start;
+}
+
+static DHCPCD_WI_SCAN *
+dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa)
+{
+       size_t i;
+       ssize_t bytes, dl;
+       DHCPCD_WI_SCAN *wis, *w, *l;
+       char *s, *p, buf[32];
+       char wssid[sizeof(w->ssid)];
+       const char *proto;
+
+       if (!dhcpcd_realloc(wpa->con, 2048))
                return NULL;
+       wis = NULL;
+       for (i = 0; i < 1000; i++) {
+               snprintf(buf, sizeof(buf), "BSS %zu", i);
+               bytes = wpa_cmd(wpa->command_fd, buf,
+                   wpa->con->buf, wpa->con->buflen);
+               if (bytes == 0 || bytes == -1 ||
+                   strncmp(wpa->con->buf, "FAIL", 4) == 0)
+                       break;
+               p = wpa->con->buf;
+               w = calloc(1, sizeof(*w));
+               if (w == NULL)
+                       break;
+               dl = 0;
+               wssid[0] = '\0';
+               while ((s = strsep(&p, "\n"))) {
+                       if (*s == '\0')
+                               continue;
+                       if (strncmp(s, "bssid=", 6) == 0)
+                               strlcpy(w->bssid, s + 6, sizeof(w->bssid));
+                       else if (strncmp(s, "freq=", 5) == 0)
+                               dhcpcd_strtoi(&w->frequency, s + 5);
+//                     else if (strncmp(s, "beacon_int=", 11) == 0)
+//                             ;
+                       else if (strncmp(s, "qual=", 5) == 0)
+                               dhcpcd_strtoi(&w->quality.value, s + 5);
+                       else if (strncmp(s, "noise=", 6) == 0)
+                               dhcpcd_strtoi(&w->noise.value, s + 6);
+                       else if (strncmp(s, "level=", 6) == 0)
+                               dhcpcd_strtoi(&w->level.value, s + 6);
+                       else if (strncmp(s, "flags=", 6) == 0)
+                               strlcpy(w->wpa_flags, s + 6,
+                                   sizeof(w->wpa_flags));
+                       else if (strncmp(s, "ssid=", 5) == 0) {
+                               /* Decode it from \xNN to \NNN
+                                * so we're consistent */
+                               dl = dhcpcd_wpa_decode_ssid(wssid,
+                                   sizeof(wssid), s + 5);
+                               if (dl == -1)
+                                       break;
+                               dl = dhcpcd_encode_string_escape(w->ssid,
+                                   sizeof(w->ssid), wssid, (size_t)dl);
+                               if (dl == -1)
+                                       break;
+                       }
+               }
+               if (dl == -1) {
+                       free(w);
+                       break;
+               }
+
+               if (wis == NULL)
+                       wis = w;
+               else
+                       l->next = w;
+               l = w;
+
+               if ((proto = strstr(w->wpa_flags, "[WPA-")) ||
+                   (proto = strstr(w->wpa_flags, "[WPA2-")) ||
+                   (proto = strstr(w->wpa_flags, "[RSN-")))
+               {
+                       const char *endp, *psk;
+
+                       w->flags = WSF_WPA | WSF_SECURE;
+                       endp = strchr(proto, ']');
+                       if ((psk = strstr(proto, "-PSK]")) ||
+                           (psk = strstr(proto, "-PSK-")) ||
+                           (psk = strstr(proto, "-PSK+")))
+                       {
+                               if (psk < endp)
+                                       w->flags |= WSF_PSK;
+                       }
+               }
+               if (strstr(w->wpa_flags, "[WEP]"))
+                       w->flags = WSF_WEP | WSF_PSK | WSF_SECURE;
+
+               w->strength.value = w->level.value;
+               if (w->strength.value > 110 && w->strength.value < 256)
+                       /* Convert WEXT level to dBm */
+                       w->strength.value -= 256;
+
+               if (w->strength.value < 0) {
+                       /* Assume dBm */
+                       w->strength.value =
+                           abs(CLAMP(w->strength.value, -100, -40) + 40);
+                       w->strength.value =
+                           100 - ((100 * w->strength.value) / 60);
+               } else {
+                       /* Assume quality percentage */
+                       w->strength.value = CLAMP(w->strength.value, 0, 100);
+               }
        }
        return wis;
 }
 
+int
+dhcpcd_wi_scan_compare(DHCPCD_WI_SCAN *a, DHCPCD_WI_SCAN *b)
+{
+       int cmp;
+
+       /* Fist sort non case sensitive, then case sensitive */
+       cmp = strcasecmp(a->ssid, b->ssid);
+       if (cmp == 0)
+               cmp = strcmp(a->ssid, b->ssid);
+
+       /* If still the same, return strongest first */
+       if (cmp == 0)
+               cmp = b->strength.value - a->strength.value;
+
+       return cmp;
+}
+
+/*
+ * This function is copyright 2001 Simon Tatham.
+ *
+ * Permission is hereby granted, free of charge, to any person
+ * obtaining a copy of this software and associated documentation
+ * files (the "Software"), to deal in the Software without
+ * restriction, including without limitation the rights to use,
+ * copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following
+ * conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+static DHCPCD_WI_SCAN *
+dhcpcd_wi_scans_sort(DHCPCD_WI_SCAN *list)
+{
+       DHCPCD_WI_SCAN *p, *q, *e, *tail;
+       size_t insize, nmerges, psize, qsize, i;
+
+       /* Silly special case: if `list' was passed in as NULL, return
+        * NULL immediately. */
+       if (!list)
+               return NULL;
+
+       insize = 1;
+
+       for (;;) {
+               p = list;
+               list = tail = NULL;
+               nmerges = 0;  /* count number of merges we do in this pass */
+
+               while (p) {
+                       nmerges++;  /* there exists a merge to be done */
+                       /* step `insize' places along from p */
+                       q = p;
+                       psize = 0;
+                       for (i = 0; i < insize; i++) {
+                               psize++;
+                               q = q->next;
+                               if (!q)
+                                       break;
+                       }
+
+                       /* if q hasn't fallen off end,
+                        * we have two lists to merge */
+                       qsize = insize;
+
+                       /* now we have two lists; merge them */
+                       while (psize > 0 || (qsize > 0 && q)) {
+                               /* decide whether next element of merge comes
+                                * from p or q */
+                               if (psize == 0) {
+                                       /* p is empty; e must come from q. */
+                                       e = q; q = q->next; qsize--;
+                               } else if (qsize == 0 || !q) {
+                                       /* q is empty; e must come from p. */
+                                       e = p; p = p->next; psize--;
+                               } else if (dhcpcd_wi_scan_compare(p, q) <= 0) {
+                                       /* First element of p is lower
+                                        * (or same); e must come from p. */
+                                       e = p; p = p->next; psize--;
+                               } else {
+                                       /* First element of q is lower;
+                                        * e must come from q. */
+                                       e = q; q = q->next; qsize--;
+                               }
+
+                               /* add the next element to the merged list */
+                               if (tail)
+                                       tail->next = e;
+                               else
+                                       list = e;
+                               tail = e;
+                       }
+
+                       /* now p has stepped `insize' places along,
+                        * and q has too */
+                       p = q;
+               }
+               tail->next = NULL;
+
+               /* If we have done only one merge, we're finished. */
+               if (nmerges <= 1)   /* allow for nmerges==0, the empty list */
+                       return list;
+
+               /* Otherwise repeat, merging lists twice the size */
+               insize *= 2;
+       }
+}
+
 DHCPCD_WI_SCAN *
-dhcpcd_wi_scans(DHCPCD_CONNECTION *con, DHCPCD_IF *i)
+dhcpcd_wi_scans(DHCPCD_IF *i)
 {
-       DBusMessage *msg;
-       DBusMessageIter args, array;
-       DHCPCD_WI_SCAN *wis, *scans, *l;
+       DHCPCD_WPA *wpa;
+       DHCPCD_WI_SCAN *wis, *w, *n, *p;
+       int nh;
        DHCPCD_WI_HIST *h, *hl;
-       int errors, nh;
 
-       msg = dhcpcd_message_reply(con, "ScanResults", i->ifname);
-       if (msg == NULL)
+       wpa = dhcpcd_wpa_find(i->con, i->ifname);
+       if (wpa == NULL)
                return NULL;
-       if (!dbus_message_iter_init(msg, &args) ||
-           dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
-       {
-               dhcpcd_error_set(con, NULL, EINVAL);
-               return NULL;
-       }
+       wis = dhcpcd_wpa_scans_read(wpa);
+
+       /* Sort the resultant list alphabetically and then by strength */
+       wis = dhcpcd_wi_scans_sort(wis);
+
+       p = NULL;
+       for (w = wis; w && (n = w->next, 1); w = n) {
+               /* Currently we don't support non SSID broadcasting APs */
+               if (*w->ssid == '\0') {
+                       if (p == NULL)
+                               wis = n;
+                       else
+                               p->next = n;
+                       free(w);
+                       continue;
+               }
+               /* Strip duplicated SSIDs, only show the strongest */
+               if (p && strcmp(p->ssid, w->ssid) == 0) {
+                       p->next = n;
+                       free(w);
+                       continue;
+               }
+               /* Remember this as the previos next time */
+               p = w;
 
-       scans = l = NULL;
-       errors = con->errors;
-       dbus_message_iter_recurse(&args, &array);
-       for(;
-           dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_ARRAY;
-           dbus_message_iter_next(&array))
-       {
-               wis = dhcpcd_scanresult_new(con, &array);
-               if (wis == NULL)
-                       break;
                nh = 1;
                hl = NULL;
-               wis->quality.average = wis->quality.value;
-               wis->noise.average = wis->noise.value;
-               wis->level.average = wis->level.value;
-               for (h = con->wi_history; h; h = h->next) {
+               w->quality.average = w->quality.value;
+               w->noise.average = w->noise.value;
+               w->level.average = w->level.value;
+               w->strength.average = w->strength.value;
+
+               for (h = wpa->con->wi_history; h; h = h->next) {
                        if (strcmp(h->ifname, i->ifname) == 0 &&
                            strcmp(h->bssid, wis->bssid) == 0)
                        {
-                               wis->quality.average += h->quality;
-                               wis->noise.average += h->noise;
-                               wis->level.average += h->level;
-                               if (++nh == HIST_MAX) {
+                               w->quality.average += h->quality;
+                               w->noise.average += h->noise;
+                               w->level.average += h->level;
+                               w->strength.average += h->strength;
+                               if (++nh == DHCPCD_WI_HIST_MAX) {
                                        hl->next = h->next;
                                        free(h);
                                        break;
@@ -174,196 +552,594 @@ dhcpcd_wi_scans(DHCPCD_CONNECTION *con, DHCPCD_IF *i)
                        }
                        hl = h;
                }
+
                if (nh != 1) {
-                       wis->quality.average /= nh;
-                       wis->noise.average /= nh;
-                       wis->level.average /= nh;
+                       w->quality.average /= nh;
+                       w->noise.average /= nh;
+                       w->level.average /= nh;
+                       w->strength.average /= nh;
                }
                h = malloc(sizeof(*h));
                if (h) {
                        strlcpy(h->ifname, i->ifname, sizeof(h->ifname));
-                       strlcpy(h->bssid, wis->bssid, sizeof(h->bssid));
-                       h->quality = wis->quality.value;
-                       h->noise = wis->noise.value;
-                       h->level = wis->level.value;
-                       h->next = con->wi_history;
-                       con->wi_history = h;
-               }
-               if (l == NULL)
-                       scans = l = wis;
-               else {
-                       l->next = wis;
-                       l = l->next;
+                       strlcpy(h->bssid, w->bssid, sizeof(h->bssid));
+                       h->quality = w->quality.value;
+                       h->noise = w->noise.value;
+                       h->level = w->level.value;
+                       h->strength = w->strength.value;
+                       h->next = wpa->con->wi_history;
+                       wpa->con->wi_history = h;
                }
        }
-       if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_INVALID) {
-               if (con->errors == errors)
-                       dhcpcd_error_set(con, NULL, EINVAL);
-               dhcpcd_wi_scans_free(scans);
-               scans = NULL;
+
+       return wis;
+}
+
+bool
+dhcpcd_wpa_reconfigure(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_wpa_command(wpa, "RECONFIGURE");
+}
+
+bool
+dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_wpa_command(wpa, "REASSOCIATE");
+}
+
+bool
+dhcpcd_wpa_disconnect(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_wpa_command(wpa, "DISCONNECT");
+}
+
+bool
+dhcpcd_wpa_config_write(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_wpa_command(wpa, "SAVE_CONFIG");
+}
+
+static bool
+dhcpcd_wpa_network(DHCPCD_WPA *wpa, const char *cmd, int id)
+{
+       size_t len;
+
+       len = strlen(cmd) + 32;
+       if (!dhcpcd_realloc(wpa->con, len))
+               return false;
+       snprintf(wpa->con->buf, wpa->con->buflen, "%s %d", cmd, id);
+       return dhcpcd_wpa_command(wpa, wpa->con->buf);
+}
+
+bool
+dhcpcd_wpa_network_disable(DHCPCD_WPA *wpa, int id)
+{
+
+       return dhcpcd_wpa_network(wpa, "DISABLE_NETWORK", id);
+}
+
+bool
+dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int id)
+{
+
+       return dhcpcd_wpa_network(wpa, "ENABLE_NETWORK", id);
+}
+
+bool
+dhcpcd_wpa_network_select(DHCPCD_WPA *wpa, int id)
+{
+
+       return dhcpcd_wpa_network(wpa, "SELECT_NETWORK", id);
+}
+
+bool
+dhcpcd_wpa_network_remove(DHCPCD_WPA *wpa, int id)
+{
+
+       return dhcpcd_wpa_network(wpa, "REMOVE_NETWORK", id);
+}
+
+char *
+dhcpcd_wpa_network_get(DHCPCD_WPA *wpa, int id, const char *param)
+{
+       ssize_t bytes;
+
+       if (!dhcpcd_realloc(wpa->con, 2048))
+               return NULL;
+       snprintf(wpa->con->buf, wpa->con->buflen, "GET_NETWORK %d %s",
+           id, param);
+       bytes = wpa_cmd(wpa->command_fd, wpa->con->buf,
+           wpa->con->buf, wpa->con->buflen);
+       if (bytes == 0 || bytes == -1)
+               return NULL;
+       if (strcmp(wpa->con->buf, "FAIL\n") == 0) {
+               errno = EINVAL;
+               return NULL;
        }
-       dbus_message_unref(msg);
-       return scans;
+       return wpa->con->buf;
+}
+
+bool
+dhcpcd_wpa_network_set(DHCPCD_WPA *wpa, int id,
+    const char *param, const char *value)
+{
+       size_t len;
+
+       len = strlen("SET_NETWORK") + 32 + strlen(param) + strlen(value) + 3;
+       if (!dhcpcd_realloc(wpa->con, len))
+               return false;
+       snprintf(wpa->con->buf, wpa->con->buflen, "SET_NETWORK %d %s %s",
+           id, param, value);
+       return dhcpcd_wpa_command(wpa, wpa->con->buf);
 }
 
 static int
-dhcpcd_wpa_find_network(DHCPCD_CONNECTION *con, DHCPCD_IF *i, const char *ssid)
+dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
 {
-       DBusMessage *msg;
-       DBusMessageIter args, array, entry;
-       int32_t id;
-       char *s;
-       int errors;
+       ssize_t bytes, dl, tl;
+       size_t fl;
+       char *s, *t, *ssid, *bssid, *flags;
+       char dssid[IF_SSIDSIZE], tssid[IF_SSIDSIZE];
+       long l;
 
-       msg = dhcpcd_message_reply(con, "ListNetworks", i->ifname);
-       if (msg == NULL)
-               return -1;
-       if (!dbus_message_iter_init(msg, &args)) {
-               dhcpcd_error_set(con, NULL, EINVAL);
+       dhcpcd_realloc(wpa->con, 2048);
+       bytes = wpa_cmd(wpa->command_fd, "LIST_NETWORKS",
+           wpa->con->buf, wpa->con->buflen);
+       if (bytes == 0 || bytes == -1)
                return -1;
-       }
 
-       errors = con->errors;
-       for(;
-           dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_ARRAY;
-           dbus_message_iter_next(&args))
-       {
-               dbus_message_iter_recurse(&args, &array);
-               for(;
-                   dbus_message_iter_get_arg_type(&array) == DBUS_TYPE_STRUCT;
-                   dbus_message_iter_next(&array))
-               {
-                       dbus_message_iter_recurse(&array, &entry);
-                       if (!dhcpcd_iter_get(con, &entry,
-                               DBUS_TYPE_INT32, &id) ||
-                           !dhcpcd_iter_get(con, &entry,
-                               DBUS_TYPE_STRING, &s))
-                               break;
-                       if (strcmp(s, ssid) == 0) {
-                               dbus_message_unref(msg);
-                               return (int)id;
-                       }
+       fl = strlen(fssid);
+
+       s = strchr(wpa->con->buf, '\n');
+       if (s == NULL)
+               return -1;
+       while ((t = strsep(&s, "\b\n"))) {
+               if (*t == '\0')
+                       continue;
+               ssid = strchr(t, '\t');
+               if (ssid == NULL)
+                       break;
+               *ssid++ = '\0';
+               bssid = strchr(ssid, '\t');
+               if (bssid == NULL)
+                       break;
+               *bssid++ = '\0';
+               flags = strchr(bssid, '\t');
+               if (flags == NULL)
+                       break;
+               *flags++ = '\0';
+               l = strtol(t, NULL, 0);
+               if (l < 0 || l > INT_MAX) {
+                       errno = ERANGE;
+                       break;
                }
+
+               /* Decode the wpa_supplicant SSID into raw chars and
+                * then encode into our octal escaped string to
+                * compare. */
+               dl = dhcpcd_wpa_decode_ssid(dssid, sizeof(dssid), ssid);
+               if (dl == -1)
+                       return -1;
+               tl = dhcpcd_encode_string_escape(tssid,
+                   sizeof(tssid), dssid, (size_t)dl);
+               if (tl == -1)
+                       return -1;
+               if ((size_t)tl == fl && memcmp(tssid, fssid, (size_t)tl) == 0)
+                       return (int)l;
        }
-       if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_INVALID &&
-           con->errors == errors)
-               dhcpcd_error_set(con, NULL, EINVAL);
-       dbus_message_unref(msg);
+       errno = ENOENT;
        return -1;
 }
 
 static int
-dhcpcd_wpa_add_network(DHCPCD_CONNECTION *con, DHCPCD_IF *i)
+dhcpcd_wpa_network_new(DHCPCD_WPA *wpa)
 {
-       DBusMessage *msg;
-       DBusMessageIter args;
-       int32_t id;
-       int ret;
+       ssize_t bytes;
+       long l;
 
-       msg = dhcpcd_message_reply(con, "AddNetwork", i->ifname);
-       if (msg == NULL)
+       dhcpcd_realloc(wpa->con, 32);
+       bytes = wpa_cmd(wpa->command_fd, "ADD_NETWORK",
+           wpa->con->buf, sizeof(wpa->con->buf));
+       if (bytes == 0 || bytes == -1)
+               return -1;
+       l = strtol(wpa->con->buf, NULL, 0);
+       if (l < 0 || l > INT_MAX) {
+               errno = ERANGE;
                return -1;
-       ret = -1;
-       if (dbus_message_iter_init(msg, &args)) {
-               if (dhcpcd_iter_get(con, &args, DBUS_TYPE_INT32, &id))
-                       ret = id;
-       } else
-               dhcpcd_error_set(con, NULL, EINVAL);
-       dbus_message_unref(msg);
-       return ret;
+       }
+       return (int)l;
 }
 
-bool
-dhcpcd_wpa_set_network(DHCPCD_CONNECTION *con, DHCPCD_IF *i, int id,
-    const char *opt, const char *val)
-{
-       DBusMessage *msg, *reply;
-       DBusMessageIter args;
-       bool retval;
-       char *ifname;
-
-       msg = dbus_message_new_method_call(DHCPCD_SERVICE, DHCPCD_PATH,
-           DHCPCD_SERVICE, "SetNetwork");
-       if (msg == NULL) {
-               dhcpcd_error_set(con, 0, errno);
-               return false;
+static const char hexchrs[] = "0123456789abcdef";
+int
+dhcpcd_wpa_network_find_new(DHCPCD_WPA *wpa, const char *ssid)
+{
+       int id;
+       char dssid[IF_SSIDSIZE], essid[IF_SSIDSIZE], *ep;
+       ssize_t dl, i;
+       char *dp;
+
+       id = dhcpcd_wpa_network_find(wpa, ssid);
+       if (id != -1)
+               return id;
+
+       dl = dhcpcd_decode_string_escape(dssid, sizeof(dssid), ssid);
+       if (dl == -1)
+               return -1;
+
+       for (i = 0; i < dl; i++) {
+               if (!isascii((int)dssid[i]) && !isprint((int)dssid[i]))
+                       break;
        }
-       dbus_message_iter_init_append(msg, &args);
-       ifname = i->ifname;
-       dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &ifname);
-       dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &id);
-       dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &opt);
-       dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &val);
-       reply = dhcpcd_send_reply(con, msg);
-       dbus_message_unref(msg);
-       if (reply == NULL)
-               retval = false;
-       else {
-               dbus_message_unref(reply);
-               retval = true;
+       dp = dssid;
+       ep = essid;
+       if (i < dl) {
+               /* Non standard characters found! Encode as hex string */
+               unsigned char c;
+
+               for (; dl; dl--) {
+                       c = (unsigned char)*dp++;
+                       *ep++ = hexchrs[(c & 0xf0) >> 4];
+                       *ep++ = hexchrs[(c & 0x0f)];
+               }
+       } else {
+               *ep++ = '\"';
+               do
+                       *ep++ = *dp;
+               while (*++dp != '\0');
+               *ep++ = '\"';
        }
-       return retval;
+       *ep = '\0';
+
+       id = dhcpcd_wpa_network_new(wpa);
+       if (id != -1)
+               dhcpcd_wpa_network_set(wpa, id, "ssid", essid);
+       return id;
+}
+
+void
+dhcpcd_wpa_close(DHCPCD_WPA *wpa)
+{
+
+       assert(wpa);
+
+       if (wpa->command_fd == -1 || !wpa->open)
+               return;
+
+       wpa->open = false;
+       dhcpcd_attach_detach(wpa, false);
+       shutdown(wpa->command_fd, SHUT_RDWR);
+       shutdown(wpa->listen_fd, SHUT_RDWR);
+
+       if (wpa->con->wpa_status_cb)
+               wpa->con->wpa_status_cb(wpa, "down",
+                   wpa->con->wpa_status_context);
+
+       close(wpa->command_fd);
+       wpa->command_fd = -1;
+       close(wpa->listen_fd);
+       wpa->listen_fd = -1;
+       unlink(wpa->command_path);
+       free(wpa->command_path);
+       wpa->command_path = NULL;
+       unlink(wpa->listen_path);
+       free(wpa->listen_path);
+       wpa->listen_path = NULL;
+}
+
+DHCPCD_WPA *
+dhcpcd_wpa_find(DHCPCD_CONNECTION *con, const char *ifname)
+{
+       DHCPCD_WPA *wpa;
+
+       for (wpa = con->wpa; wpa; wpa = wpa->next) {
+               if (strcmp(wpa->ifname, ifname) == 0)
+                       return wpa;
+       }
+       errno = ENOENT;
+       return NULL;
+}
+
+DHCPCD_WPA *
+dhcpcd_wpa_new(DHCPCD_CONNECTION *con, const char *ifname)
+{
+       DHCPCD_WPA *wpa;
+
+       wpa = dhcpcd_wpa_find(con, ifname);
+       if (wpa)
+               return wpa;
+
+       wpa = malloc(sizeof(*wpa));
+       if (wpa == NULL)
+               return NULL;
+
+       wpa->con = con;
+       strlcpy(wpa->ifname, ifname, sizeof(wpa->ifname));
+       wpa->command_fd = wpa->listen_fd = -1;
+       wpa->command_path = wpa->listen_path = NULL;
+       wpa->next = con->wpa;
+       con->wpa = wpa;
+       return wpa;
+}
+
+DHCPCD_CONNECTION *
+dhcpcd_wpa_connection(DHCPCD_WPA *wpa)
+{
+
+       assert(wpa);
+       return wpa->con;
+}
+
+DHCPCD_IF *
+dhcpcd_wpa_if(DHCPCD_WPA *wpa)
+{
+
+       return dhcpcd_get_if(wpa->con, wpa->ifname, "link");
 }
 
 int
-dhcpcd_wpa_find_network_new(DHCPCD_CONNECTION *con, DHCPCD_IF *i,
-    const char *ssid)
+dhcpcd_wpa_open(DHCPCD_WPA *wpa)
 {
-       int errors, id;
-       char *q;
-       size_t len;
-       bool retval;
+       int cmd_fd, list_fd = -1;
+       char *cmd_path = NULL, *list_path = NULL;
 
-       len = strlen(ssid) + 3;
-       q = malloc(len);
-       if (q == NULL) {
-               dhcpcd_error_set(con, 0, errno);
+       if (wpa->listen_fd != -1) {
+               if (!wpa->open) {
+                       errno = EISCONN;
+                       return -1;
+               }
+               return wpa->listen_fd;
+       }
+
+       cmd_fd = wpa_open(wpa->ifname, &cmd_path);
+       if (cmd_fd == -1)
+               goto fail;
+
+       list_fd = wpa_open(wpa->ifname, &list_path);
+       if (list_fd == -1)
+               goto fail;
+
+       wpa->open = true;
+       wpa->attached = false;
+       wpa->command_fd = cmd_fd;
+       wpa->command_path = cmd_path;
+       wpa->listen_fd = list_fd;
+       wpa->listen_path = list_path;
+       if (!dhcpcd_attach_detach(wpa, true)) {
+               dhcpcd_wpa_close(wpa);
                return -1;
        }
-       errors = con->errors;
-       id = dhcpcd_wpa_find_network(con, i, ssid);
-       if (id != -1 || con->errors != errors) {
-               free(q);
-               return id;
+
+       if (wpa->con->wi_scanresults_cb)
+               wpa->con->wi_scanresults_cb(wpa,
+                   wpa->con->wi_scanresults_context);
+
+       return wpa->listen_fd;
+
+fail:
+       if (cmd_fd != -1)
+               close(cmd_fd);
+       if (list_fd != -1)
+               close(list_fd);
+       if (cmd_path)
+               unlink(cmd_path);
+       free(cmd_path);
+       if (list_path)
+               free(list_path);
+       return -1;
+}
+
+int
+dhcpcd_wpa_get_fd(DHCPCD_WPA *wpa)
+{
+
+       assert(wpa);
+       return wpa->open ? wpa->listen_fd : -1;
+}
+
+void
+dhcpcd_wpa_set_scan_callback(DHCPCD_CONNECTION *con,
+    void (*cb)(DHCPCD_WPA *, void *), void *context)
+{
+
+       assert(con);
+       con->wi_scanresults_cb = cb;
+       con->wi_scanresults_context = context;
+}
+
+
+void dhcpcd_wpa_set_status_callback(DHCPCD_CONNECTION * con,
+    void (*cb)(DHCPCD_WPA *, const char *, void *), void *context)
+{
+
+       assert(con);
+       con->wpa_status_cb = cb;
+       con->wpa_status_context = context;
+}
+
+void
+dhcpcd_wpa_dispatch(DHCPCD_WPA *wpa)
+{
+       char buffer[256], *p;
+       size_t bytes;
+
+       assert(wpa);
+       bytes = (size_t)read(wpa->listen_fd, buffer, sizeof(buffer));
+       if ((ssize_t)bytes == -1 || bytes == 0) {
+               dhcpcd_wpa_close(wpa);
+               return;
        }
-       id = dhcpcd_wpa_add_network(con, i);
-       if (id == -1) {
-               free(q);
-               return -1;
+
+       buffer[bytes] = '\0';
+       bytes = strlen(buffer);
+       if (buffer[bytes - 1] == ' ')
+               buffer[--bytes] = '\0';
+       for (p = buffer + 1; *p != '\0'; p++)
+               if (*p == '>') {
+                       p++;
+                       break;
+               }
+       if (strcmp(p, "CTRL-EVENT-SCAN-RESULTS") == 0 &&
+           wpa->con->wi_scanresults_cb)
+               wpa->con->wi_scanresults_cb(wpa,
+                   wpa->con->wi_scanresults_context);
+       return;
+}
+
+void
+dhcpcd_wpa_if_event(DHCPCD_IF *i)
+{
+       DHCPCD_WPA *wpa;
+
+       assert(i);
+       if (strcmp(i->type, "link") == 0) {
+               if (strcmp(i->reason, "STOPPED") == 0 ||
+                   strcmp(i->reason, "DEPARTED") == 0)
+               {
+                       wpa = dhcpcd_wpa_find(i->con, i->ifname);
+                       if (wpa)
+                               dhcpcd_wpa_close(wpa);
+               } else if (i->wireless && i->con->wpa_started) {
+                       wpa = dhcpcd_wpa_new(i->con, i->ifname);
+                       if (wpa && wpa->listen_fd == -1)
+                               dhcpcd_wpa_open(wpa);
+               }
+       }
+}
+
+void
+dhcpcd_wpa_start(DHCPCD_CONNECTION *con)
+{
+       DHCPCD_IF *i;
+
+       assert(con);
+       con->wpa_started = true;
+
+       for (i = con->interfaces; i; i = i->next)
+               dhcpcd_wpa_if_event(i);
+}
+
+static const char *
+dhcpcd_wpa_var_psk(DHCPCD_WI_SCAN *s)
+{
+
+       if (s->flags & WSF_WEP)
+               return "wep_key0";
+       else if ((s->flags & (WSF_WPA | WSF_PSK)) == (WSF_WPA | WSF_PSK))
+               return "psk";
+       return NULL;
+}
+
+static const char *
+dhcpcd_wpa_var_mgmt(DHCPCD_WI_SCAN *s)
+{
+
+       if (s->flags & WSF_WPA) {
+               if (s->flags & WSF_PSK)
+                       return "WPA-PSK";
        }
-       snprintf(q, len, "\"%s\"", ssid);
-       retval = dhcpcd_wpa_set_network(con, i, id, "ssid", q);
-       free(q);
+       return "NONE";
+}
+
+static int
+dhcpcd_wpa_configure1(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
+{
+       const char *mgmt, *var;
+       int id, retval;
+       char *npsk;
+       size_t psk_len;
+       bool r;
+
+       if (!dhcpcd_wpa_disconnect(wpa))
+               return DHCPCD_WPA_ERR_DISCONN;
+
+       /* reload the configuration so that when we don't save
+        * the disabled networks to the config file. */
+       if (!dhcpcd_wpa_reconfigure(wpa))
+               return DHCPCD_WPA_ERR_RECONF;
+
+       id = dhcpcd_wpa_network_find_new(wpa, s->ssid);
+       if (id == -1)
+               return DHCPCD_WPA_ERR;
+
+       mgmt = dhcpcd_wpa_var_mgmt(s);
+       var = dhcpcd_wpa_var_psk(s);
+       if (mgmt && var) {
+               if (!dhcpcd_wpa_network_set(wpa, id, "key_mgmt", mgmt))
+                       return DHCPCD_WPA_ERR_SET;
+
+               if (psk)
+                       psk_len = strlen(psk);
+               else
+                       psk_len = 0;
+               npsk = malloc(psk_len + 3);
+               if (npsk == NULL)
+                       return DHCPCD_WPA_ERR;
+               npsk[0] = '"';
+               if (psk_len)
+                       memcpy(npsk + 1, psk, psk_len);
+               npsk[psk_len + 1] = '"';
+               npsk[psk_len + 2] = '\0';
+               r = dhcpcd_wpa_network_set(wpa, id, var, npsk);
+               free(npsk);
+               if (!r)
+                       return DHCPCD_WPA_ERR_SET_PSK;
+       }
+
+       if (!dhcpcd_wpa_network_enable(wpa, id))
+               return DHCPCD_WPA_ERR_ENABLE;
+       if (dhcpcd_wpa_config_write(wpa))
+               retval = DHCPCD_WPA_SUCCESS;
+       else
+               retval = DHCPCD_WPA_ERR_WRITE;
+       /* Selecting a network disbales the others.
+        * This should not be saved. */
+       if (!dhcpcd_wpa_network_select(wpa, id))
+               return DHCPCD_WPA_ERR_SELECT;
        return retval;
 }
 
-bool
-dhcpcd_wpa_command(DHCPCD_CONNECTION *con, DHCPCD_IF *i,
-    const char *cmd, int id)
-{
-       DBusMessage *msg, *reply;
-       DBusMessageIter args;
-       char *ifname;
-       bool retval;
-
-       msg = dbus_message_new_method_call(DHCPCD_SERVICE, DHCPCD_PATH,
-           DHCPCD_SERVICE, cmd);
-       if (msg == NULL) {
-               dhcpcd_error_set(con, 0, errno);
-               return false;
+int
+dhcpcd_wpa_configure(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
+{
+       int retval;
+
+       retval = dhcpcd_wpa_configure1(wpa, s, psk);
+       /* Always reassociate */
+       if (!dhcpcd_wpa_reassociate(wpa)) {
+               if (retval == DHCPCD_WPA_SUCCESS)
+                       retval = DHCPCD_WPA_ERR_ASSOC;
        }
-       dbus_message_iter_init_append(msg, &args);
-       ifname = i->ifname;
-       dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &ifname);
-       if (id != -1)
-               dbus_message_iter_append_basic(&args, DBUS_TYPE_INT32, &id);
-       reply = dhcpcd_send_reply(con, msg);
-       dbus_message_unref(msg);
-       if (reply == NULL)
-               retval = false;
-       else {
-               dbus_message_unref(reply);
-               retval = true;
+       return retval;
+}
+
+int
+dhcpcd_wpa_select(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s)
+{
+       int id, retval;
+
+       assert(wpa);
+       assert(s);
+
+       id = dhcpcd_wpa_network_find(wpa, s->ssid);
+       if (id == -1)
+               return DHCPCD_WPA_ERR;
+
+       if (!dhcpcd_wpa_disconnect(wpa))
+               retval = DHCPCD_WPA_ERR_DISCONN;
+       else if (!dhcpcd_wpa_network_select(wpa, id))
+               retval = DHCPCD_WPA_ERR_SELECT;
+       else
+               retval = DHCPCD_WPA_SUCCESS;
+
+       /* Always reassociate */
+       if (!dhcpcd_wpa_reassociate(wpa)) {
+               if (retval == DHCPCD_WPA_SUCCESS)
+                       retval = DHCPCD_WPA_ERR_ASSOC;
        }
        return retval;
 }