X-Git-Url: https://roy.marples.name/git diff --git a/src/libdhcpcd/wpa.c b/src/libdhcpcd/wpa.c index 4861c5c..358b373 100644 --- a/src/libdhcpcd/wpa.c +++ b/src/libdhcpcd/wpa.c @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -191,13 +192,92 @@ dhcpcd_strtoi(int *val, const char *s) 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; @@ -213,11 +293,8 @@ dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa) 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; @@ -234,10 +311,50 @@ dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa) 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) @@ -254,16 +371,133 @@ dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa) /* 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; @@ -271,7 +505,30 @@ dhcpcd_wi_scans(DHCPCD_IF *i) 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; @@ -319,6 +576,13 @@ dhcpcd_wi_scans(DHCPCD_IF *i) } bool +dhcpcd_wpa_reconfigure(DHCPCD_WPA *wpa) +{ + + return dhcpcd_wpa_command(wpa, "RECONFIGURE"); +} + +bool dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa) { @@ -366,6 +630,13 @@ dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int 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) { @@ -409,8 +680,10 @@ dhcpcd_wpa_network_set(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); @@ -419,10 +692,12 @@ dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid) 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'); @@ -442,7 +717,18 @@ dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid) 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; @@ -468,14 +754,50 @@ dhcpcd_wpa_network_new(DHCPCD_WPA *wpa) 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; } @@ -701,55 +1023,123 @@ dhcpcd_wpa_start(DHCPCD_CONNECTION *con) 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; }