3 * Copyright 2009-2014 Roy Marples <roy@marples.name>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 #include <sys/socket.h>
47 (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
50 #define CLAMP(x, low, high) \
51 (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
54 wpa_open(const char *ifname, char **path)
59 struct sockaddr_un sun;
61 if ((fd = socket(AF_UNIX,
62 SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1)
64 memset(&sun, 0, sizeof(sun));
65 sun.sun_family = AF_UNIX;
66 snprintf(sun.sun_path, sizeof(sun.sun_path),
67 "/tmp/libdhcpcd-wpa-%d.%d", getpid(), counter++);
68 *path = strdup(sun.sun_path);
69 len = (socklen_t)SUN_LEN(&sun);
70 if (bind(fd, (struct sockaddr *)&sun, len) == -1) {
77 snprintf(sun.sun_path, sizeof(sun.sun_path),
78 WPA_CTRL_DIR "/%s", ifname);
79 len = (socklen_t)SUN_LEN(&sun);
80 if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
92 wpa_cmd(int fd, const char *cmd, char *buffer, size_t len)
100 bytes = write(fd, cmd, strlen(cmd));
101 if (bytes == -1 || bytes == 0)
103 if (buffer == NULL || len == 0)
106 pfd.events = POLLIN | POLLHUP;
108 retval = poll(&pfd, 1, 2000);
111 if (retval == 0 || !(pfd.revents & (POLLIN | POLLHUP)))
114 bytes = read(fd, buffer, len == 1 ? 1 : len - 1);
116 buffer[bytes] = '\0';
121 dhcpcd_wpa_command(DHCPCD_WPA *wpa, const char *cmd)
126 bytes = wpa_cmd(wpa->command_fd, cmd, buf, sizeof(buf));
127 return (bytes == -1 || bytes == 0 ||
128 strcmp(buf, "OK\n")) ? false : true;
132 dhcpcd_wpa_command_arg(DHCPCD_WPA *wpa, const char *cmd, const char *arg)
136 cmdlen = strlen(cmd);
137 nlen = cmdlen + strlen(arg) + 2;
138 if (!dhcpcd_realloc(wpa->con, nlen))
140 strlcpy(wpa->con->buf, cmd, wpa->con->buflen);
141 wpa->con->buf[cmdlen] = ' ';
142 strlcpy(wpa->con->buf + cmdlen + 1, arg, wpa->con->buflen - 1 - cmdlen);
143 return dhcpcd_wpa_command(wpa, wpa->con->buf);
147 dhcpcd_attach_detach(DHCPCD_WPA *wpa, bool attach)
152 if (wpa->attached == attach)
155 bytes = wpa_cmd(wpa->listen_fd, attach > 0 ? "ATTACH" : "DETACH",
157 if (bytes == -1 || bytes == 0 || strcmp(buf, "OK\n"))
160 wpa->attached = attach;
165 dhcpcd_wpa_scan(DHCPCD_WPA *wpa)
168 return dhcpcd_wpa_command(wpa, "SCAN");
172 dhcpcd_wi_scans_free(DHCPCD_WI_SCAN *wis)
184 dhcpcd_strtoi(int *val, const char *s)
188 l = strtol(s, NULL, 0);
189 if (l >= INT_MIN && l <= INT_MAX)
196 dhcpcd_wpa_hex2num(char c)
199 if (c >= '0' && c <= '9')
201 if (c >= 'a' && c <= 'f')
203 if (c >= 'A' && c <= 'F')
209 dhcpcd_wpa_hex2byte(const char *src)
213 if ((h = dhcpcd_wpa_hex2num(*src++)) == -1 ||
214 (l = dhcpcd_wpa_hex2num(*src)) == -1)
220 dhcpcd_wpa_decode_ssid(char *dst, size_t dlen, const char *src)
244 case '"': *dst++ = esc; break;
245 case 'n': *dst++ = '\n'; break;
246 case 'r': *dst++ = '\r'; break;
247 case 't': *dst++ = '\t'; break;
248 case 'e': *dst++ = '\033'; break;
250 if (src[0] == '\0' || src[1] == '\0') {
254 if ((xb = dhcpcd_wpa_hex2byte(src)) == -1)
259 default: errno = EINVAL; return -1;
261 default: *dst++ = c; break;
272 static DHCPCD_WI_SCAN *
273 dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa)
277 DHCPCD_WI_SCAN *wis, *w, *l;
278 char *s, *p, buf[32];
279 char wssid[sizeof(w->ssid)];
281 if (!dhcpcd_realloc(wpa->con, 2048))
284 for (i = 0; i < 1000; i++) {
285 snprintf(buf, sizeof(buf), "BSS %zu", i);
286 bytes = wpa_cmd(wpa->command_fd, buf,
287 wpa->con->buf, wpa->con->buflen);
288 if (bytes == 0 || bytes == -1 ||
289 strncmp(wpa->con->buf, "FAIL", 4) == 0)
292 w = calloc(1, sizeof(*w));
297 while ((s = strsep(&p, "\n"))) {
300 if (strncmp(s, "bssid=", 6) == 0)
301 strlcpy(w->bssid, s + 6, sizeof(w->bssid));
302 else if (strncmp(s, "freq=", 5) == 0)
303 dhcpcd_strtoi(&w->frequency, s + 5);
304 // else if (strncmp(s, "beacon_int=", 11) == 0)
306 else if (strncmp(s, "qual=", 5) == 0)
307 dhcpcd_strtoi(&w->quality.value, s + 5);
308 else if (strncmp(s, "noise=", 6) == 0)
309 dhcpcd_strtoi(&w->noise.value, s + 6);
310 else if (strncmp(s, "level=", 6) == 0)
311 dhcpcd_strtoi(&w->level.value, s + 6);
312 else if (strncmp(s, "flags=", 6) == 0)
313 strlcpy(w->flags, s + 6, sizeof(w->flags));
314 else if (strncmp(s, "ssid=", 5) == 0) {
315 /* Decode it from \xNN to \NNN
316 * so we're consistent */
317 dl = dhcpcd_wpa_decode_ssid(wssid,
318 sizeof(wssid), s + 5);
321 dl = dhcpcd_encode_string_escape(w->ssid,
322 sizeof(w->ssid), wssid, (size_t)dl);
338 w->strength.value = w->level.value;
339 if (w->strength.value > 110 && w->strength.value < 256)
340 /* Convert WEXT level to dBm */
341 w->strength.value -= 256;
343 if (w->strength.value < 0) {
346 abs(CLAMP(w->strength.value, -100, -40) + 40);
348 100 - ((100 * w->strength.value) / 60);
350 /* Assume quality percentage */
351 w->strength.value = CLAMP(w->strength.value, 0, 100);
358 dhcpcd_wi_scans(DHCPCD_IF *i)
361 DHCPCD_WI_SCAN *wis, *w;
363 DHCPCD_WI_HIST *h, *hl;
365 wpa = dhcpcd_wpa_find(i->con, i->ifname);
368 wis = dhcpcd_wpa_scans_read(wpa);
369 for (w = wis; w; w = w->next) {
372 w->quality.average = w->quality.value;
373 w->noise.average = w->noise.value;
374 w->level.average = w->level.value;
375 w->strength.average = w->strength.value;
377 for (h = wpa->con->wi_history; h; h = h->next) {
378 if (strcmp(h->ifname, i->ifname) == 0 &&
379 strcmp(h->bssid, wis->bssid) == 0)
381 w->quality.average += h->quality;
382 w->noise.average += h->noise;
383 w->level.average += h->level;
384 w->strength.average += h->strength;
385 if (++nh == DHCPCD_WI_HIST_MAX) {
395 w->quality.average /= nh;
396 w->noise.average /= nh;
397 w->level.average /= nh;
398 w->strength.average /= nh;
400 h = malloc(sizeof(*h));
402 strlcpy(h->ifname, i->ifname, sizeof(h->ifname));
403 strlcpy(h->bssid, w->bssid, sizeof(h->bssid));
404 h->quality = w->quality.value;
405 h->noise = w->noise.value;
406 h->level = w->level.value;
407 h->strength = w->strength.value;
408 h->next = wpa->con->wi_history;
409 wpa->con->wi_history = h;
417 dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
420 return dhcpcd_wpa_command(wpa, "REASSOCIATE");
424 dhcpcd_wpa_disconnect(DHCPCD_WPA *wpa)
427 return dhcpcd_wpa_command(wpa, "DISCONNECT");
431 dhcpcd_wpa_config_write(DHCPCD_WPA *wpa)
434 return dhcpcd_wpa_command(wpa, "SAVE_CONFIG");
438 dhcpcd_wpa_network(DHCPCD_WPA *wpa, const char *cmd, int id)
442 len = strlen(cmd) + 32;
443 if (!dhcpcd_realloc(wpa->con, len))
445 snprintf(wpa->con->buf, wpa->con->buflen, "%s %d", cmd, id);
446 return dhcpcd_wpa_command(wpa, wpa->con->buf);
450 dhcpcd_wpa_network_disable(DHCPCD_WPA *wpa, int id)
453 return dhcpcd_wpa_network(wpa, "DISABLE_NETWORK", id);
457 dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int id)
460 return dhcpcd_wpa_network(wpa, "ENABLE_NETWORK", id);
464 dhcpcd_wpa_network_remove(DHCPCD_WPA *wpa, int id)
467 return dhcpcd_wpa_network(wpa, "REMOVE_NETWORK", id);
471 dhcpcd_wpa_network_get(DHCPCD_WPA *wpa, int id, const char *param)
475 if (!dhcpcd_realloc(wpa->con, 2048))
477 snprintf(wpa->con->buf, wpa->con->buflen, "GET_NETWORK %d %s",
479 bytes = wpa_cmd(wpa->command_fd, wpa->con->buf,
480 wpa->con->buf, wpa->con->buflen);
481 if (bytes == 0 || bytes == -1)
483 if (strcmp(wpa->con->buf, "FAIL\n") == 0) {
487 return wpa->con->buf;
491 dhcpcd_wpa_network_set(DHCPCD_WPA *wpa, int id,
492 const char *param, const char *value)
496 len = strlen("SET_NETWORK") + 32 + strlen(param) + strlen(value) + 3;
497 if (!dhcpcd_realloc(wpa->con, len))
499 snprintf(wpa->con->buf, wpa->con->buflen, "SET_NETWORK %d %s %s",
501 return dhcpcd_wpa_command(wpa, wpa->con->buf);
505 dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
507 ssize_t bytes, dl, tl;
509 char *s, *t, *ssid, *bssid, *flags;
510 char dssid[IF_SSIDSIZE], tssid[IF_SSIDSIZE];
513 dhcpcd_realloc(wpa->con, 2048);
514 bytes = wpa_cmd(wpa->command_fd, "LIST_NETWORKS",
515 wpa->con->buf, wpa->con->buflen);
516 if (bytes == 0 || bytes == -1)
521 s = strchr(wpa->con->buf, '\n');
524 while ((t = strsep(&s, "\b"))) {
527 ssid = strchr(t, '\t');
531 bssid = strchr(ssid, '\t');
535 flags = strchr(bssid, '\t');
539 l = strtol(t, NULL, 0);
540 if (l < 0 || l > INT_MAX) {
545 /* Decode the wpa_supplicant SSID into raw chars and
546 * then encode into our octal escaped string to
548 dl = dhcpcd_wpa_decode_ssid(dssid, sizeof(dssid), ssid);
551 tl = dhcpcd_encode_string_escape(tssid,
552 sizeof(tssid), dssid, (size_t)dl);
555 if ((size_t)tl == fl && memcmp(tssid, fssid, (size_t)tl) == 0)
563 dhcpcd_wpa_network_new(DHCPCD_WPA *wpa)
568 dhcpcd_realloc(wpa->con, 32);
569 bytes = wpa_cmd(wpa->command_fd, "ADD_NETWORK",
570 wpa->con->buf, sizeof(wpa->con->buf));
571 if (bytes == 0 || bytes == -1)
573 l = strtol(wpa->con->buf, NULL, 0);
574 if (l < 0 || l > INT_MAX) {
581 static const char hexstr[] = "0123456789abcdef";
583 dhcpcd_wpa_network_find_new(DHCPCD_WPA *wpa, const char *ssid)
586 char dssid[IF_SSIDSIZE], essid[IF_SSIDSIZE], *ep;
589 id = dhcpcd_wpa_network_find(wpa, ssid);
593 dl = dhcpcd_decode_string_escape(dssid, sizeof(dssid), ssid);
597 for (i = 0; i < dl; i++) {
598 if (!isascii((int)dssid[i]) && !isprint((int)dssid[i]))
603 /* Non standard characters found! Encode as hex string */
609 c = (unsigned char)*dp++;
610 *ep++ = hexstr[c >> 4];
611 *ep++ = hexstr[c & 0xf];
615 ep = stpcpy(ep, dssid);
620 id = dhcpcd_wpa_network_new(wpa);
622 dhcpcd_wpa_network_set(wpa, id, "ssid", essid);
627 dhcpcd_wpa_close(DHCPCD_WPA *wpa)
632 if (wpa->command_fd == -1 || !wpa->open)
636 dhcpcd_attach_detach(wpa, false);
637 shutdown(wpa->command_fd, SHUT_RDWR);
638 shutdown(wpa->listen_fd, SHUT_RDWR);
640 if (wpa->con->wpa_status_cb)
641 wpa->con->wpa_status_cb(wpa, "down",
642 wpa->con->wpa_status_context);
644 close(wpa->command_fd);
645 wpa->command_fd = -1;
646 close(wpa->listen_fd);
648 unlink(wpa->command_path);
649 free(wpa->command_path);
650 wpa->command_path = NULL;
651 unlink(wpa->listen_path);
652 free(wpa->listen_path);
653 wpa->listen_path = NULL;
657 dhcpcd_wpa_find(DHCPCD_CONNECTION *con, const char *ifname)
661 for (wpa = con->wpa; wpa; wpa = wpa->next) {
662 if (strcmp(wpa->ifname, ifname) == 0)
670 dhcpcd_wpa_new(DHCPCD_CONNECTION *con, const char *ifname)
674 wpa = dhcpcd_wpa_find(con, ifname);
678 wpa = malloc(sizeof(*wpa));
683 strlcpy(wpa->ifname, ifname, sizeof(wpa->ifname));
684 wpa->command_fd = wpa->listen_fd = -1;
685 wpa->command_path = wpa->listen_path = NULL;
686 wpa->next = con->wpa;
692 dhcpcd_wpa_connection(DHCPCD_WPA *wpa)
700 dhcpcd_wpa_if(DHCPCD_WPA *wpa)
703 return dhcpcd_get_if(wpa->con, wpa->ifname, "link");
707 dhcpcd_wpa_open(DHCPCD_WPA *wpa)
709 int cmd_fd, list_fd = -1;
710 char *cmd_path = NULL, *list_path = NULL;
712 if (wpa->listen_fd != -1) {
717 return wpa->listen_fd;
720 cmd_fd = wpa_open(wpa->ifname, &cmd_path);
724 list_fd = wpa_open(wpa->ifname, &list_path);
729 wpa->attached = false;
730 wpa->command_fd = cmd_fd;
731 wpa->command_path = cmd_path;
732 wpa->listen_fd = list_fd;
733 wpa->listen_path = list_path;
734 if (!dhcpcd_attach_detach(wpa, true)) {
735 dhcpcd_wpa_close(wpa);
739 if (wpa->con->wi_scanresults_cb)
740 wpa->con->wi_scanresults_cb(wpa,
741 wpa->con->wi_scanresults_context);
743 return wpa->listen_fd;
759 dhcpcd_wpa_get_fd(DHCPCD_WPA *wpa)
763 return wpa->open ? wpa->listen_fd : -1;
767 dhcpcd_wpa_set_scan_callback(DHCPCD_CONNECTION *con,
768 void (*cb)(DHCPCD_WPA *, void *), void *context)
772 con->wi_scanresults_cb = cb;
773 con->wi_scanresults_context = context;
777 void dhcpcd_wpa_set_status_callback(DHCPCD_CONNECTION * con,
778 void (*cb)(DHCPCD_WPA *, const char *, void *), void *context)
782 con->wpa_status_cb = cb;
783 con->wpa_status_context = context;
787 dhcpcd_wpa_dispatch(DHCPCD_WPA *wpa)
789 char buffer[256], *p;
793 bytes = (size_t)read(wpa->listen_fd, buffer, sizeof(buffer));
794 if ((ssize_t)bytes == -1 || bytes == 0) {
795 dhcpcd_wpa_close(wpa);
799 buffer[bytes] = '\0';
800 bytes = strlen(buffer);
801 if (buffer[bytes - 1] == ' ')
802 buffer[--bytes] = '\0';
803 for (p = buffer + 1; *p != '\0'; p++)
808 if (strcmp(p, "CTRL-EVENT-SCAN-RESULTS") == 0 &&
809 wpa->con->wi_scanresults_cb)
810 wpa->con->wi_scanresults_cb(wpa,
811 wpa->con->wi_scanresults_context);
816 dhcpcd_wpa_if_event(DHCPCD_IF *i)
821 if (strcmp(i->type, "link") == 0) {
822 if (strcmp(i->reason, "STOPPED") == 0 ||
823 strcmp(i->reason, "DEPARTED") == 0)
825 wpa = dhcpcd_wpa_find(i->con, i->ifname);
827 dhcpcd_wpa_close(wpa);
828 } else if (i->wireless && i->con->wpa_started) {
829 wpa = dhcpcd_wpa_new(i->con, i->ifname);
830 if (wpa && wpa->listen_fd == -1)
831 dhcpcd_wpa_open(wpa);
837 dhcpcd_wpa_start(DHCPCD_CONNECTION *con)
842 con->wpa_started = true;
844 for (i = con->interfaces; i; i = i->next)
845 dhcpcd_wpa_if_event(i);
849 dhcpcd_wpa_configure_psk(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
851 const char *mgmt, *var;
860 id = dhcpcd_wpa_network_find_new(wpa, s->ssid);
862 return DHCPCD_WPA_ERR;
864 if (strcmp(s->flags, "[WEP]") == 0) {
872 if (!dhcpcd_wpa_network_set(wpa, id, "key_mgmt", mgmt))
873 return DHCPCD_WPA_ERR_SET;
876 psk_len = strlen(psk);
879 npsk = malloc(psk_len + 3);
881 return DHCPCD_WPA_ERR;
884 memcpy(npsk + 1, psk, psk_len);
885 npsk[psk_len + 1] = '"';
886 npsk[psk_len + 2] = '\0';
887 r = dhcpcd_wpa_network_set(wpa, id, var, npsk);
890 return DHCPCD_WPA_ERR_SET_PSK;
892 if (!dhcpcd_wpa_network_enable(wpa, id))
893 return DHCPCD_WPA_ERR_ENABLE;
894 if (!dhcpcd_wpa_reassociate(wpa))
895 return DHCPCD_WPA_ERR_ASSOC;
896 if (!dhcpcd_wpa_config_write(wpa))
897 return DHCPCD_WPA_ERR_WRITE;
898 return DHCPCD_WPA_SUCCESS;