92a690696e951f1fc43b62f1aa9e9d01bc713584
[dhcpcd-ui] / src / libdhcpcd / wpa.c
1 /*
2  * libdhcpcd
3  * Copyright 2009-2014 Roy Marples <roy@marples.name>
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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.
13  *
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
24  * SUCH DAMAGE.
25  */
26
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/un.h>
30
31 #include <assert.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <poll.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <unistd.h>
39
40 #define IN_LIBDHCPCD
41 #include "config.h"
42 #include "dhcpcd.h"
43
44 #ifndef SUN_LEN
45 #define SUN_LEN(su) \
46         (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
47 #endif
48
49 #define CLAMP(x, low, high) \
50         (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
51
52 static int
53 wpa_open(const char *ifname, char **path)
54 {
55         static int counter;
56         int fd;
57         socklen_t len;
58         struct sockaddr_un sun;
59
60         if ((fd = socket(AF_UNIX,
61             SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1)
62                 return -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) {
70                 close(fd);
71                 unlink(*path);
72                 free(*path);
73                 *path = NULL;
74                 return -1;
75         }
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) {
80                 close(fd);
81                 unlink(*path);
82                 free(*path);
83                 *path = NULL;
84                 return -1;
85         }
86
87         return fd;
88 }
89
90 static ssize_t
91 wpa_cmd(int fd, const char *cmd, char *buffer, size_t len)
92 {
93         int retval;
94         ssize_t bytes;
95         struct pollfd pfd;
96
97         if (buffer)
98                 *buffer = '\0';
99         bytes = write(fd, cmd, strlen(cmd));
100         if (bytes == -1 || bytes == 0)
101                 return -1;
102         if (buffer == NULL || len == 0)
103                 return 0;
104         pfd.fd = fd;
105         pfd.events = POLLIN | POLLHUP;
106         pfd.revents = 0;
107         retval = poll(&pfd, 1, 2000);
108         if (retval == -1)
109                 return -1;
110         if (retval == 0 || !(pfd.revents & (POLLIN | POLLHUP)))
111                 return -1;
112
113         bytes = read(fd, buffer, len == 1 ? 1 : len - 1);
114         if (bytes != -1)
115                 buffer[bytes] = '\0';
116         return bytes;
117 }
118
119 bool
120 dhcpcd_wpa_command(DHCPCD_WPA *wpa, const char *cmd)
121 {
122         char buf[10];
123         ssize_t bytes;
124
125         bytes = wpa_cmd(wpa->command_fd, cmd, buf, sizeof(buf));
126         return (bytes == -1 || bytes == 0 ||
127             strcmp(buf, "OK\n")) ? false : true;
128 }
129
130 bool
131 dhcpcd_wpa_command_arg(DHCPCD_WPA *wpa, const char *cmd, const char *arg)
132 {
133         size_t cmdlen, nlen;
134
135         cmdlen = strlen(cmd);
136         nlen = cmdlen + strlen(arg) + 2;
137         if (!dhcpcd_realloc(wpa->con, nlen))
138                 return -1;
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);
143 }
144
145 static bool
146 dhcpcd_attach_detach(DHCPCD_WPA *wpa, bool attach)
147 {
148         char buf[10];
149         ssize_t bytes;
150
151         if (wpa->attached == attach)
152                 return true;
153
154         bytes = wpa_cmd(wpa->listen_fd, attach > 0 ? "ATTACH" : "DETACH",
155             buf, sizeof(buf));
156         if (bytes == -1 || bytes == 0 || strcmp(buf, "OK\n"))
157                 return false;
158
159         wpa->attached = attach;
160         return true;
161 }
162
163 bool
164 dhcpcd_wpa_scan(DHCPCD_WPA *wpa)
165 {
166
167         return dhcpcd_wpa_command(wpa, "SCAN");
168 }
169
170 void
171 dhcpcd_wi_scans_free(DHCPCD_WI_SCAN *wis)
172 {
173         DHCPCD_WI_SCAN *n;
174
175         while (wis) {
176                 n = wis->next;
177                 free(wis);
178                 wis = n;
179         }
180 }
181
182 static void
183 dhcpcd_strtoi(int *val, const char *s)
184 {
185         long l;
186
187         l = strtol(s, NULL, 0);
188         if (l >= INT_MIN && l <= INT_MAX)
189                 *val = (int)l;
190         else
191                 errno = ERANGE;
192 }
193
194 static DHCPCD_WI_SCAN *
195 dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa)
196 {
197         size_t i;
198         ssize_t bytes;
199         DHCPCD_WI_SCAN *wis, *w, *l;
200         char *s, *p, buf[32];
201
202         if (!dhcpcd_realloc(wpa->con, 2048))
203                 return NULL;
204         wis = NULL;
205         for (i = 0; i < 1000; i++) {
206                 snprintf(buf, sizeof(buf), "BSS %zu", i);
207                 bytes = wpa_cmd(wpa->command_fd, buf,
208                     wpa->con->buf, wpa->con->buflen);
209                 if (bytes == 0 || bytes == -1 ||
210                     strncmp(wpa->con->buf, "FAIL", 4) == 0)
211                         break;
212                 p = wpa->con->buf;
213                 w = calloc(1, sizeof(*w));
214                 if (w == NULL)
215                         break;
216                 if (wis == NULL)
217                         wis = w;
218                 else
219                         l->next = w;
220                 l = w;
221                 while ((s = strsep(&p, "\n"))) {
222                         if (*s == '\0')
223                                 continue;
224                         if (strncmp(s, "bssid=", 6) == 0)
225                                 strlcpy(w->bssid, s + 6, sizeof(w->bssid));
226                         else if (strncmp(s, "freq=", 5) == 0)
227                                 dhcpcd_strtoi(&w->frequency, s + 5);
228 //                      else if (strncmp(s, "beacon_int=", 11) == 0)
229 //                              ;
230                         else if (strncmp(s, "qual=", 5) == 0)
231                                 dhcpcd_strtoi(&w->quality.value, s + 5);
232                         else if (strncmp(s, "noise=", 6) == 0)
233                                 dhcpcd_strtoi(&w->noise.value, s + 6);
234                         else if (strncmp(s, "level=", 6) == 0)
235                                 dhcpcd_strtoi(&w->level.value, s + 6);
236                         else if (strncmp(s, "flags=", 6) == 0)
237                                 strlcpy(w->flags, s + 6, sizeof(w->flags));
238                         else if (strncmp(s, "ssid=", 5) == 0)
239                                 strlcpy(w->ssid, s + 5, sizeof(w->ssid));
240                 }
241
242                 w->strength.value = w->level.value;
243                 if (w->strength.value > 110 && w->strength.value < 256)
244                         /* Convert WEXT level to dBm */
245                         w->strength.value -= 256;
246
247                 if (w->strength.value < 0) {
248                         /* Assume dBm */
249                         w->strength.value =
250                             abs(CLAMP(w->strength.value, -100, -40) + 40);
251                         w->strength.value =
252                             100 - ((100 * w->strength.value) / 60);
253                 } else {
254                         /* Assume quality percentage */
255                         w->strength.value = CLAMP(w->strength.value, 0, 100);
256                 }
257
258         }
259         return wis;
260 }
261
262 DHCPCD_WI_SCAN *
263 dhcpcd_wi_scans(DHCPCD_IF *i)
264 {
265         DHCPCD_WPA *wpa;
266         DHCPCD_WI_SCAN *wis, *w;
267         int nh;
268         DHCPCD_WI_HIST *h, *hl;
269
270         wpa = dhcpcd_wpa_find(i->con, i->ifname);
271         if (wpa == NULL)
272                 return NULL;
273         wis = dhcpcd_wpa_scans_read(wpa);
274         for (w = wis; w; w = w->next) {
275                 nh = 1;
276                 hl = NULL;
277                 w->quality.average = w->quality.value;
278                 w->noise.average = w->noise.value;
279                 w->level.average = w->level.value;
280                 w->strength.average = w->strength.value;
281
282                 for (h = wpa->con->wi_history; h; h = h->next) {
283                         if (strcmp(h->ifname, i->ifname) == 0 &&
284                             strcmp(h->bssid, wis->bssid) == 0)
285                         {
286                                 w->quality.average += h->quality;
287                                 w->noise.average += h->noise;
288                                 w->level.average += h->level;
289                                 w->strength.average += h->strength;
290                                 if (++nh == DHCPCD_WI_HIST_MAX) {
291                                         hl->next = h->next;
292                                         free(h);
293                                         break;
294                                 }
295                         }
296                         hl = h;
297                 }
298
299                 if (nh != 1) {
300                         w->quality.average /= nh;
301                         w->noise.average /= nh;
302                         w->level.average /= nh;
303                         w->strength.average /= nh;
304                 }
305                 h = malloc(sizeof(*h));
306                 if (h) {
307                         strlcpy(h->ifname, i->ifname, sizeof(h->ifname));
308                         strlcpy(h->bssid, w->bssid, sizeof(h->bssid));
309                         h->quality = w->quality.value;
310                         h->noise = w->noise.value;
311                         h->level = w->level.value;
312                         h->strength = w->strength.value;
313                         h->next = wpa->con->wi_history;
314                         wpa->con->wi_history = h;
315                 }
316         }
317
318         return wis;
319 }
320
321 bool
322 dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
323 {
324
325         return dhcpcd_wpa_command(wpa, "REASSOCIATE");
326 }
327
328 bool
329 dhcpcd_wpa_disconnect(DHCPCD_WPA *wpa)
330 {
331
332         return dhcpcd_wpa_command(wpa, "DISCONNECT");
333 }
334
335 bool
336 dhcpcd_wpa_config_write(DHCPCD_WPA *wpa)
337 {
338
339         return dhcpcd_wpa_command(wpa, "SAVE_CONFIG");
340 }
341
342 static bool
343 dhcpcd_wpa_network(DHCPCD_WPA *wpa, const char *cmd, int id)
344 {
345         size_t len;
346
347         len = strlen(cmd) + 32;
348         if (!dhcpcd_realloc(wpa->con, len))
349                 return false;
350         snprintf(wpa->con->buf, wpa->con->buflen, "%s %d", cmd, id);
351         return dhcpcd_wpa_command(wpa, wpa->con->buf);
352 }
353
354 bool
355 dhcpcd_wpa_network_disable(DHCPCD_WPA *wpa, int id)
356 {
357
358         return dhcpcd_wpa_network(wpa, "DISABLE_NETWORK", id);
359 }
360
361 bool
362 dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int id)
363 {
364
365         return dhcpcd_wpa_network(wpa, "ENABLE_NETWORK", id);
366 }
367
368 bool
369 dhcpcd_wpa_network_remove(DHCPCD_WPA *wpa, int id)
370 {
371
372         return dhcpcd_wpa_network(wpa, "REMOVE_NETWORK", id);
373 }
374
375 char *
376 dhcpcd_wpa_network_get(DHCPCD_WPA *wpa, int id, const char *param)
377 {
378         ssize_t bytes;
379
380         if (!dhcpcd_realloc(wpa->con, 2048))
381                 return NULL;
382         snprintf(wpa->con->buf, wpa->con->buflen, "GET_NETWORK %d %s",
383             id, param);
384         bytes = wpa_cmd(wpa->command_fd, wpa->con->buf,
385             wpa->con->buf, wpa->con->buflen);
386         if (bytes == 0 || bytes == -1)
387                 return NULL;
388         if (strcmp(wpa->con->buf, "FAIL\n") == 0) {
389                 errno = EINVAL;
390                 return NULL;
391         }
392         return wpa->con->buf;
393 }
394
395 bool
396 dhcpcd_wpa_network_set(DHCPCD_WPA *wpa, int id,
397     const char *param, const char *value)
398 {
399         size_t len;
400
401         len = strlen("SET_NETWORK") + 32 + strlen(param) + strlen(value) + 3;
402         if (!dhcpcd_realloc(wpa->con, len))
403                 return false;
404         snprintf(wpa->con->buf, wpa->con->buflen, "SET_NETWORK %d %s %s",
405             id, param, value);
406         return dhcpcd_wpa_command(wpa, wpa->con->buf);
407 }
408
409 static int
410 dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
411 {
412         ssize_t bytes;
413         char *s, *t, *ssid, *bssid, *flags;
414         long l;
415
416         dhcpcd_realloc(wpa->con, 2048);
417         bytes = wpa_cmd(wpa->command_fd, "LIST_NETWORKS",
418             wpa->con->buf, wpa->con->buflen);
419         if (bytes == 0 || bytes == -1)
420                 return -1;
421
422         s = strchr(wpa->con->buf, '\n');
423         if (s == NULL)
424                 return -1;
425         while ((t = strsep(&s, "\b"))) {
426                 if (*t == '\0')
427                         continue;
428                 ssid = strchr(t, '\t');
429                 if (ssid == NULL)
430                         break;
431                 *ssid++ = '\0';
432                 bssid = strchr(ssid, '\t');
433                 if (bssid == NULL)
434                         break;
435                 *bssid++ = '\0';
436                 flags = strchr(bssid, '\t');
437                 if (flags == NULL)
438                         break;
439                 *flags++ = '\0';
440                 l = strtol(t, NULL, 0);
441                 if (l < 0 || l > INT_MAX) {
442                         errno = ERANGE;
443                         break;
444                 }
445                 if (strcmp(ssid, fssid) == 0)
446                         return (int)l;
447         }
448         errno = ENOENT;
449         return -1;
450 }
451
452 static int
453 dhcpcd_wpa_network_new(DHCPCD_WPA *wpa)
454 {
455         ssize_t bytes;
456         long l;
457
458         dhcpcd_realloc(wpa->con, 32);
459         bytes = wpa_cmd(wpa->command_fd, "ADD_NETWORK",
460             wpa->con->buf, sizeof(wpa->con->buf));
461         if (bytes == 0 || bytes == -1)
462                 return -1;
463         l = strtol(wpa->con->buf, NULL, 0);
464         if (l < 0 || l > INT_MAX) {
465                 errno = ERANGE;
466                 return -1;
467         }
468         return (int)l;
469 }
470
471 int
472 dhcpcd_wpa_network_find_new(DHCPCD_WPA *wpa, const char *ssid)
473 {
474         int id;
475
476         id = dhcpcd_wpa_network_find(wpa, ssid);
477         if (id == -1)
478                 id = dhcpcd_wpa_network_new(wpa);
479         return id;
480 }
481
482 void
483 dhcpcd_wpa_close(DHCPCD_WPA *wpa)
484 {
485
486         assert(wpa);
487
488         if (wpa->command_fd == -1 || !wpa->open)
489                 return;
490
491         wpa->open = false;
492         dhcpcd_attach_detach(wpa, false);
493         shutdown(wpa->command_fd, SHUT_RDWR);
494         shutdown(wpa->listen_fd, SHUT_RDWR);
495
496         if (wpa->con->wpa_status_cb)
497                 wpa->con->wpa_status_cb(wpa, "down",
498                     wpa->con->wpa_status_context);
499
500         close(wpa->command_fd);
501         wpa->command_fd = -1;
502         close(wpa->listen_fd);
503         wpa->listen_fd = -1;
504         unlink(wpa->command_path);
505         free(wpa->command_path);
506         wpa->command_path = NULL;
507         unlink(wpa->listen_path);
508         free(wpa->listen_path);
509         wpa->listen_path = NULL;
510 }
511
512 DHCPCD_WPA *
513 dhcpcd_wpa_find(DHCPCD_CONNECTION *con, const char *ifname)
514 {
515         DHCPCD_WPA *wpa;
516
517         for (wpa = con->wpa; wpa; wpa = wpa->next) {
518                 if (strcmp(wpa->ifname, ifname) == 0)
519                         return wpa;
520         }
521         errno = ENOENT;
522         return NULL;
523 }
524
525 DHCPCD_WPA *
526 dhcpcd_wpa_new(DHCPCD_CONNECTION *con, const char *ifname)
527 {
528         DHCPCD_WPA *wpa;
529
530         wpa = dhcpcd_wpa_find(con, ifname);
531         if (wpa)
532                 return wpa;
533
534         wpa = malloc(sizeof(*wpa));
535         if (wpa == NULL)
536                 return NULL;
537
538         wpa->con = con;
539         strlcpy(wpa->ifname, ifname, sizeof(wpa->ifname));
540         wpa->command_fd = wpa->listen_fd = -1;
541         wpa->command_path = wpa->listen_path = NULL;
542         wpa->next = con->wpa;
543         con->wpa = wpa;
544         return wpa;
545 }
546
547 DHCPCD_CONNECTION *
548 dhcpcd_wpa_connection(DHCPCD_WPA *wpa)
549 {
550
551         assert(wpa);
552         return wpa->con;
553 }
554
555 DHCPCD_IF *
556 dhcpcd_wpa_if(DHCPCD_WPA *wpa)
557 {
558
559         return dhcpcd_get_if(wpa->con, wpa->ifname, "link");
560 }
561
562 int
563 dhcpcd_wpa_open(DHCPCD_WPA *wpa)
564 {
565         int cmd_fd, list_fd = -1;
566         char *cmd_path = NULL, *list_path = NULL;
567
568         if (wpa->listen_fd != -1) {
569                 if (!wpa->open) {
570                         errno = EISCONN;
571                         return -1;
572                 }
573                 return wpa->listen_fd;
574         }
575
576         cmd_fd = wpa_open(wpa->ifname, &cmd_path);
577         if (cmd_fd == -1)
578                 goto fail;
579
580         list_fd = wpa_open(wpa->ifname, &list_path);
581         if (list_fd == -1)
582                 goto fail;
583
584         wpa->open = true;
585         wpa->attached = false;
586         wpa->command_fd = cmd_fd;
587         wpa->command_path = cmd_path;
588         wpa->listen_fd = list_fd;
589         wpa->listen_path = list_path;
590         if (!dhcpcd_attach_detach(wpa, true)) {
591                 dhcpcd_wpa_close(wpa);
592                 return -1;
593         }
594
595         if (wpa->con->wi_scanresults_cb)
596                 wpa->con->wi_scanresults_cb(wpa,
597                     wpa->con->wi_scanresults_context);
598
599         return wpa->listen_fd;
600
601 fail:
602         if (cmd_fd != -1)
603                 close(cmd_fd);
604         if (list_fd != -1)
605                 close(list_fd);
606         if (cmd_path)
607                 unlink(cmd_path);
608         free(cmd_path);
609         if (list_path)
610                 free(list_path);
611         return -1;
612 }
613
614 int
615 dhcpcd_wpa_get_fd(DHCPCD_WPA *wpa)
616 {
617
618         assert(wpa);
619         return wpa->open ? wpa->listen_fd : -1;
620 }
621
622 void
623 dhcpcd_wpa_set_scan_callback(DHCPCD_CONNECTION *con,
624     void (*cb)(DHCPCD_WPA *, void *), void *context)
625 {
626
627         assert(con);
628         con->wi_scanresults_cb = cb;
629         con->wi_scanresults_context = context;
630 }
631
632 void
633 dhcpcd_wpa_dispatch(DHCPCD_WPA *wpa)
634 {
635         char buffer[256], *p;
636         size_t bytes;
637
638         assert(wpa);
639         bytes = (size_t)read(wpa->listen_fd, buffer, sizeof(buffer));
640         if ((ssize_t)bytes == -1 || bytes == 0) {
641                 dhcpcd_wpa_close(wpa);
642                 return;
643         }
644
645         buffer[bytes] = '\0';
646         bytes = strlen(buffer);
647         if (buffer[bytes - 1] == ' ')
648                 buffer[--bytes] = '\0';
649         for (p = buffer + 1; *p != '\0'; p++)
650                 if (*p == '>') {
651                         p++;
652                         break;
653                 }
654         if (strcmp(p, "CTRL-EVENT-SCAN-RESULTS") == 0 &&
655             wpa->con->wi_scanresults_cb)
656                 wpa->con->wi_scanresults_cb(wpa,
657                     wpa->con->wi_scanresults_context);
658         return;
659 }
660
661 void
662 dhcpcd_wpa_if_event(DHCPCD_IF *i)
663 {
664         DHCPCD_WPA *wpa;
665
666         assert(i);
667         if (i->wireless && strcmp(i->type, "link") == 0) {
668                 if (strcmp(i->reason, "STOPPED") == 0) {
669                         wpa = dhcpcd_wpa_find(i->con, i->ifname);
670                         if (wpa)
671                                 dhcpcd_wpa_close(wpa);
672                 } else if (i->con->wpa_started) {
673                         wpa = dhcpcd_wpa_new(i->con, i->ifname);
674                         if (wpa && wpa->listen_fd == -1)
675                                 dhcpcd_wpa_open(wpa);
676                 }
677         }
678 }
679
680 void
681 dhcpcd_wpa_start(DHCPCD_CONNECTION *con)
682 {
683         DHCPCD_IF *i;
684
685         assert(con);
686         con->wpa_started = true;
687
688         for (i = con->interfaces; i; i = i->next)
689                 dhcpcd_wpa_if_event(i);
690 }