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>
46 (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
49 #define CLAMP(x, low, high) \
50 (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
53 wpa_open(const char *ifname, char **path)
58 struct sockaddr_un sun;
60 if ((fd = socket(AF_UNIX,
61 SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1)
63 memset(&sun, 0, sizeof(sun));
64 sun.sun_family = AF_UNIX;
65 snprintf(sun.sun_path, sizeof(sun.sun_path),
66 "/tmp/libdhcpcd-wpa-%d.%d", getpid(), counter++);
67 *path = strdup(sun.sun_path);
68 len = (socklen_t)SUN_LEN(&sun);
69 if (bind(fd, (struct sockaddr *)&sun, len) == -1) {
76 snprintf(sun.sun_path, sizeof(sun.sun_path),
77 WPA_CTRL_DIR "/%s", ifname);
78 len = (socklen_t)SUN_LEN(&sun);
79 if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
91 wpa_cmd(int fd, const char *cmd, char *buffer, size_t len)
99 bytes = write(fd, cmd, strlen(cmd));
100 if (bytes == -1 || bytes == 0)
102 if (buffer == NULL || len == 0)
105 pfd.events = POLLIN | POLLHUP;
107 retval = poll(&pfd, 1, 2000);
110 if (retval == 0 || !(pfd.revents & (POLLIN | POLLHUP)))
113 bytes = read(fd, buffer, len == 1 ? 1 : len - 1);
115 buffer[bytes] = '\0';
120 dhcpcd_wpa_command(DHCPCD_WPA *wpa, const char *cmd)
125 bytes = wpa_cmd(wpa->command_fd, cmd, buf, sizeof(buf));
126 return (bytes == -1 || bytes == 0 ||
127 strcmp(buf, "OK\n")) ? false : true;
131 dhcpcd_wpa_command_arg(DHCPCD_WPA *wpa, const char *cmd, const char *arg)
135 cmdlen = strlen(cmd);
136 nlen = cmdlen + strlen(arg) + 2;
137 if (!dhcpcd_realloc(wpa->con, nlen))
139 strlcpy(wpa->con->buf, cmd, wpa->con->buflen);
140 wpa->con->buf[cmdlen] = ' ';
141 strlcpy(wpa->con->buf + cmdlen + 1, arg, wpa->con->buflen - 1 - cmdlen);
142 return dhcpcd_wpa_command(wpa, wpa->con->buf);
146 dhcpcd_attach_detach(DHCPCD_WPA *wpa, bool attach)
151 if (wpa->attached == attach)
154 bytes = wpa_cmd(wpa->listen_fd, attach > 0 ? "ATTACH" : "DETACH",
156 if (bytes == -1 || bytes == 0 || strcmp(buf, "OK\n"))
159 wpa->attached = attach;
164 dhcpcd_wpa_scan(DHCPCD_WPA *wpa)
167 return dhcpcd_wpa_command(wpa, "SCAN");
171 dhcpcd_wi_scans_free(DHCPCD_WI_SCAN *wis)
183 dhcpcd_strtoi(int *val, const char *s)
187 l = strtol(s, NULL, 0);
188 if (l >= INT_MIN && l <= INT_MAX)
194 static DHCPCD_WI_SCAN *
195 dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa)
199 DHCPCD_WI_SCAN *wis, *w, *l;
200 char *s, *p, buf[32];
201 char wssid[sizeof(w->ssid)], tssid[sizeof(w->ssid)];
203 if (!dhcpcd_realloc(wpa->con, 2048))
206 for (i = 0; i < 1000; i++) {
207 snprintf(buf, sizeof(buf), "BSS %zu", i);
208 bytes = wpa_cmd(wpa->command_fd, buf,
209 wpa->con->buf, wpa->con->buflen);
210 if (bytes == 0 || bytes == -1 ||
211 strncmp(wpa->con->buf, "FAIL", 4) == 0)
214 w = calloc(1, sizeof(*w));
222 while ((s = strsep(&p, "\n"))) {
225 if (strncmp(s, "bssid=", 6) == 0)
226 strlcpy(w->bssid, s + 6, sizeof(w->bssid));
227 else if (strncmp(s, "freq=", 5) == 0)
228 dhcpcd_strtoi(&w->frequency, s + 5);
229 // else if (strncmp(s, "beacon_int=", 11) == 0)
231 else if (strncmp(s, "qual=", 5) == 0)
232 dhcpcd_strtoi(&w->quality.value, s + 5);
233 else if (strncmp(s, "noise=", 6) == 0)
234 dhcpcd_strtoi(&w->noise.value, s + 6);
235 else if (strncmp(s, "level=", 6) == 0)
236 dhcpcd_strtoi(&w->level.value, s + 6);
237 else if (strncmp(s, "flags=", 6) == 0)
238 strlcpy(w->flags, s + 6, sizeof(w->flags));
239 else if (strncmp(s, "ssid=", 5) == 0) {
240 /* Decode it from \xNN to \NNN
241 * so we're consistent */
242 strlcpy(wssid, s + 5, sizeof(wssid));
243 dl = dhcpcd_decode(tssid, sizeof(tssid), wssid);
244 dhcpcd_encode(w->ssid, sizeof(w->ssid),
249 w->strength.value = w->level.value;
250 if (w->strength.value > 110 && w->strength.value < 256)
251 /* Convert WEXT level to dBm */
252 w->strength.value -= 256;
254 if (w->strength.value < 0) {
257 abs(CLAMP(w->strength.value, -100, -40) + 40);
259 100 - ((100 * w->strength.value) / 60);
261 /* Assume quality percentage */
262 w->strength.value = CLAMP(w->strength.value, 0, 100);
270 dhcpcd_wi_scans(DHCPCD_IF *i)
273 DHCPCD_WI_SCAN *wis, *w;
275 DHCPCD_WI_HIST *h, *hl;
277 wpa = dhcpcd_wpa_find(i->con, i->ifname);
280 wis = dhcpcd_wpa_scans_read(wpa);
281 for (w = wis; w; w = w->next) {
284 w->quality.average = w->quality.value;
285 w->noise.average = w->noise.value;
286 w->level.average = w->level.value;
287 w->strength.average = w->strength.value;
289 for (h = wpa->con->wi_history; h; h = h->next) {
290 if (strcmp(h->ifname, i->ifname) == 0 &&
291 strcmp(h->bssid, wis->bssid) == 0)
293 w->quality.average += h->quality;
294 w->noise.average += h->noise;
295 w->level.average += h->level;
296 w->strength.average += h->strength;
297 if (++nh == DHCPCD_WI_HIST_MAX) {
307 w->quality.average /= nh;
308 w->noise.average /= nh;
309 w->level.average /= nh;
310 w->strength.average /= nh;
312 h = malloc(sizeof(*h));
314 strlcpy(h->ifname, i->ifname, sizeof(h->ifname));
315 strlcpy(h->bssid, w->bssid, sizeof(h->bssid));
316 h->quality = w->quality.value;
317 h->noise = w->noise.value;
318 h->level = w->level.value;
319 h->strength = w->strength.value;
320 h->next = wpa->con->wi_history;
321 wpa->con->wi_history = h;
329 dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
332 return dhcpcd_wpa_command(wpa, "REASSOCIATE");
336 dhcpcd_wpa_disconnect(DHCPCD_WPA *wpa)
339 return dhcpcd_wpa_command(wpa, "DISCONNECT");
343 dhcpcd_wpa_config_write(DHCPCD_WPA *wpa)
346 return dhcpcd_wpa_command(wpa, "SAVE_CONFIG");
350 dhcpcd_wpa_network(DHCPCD_WPA *wpa, const char *cmd, int id)
354 len = strlen(cmd) + 32;
355 if (!dhcpcd_realloc(wpa->con, len))
357 snprintf(wpa->con->buf, wpa->con->buflen, "%s %d", cmd, id);
358 return dhcpcd_wpa_command(wpa, wpa->con->buf);
362 dhcpcd_wpa_network_disable(DHCPCD_WPA *wpa, int id)
365 return dhcpcd_wpa_network(wpa, "DISABLE_NETWORK", id);
369 dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int id)
372 return dhcpcd_wpa_network(wpa, "ENABLE_NETWORK", id);
376 dhcpcd_wpa_network_remove(DHCPCD_WPA *wpa, int id)
379 return dhcpcd_wpa_network(wpa, "REMOVE_NETWORK", id);
383 dhcpcd_wpa_network_get(DHCPCD_WPA *wpa, int id, const char *param)
387 if (!dhcpcd_realloc(wpa->con, 2048))
389 snprintf(wpa->con->buf, wpa->con->buflen, "GET_NETWORK %d %s",
391 bytes = wpa_cmd(wpa->command_fd, wpa->con->buf,
392 wpa->con->buf, wpa->con->buflen);
393 if (bytes == 0 || bytes == -1)
395 if (strcmp(wpa->con->buf, "FAIL\n") == 0) {
399 return wpa->con->buf;
403 dhcpcd_wpa_network_set(DHCPCD_WPA *wpa, int id,
404 const char *param, const char *value)
408 len = strlen("SET_NETWORK") + 32 + strlen(param) + strlen(value) + 3;
409 if (!dhcpcd_realloc(wpa->con, len))
411 snprintf(wpa->con->buf, wpa->con->buflen, "SET_NETWORK %d %s %s",
413 return dhcpcd_wpa_command(wpa, wpa->con->buf);
417 dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
419 ssize_t bytes, dl, tl;
420 char *s, *t, *ssid, *bssid, *flags;
421 char dssid[IF_SSIDSIZE], tssid[IF_SSIDSIZE];
424 dhcpcd_realloc(wpa->con, 2048);
425 bytes = wpa_cmd(wpa->command_fd, "LIST_NETWORKS",
426 wpa->con->buf, wpa->con->buflen);
427 if (bytes == 0 || bytes == -1)
430 if ((dl = dhcpcd_decode(dssid, sizeof(dssid), fssid)) == -1)
433 s = strchr(wpa->con->buf, '\n');
436 while ((t = strsep(&s, "\b"))) {
439 ssid = strchr(t, '\t');
443 bssid = strchr(ssid, '\t');
447 flags = strchr(bssid, '\t');
451 l = strtol(t, NULL, 0);
452 if (l < 0 || l > INT_MAX) {
457 if ((tl = dhcpcd_decode(tssid, sizeof(tssid), ssid)) == -1)
459 if (tl == dl && memcmp(tssid, dssid, (size_t)tl) == 0)
467 dhcpcd_wpa_network_new(DHCPCD_WPA *wpa)
472 dhcpcd_realloc(wpa->con, 32);
473 bytes = wpa_cmd(wpa->command_fd, "ADD_NETWORK",
474 wpa->con->buf, sizeof(wpa->con->buf));
475 if (bytes == 0 || bytes == -1)
477 l = strtol(wpa->con->buf, NULL, 0);
478 if (l < 0 || l > INT_MAX) {
485 static const char hexstr[] = "0123456789ABCDEF";
487 dhcpcd_wpa_network_find_new(DHCPCD_WPA *wpa, const char *ssid)
490 char dssid[IF_SSIDSIZE], essid[IF_SSIDSIZE], *ep;
493 id = dhcpcd_wpa_network_find(wpa, ssid);
497 dl = dhcpcd_decode(dssid, sizeof(dssid), ssid);
502 if ((size_t)dl != strlen(ssid) || memcmp(dssid, ssid, (size_t)dl)) {
503 /* Non standard characters found! Encode as hex string */
509 c = (unsigned char)*dp++;
510 *ep++ = hexstr[c >> 4];
511 *ep++ = hexstr[c & 0xf];
515 ep = stpcpy(ep, ssid);
520 id = dhcpcd_wpa_network_new(wpa);
522 dhcpcd_wpa_network_set(wpa, id, "ssid", essid);
527 dhcpcd_wpa_close(DHCPCD_WPA *wpa)
532 if (wpa->command_fd == -1 || !wpa->open)
536 dhcpcd_attach_detach(wpa, false);
537 shutdown(wpa->command_fd, SHUT_RDWR);
538 shutdown(wpa->listen_fd, SHUT_RDWR);
540 if (wpa->con->wpa_status_cb)
541 wpa->con->wpa_status_cb(wpa, "down",
542 wpa->con->wpa_status_context);
544 close(wpa->command_fd);
545 wpa->command_fd = -1;
546 close(wpa->listen_fd);
548 unlink(wpa->command_path);
549 free(wpa->command_path);
550 wpa->command_path = NULL;
551 unlink(wpa->listen_path);
552 free(wpa->listen_path);
553 wpa->listen_path = NULL;
557 dhcpcd_wpa_find(DHCPCD_CONNECTION *con, const char *ifname)
561 for (wpa = con->wpa; wpa; wpa = wpa->next) {
562 if (strcmp(wpa->ifname, ifname) == 0)
570 dhcpcd_wpa_new(DHCPCD_CONNECTION *con, const char *ifname)
574 wpa = dhcpcd_wpa_find(con, ifname);
578 wpa = malloc(sizeof(*wpa));
583 strlcpy(wpa->ifname, ifname, sizeof(wpa->ifname));
584 wpa->command_fd = wpa->listen_fd = -1;
585 wpa->command_path = wpa->listen_path = NULL;
586 wpa->next = con->wpa;
592 dhcpcd_wpa_connection(DHCPCD_WPA *wpa)
600 dhcpcd_wpa_if(DHCPCD_WPA *wpa)
603 return dhcpcd_get_if(wpa->con, wpa->ifname, "link");
607 dhcpcd_wpa_open(DHCPCD_WPA *wpa)
609 int cmd_fd, list_fd = -1;
610 char *cmd_path = NULL, *list_path = NULL;
612 if (wpa->listen_fd != -1) {
617 return wpa->listen_fd;
620 cmd_fd = wpa_open(wpa->ifname, &cmd_path);
624 list_fd = wpa_open(wpa->ifname, &list_path);
629 wpa->attached = false;
630 wpa->command_fd = cmd_fd;
631 wpa->command_path = cmd_path;
632 wpa->listen_fd = list_fd;
633 wpa->listen_path = list_path;
634 if (!dhcpcd_attach_detach(wpa, true)) {
635 dhcpcd_wpa_close(wpa);
639 if (wpa->con->wi_scanresults_cb)
640 wpa->con->wi_scanresults_cb(wpa,
641 wpa->con->wi_scanresults_context);
643 return wpa->listen_fd;
659 dhcpcd_wpa_get_fd(DHCPCD_WPA *wpa)
663 return wpa->open ? wpa->listen_fd : -1;
667 dhcpcd_wpa_set_scan_callback(DHCPCD_CONNECTION *con,
668 void (*cb)(DHCPCD_WPA *, void *), void *context)
672 con->wi_scanresults_cb = cb;
673 con->wi_scanresults_context = context;
677 void dhcpcd_wpa_set_status_callback(DHCPCD_CONNECTION * con,
678 void (*cb)(DHCPCD_WPA *, const char *, void *), void *context)
682 con->wpa_status_cb = cb;
683 con->wpa_status_context = context;
687 dhcpcd_wpa_dispatch(DHCPCD_WPA *wpa)
689 char buffer[256], *p;
693 bytes = (size_t)read(wpa->listen_fd, buffer, sizeof(buffer));
694 if ((ssize_t)bytes == -1 || bytes == 0) {
695 dhcpcd_wpa_close(wpa);
699 buffer[bytes] = '\0';
700 bytes = strlen(buffer);
701 if (buffer[bytes - 1] == ' ')
702 buffer[--bytes] = '\0';
703 for (p = buffer + 1; *p != '\0'; p++)
708 if (strcmp(p, "CTRL-EVENT-SCAN-RESULTS") == 0 &&
709 wpa->con->wi_scanresults_cb)
710 wpa->con->wi_scanresults_cb(wpa,
711 wpa->con->wi_scanresults_context);
716 dhcpcd_wpa_if_event(DHCPCD_IF *i)
721 if (strcmp(i->type, "link") == 0) {
722 if (strcmp(i->reason, "STOPPED") == 0 ||
723 strcmp(i->reason, "DEPARTED") == 0)
725 wpa = dhcpcd_wpa_find(i->con, i->ifname);
727 dhcpcd_wpa_close(wpa);
728 } else if (i->wireless && i->con->wpa_started) {
729 wpa = dhcpcd_wpa_new(i->con, i->ifname);
730 if (wpa && wpa->listen_fd == -1)
731 dhcpcd_wpa_open(wpa);
737 dhcpcd_wpa_start(DHCPCD_CONNECTION *con)
742 con->wpa_started = true;
744 for (i = con->interfaces; i; i = i->next)
745 dhcpcd_wpa_if_event(i);
749 dhcpcd_wpa_configure_psk(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
751 const char *mgmt, *var;
760 id = dhcpcd_wpa_network_find_new(wpa, s->ssid);
762 return DHCPCD_WPA_ERR;
764 if (strcmp(s->flags, "[WEP]") == 0) {
772 if (!dhcpcd_wpa_network_set(wpa, id, "key_mgmt", mgmt))
773 return DHCPCD_WPA_ERR_SET;
776 psk_len = strlen(psk);
779 npsk = malloc(psk_len + 3);
781 return DHCPCD_WPA_ERR;
784 memcpy(npsk + 1, psk, psk_len);
785 npsk[psk_len + 1] = '"';
786 npsk[psk_len + 2] = '\0';
787 r = dhcpcd_wpa_network_set(wpa, id, var, npsk);
790 return DHCPCD_WPA_ERR_SET_PSK;
792 if (!dhcpcd_wpa_network_enable(wpa, id))
793 return DHCPCD_WPA_ERR_ENABLE;
794 if (!dhcpcd_wpa_reassociate(wpa))
795 return DHCPCD_WPA_ERR_ASSOC;
796 if (!dhcpcd_wpa_config_write(wpa))
797 return DHCPCD_WPA_ERR_WRITE;
798 return DHCPCD_WPA_SUCCESS;