#include <sys/un.h>
#include <assert.h>
+#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <poll.h>
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;
+ 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;
+ default: errno = EINVAL; return -1;
+ }
+ default: *dst++ = c; break;
+ }
+ }
+ 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;
+ 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;
w = calloc(1, sizeof(*w));
if (w == NULL)
break;
- if (wis == NULL)
- wis = w;
- else
- l->next = w;
- l = w;
+ dl = 0;
+ wssid[0] = '\0';
while ((s = strsep(&p, "\n"))) {
if (*s == '\0')
continue;
else if (strncmp(s, "level=", 6) == 0)
dhcpcd_strtoi(&w->level.value, s + 6);
else if (strncmp(s, "flags=", 6) == 0)
- strlcpy(w->flags, s + 6, sizeof(w->flags));
- else if (strncmp(s, "ssid=", 5) == 0)
- strlcpy(w->ssid, s + 5, sizeof(w->ssid));
+ 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)
/* 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_IF *i)
{
DHCPCD_WPA *wpa;
- DHCPCD_WI_SCAN *wis, *w;
+ DHCPCD_WI_SCAN *wis, *w, *n, *p;
int nh;
DHCPCD_WI_HIST *h, *hl;
if (wpa == NULL)
return NULL;
wis = dhcpcd_wpa_scans_read(wpa);
- for (w = wis; w; w = w->next) {
+
+ /* 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;
+
nh = 1;
hl = NULL;
w->quality.average = w->quality.value;
}
bool
+dhcpcd_wpa_reconfigure(DHCPCD_WPA *wpa)
+{
+
+ return dhcpcd_wpa_command(wpa, "RECONFIGURE");
+}
+
+bool
dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
{
}
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)
{
static int
dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
{
- ssize_t bytes;
+ ssize_t bytes, dl, tl;
+ size_t fl;
char *s, *t, *ssid, *bssid, *flags;
+ char dssid[IF_SSIDSIZE], tssid[IF_SSIDSIZE];
long l;
dhcpcd_realloc(wpa->con, 2048);
if (bytes == 0 || bytes == -1)
return -1;
+ fl = strlen(fssid);
+
s = strchr(wpa->con->buf, '\n');
if (s == NULL)
return -1;
- while ((t = strsep(&s, "\b"))) {
+ while ((t = strsep(&s, "\b\n"))) {
if (*t == '\0')
continue;
ssid = strchr(t, '\t');
errno = ERANGE;
break;
}
- if (strcmp(ssid, fssid) == 0)
+
+ /* 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;
}
errno = ENOENT;
return (int)l;
}
+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)
- id = dhcpcd_wpa_network_new(wpa);
+ 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;
+ }
+ 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++ = '\"';
+ }
+ *ep = '\0';
+
+ id = dhcpcd_wpa_network_new(wpa);
+ if (id != -1)
+ dhcpcd_wpa_network_set(wpa, id, "ssid", essid);
return id;
}
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)
{
DHCPCD_WPA *wpa;
assert(i);
- if (i->wireless && strcmp(i->type, "link") == 0) {
- if (strcmp(i->reason, "STOPPED") == 0) {
+ 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->con->wpa_started) {
+ } 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);
dhcpcd_wpa_if_event(i);
}
-int
-dhcpcd_wpa_configure_psk(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
+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";
+ }
+ return "NONE";
+}
+
+static int
+dhcpcd_wpa_configure1(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
{
const char *mgmt, *var;
- int id;
+ int id, retval;
char *npsk;
size_t psk_len;
bool r;
- assert(wpa);
- assert(s);
+ 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;
- if (strcmp(s->flags, "[WEP]") == 0) {
- mgmt = "NONE";
- var = "wep_key0";
- } else {
- mgmt = "WPA-PSK";
- var = "psk";
- }
+ 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 (!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 (psk)
- psk_len = strlen(psk);
+ if (!dhcpcd_wpa_network_enable(wpa, id))
+ return DHCPCD_WPA_ERR_ENABLE;
+ if (dhcpcd_wpa_config_write(wpa))
+ retval = DHCPCD_WPA_SUCCESS;
else
- psk_len = 0;
- npsk = malloc(psk_len + 3);
- if (npsk == NULL)
+ 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;
+}
+
+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;
+ }
+ 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;
- 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_reassociate(wpa))
- return DHCPCD_WPA_ERR_ASSOC;
- if (!dhcpcd_wpa_config_write(wpa))
- return DHCPCD_WPA_ERR_WRITE;
- return DHCPCD_WPA_SUCCESS;
+ 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;
}