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