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