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