739b9bdb205e698c6b1047aa122380c7f3f80034
[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 __printflike(2, 3) 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 __printflike(2, 3) 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 __printflike(2, 3) 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 ||
231                     strcmp(ctx->last_status, "down") == 0)
232                 {
233                         debug(ctx, _("Connected to dhcpcd-%s"),
234                             dhcpcd_version(con));
235                         refresh = true;
236                 } else
237                         refresh =
238                             strcmp(ctx->last_status, "opened") ? false : true;
239                 update_online(ctx, refresh);
240         }
241
242         free(ctx->last_status);
243         ctx->last_status = strdup(status);
244
245         if (strcmp(status, "down") == 0) {
246                 ctx->online = ctx->carrier = false;
247                 eloop_timeout_add_sec(ctx->eloop, DHCPCD_RETRYOPEN,
248                     try_open, ctx);
249                 return;
250         }
251 }
252
253 static void
254 if_cb(DHCPCD_IF *i, void *arg)
255 {
256         struct ctx *ctx = arg;
257
258         if (strcmp(i->reason, "RENEW") &&
259             strcmp(i->reason, "STOP") &&
260             strcmp(i->reason, "STOPPED"))
261         {
262                 char *msg;
263                 bool new_msg;
264
265                 msg = dhcpcd_if_message(i, &new_msg);
266                 if (msg) {
267                         if (i->up)
268                                 warning(ctx, "%s", msg);
269                         else
270                                 notify(ctx, "%s", msg);
271                         free(msg);
272                 }
273         }
274
275         update_online(ctx, false);
276
277         if (i->wireless) {
278                 /* PROCESS SCANS */
279         }
280 }
281
282 static void
283 wpa_dispatch(void *arg)
284 {
285         DHCPCD_WPA *wpa = arg;
286
287         dhcpcd_wpa_dispatch(wpa);
288 }
289
290
291 static void
292 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
293 {
294         struct ctx *ctx = arg;
295         DHCPCD_IF *i;
296         WI_SCAN *wi;
297         DHCPCD_WI_SCAN *scans, *s1, *s2;
298         int fd, lerrno;
299
300         /* This could be a new WPA so watch it */
301         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
302                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
303                 return;
304         }
305         eloop_event_add(ctx->eloop,
306             dhcpcd_wpa_get_fd(wpa), wpa_dispatch, wpa, NULL, NULL);
307
308         i = dhcpcd_wpa_if(wpa);
309         if (i == NULL) {
310                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
311                 return;
312         }
313         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
314         lerrno = errno;
315         errno = 0;
316         scans = dhcpcd_wi_scans(i);
317         if (scans == NULL && errno)
318                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
319         errno = lerrno;
320         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
321                 if (wi->interface == i)
322                         break;
323         }
324         if (wi == NULL) {
325                 wi = malloc(sizeof(*wi));
326                 wi->interface = i;
327                 wi->scans = scans;
328                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
329         } else {
330                 const char *title;
331                 char *msgs, *nmsg;
332                 size_t msgs_len, mlen;
333
334                 title = NULL;
335                 msgs = NULL;
336                 for (s1 = scans; s1; s1 = s1->next) {
337                         for (s2 = wi->scans; s2; s2 = s2->next)
338                                 if (strcmp(s1->ssid, s2->ssid) == 0)
339                                         break;
340                         if (s2 == NULL) {
341                                 if (msgs == NULL) {
342                                         msgs = strdup(s1->ssid);
343                                         msgs_len = strlen(msgs) + 1;
344                                 } else {
345                                         if (title == NULL)
346                                                 title = _("New Access Points");
347                                         mlen = strlen(s1->ssid) + 1;
348                                         nmsg = realloc(msgs, msgs_len + mlen);
349                                         if (nmsg) {
350                                                 msgs = nmsg;
351                                                 msgs[msgs_len - 1] = '\n';
352                                                 memcpy(msgs + msgs_len,
353                                                     s1->ssid, mlen);
354                                                 msgs_len += mlen;
355                                         } else
356                                                 warn("realloc");
357                                 }
358                         }
359                 }
360                 if (msgs) {
361                         if (title == NULL)
362                                 title = _("New Access Point");
363                         mlen = strlen(title) + 1;
364                         nmsg = realloc(msgs, msgs_len + mlen);
365                         if (nmsg) {
366                                 msgs = nmsg;
367                                 memmove(msgs + mlen, msgs, msgs_len);
368                                 memcpy(msgs, title, mlen);
369                                 msgs[mlen - 1] = '\n';
370                         } else
371                                 warn("realloc");
372                         notify(ctx, "%s", msgs);
373                         free(msgs);
374                 }
375
376                 dhcpcd_wi_scans_free(wi->scans);
377                 wi->scans = scans;
378         }
379 }
380
381 static void
382 wpa_status_cb(DHCPCD_WPA *wpa, const char *status, void *arg)
383 {
384         struct ctx *ctx = arg;
385         DHCPCD_IF *i;
386         WI_SCAN *w, *wn;
387
388         i = dhcpcd_wpa_if(wpa);
389         debug(ctx, _("%s: WPA status %s"), i->ifname, status);
390         if (strcmp(status, "down") == 0) {
391                 eloop_event_delete(ctx->eloop, dhcpcd_wpa_get_fd(wpa), 0);
392                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
393                         if (w->interface == i) {
394                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
395                                 dhcpcd_wi_scans_free(w->scans);
396                                 free(w);
397                         }
398                 }
399         }
400 }
401
402 #ifdef BG_SCAN
403 static void
404 bg_scan(void *arg)
405 {
406         struct ctx *ctx = arg;
407         WI_SCAN *w;
408         DHCPCD_WPA *wpa;
409
410         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
411                 if (w->interface->wireless && w->interface->up) {
412                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
413                         if (wpa)
414                                 dhcpcd_wpa_scan(wpa);
415                 }
416         }
417
418         /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
419         eloop_timeout_add_sec(ctx->eloop,
420              DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, ctx);
421 }
422 #endif
423
424 static void
425 signal_handler(int sig)
426 {
427         struct winsize ws;
428
429         switch(sig) {
430         case SIGWINCH:
431                 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
432                         resizeterm(ws.ws_row, ws.ws_col);
433                 break;
434         case SIGINT:
435                 debug(_ctx, _("SIGINT caught, exiting"));
436                 eloop_exit(_ctx->eloop, EXIT_FAILURE);
437                 break;
438         case SIGTERM:
439                 debug(_ctx, _("SIGTERM caught, exiting"));
440                 eloop_exit(_ctx->eloop, EXIT_FAILURE);
441                 break;
442         case SIGHUP:
443                 debug(_ctx, _("SIGHUP caught, ignoring"));
444                 break;
445         case SIGPIPE:
446                 debug(_ctx, _("SIGPIPE caught, ignoring"));
447                 break;
448         }
449 }
450
451 static int
452 setup_signals()
453 {
454         struct sigaction sa;
455         int i;
456
457         memset(&sa, 0, sizeof(sa));
458         sa.sa_handler = signal_handler;
459
460         for (i = 0; handle_sigs[i]; i++) {
461                 if (sigaction(handle_sigs[i], &sa, NULL) == -1)
462                         return -1;
463         }
464         return 0;
465 }
466
467 static int
468 create_windows(struct ctx *ctx)
469 {
470         int h, w;
471
472         getmaxyx(ctx->stdscr, h, w);
473
474         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
475                 return -1;
476
477         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
478                 return -1;
479         box(ctx->win_summary_border, 0, 0);
480         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
481             _("Connection Summary"));
482         wrefresh(ctx->win_summary_border);
483         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
484                 return -1;
485         scrollok(ctx->win_summary, TRUE);
486
487 #if 1
488         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
489                 return -1;
490         box(ctx->win_debug_border, 0, 0);
491         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
492             _("Event Log"));
493         wrefresh(ctx->win_debug_border);
494         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
495                 return -1;
496         scrollok(ctx->win_debug, TRUE);
497 #endif
498         return 0;
499 }
500
501 int
502 main(void)
503 {
504         struct ctx ctx;
505         WI_SCAN *wi;
506
507         memset(&ctx, 0, sizeof(ctx));
508         ctx.fd = -1;
509         TAILQ_INIT(&ctx.wi_scans);
510         _ctx = &ctx;
511
512         if (setup_signals() == -1)
513                 err(EXIT_FAILURE, "setup_signals");
514
515         if ((ctx.con = dhcpcd_new()) == NULL)
516                 err(EXIT_FAILURE, "dhcpcd_new");
517
518         if ((ctx.eloop = eloop_init()) == NULL)
519                 err(EXIT_FAILURE, "malloc");
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 #ifdef BG_SCAN
540         /* DHCPCD_WPA_SCAN_SHORT is in milliseconds */
541         eloop_timeout_add_sec(ctx.eloop,
542             DHCPCD_WPA_SCAN_SHORT / 1000, bg_scan, &ctx);
543 #endif
544         eloop_start(ctx.eloop);
545
546         /* Un-resgister the callbacks to avoid spam on close */
547         dhcpcd_set_status_callback(ctx.con, NULL, NULL);
548         dhcpcd_set_if_callback(ctx.con, NULL, NULL);
549         dhcpcd_wpa_set_scan_callback(ctx.con, NULL, NULL);
550         dhcpcd_wpa_set_status_callback(ctx.con, NULL, NULL);
551         dhcpcd_close(ctx.con);
552         dhcpcd_free(ctx.con);
553
554         /* Free our saved scans */
555         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
556                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
557                 dhcpcd_wi_scans_free(wi->scans);
558                 free(wi);
559         }
560
561         /* Free everything else */
562         eloop_free(ctx.eloop);
563         endwin();
564         free(ctx.last_status);
565
566 #ifdef HAVE_NC_FREE_AND_EXIT
567         /* undefined ncurses function to allow valgrind debugging */
568         _nc_free_and_exit();
569 #endif
570
571         return EXIT_SUCCESS;
572 }