Improve WPA interaction by allowing the fd to persist in the status cb
[dhcpcd-ui] / src / dhcpcd-curses / dhcpcd-curses.c
1 /*
2  * dhcpcd - DHCP client daemon
3  * Copyright (c) 2006-2015 Roy Marples <roy@marples.name>
4  * All rights reserved
5
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27
28 #include <sys/ioctl.h>
29
30 #include <curses.h>
31 #include <err.h>
32 #include <errno.h>
33 #include <signal.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 #include "dhcpcd-curses.h"
40
41 #ifdef HAVE_NC_FREE_AND_EXIT
42         void _nc_free_and_exit(void);
43 #endif
44
45 const int sigs[] = {
46         SIGHUP,
47         SIGINT,
48         SIGPIPE,
49         SIGTERM,
50         SIGWINCH,
51         0
52 };
53
54 static void try_open(void *);
55
56 static void
57 set_status(struct ctx *ctx, const char *status)
58 {
59         int w;
60         size_t slen;
61
62         w = getmaxx(ctx->win_status);
63         w -= (int)(slen = strlen(status));
64         if (ctx->status_len > slen) {
65                 wmove(ctx->win_status, 0, w - (int)(ctx->status_len - slen));
66                 wclrtoeol(ctx->win_status);
67         }
68         mvwprintw(ctx->win_status, 0, w, "%s", status);
69         wrefresh(ctx->win_status);
70         ctx->status_len = slen;
71 }
72
73 static int
74 set_summary(struct ctx *ctx, const char *msg)
75 {
76         int r;
77
78         wclear(ctx->win_summary);
79         if (msg)
80                 r = wprintw(ctx->win_summary, "%s", msg);
81         else
82                 r = 0;
83         wrefresh(ctx->win_summary);
84         return r;
85 }
86
87 __printflike(2, 3) static int
88 debug(struct ctx *ctx, const char *fmt, ...)
89 {
90         va_list args;
91         int r;
92
93         if (ctx->win_debug == NULL)
94                 return 0;
95         waddch(ctx->win_debug, '\n');
96         va_start(args, fmt);
97         r = vwprintw(ctx->win_debug, fmt, args);
98         va_end(args);
99         wrefresh(ctx->win_debug);
100         return r;
101 }
102
103 __printflike(2, 3) static int
104 warning(struct ctx *ctx, const char *fmt, ...)
105 {
106         va_list args;
107         int r;
108
109         if (ctx->win_debug == NULL)
110                 return 0;
111         waddch(ctx->win_debug, '\n');
112         va_start(args, fmt);
113         r = vwprintw(ctx->win_debug, fmt, args);
114         va_end(args);
115         wrefresh(ctx->win_debug);
116         return r;
117 }
118
119 __printflike(2, 3) static int
120 notify(struct ctx *ctx, const char *fmt, ...)
121 {
122         va_list args;
123         int r;
124
125         if (ctx->win_debug == NULL)
126                 return 0;
127         waddch(ctx->win_debug, '\n');
128         va_start(args, fmt);
129         r = vwprintw(ctx->win_debug, fmt, args);
130         va_end(args);
131         wrefresh(ctx->win_debug);
132         return r;
133 }
134
135 static void
136 update_online(struct ctx *ctx, bool show_if)
137 {
138         bool online, carrier;
139         char *msg, *msgs, *nmsg;
140         size_t msgs_len, mlen;
141         DHCPCD_IF *ifs, *i;
142
143         online = carrier = false;
144         msgs = NULL;
145         msgs_len = 0;
146         ifs = dhcpcd_interfaces(ctx->con);
147         for (i = ifs; i; i = i->next) {
148                 if (i->type == DHT_LINK) {
149                         if (i->up)
150                                 carrier = true;
151                 } else {
152                         if (i->up)
153                                 online = true;
154                 }
155                 msg = dhcpcd_if_message(i, NULL);
156                 if (msg) {
157                         if (show_if) {
158                                 if (i->up)
159                                         notify(ctx, "%s", msg);
160                                 else
161                                         warning(ctx, "%s", msg);
162                         }
163                         if (msgs == NULL) {
164                                 msgs = msg;
165                                 msgs_len = strlen(msgs) + 1;
166                         } else {
167                                 mlen = strlen(msg) + 1;
168                                 nmsg = realloc(msgs, msgs_len + mlen);
169                                 if (nmsg) {
170                                         msgs = nmsg;
171                                         msgs[msgs_len - 1] = '\n';
172                                         memcpy(msgs + msgs_len, msg, mlen);
173                                         msgs_len += mlen;
174                                 } else
175                                         warn("realloc");
176                                 free(msg);
177                         }
178                 } else if (show_if) {
179                         if (i->up)
180                                 notify(ctx, "%s: %s", i->ifname, i->reason);
181                         else
182                                 warning(ctx, "%s: %s", i->ifname, i->reason);
183                 }
184         }
185
186         set_summary(ctx, msgs);
187         free(msgs);
188 }
189
190 static void
191 dispatch(void *arg)
192 {
193         struct ctx *ctx = arg;
194
195         dhcpcd_dispatch(ctx->con);
196 }
197
198 static void
199 try_open(void *arg)
200 {
201         struct ctx *ctx = arg;
202         static int last_error;
203
204         ctx->fd = dhcpcd_open(ctx->con, true);
205         if (ctx->fd == -1) {
206                 if (errno == EACCES || errno == EPERM) {
207                         ctx->fd = dhcpcd_open(ctx->con, false);
208                         if (ctx->fd != -1)
209                                 goto unprived;
210                 }
211                 if (errno != last_error) {
212                         last_error = errno;
213                         set_status(ctx, strerror(errno));
214                 }
215                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
216                     try_open, ctx);
217                 return;
218         }
219
220 unprived:
221         last_error = 0;
222
223         /* Start listening to WPA events */
224         dhcpcd_wpa_start(ctx->con);
225
226         eloop_event_add(ctx->eloop, ctx->fd, dispatch, ctx, NULL, NULL);
227 }
228
229 static void
230 status_cb(DHCPCD_CONNECTION *con,
231     unsigned int status, const char *status_msg, void *arg)
232 {
233         struct ctx *ctx = arg;
234
235         debug(ctx, _("Status changed to %s"), status_msg);
236         set_status(ctx, status_msg);
237
238         if (status == DHC_DOWN) {
239                 int fd;
240
241                 fd = dhcpcd_get_fd(ctx->con);
242                 eloop_event_delete(ctx->eloop, fd, 0);
243                 ctx->online = ctx->carrier = false;
244                 eloop_timeout_delete(ctx->eloop, NULL, ctx);
245                 set_summary(ctx, NULL);
246                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
247                     try_open, ctx);
248         } else {
249                 bool refresh;
250
251                 if (ctx->last_status == DHC_UNKNOWN ||
252                     ctx->last_status == DHC_DOWN)
253                 {
254                         debug(ctx, _("Connected to dhcpcd-%s"),
255                             dhcpcd_version(con));
256                         refresh = true;
257                 } else
258                         refresh =
259                             ctx->last_status == DHC_OPENED ? true : false;
260                 update_online(ctx, refresh);
261         }
262
263         ctx->last_status = status;
264 }
265
266 static void
267 if_cb(DHCPCD_IF *i, void *arg)
268 {
269         struct ctx *ctx = arg;
270
271         if (i->state == DHS_RENEW ||
272             i->state == DHS_STOP || i->state == DHS_STOPPED)
273         {
274                 char *msg;
275                 bool new_msg;
276
277                 msg = dhcpcd_if_message(i, &new_msg);
278                 if (msg) {
279                         if (i->up)
280                                 warning(ctx, "%s", msg);
281                         else
282                                 notify(ctx, "%s", msg);
283                         free(msg);
284                 }
285         }
286
287         update_online(ctx, false);
288
289         if (i->wireless) {
290                 /* PROCESS SCANS */
291         }
292 }
293
294 static void
295 wpa_dispatch(void *arg)
296 {
297         DHCPCD_WPA *wpa = arg;
298
299         dhcpcd_wpa_dispatch(wpa);
300 }
301
302 static void
303 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
304 {
305         struct ctx *ctx = arg;
306         DHCPCD_IF *i;
307         WI_SCAN *wi;
308         DHCPCD_WI_SCAN *scans, *s1, *s2;
309         int fd, lerrno;
310
311         /* This could be a new WPA so watch it */
312         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
313                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
314                 return;
315         }
316         eloop_event_add(ctx->eloop, fd, wpa_dispatch, wpa, NULL, NULL);
317
318         i = dhcpcd_wpa_if(wpa);
319         if (i == NULL) {
320                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
321                 return;
322         }
323         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
324         lerrno = errno;
325         errno = 0;
326         scans = dhcpcd_wi_scans(i);
327         if (scans == NULL && errno)
328                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
329         errno = lerrno;
330         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
331                 if (wi->interface == i)
332                         break;
333         }
334         if (wi == NULL) {
335                 wi = malloc(sizeof(*wi));
336                 wi->interface = i;
337                 wi->scans = scans;
338                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
339         } else {
340                 const char *title;
341                 char *msgs, *nmsg;
342                 size_t msgs_len, mlen;
343
344                 title = NULL;
345                 msgs = NULL;
346                 for (s1 = scans; s1; s1 = s1->next) {
347                         for (s2 = wi->scans; s2; s2 = s2->next)
348                                 if (strcmp(s1->ssid, s2->ssid) == 0)
349                                         break;
350                         if (s2 == NULL) {
351                                 if (msgs == NULL) {
352                                         msgs = strdup(s1->ssid);
353                                         msgs_len = strlen(msgs) + 1;
354                                 } else {
355                                         if (title == NULL)
356                                                 title = _("New Access Points");
357                                         mlen = strlen(s1->ssid) + 1;
358                                         nmsg = realloc(msgs, msgs_len + mlen);
359                                         if (nmsg) {
360                                                 msgs = nmsg;
361                                                 msgs[msgs_len - 1] = '\n';
362                                                 memcpy(msgs + msgs_len,
363                                                     s1->ssid, mlen);
364                                                 msgs_len += mlen;
365                                         } else
366                                                 warn("realloc");
367                                 }
368                         }
369                 }
370                 if (msgs) {
371                         if (title == NULL)
372                                 title = _("New Access Point");
373                         mlen = strlen(title) + 1;
374                         nmsg = realloc(msgs, msgs_len + mlen);
375                         if (nmsg) {
376                                 msgs = nmsg;
377                                 memmove(msgs + mlen, msgs, msgs_len);
378                                 memcpy(msgs, title, mlen);
379                                 msgs[mlen - 1] = '\n';
380                         } else
381                                 warn("realloc");
382                         notify(ctx, "%s", msgs);
383                         free(msgs);
384                 }
385
386                 dhcpcd_wi_scans_free(wi->scans);
387                 wi->scans = scans;
388         }
389 }
390
391 static void
392 wpa_status_cb(DHCPCD_WPA *wpa,
393     unsigned int status, const char *status_msg, void *arg)
394 {
395         struct ctx *ctx = arg;
396         DHCPCD_IF *i;
397         WI_SCAN *w, *wn;
398
399         i = dhcpcd_wpa_if(wpa);
400         debug(ctx, _("%s: WPA status %s"), i->ifname, status_msg);
401         if (status == DHC_DOWN) {
402                 int fd;
403
404                 fd = dhcpcd_wpa_get_fd(wpa);
405                 eloop_event_delete(ctx->eloop, fd, 0);
406                 dhcpcd_wpa_close(wpa);
407                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
408                         if (w->interface == i) {
409                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
410                                 dhcpcd_wi_scans_free(w->scans);
411                                 free(w);
412                         }
413                 }
414         }
415 }
416
417 static void
418 bg_scan(void *arg)
419 {
420         struct ctx *ctx = arg;
421         WI_SCAN *w;
422         DHCPCD_WPA *wpa;
423
424         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
425                 if (w->interface->wireless& w->interface->up) {
426                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
427                         if (wpa &&
428                             (!w->interface->up ||
429                             dhcpcd_wpa_can_background_scan(wpa)))
430                                 dhcpcd_wpa_scan(wpa);
431                 }
432         }
433
434         eloop_timeout_add_msec(ctx->eloop, DHCPCD_WPA_SCAN_SHORT,
435             bg_scan, ctx);
436 }
437
438 static void
439 signal_cb(int sig, void *arg)
440 {
441         struct ctx *ctx = arg;
442         struct winsize ws;
443
444         switch(sig) {
445         case SIGWINCH:
446                 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
447                         resizeterm(ws.ws_row, ws.ws_col);
448                 break;
449         case SIGINT:
450                 debug(ctx, _("SIGINT caught, exiting"));
451                 eloop_exit(ctx->eloop, EXIT_FAILURE);
452                 break;
453         case SIGTERM:
454                 debug(ctx, _("SIGTERM caught, exiting"));
455                 eloop_exit(ctx->eloop, EXIT_FAILURE);
456                 break;
457         case SIGHUP:
458                 debug(ctx, ("SIGHUP caught, ignoring"));
459                 break;
460         case SIGPIPE:
461                 /* ignore and don't report */
462                 break;
463         }
464 }
465
466 static int
467 create_windows(struct ctx *ctx)
468 {
469         int h, w;
470
471         getmaxyx(ctx->stdscr, h, w);
472
473         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
474                 return -1;
475
476         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
477                 return -1;
478         box(ctx->win_summary_border, 0, 0);
479         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
480             _("Connection Summary"));
481         wrefresh(ctx->win_summary_border);
482         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
483                 return -1;
484         scrollok(ctx->win_summary, TRUE);
485
486 #if 1
487         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
488                 return -1;
489         box(ctx->win_debug_border, 0, 0);
490         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
491             _("Event Log"));
492         wrefresh(ctx->win_debug_border);
493         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
494                 return -1;
495         scrollok(ctx->win_debug, TRUE);
496 #endif
497         return 0;
498 }
499
500 int
501 main(void)
502 {
503         struct ctx ctx;
504         WI_SCAN *wi;
505         sigset_t sigmask;
506
507         memset(&ctx, 0, sizeof(ctx));
508         ctx.fd = -1;
509         TAILQ_INIT(&ctx.wi_scans);
510
511         if ((ctx.eloop = eloop_new()) == NULL)
512                 err(EXIT_FAILURE, "eloop_new");
513         if (eloop_signal_set_cb(ctx.eloop, sigs, signal_cb, &ctx) == -1)
514                 err(EXIT_FAILURE, "eloop_signal_set_cb");
515         if (eloop_signal_mask(ctx.eloop, &sigmask) == -1)
516                 err(EXIT_FAILURE, "eloop_signal_mask");
517
518         if ((ctx.con = dhcpcd_new()) == NULL)
519                 err(EXIT_FAILURE, "dhcpcd_new");
520
521         if ((ctx.stdscr = initscr()) == NULL)
522                 err(EXIT_FAILURE, "initscr");
523
524         if (create_windows(&ctx) == -1)
525                 err(EXIT_FAILURE, "create_windows");
526
527         curs_set(0);
528         noecho();
529         keypad(ctx.stdscr, TRUE);
530
531         wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
532         dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
533         dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
534         dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
535         dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
536         dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
537
538         eloop_timeout_add_sec(ctx.eloop, 0, try_open, &ctx);
539         eloop_timeout_add_msec(ctx.eloop, DHCPCD_WPA_SCAN_SHORT,
540             bg_scan, &ctx);
541         eloop_start(ctx.eloop, &sigmask);
542
543         /* Un-resgister the callbacks to avoid spam on close */
544         dhcpcd_set_status_callback(ctx.con, NULL, NULL);
545         dhcpcd_set_if_callback(ctx.con, NULL, NULL);
546         dhcpcd_wpa_set_scan_callback(ctx.con, NULL, NULL);
547         dhcpcd_wpa_set_status_callback(ctx.con, NULL, NULL);
548         dhcpcd_close(ctx.con);
549         dhcpcd_free(ctx.con);
550
551         /* Free our saved scans */
552         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
553                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
554                 dhcpcd_wi_scans_free(wi->scans);
555                 free(wi);
556         }
557
558         /* Free everything else */
559         eloop_free(ctx.eloop);
560         endwin();
561
562 #ifdef HAVE_NC_FREE_AND_EXIT
563         /* undefined ncurses function to allow valgrind debugging */
564         _nc_free_and_exit();
565 #endif
566
567         return EXIT_SUCCESS;
568 }