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