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