8d53351c504ea74282b7b6674c0b7e5233376a99
[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 <ctype.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <poll.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 #define IN_LIBDHCPCD
42 #include "config.h"
43 #include "dhcpcd.h"
44
45 #ifndef SUN_LEN
46 #define SUN_LEN(su) \
47         (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
48 #endif
49
50 #define CLAMP(x, low, high) \
51         (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x)))
52
53 static int
54 wpa_open(const char *ifname, char **path)
55 {
56         static int counter;
57         int fd;
58         socklen_t len;
59         struct sockaddr_un sun;
60
61         if ((fd = socket(AF_UNIX,
62             SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)) == -1)
63                 return -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) {
71                 close(fd);
72                 unlink(*path);
73                 free(*path);
74                 *path = NULL;
75                 return -1;
76         }
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) {
81                 close(fd);
82                 unlink(*path);
83                 free(*path);
84                 *path = NULL;
85                 return -1;
86         }
87
88         return fd;
89 }
90
91 static ssize_t
92 wpa_cmd(int fd, const char *cmd, char *buffer, size_t len)
93 {
94         int retval;
95         ssize_t bytes;
96         struct pollfd pfd;
97
98         if (buffer)
99                 *buffer = '\0';
100         bytes = write(fd, cmd, strlen(cmd));
101         if (bytes == -1 || bytes == 0)
102                 return -1;
103         if (buffer == NULL || len == 0)
104                 return 0;
105         pfd.fd = fd;
106         pfd.events = POLLIN | POLLHUP;
107         pfd.revents = 0;
108         retval = poll(&pfd, 1, 2000);
109         if (retval == -1)
110                 return -1;
111         if (retval == 0 || !(pfd.revents & (POLLIN | POLLHUP)))
112                 return -1;
113
114         bytes = read(fd, buffer, len == 1 ? 1 : len - 1);
115         if (bytes != -1)
116                 buffer[bytes] = '\0';
117         return bytes;
118 }
119
120 bool
121 dhcpcd_wpa_command(DHCPCD_WPA *wpa, const char *cmd)
122 {
123         char buf[10];
124         ssize_t bytes;
125
126         bytes = wpa_cmd(wpa->command_fd, cmd, buf, sizeof(buf));
127         return (bytes == -1 || bytes == 0 ||
128             strcmp(buf, "OK\n")) ? false : true;
129 }
130
131 bool
132 dhcpcd_wpa_command_arg(DHCPCD_WPA *wpa, const char *cmd, const char *arg)
133 {
134         size_t cmdlen, nlen;
135
136         cmdlen = strlen(cmd);
137         nlen = cmdlen + strlen(arg) + 2;
138         if (!dhcpcd_realloc(wpa->con, nlen))
139                 return -1;
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);
144 }
145
146 static bool
147 dhcpcd_attach_detach(DHCPCD_WPA *wpa, bool attach)
148 {
149         char buf[10];
150         ssize_t bytes;
151
152         if (wpa->attached == attach)
153                 return true;
154
155         bytes = wpa_cmd(wpa->listen_fd, attach > 0 ? "ATTACH" : "DETACH",
156             buf, sizeof(buf));
157         if (bytes == -1 || bytes == 0 || strcmp(buf, "OK\n"))
158                 return false;
159
160         wpa->attached = attach;
161         return true;
162 }
163
164 bool
165 dhcpcd_wpa_scan(DHCPCD_WPA *wpa)
166 {
167
168         return dhcpcd_wpa_command(wpa, "SCAN");
169 }
170
171 bool
172 dhcpcd_wi_associated(DHCPCD_IF *i, DHCPCD_WI_SCAN *scan)
173 {
174
175         assert(i);
176         assert(scan);
177
178         return (i->up && i->ssid && strcmp(i->ssid, scan->ssid) == 0);
179 }
180
181 void
182 dhcpcd_wi_scans_free(DHCPCD_WI_SCAN *wis)
183 {
184         DHCPCD_WI_SCAN *n;
185
186         while (wis) {
187                 n = wis->next;
188                 free(wis);
189                 wis = n;
190         }
191 }
192
193 static void
194 dhcpcd_strtoi(int *val, const char *s)
195 {
196         long l;
197
198         l = strtol(s, NULL, 0);
199         if (l >= INT_MIN && l <= INT_MAX)
200                 *val = (int)l;
201         else
202                 errno = ERANGE;
203 }
204
205 static int
206 dhcpcd_wpa_hex2num(char c)
207 {
208
209         if (c >= '0' && c <= '9')
210                 return c - '0';
211         if (c >= 'a' && c <= 'f')
212                 return c - 'a' + 10;
213         if (c >= 'A' && c <= 'F')
214                 return c - 'A' + 10;
215         return -1;
216 }
217
218 static int
219 dhcpcd_wpa_hex2byte(const char *src)
220 {
221         int h, l;
222
223         if ((h = dhcpcd_wpa_hex2num(*src++)) == -1 ||
224             (l = dhcpcd_wpa_hex2num(*src)) == -1)
225                 return -1;
226         return (h << 4) | l;
227 }
228
229 static ssize_t
230 dhcpcd_wpa_decode_ssid(char *dst, size_t dlen, const char *src)
231 {
232         const char *start;
233         char c, esc;
234         int xb;
235
236         start = dst;
237         for (;;) {
238                 if (*src == '\0')
239                         break;
240                 if (--dlen == 0) {
241                         errno = ENOSPC;
242                         return -1;
243                 }
244                 c = *src++;
245                 switch (c) {
246                 case '\\':
247                         if (*src == '\0') {
248                                 errno = EINVAL;
249                                 return -1;
250                         }
251                         esc = *src++;
252                         switch (esc) {
253                         case '\\':
254                         case '"': *dst++ = esc; break;
255                         case 'n': *dst++ = '\n'; break;
256                         case 'r': *dst++ = '\r'; break;
257                         case 't': *dst++ = '\t'; break;
258                         case 'e': *dst++ = '\033'; break;
259                         case 'x':
260                                 if (src[0] == '\0' || src[1] == '\0') {
261                                         errno = EINVAL;
262                                         return -1;
263                                 }
264                                 if ((xb = dhcpcd_wpa_hex2byte(src)) == -1)
265                                         return -1;
266                                 *dst++ = (char)xb;
267                                 src += 2;
268                                 break;
269                         default: errno = EINVAL; return -1;
270                         }
271                 default: *dst++ = c; break;
272                 }
273         }
274         if (dlen == 0) {
275                 errno = ENOSPC;
276                 return -1;
277         }
278         *dst = '\0';
279         return dst - start;
280 }
281
282 static DHCPCD_WI_SCAN *
283 dhcpcd_wpa_scans_read(DHCPCD_WPA *wpa)
284 {
285         size_t i;
286         ssize_t bytes, dl;
287         DHCPCD_WI_SCAN *wis, *w, *l;
288         char *s, *p, buf[32];
289         char wssid[sizeof(w->ssid)];
290         const char *proto;
291
292         if (!dhcpcd_realloc(wpa->con, 2048))
293                 return NULL;
294         wis = NULL;
295         for (i = 0; i < 1000; i++) {
296                 snprintf(buf, sizeof(buf), "BSS %zu", i);
297                 bytes = wpa_cmd(wpa->command_fd, buf,
298                     wpa->con->buf, wpa->con->buflen);
299                 if (bytes == 0 || bytes == -1 ||
300                     strncmp(wpa->con->buf, "FAIL", 4) == 0)
301                         break;
302                 p = wpa->con->buf;
303                 w = calloc(1, sizeof(*w));
304                 if (w == NULL)
305                         break;
306                 dl = 0;
307                 wssid[0] = '\0';
308                 while ((s = strsep(&p, "\n"))) {
309                         if (*s == '\0')
310                                 continue;
311                         if (strncmp(s, "bssid=", 6) == 0)
312                                 strlcpy(w->bssid, s + 6, sizeof(w->bssid));
313                         else if (strncmp(s, "freq=", 5) == 0)
314                                 dhcpcd_strtoi(&w->frequency, s + 5);
315 //                      else if (strncmp(s, "beacon_int=", 11) == 0)
316 //                              ;
317                         else if (strncmp(s, "qual=", 5) == 0)
318                                 dhcpcd_strtoi(&w->quality.value, s + 5);
319                         else if (strncmp(s, "noise=", 6) == 0)
320                                 dhcpcd_strtoi(&w->noise.value, s + 6);
321                         else if (strncmp(s, "level=", 6) == 0)
322                                 dhcpcd_strtoi(&w->level.value, s + 6);
323                         else if (strncmp(s, "flags=", 6) == 0)
324                                 strlcpy(w->wpa_flags, s + 6,
325                                     sizeof(w->wpa_flags));
326                         else if (strncmp(s, "ssid=", 5) == 0) {
327                                 /* Decode it from \xNN to \NNN
328                                  * so we're consistent */
329                                 dl = dhcpcd_wpa_decode_ssid(wssid,
330                                     sizeof(wssid), s + 5);
331                                 if (dl == -1)
332                                         break;
333                                 dl = dhcpcd_encode_string_escape(w->ssid,
334                                     sizeof(w->ssid), wssid, (size_t)dl);
335                                 if (dl == -1)
336                                         break;
337                         }
338                 }
339                 if (dl == -1) {
340                         free(w);
341                         break;
342                 }
343
344                 if (wis == NULL)
345                         wis = w;
346                 else
347                         l->next = w;
348                 l = w;
349
350                 if ((proto = strstr(w->wpa_flags, "[WPA-")) ||
351                     (proto = strstr(w->wpa_flags, "[WPA2-")) ||
352                     (proto = strstr(w->wpa_flags, "[RSN-")))
353                 {
354                         const char *endp, *psk;
355
356                         w->flags = WSF_WPA | WSF_SECURE;
357                         endp = strchr(proto, ']');
358                         if ((psk = strstr(proto, "-PSK]")) ||
359                             (psk = strstr(proto, "-PSK-")) ||
360                             (psk = strstr(proto, "-PSK+")))
361                         {
362                                 if (psk < endp)
363                                         w->flags |= WSF_PSK;
364                         }
365                 }
366                 if (strstr(w->wpa_flags, "[WEP]"))
367                         w->flags = WSF_WEP | WSF_PSK | WSF_SECURE;
368
369                 w->strength.value = w->level.value;
370                 if (w->strength.value > 110 && w->strength.value < 256)
371                         /* Convert WEXT level to dBm */
372                         w->strength.value -= 256;
373
374                 if (w->strength.value < 0) {
375                         /* Assume dBm */
376                         w->strength.value =
377                             abs(CLAMP(w->strength.value, -100, -40) + 40);
378                         w->strength.value =
379                             100 - ((100 * w->strength.value) / 60);
380                 } else {
381                         /* Assume quality percentage */
382                         w->strength.value = CLAMP(w->strength.value, 0, 100);
383                 }
384         }
385         return wis;
386 }
387
388 int
389 dhcpcd_wi_scan_compare(DHCPCD_WI_SCAN *a, DHCPCD_WI_SCAN *b)
390 {
391         int cmp;
392
393         /* Fist sort non case sensitive, then case sensitive */
394         cmp = strcasecmp(a->ssid, b->ssid);
395         if (cmp == 0)
396                 cmp = strcmp(a->ssid, b->ssid);
397
398         /* If still the same, return strongest first */
399         if (cmp == 0)
400                 cmp = b->strength.value - a->strength.value;
401
402         return cmp;
403 }
404
405 /*
406  * This function is copyright 2001 Simon Tatham.
407  *
408  * Permission is hereby granted, free of charge, to any person
409  * obtaining a copy of this software and associated documentation
410  * files (the "Software"), to deal in the Software without
411  * restriction, including without limitation the rights to use,
412  * copy, modify, merge, publish, distribute, sublicense, and/or
413  * sell copies of the Software, and to permit persons to whom the
414  * Software is furnished to do so, subject to the following
415  * conditions:
416  *
417  * The above copyright notice and this permission notice shall be
418  * included in all copies or substantial portions of the Software.
419  *
420  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
421  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
422  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
423  * NONINFRINGEMENT.  IN NO EVENT SHALL SIMON TATHAM BE LIABLE FOR
424  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
425  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
426  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
427  * SOFTWARE.
428  */
429 static DHCPCD_WI_SCAN *
430 dhcpcd_wi_scans_sort(DHCPCD_WI_SCAN *list)
431 {
432         DHCPCD_WI_SCAN *p, *q, *e, *tail;
433         size_t insize, nmerges, psize, qsize, i;
434
435         /* Silly special case: if `list' was passed in as NULL, return
436          * NULL immediately. */
437         if (!list)
438                 return NULL;
439
440         insize = 1;
441
442         for (;;) {
443                 p = list;
444                 list = tail = NULL;
445                 nmerges = 0;  /* count number of merges we do in this pass */
446
447                 while (p) {
448                         nmerges++;  /* there exists a merge to be done */
449                         /* step `insize' places along from p */
450                         q = p;
451                         psize = 0;
452                         for (i = 0; i < insize; i++) {
453                                 psize++;
454                                 q = q->next;
455                                 if (!q)
456                                         break;
457                         }
458
459                         /* if q hasn't fallen off end,
460                          * we have two lists to merge */
461                         qsize = insize;
462
463                         /* now we have two lists; merge them */
464                         while (psize > 0 || (qsize > 0 && q)) {
465                                 /* decide whether next element of merge comes
466                                  * from p or q */
467                                 if (psize == 0) {
468                                         /* p is empty; e must come from q. */
469                                         e = q; q = q->next; qsize--;
470                                 } else if (qsize == 0 || !q) {
471                                         /* q is empty; e must come from p. */
472                                         e = p; p = p->next; psize--;
473                                 } else if (dhcpcd_wi_scan_compare(p, q) <= 0) {
474                                         /* First element of p is lower
475                                          * (or same); e must come from p. */
476                                         e = p; p = p->next; psize--;
477                                 } else {
478                                         /* First element of q is lower;
479                                          * e must come from q. */
480                                         e = q; q = q->next; qsize--;
481                                 }
482
483                                 /* add the next element to the merged list */
484                                 if (tail)
485                                         tail->next = e;
486                                 else
487                                         list = e;
488                                 tail = e;
489                         }
490
491                         /* now p has stepped `insize' places along,
492                          * and q has too */
493                         p = q;
494                 }
495                 tail->next = NULL;
496
497                 /* If we have done only one merge, we're finished. */
498                 if (nmerges <= 1)   /* allow for nmerges==0, the empty list */
499                         return list;
500
501                 /* Otherwise repeat, merging lists twice the size */
502                 insize *= 2;
503         }
504 }
505
506 DHCPCD_WI_SCAN *
507 dhcpcd_wi_scans(DHCPCD_IF *i)
508 {
509         DHCPCD_WPA *wpa;
510         DHCPCD_WI_SCAN *wis, *w, *n, *p;
511         int nh;
512         DHCPCD_WI_HIST *h, *hl;
513
514         wpa = dhcpcd_wpa_find(i->con, i->ifname);
515         if (wpa == NULL)
516                 return NULL;
517         wis = dhcpcd_wpa_scans_read(wpa);
518
519         /* Sort the resultant list alphabetically and then by strength */
520         wis = dhcpcd_wi_scans_sort(wis);
521
522         p = NULL;
523         for (w = wis; w && (n = w->next, 1); w = n) {
524                 /* Currently we don't support non SSID broadcasting APs */
525                 if (*w->ssid == '\0') {
526                         if (p == NULL)
527                                 wis = n;
528                         else
529                                 p->next = n;
530                         free(w);
531                         continue;
532                 }
533                 /* Strip duplicated SSIDs, only show the strongest */
534                 if (p && strcmp(p->ssid, w->ssid) == 0) {
535                         p->next = n;
536                         free(w);
537                         continue;
538                 }
539                 /* Remember this as the previos next time */
540                 p = w;
541
542                 nh = 1;
543                 hl = NULL;
544                 w->quality.average = w->quality.value;
545                 w->noise.average = w->noise.value;
546                 w->level.average = w->level.value;
547                 w->strength.average = w->strength.value;
548
549                 for (h = wpa->con->wi_history; h; h = h->next) {
550                         if (strcmp(h->ifname, i->ifname) == 0 &&
551                             strcmp(h->bssid, wis->bssid) == 0)
552                         {
553                                 w->quality.average += h->quality;
554                                 w->noise.average += h->noise;
555                                 w->level.average += h->level;
556                                 w->strength.average += h->strength;
557                                 if (++nh == DHCPCD_WI_HIST_MAX) {
558                                         hl->next = h->next;
559                                         free(h);
560                                         break;
561                                 }
562                         }
563                         hl = h;
564                 }
565
566                 if (nh != 1) {
567                         w->quality.average /= nh;
568                         w->noise.average /= nh;
569                         w->level.average /= nh;
570                         w->strength.average /= nh;
571                 }
572                 h = malloc(sizeof(*h));
573                 if (h) {
574                         strlcpy(h->ifname, i->ifname, sizeof(h->ifname));
575                         strlcpy(h->bssid, w->bssid, sizeof(h->bssid));
576                         h->quality = w->quality.value;
577                         h->noise = w->noise.value;
578                         h->level = w->level.value;
579                         h->strength = w->strength.value;
580                         h->next = wpa->con->wi_history;
581                         wpa->con->wi_history = h;
582                 }
583         }
584
585         return wis;
586 }
587
588 bool
589 dhcpcd_wpa_reconfigure(DHCPCD_WPA *wpa)
590 {
591
592         return dhcpcd_wpa_command(wpa, "RECONFIGURE");
593 }
594
595 bool
596 dhcpcd_wpa_reassociate(DHCPCD_WPA *wpa)
597 {
598
599         return dhcpcd_wpa_command(wpa, "REASSOCIATE");
600 }
601
602 bool
603 dhcpcd_wpa_disconnect(DHCPCD_WPA *wpa)
604 {
605
606         return dhcpcd_wpa_command(wpa, "DISCONNECT");
607 }
608
609 bool
610 dhcpcd_wpa_config_write(DHCPCD_WPA *wpa)
611 {
612
613         return dhcpcd_wpa_command(wpa, "SAVE_CONFIG");
614 }
615
616 static bool
617 dhcpcd_wpa_network(DHCPCD_WPA *wpa, const char *cmd, int id)
618 {
619         size_t len;
620
621         len = strlen(cmd) + 32;
622         if (!dhcpcd_realloc(wpa->con, len))
623                 return false;
624         snprintf(wpa->con->buf, wpa->con->buflen, "%s %d", cmd, id);
625         return dhcpcd_wpa_command(wpa, wpa->con->buf);
626 }
627
628 bool
629 dhcpcd_wpa_network_disable(DHCPCD_WPA *wpa, int id)
630 {
631
632         return dhcpcd_wpa_network(wpa, "DISABLE_NETWORK", id);
633 }
634
635 bool
636 dhcpcd_wpa_network_enable(DHCPCD_WPA *wpa, int id)
637 {
638
639         return dhcpcd_wpa_network(wpa, "ENABLE_NETWORK", id);
640 }
641
642 bool
643 dhcpcd_wpa_network_select(DHCPCD_WPA *wpa, int id)
644 {
645
646         return dhcpcd_wpa_network(wpa, "SELECT_NETWORK", id);
647 }
648
649 bool
650 dhcpcd_wpa_network_remove(DHCPCD_WPA *wpa, int id)
651 {
652
653         return dhcpcd_wpa_network(wpa, "REMOVE_NETWORK", id);
654 }
655
656 char *
657 dhcpcd_wpa_network_get(DHCPCD_WPA *wpa, int id, const char *param)
658 {
659         ssize_t bytes;
660
661         if (!dhcpcd_realloc(wpa->con, 2048))
662                 return NULL;
663         snprintf(wpa->con->buf, wpa->con->buflen, "GET_NETWORK %d %s",
664             id, param);
665         bytes = wpa_cmd(wpa->command_fd, wpa->con->buf,
666             wpa->con->buf, wpa->con->buflen);
667         if (bytes == 0 || bytes == -1)
668                 return NULL;
669         if (strcmp(wpa->con->buf, "FAIL\n") == 0) {
670                 errno = EINVAL;
671                 return NULL;
672         }
673         return wpa->con->buf;
674 }
675
676 bool
677 dhcpcd_wpa_network_set(DHCPCD_WPA *wpa, int id,
678     const char *param, const char *value)
679 {
680         size_t len;
681
682         len = strlen("SET_NETWORK") + 32 + strlen(param) + strlen(value) + 3;
683         if (!dhcpcd_realloc(wpa->con, len))
684                 return false;
685         snprintf(wpa->con->buf, wpa->con->buflen, "SET_NETWORK %d %s %s",
686             id, param, value);
687         return dhcpcd_wpa_command(wpa, wpa->con->buf);
688 }
689
690 static int
691 dhcpcd_wpa_network_find(DHCPCD_WPA *wpa, const char *fssid)
692 {
693         ssize_t bytes, dl, tl;
694         size_t fl;
695         char *s, *t, *ssid, *bssid, *flags;
696         char dssid[IF_SSIDSIZE], tssid[IF_SSIDSIZE];
697         long l;
698
699         dhcpcd_realloc(wpa->con, 2048);
700         bytes = wpa_cmd(wpa->command_fd, "LIST_NETWORKS",
701             wpa->con->buf, wpa->con->buflen);
702         if (bytes == 0 || bytes == -1)
703                 return -1;
704
705         fl = strlen(fssid);
706
707         s = strchr(wpa->con->buf, '\n');
708         if (s == NULL)
709                 return -1;
710         while ((t = strsep(&s, "\b\n"))) {
711                 if (*t == '\0')
712                         continue;
713                 ssid = strchr(t, '\t');
714                 if (ssid == NULL)
715                         break;
716                 *ssid++ = '\0';
717                 bssid = strchr(ssid, '\t');
718                 if (bssid == NULL)
719                         break;
720                 *bssid++ = '\0';
721                 flags = strchr(bssid, '\t');
722                 if (flags == NULL)
723                         break;
724                 *flags++ = '\0';
725                 l = strtol(t, NULL, 0);
726                 if (l < 0 || l > INT_MAX) {
727                         errno = ERANGE;
728                         break;
729                 }
730
731                 /* Decode the wpa_supplicant SSID into raw chars and
732                  * then encode into our octal escaped string to
733                  * compare. */
734                 dl = dhcpcd_wpa_decode_ssid(dssid, sizeof(dssid), ssid);
735                 if (dl == -1)
736                         return -1;
737                 tl = dhcpcd_encode_string_escape(tssid,
738                     sizeof(tssid), dssid, (size_t)dl);
739                 if (tl == -1)
740                         return -1;
741                 if ((size_t)tl == fl && memcmp(tssid, fssid, (size_t)tl) == 0)
742                         return (int)l;
743         }
744         errno = ENOENT;
745         return -1;
746 }
747
748 static int
749 dhcpcd_wpa_network_new(DHCPCD_WPA *wpa)
750 {
751         ssize_t bytes;
752         long l;
753
754         dhcpcd_realloc(wpa->con, 32);
755         bytes = wpa_cmd(wpa->command_fd, "ADD_NETWORK",
756             wpa->con->buf, sizeof(wpa->con->buf));
757         if (bytes == 0 || bytes == -1)
758                 return -1;
759         l = strtol(wpa->con->buf, NULL, 0);
760         if (l < 0 || l > INT_MAX) {
761                 errno = ERANGE;
762                 return -1;
763         }
764         return (int)l;
765 }
766
767 static const char hexchrs[] = "0123456789abcdef";
768 int
769 dhcpcd_wpa_network_find_new(DHCPCD_WPA *wpa, const char *ssid)
770 {
771         int id;
772         char dssid[IF_SSIDSIZE], essid[IF_SSIDSIZE], *ep;
773         ssize_t dl, i;
774         char *dp;
775
776         id = dhcpcd_wpa_network_find(wpa, ssid);
777         if (id != -1)
778                 return id;
779
780         dl = dhcpcd_decode_string_escape(dssid, sizeof(dssid), ssid);
781         if (dl == -1)
782                 return -1;
783
784         for (i = 0; i < dl; i++) {
785                 if (!isascii((int)dssid[i]) && !isprint((int)dssid[i]))
786                         break;
787         }
788         dp = dssid;
789         ep = essid;
790         if (i < dl) {
791                 /* Non standard characters found! Encode as hex string */
792                 unsigned char c;
793
794                 for (; dl; dl--) {
795                         c = (unsigned char)*dp++;
796                         *ep++ = hexchrs[(c & 0xf0) >> 4];
797                         *ep++ = hexchrs[(c & 0x0f)];
798                 }
799         } else {
800                 *ep++ = '\"';
801                 do
802                         *ep++ = *dp;
803                 while (*++dp != '\0');
804                 *ep++ = '\"';
805         }
806         *ep = '\0';
807
808         id = dhcpcd_wpa_network_new(wpa);
809         if (id != -1)
810                 dhcpcd_wpa_network_set(wpa, id, "ssid", essid);
811         return id;
812 }
813
814 void
815 dhcpcd_wpa_close(DHCPCD_WPA *wpa)
816 {
817
818         assert(wpa);
819
820         if (wpa->command_fd == -1 || !wpa->open)
821                 return;
822
823         wpa->open = false;
824         dhcpcd_attach_detach(wpa, false);
825         shutdown(wpa->command_fd, SHUT_RDWR);
826         shutdown(wpa->listen_fd, SHUT_RDWR);
827
828         if (wpa->con->wpa_status_cb)
829                 wpa->con->wpa_status_cb(wpa, "down",
830                     wpa->con->wpa_status_context);
831
832         close(wpa->command_fd);
833         wpa->command_fd = -1;
834         close(wpa->listen_fd);
835         wpa->listen_fd = -1;
836         unlink(wpa->command_path);
837         free(wpa->command_path);
838         wpa->command_path = NULL;
839         unlink(wpa->listen_path);
840         free(wpa->listen_path);
841         wpa->listen_path = NULL;
842 }
843
844 DHCPCD_WPA *
845 dhcpcd_wpa_find(DHCPCD_CONNECTION *con, const char *ifname)
846 {
847         DHCPCD_WPA *wpa;
848
849         for (wpa = con->wpa; wpa; wpa = wpa->next) {
850                 if (strcmp(wpa->ifname, ifname) == 0)
851                         return wpa;
852         }
853         errno = ENOENT;
854         return NULL;
855 }
856
857 DHCPCD_WPA *
858 dhcpcd_wpa_new(DHCPCD_CONNECTION *con, const char *ifname)
859 {
860         DHCPCD_WPA *wpa;
861
862         wpa = dhcpcd_wpa_find(con, ifname);
863         if (wpa)
864                 return wpa;
865
866         wpa = malloc(sizeof(*wpa));
867         if (wpa == NULL)
868                 return NULL;
869
870         wpa->con = con;
871         strlcpy(wpa->ifname, ifname, sizeof(wpa->ifname));
872         wpa->command_fd = wpa->listen_fd = -1;
873         wpa->command_path = wpa->listen_path = NULL;
874         wpa->next = con->wpa;
875         con->wpa = wpa;
876         return wpa;
877 }
878
879 DHCPCD_CONNECTION *
880 dhcpcd_wpa_connection(DHCPCD_WPA *wpa)
881 {
882
883         assert(wpa);
884         return wpa->con;
885 }
886
887 DHCPCD_IF *
888 dhcpcd_wpa_if(DHCPCD_WPA *wpa)
889 {
890
891         return dhcpcd_get_if(wpa->con, wpa->ifname, "link");
892 }
893
894 int
895 dhcpcd_wpa_open(DHCPCD_WPA *wpa)
896 {
897         int cmd_fd, list_fd = -1;
898         char *cmd_path = NULL, *list_path = NULL;
899
900         if (wpa->listen_fd != -1) {
901                 if (!wpa->open) {
902                         errno = EISCONN;
903                         return -1;
904                 }
905                 return wpa->listen_fd;
906         }
907
908         cmd_fd = wpa_open(wpa->ifname, &cmd_path);
909         if (cmd_fd == -1)
910                 goto fail;
911
912         list_fd = wpa_open(wpa->ifname, &list_path);
913         if (list_fd == -1)
914                 goto fail;
915
916         wpa->open = true;
917         wpa->attached = false;
918         wpa->command_fd = cmd_fd;
919         wpa->command_path = cmd_path;
920         wpa->listen_fd = list_fd;
921         wpa->listen_path = list_path;
922         if (!dhcpcd_attach_detach(wpa, true)) {
923                 dhcpcd_wpa_close(wpa);
924                 return -1;
925         }
926
927         if (wpa->con->wi_scanresults_cb)
928                 wpa->con->wi_scanresults_cb(wpa,
929                     wpa->con->wi_scanresults_context);
930
931         return wpa->listen_fd;
932
933 fail:
934         if (cmd_fd != -1)
935                 close(cmd_fd);
936         if (list_fd != -1)
937                 close(list_fd);
938         if (cmd_path)
939                 unlink(cmd_path);
940         free(cmd_path);
941         if (list_path)
942                 free(list_path);
943         return -1;
944 }
945
946 int
947 dhcpcd_wpa_get_fd(DHCPCD_WPA *wpa)
948 {
949
950         assert(wpa);
951         return wpa->open ? wpa->listen_fd : -1;
952 }
953
954 void
955 dhcpcd_wpa_set_scan_callback(DHCPCD_CONNECTION *con,
956     void (*cb)(DHCPCD_WPA *, void *), void *context)
957 {
958
959         assert(con);
960         con->wi_scanresults_cb = cb;
961         con->wi_scanresults_context = context;
962 }
963
964
965 void dhcpcd_wpa_set_status_callback(DHCPCD_CONNECTION * con,
966     void (*cb)(DHCPCD_WPA *, const char *, void *), void *context)
967 {
968
969         assert(con);
970         con->wpa_status_cb = cb;
971         con->wpa_status_context = context;
972 }
973
974 void
975 dhcpcd_wpa_dispatch(DHCPCD_WPA *wpa)
976 {
977         char buffer[256], *p;
978         size_t bytes;
979
980         assert(wpa);
981         bytes = (size_t)read(wpa->listen_fd, buffer, sizeof(buffer));
982         if ((ssize_t)bytes == -1 || bytes == 0) {
983                 dhcpcd_wpa_close(wpa);
984                 return;
985         }
986
987         buffer[bytes] = '\0';
988         bytes = strlen(buffer);
989         if (buffer[bytes - 1] == ' ')
990                 buffer[--bytes] = '\0';
991         for (p = buffer + 1; *p != '\0'; p++)
992                 if (*p == '>') {
993                         p++;
994                         break;
995                 }
996         if (strcmp(p, "CTRL-EVENT-SCAN-RESULTS") == 0 &&
997             wpa->con->wi_scanresults_cb)
998                 wpa->con->wi_scanresults_cb(wpa,
999                     wpa->con->wi_scanresults_context);
1000         return;
1001 }
1002
1003 void
1004 dhcpcd_wpa_if_event(DHCPCD_IF *i)
1005 {
1006         DHCPCD_WPA *wpa;
1007
1008         assert(i);
1009         if (strcmp(i->type, "link") == 0) {
1010                 if (strcmp(i->reason, "STOPPED") == 0 ||
1011                     strcmp(i->reason, "DEPARTED") == 0)
1012                 {
1013                         wpa = dhcpcd_wpa_find(i->con, i->ifname);
1014                         if (wpa)
1015                                 dhcpcd_wpa_close(wpa);
1016                 } else if (i->wireless && i->con->wpa_started) {
1017                         wpa = dhcpcd_wpa_new(i->con, i->ifname);
1018                         if (wpa && wpa->listen_fd == -1)
1019                                 dhcpcd_wpa_open(wpa);
1020                 }
1021         }
1022 }
1023
1024 void
1025 dhcpcd_wpa_start(DHCPCD_CONNECTION *con)
1026 {
1027         DHCPCD_IF *i;
1028
1029         assert(con);
1030         con->wpa_started = true;
1031
1032         for (i = con->interfaces; i; i = i->next)
1033                 dhcpcd_wpa_if_event(i);
1034 }
1035
1036 static const char *
1037 dhcpcd_wpa_var_psk(DHCPCD_WI_SCAN *s)
1038 {
1039
1040         if (s->flags & WSF_WEP)
1041                 return "wep_key0";
1042         else if ((s->flags & (WSF_WPA | WSF_PSK)) == (WSF_WPA | WSF_PSK))
1043                 return "psk";
1044         return NULL;
1045 }
1046
1047 static const char *
1048 dhcpcd_wpa_var_mgmt(DHCPCD_WI_SCAN *s)
1049 {
1050
1051         if (s->flags & WSF_WPA) {
1052                 if (s->flags & WSF_PSK)
1053                         return "WPA-PSK";
1054         }
1055         return "NONE";
1056 }
1057
1058 static int
1059 dhcpcd_wpa_configure1(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
1060 {
1061         const char *mgmt, *var;
1062         int id, retval;
1063         char *npsk;
1064         size_t psk_len;
1065         bool r;
1066
1067         if (!dhcpcd_wpa_disconnect(wpa))
1068                 return DHCPCD_WPA_ERR_DISCONN;
1069
1070         /* reload the configuration so that when we don't save
1071          * the disabled networks to the config file. */
1072         if (!dhcpcd_wpa_reconfigure(wpa))
1073                 return DHCPCD_WPA_ERR_RECONF;
1074
1075         id = dhcpcd_wpa_network_find_new(wpa, s->ssid);
1076         if (id == -1)
1077                 return DHCPCD_WPA_ERR;
1078
1079         mgmt = dhcpcd_wpa_var_mgmt(s);
1080         var = dhcpcd_wpa_var_psk(s);
1081         if (mgmt && var) {
1082                 if (!dhcpcd_wpa_network_set(wpa, id, "key_mgmt", mgmt))
1083                         return DHCPCD_WPA_ERR_SET;
1084
1085                 if (psk)
1086                         psk_len = strlen(psk);
1087                 else
1088                         psk_len = 0;
1089                 npsk = malloc(psk_len + 3);
1090                 if (npsk == NULL)
1091                         return DHCPCD_WPA_ERR;
1092                 npsk[0] = '"';
1093                 if (psk_len)
1094                         memcpy(npsk + 1, psk, psk_len);
1095                 npsk[psk_len + 1] = '"';
1096                 npsk[psk_len + 2] = '\0';
1097                 r = dhcpcd_wpa_network_set(wpa, id, var, npsk);
1098                 free(npsk);
1099                 if (!r)
1100                         return DHCPCD_WPA_ERR_SET_PSK;
1101         }
1102
1103         if (!dhcpcd_wpa_network_enable(wpa, id))
1104                 return DHCPCD_WPA_ERR_ENABLE;
1105         if (dhcpcd_wpa_config_write(wpa))
1106                 retval = DHCPCD_WPA_SUCCESS;
1107         else
1108                 retval = DHCPCD_WPA_ERR_WRITE;
1109         /* Selecting a network disbales the others.
1110          * This should not be saved. */
1111         if (!dhcpcd_wpa_network_select(wpa, id))
1112                 return DHCPCD_WPA_ERR_SELECT;
1113         return retval;
1114 }
1115
1116 int
1117 dhcpcd_wpa_configure(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s, const char *psk)
1118 {
1119         int retval;
1120
1121         retval = dhcpcd_wpa_configure1(wpa, s, psk);
1122         /* Always reassociate */
1123         if (!dhcpcd_wpa_reassociate(wpa)) {
1124                 if (retval == DHCPCD_WPA_SUCCESS)
1125                         retval = DHCPCD_WPA_ERR_ASSOC;
1126         }
1127         return retval;
1128 }
1129
1130 int
1131 dhcpcd_wpa_select(DHCPCD_WPA *wpa, DHCPCD_WI_SCAN *s)
1132 {
1133         int id, retval;
1134
1135         assert(wpa);
1136         assert(s);
1137
1138         id = dhcpcd_wpa_network_find(wpa, s->ssid);
1139         if (id == -1)
1140                 return DHCPCD_WPA_ERR;
1141
1142         if (!dhcpcd_wpa_disconnect(wpa))
1143                 retval = DHCPCD_WPA_ERR_DISCONN;
1144         else if (!dhcpcd_wpa_network_select(wpa, id))
1145                 retval = DHCPCD_WPA_ERR_SELECT;
1146         else
1147                 retval = DHCPCD_WPA_SUCCESS;
1148
1149         /* Always reassociate */
1150         if (!dhcpcd_wpa_reassociate(wpa)) {
1151                 if (retval == DHCPCD_WPA_SUCCESS)
1152                         retval = DHCPCD_WPA_ERR_ASSOC;
1153         }
1154         return retval;
1155 }