Fix a segfault if >1 wifi device is present.
[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 handle_sigs[] = {
46         SIGHUP,
47         SIGINT,
48         SIGPIPE,
49         SIGTERM,
50         SIGWINCH,
51         0
52 };
53
54 /* Handling signals needs *some* context */
55 static struct ctx *_ctx;
56
57 static void
58 set_status(struct ctx *ctx, const char *status)
59 {
60         int h, w;
61
62         getmaxyx(ctx->win_status, h, w);
63         w -= strlen(status);
64         mvwprintw(ctx->win_status, 0, w, "%s", status);
65         wrefresh(ctx->win_status);
66 }
67
68 static int
69 set_summary(struct ctx *ctx, const char *msg)
70 {
71         int r;
72
73         wclear(ctx->win_summary);
74         r = wprintw(ctx->win_summary, "%s", msg);
75         wrefresh(ctx->win_summary);
76         return r;
77 }
78
79 static int
80 debug(struct ctx *ctx, const char *fmt, ...)
81 {
82         va_list args;
83         int r;
84
85         if (ctx->win_debug == NULL)
86                 return 0;
87         waddch(ctx->win_debug, '\n');
88         va_start(args, fmt);
89         r = vwprintw(ctx->win_debug, fmt, args);
90         va_end(args);
91         wrefresh(ctx->win_debug);
92         return r;
93 }
94
95 static int
96 warning(struct ctx *ctx, const char *fmt, ...)
97 {
98         va_list args;
99         int r;
100
101         if (ctx->win_debug == NULL)
102                 return 0;
103         waddch(ctx->win_debug, '\n');
104         va_start(args, fmt);
105         r = vwprintw(ctx->win_debug, fmt, args);
106         va_end(args);
107         wrefresh(ctx->win_debug);
108         return r;
109 }
110
111 static int
112 notify(struct ctx *ctx, const char *fmt, ...)
113 {
114         va_list args;
115         int r;
116
117         if (ctx->win_debug == NULL)
118                 return 0;
119         waddch(ctx->win_debug, '\n');
120         va_start(args, fmt);
121         r = vwprintw(ctx->win_debug, fmt, args);
122         va_end(args);
123         wrefresh(ctx->win_debug);
124         return r;
125 }
126
127 static void
128 update_online(struct ctx *ctx, bool show_if)
129 {
130         bool online, carrier;
131         char *msg, *msgs, *nmsg;
132         size_t msgs_len, mlen;
133         DHCPCD_IF *ifs, *i;
134
135         online = carrier = false;
136         msgs = NULL;
137         msgs_len = 0;
138         ifs = dhcpcd_interfaces(ctx->con);
139         for (i = ifs; i; i = i->next) {
140                 if (strcmp(i->type, "link") == 0) {
141                         if (i->up)
142                                 carrier = true;
143                 } else {
144                         if (i->up)
145                                 online = true;
146                 }
147                 msg = dhcpcd_if_message(i, NULL);
148                 if (msg) {
149                         if (show_if) {
150                                 if (i->up)
151                                         notify(ctx, "%s", msg);
152                                 else
153                                         warning(ctx, "%s", msg);
154                         }
155                         if (msgs == NULL) {
156                                 msgs = msg;
157                                 msgs_len = strlen(msgs) + 1;
158                         } else {
159                                 mlen = strlen(msg) + 1;
160                                 nmsg = realloc(msgs, msgs_len + mlen);
161                                 if (nmsg) {
162                                         msgs = nmsg;
163                                         msgs[msgs_len - 1] = '\n';
164                                         memcpy(msgs + msgs_len, msg, mlen);
165                                         msgs_len += mlen;
166                                 } else
167                                         warn("realloc");
168                                 free(msg);
169                         }
170                 } else if (show_if) {
171                         if (i->up)
172                                 notify(ctx, "%s: %s", i->ifname, i->reason);
173                         else
174                                 warning(ctx, "%s: %s", i->ifname, i->reason);
175                 }
176         }
177
178         set_summary(ctx, msgs);
179         free(msgs);
180 }
181
182 static void
183 dispatch(void *arg)
184 {
185         struct ctx *ctx = arg;
186
187         dhcpcd_dispatch(ctx->con);
188 }
189
190 static void
191 try_open(void *arg)
192 {
193         struct ctx *ctx = arg;
194         static int last_error;
195
196         ctx->fd = dhcpcd_open(ctx->con, true);
197         if (ctx->fd == -1) {
198                 if (errno == EACCES || errno == EPERM) {
199                         ctx->fd = dhcpcd_open(ctx->con, false);
200                         if (ctx->fd != -1)
201                                 goto unprived;
202                 }
203                 if (errno != last_error) {
204                         last_error = errno;
205                         set_status(ctx, strerror(errno));
206                 }
207                 eloop_timeout_add_sec(ctx->eloop, DHCPCD_RETRYOPEN,
208                     try_open, ctx);
209                 return;
210         }
211
212 unprived:
213         /* Start listening to WPA events */
214         dhcpcd_wpa_start(ctx->con);
215
216         eloop_event_add(ctx->eloop, ctx->fd, dispatch, ctx, NULL, NULL);
217 }
218
219 static void
220 status_cb(DHCPCD_CONNECTION *con, const char *status, void *arg)
221 {
222         struct ctx *ctx = arg;
223
224         debug(ctx, _("Status changed to %s"), status);
225         set_status(ctx, status);
226
227         if (strcmp(status, "down") != 0) {
228                 bool refresh;
229
230                 if (ctx->last_status == NULL || strcmp(ctx->last_status, "down") == 0) {
231                         debug(ctx, _("Connected to dhcpcd-%s"), dhcpcd_version(con));
232                         refresh = true;
233                 } else
234                         refresh = strcmp(ctx->last_status, "opened") ? false : true;
235                 update_online(ctx, refresh);
236         }
237
238         free(ctx->last_status);
239         ctx->last_status = strdup(status);
240
241         if (strcmp(status, "down") == 0) {
242                 ctx->online = ctx->carrier = false;
243                 eloop_timeout_add_sec(ctx->eloop, DHCPCD_RETRYOPEN,
244                     try_open, ctx);
245                 return;
246         }
247 }
248
249 static void
250 if_cb(DHCPCD_IF *i, void *arg)
251 {
252         struct ctx *ctx = arg;
253
254         if (strcmp(i->reason, "RENEW") &&
255             strcmp(i->reason, "STOP") &&
256             strcmp(i->reason, "STOPPED"))
257         {
258                 char *msg;
259                 bool new_msg;
260
261                 msg = dhcpcd_if_message(i, &new_msg);
262                 if (msg) {
263                         if (i->up)
264                                 warning(ctx, msg);
265                         else
266                                 notify(ctx, msg);
267                         free(msg);
268                 }
269         }
270
271         update_online(ctx, false);
272
273         if (i->wireless) {
274                 /* PROCESS SCANS */
275         }
276 }
277
278 static void
279 wpa_dispatch(void *arg)
280 {
281         DHCPCD_WPA *wpa = arg;
282
283         dhcpcd_wpa_dispatch(wpa);
284 }
285
286
287 static void
288 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
289 {
290         struct ctx *ctx = arg;
291         DHCPCD_IF *i;
292         WI_SCAN *wi;
293         DHCPCD_WI_SCAN *scans, *s1, *s2;
294         int fd, lerrno;
295
296         /* This could be a new WPA so watch it */
297         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
298                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
299                 return;
300         }
301         eloop_event_add(ctx->eloop,
302             dhcpcd_wpa_get_fd(wpa), wpa_dispatch, wpa, NULL, NULL);
303
304         i = dhcpcd_wpa_if(wpa);
305         if (i == NULL) {
306                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
307                 return;
308         }
309         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
310         lerrno = errno;
311         errno = 0;
312         scans = dhcpcd_wi_scans(i);
313         if (scans == NULL && errno)
314                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
315         errno = lerrno;
316         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
317                 if (wi->interface == i)
318                         break;
319         }
320         if (wi == NULL) {
321                 wi = malloc(sizeof(*wi));
322                 wi->interface = i;
323                 wi->scans = scans;
324                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
325         } else {
326                 const char *title;
327                 char *msgs, *nmsg;
328                 size_t msgs_len, mlen;
329
330                 title = NULL;
331                 msgs = NULL;
332                 for (s1 = scans; s1; s1 = s1->next) {
333                         for (s2 = wi->scans; s2; s2 = s2->next)
334                                 if (strcmp(s1->ssid, s2->ssid) == 0)
335                                         break;
336                         if (s2 == NULL) {
337                                 if (msgs == NULL) {
338                                         msgs = strdup(s1->ssid);
339                                         msgs_len = strlen(msgs) + 1;
340                                 } else {
341                                         if (title == NULL)
342                                                 title = _("New Access Points");
343                                         mlen = strlen(s1->ssid) + 1;
344                                         nmsg = realloc(msgs, msgs_len + mlen);
345                                         if (nmsg) {
346                                                 msgs = nmsg;
347                                                 msgs[msgs_len - 1] = '\n';
348                                                 memcpy(msgs + msgs_len,
349                                                     s1->ssid, mlen);
350                                                 msgs_len += mlen;
351                                         } else
352                                                 warn("realloc");
353                                 }
354                         }
355                 }
356                 if (msgs) {
357                         if (title == NULL)
358                                 title = _("New Access Point");
359                         mlen = strlen(title) + 1;
360                         nmsg = realloc(msgs, msgs_len + mlen);
361                         if (nmsg) {
362                                 msgs = nmsg;
363                                 memmove(msgs + mlen, msgs, msgs_len);
364                                 memcpy(msgs, title, mlen);
365                                 msgs[mlen - 1] = '\n';
366                         } else
367                                 warn("realloc");
368                         notify(ctx, "%s", msgs);
369                         free(msgs);
370                 }
371
372                 dhcpcd_wi_scans_free(wi->scans);
373                 wi->scans = scans;
374         }
375 }
376
377 static void
378 wpa_status_cb(DHCPCD_WPA *wpa, const char *status, void *arg)
379 {
380         struct ctx *ctx = arg;
381         DHCPCD_IF *i;
382         WI_SCAN *w, *wn;
383
384         i = dhcpcd_wpa_if(wpa);
385         debug(ctx, _("%s: WPA status %s"), i->ifname, status);
386         if (strcmp(status, "down") == 0) {
387                 eloop_event_delete(ctx->eloop, dhcpcd_wpa_get_fd(wpa), 0);
388                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
389                         if (w->interface == i) {
390                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
391                                 dhcpcd_wi_scans_free(w->scans);
392                                 free(w);
393                         }
394                 }
395         }
396 }
397
398 #ifdef BG_SCAN
399 static void
400 bg_scan(void *arg)
401 {
402         struct ctx *ctx = arg;
403         WI_SCAN *w;
404         DHCPCD_WPA *wpa;
405
406         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
407                 if (w->interface->wireless && w->interface->up) {
408                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
409                         if (wpa)
410                                 dhcpcd_wpa_scan(wpa);
411                 }
412         }
413
414         /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
415         eloop_timeout_add_sec(ctx->eloop,
416              DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, ctx);
417 }
418 #endif
419
420 static void
421 signal_handler(int sig)
422 {
423         struct winsize ws;
424
425         switch(sig) {
426         case SIGWINCH:
427                 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
428                         resizeterm(ws.ws_row, ws.ws_col);
429                 break;
430         case SIGINT:
431                 debug(_ctx, _("SIGINT caught, exiting"));
432                 eloop_exit(_ctx->eloop, EXIT_FAILURE);
433                 break;
434         case SIGTERM:
435                 debug(_ctx, _("SIGTERM caught, exiting"));
436                 eloop_exit(_ctx->eloop, EXIT_FAILURE);
437                 break;
438         case SIGHUP:
439                 debug(_ctx, _("SIGHUP caught, ignoring"));
440                 break;
441         case SIGPIPE:
442                 debug(_ctx, _("SIGPIPE caught, ignoring"));
443                 break;
444         }
445 }
446
447 static int
448 setup_signals()
449 {
450         struct sigaction sa;
451         int i;
452
453         memset(&sa, 0, sizeof(sa));
454         sa.sa_handler = signal_handler;
455
456         for (i = 0; handle_sigs[i]; i++) {
457                 if (sigaction(handle_sigs[i], &sa, NULL) == -1)
458                         return -1;
459         }
460         return 0;
461 }
462
463 static int
464 create_windows(struct ctx *ctx)
465 {
466         int h, w;
467
468         getmaxyx(ctx->stdscr, h, w);
469
470         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
471                 return -1;
472
473         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
474                 return -1;
475         box(ctx->win_summary_border, 0, 0);
476         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
477             _("Connection Summary"));
478         wrefresh(ctx->win_summary_border);
479         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
480                 return -1;
481         scrollok(ctx->win_summary, TRUE);
482
483 #if 1
484         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
485                 return -1;
486         box(ctx->win_debug_border, 0, 0);
487         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
488             _("Event Log"));
489         wrefresh(ctx->win_debug_border);
490         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
491                 return -1;
492         scrollok(ctx->win_debug, TRUE);
493 #endif
494         return 0;
495 }
496
497 int
498 main(void)
499 {
500         struct ctx ctx;
501         WI_SCAN *wi;
502
503         memset(&ctx, 0, sizeof(ctx));
504         ctx.fd = -1;
505         TAILQ_INIT(&ctx.wi_scans);
506         _ctx = &ctx;
507
508         if (setup_signals() == -1)
509                 err(EXIT_FAILURE, "setup_signals");
510
511         if ((ctx.con = dhcpcd_new()) == NULL)
512                 err(EXIT_FAILURE, "dhcpcd_new");
513
514         if ((ctx.eloop = eloop_init()) == NULL)
515                 err(EXIT_FAILURE, "malloc");
516         
517         if ((ctx.stdscr = initscr()) == NULL)
518                 err(EXIT_FAILURE, "initscr");
519
520         if (create_windows(&ctx) == -1)
521                 err(EXIT_FAILURE, "create_windows");
522
523         curs_set(0);
524         noecho();
525         keypad(ctx.stdscr, TRUE);
526
527         wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
528         dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
529         dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
530         dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
531         dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
532         dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
533
534         eloop_timeout_add_sec(ctx.eloop, 0, try_open, &ctx);
535 #ifdef BG_SCAN
536         /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
537         eloop_timeout_add_sec(ctx.eloop,
538             DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, &ctx);
539 #endif
540         eloop_start(ctx.eloop);
541
542         /* Close the dhcpcd connection first incase any callbacks trigger */
543         dhcpcd_close(ctx.con);
544         dhcpcd_free(ctx.con);
545
546         /* Free our saved scans */
547         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
548                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
549                 dhcpcd_wi_scans_free(wi->scans);
550                 free(wi);
551         }
552
553         /* Free everything else */
554         eloop_free(ctx.eloop);
555         endwin();
556         free(ctx.last_status);
557
558 #ifdef HAVE_NC_FREE_AND_EXIT
559         /* undefined ncurses function to allow valgrind debugging */
560         _nc_free_and_exit();
561 #endif
562
563         return EXIT_SUCCESS;
564 }