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