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