/*
* libdhcpcd
- * Copyright 2009 Roy Marples <roy@marples.name>
+ * Copyright 2009-2014 Roy Marples <roy@marples.name>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 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
}
}
-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 (!dbus_message_iter_init(msg, &args) ||
- dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
- {
- dhcpcd_error_set(con, NULL, EINVAL);
+ wpa = dhcpcd_wpa_find(i->con, i->ifname);
+ if (wpa == NULL)
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;
}
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;
}