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