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