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