f47e74b9ac0557ba24bb15848154f886ce4d3f33
[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         int fd;
204
205         fd = dhcpcd_open(ctx->con, true);
206         if (fd == -1) {
207                 if (errno == EACCES || errno == EPERM) {
208                         fd = dhcpcd_open(ctx->con, false);
209                         if (fd != -1)
210                                 goto unprived;
211                 }
212                 if (errno != last_error) {
213                         last_error = errno;
214                         set_status(ctx, strerror(errno));
215                 }
216                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
217                     try_open, ctx);
218                 return;
219         }
220
221 unprived:
222         last_error = 0;
223
224         /* Start listening to WPA events */
225         dhcpcd_wpa_start(ctx->con);
226
227         eloop_event_add(ctx->eloop, fd, dispatch, ctx, NULL, NULL);
228 }
229
230 static void
231 status_cb(DHCPCD_CONNECTION *con,
232     unsigned int status, const char *status_msg, void *arg)
233 {
234         struct ctx *ctx = arg;
235
236         debug(ctx, _("Status changed to %s"), status_msg);
237         set_status(ctx, status_msg);
238
239         if (status == DHC_DOWN) {
240                 int fd;
241
242                 fd = dhcpcd_get_fd(ctx->con);
243                 eloop_event_delete(ctx->eloop, fd, 0);
244                 ctx->online = ctx->carrier = false;
245                 eloop_timeout_delete(ctx->eloop, NULL, ctx);
246                 set_summary(ctx, NULL);
247                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
248                     try_open, ctx);
249         } else {
250                 bool refresh;
251
252                 if (ctx->last_status == DHC_UNKNOWN ||
253                     ctx->last_status == DHC_DOWN)
254                 {
255                         debug(ctx, _("Connected to dhcpcd-%s"),
256                             dhcpcd_version(con));
257                         refresh = true;
258                 } else
259                         refresh =
260                             ctx->last_status == DHC_OPENED ? true : false;
261                 update_online(ctx, refresh);
262         }
263
264         ctx->last_status = status;
265 }
266
267 static void
268 if_cb(DHCPCD_IF *i, void *arg)
269 {
270         struct ctx *ctx = arg;
271
272         if (i->state == DHS_RENEW ||
273             i->state == DHS_STOP || i->state == DHS_STOPPED)
274         {
275                 char *msg;
276                 bool new_msg;
277
278                 msg = dhcpcd_if_message(i, &new_msg);
279                 if (msg) {
280                         if (i->up)
281                                 warning(ctx, "%s", msg);
282                         else
283                                 notify(ctx, "%s", msg);
284                         free(msg);
285                 }
286         }
287
288         update_online(ctx, false);
289
290         if (i->wireless) {
291                 /* PROCESS SCANS */
292         }
293 }
294
295 static void
296 wpa_dispatch(void *arg)
297 {
298         DHCPCD_WPA *wpa = arg;
299
300         dhcpcd_wpa_dispatch(wpa);
301 }
302
303 static void
304 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
305 {
306         struct ctx *ctx = arg;
307         DHCPCD_IF *i;
308         WI_SCAN *wi;
309         DHCPCD_WI_SCAN *scans, *s1, *s2;
310         int fd, lerrno;
311
312         /* This could be a new WPA so watch it */
313         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
314                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
315                 return;
316         }
317         eloop_event_add(ctx->eloop, fd, wpa_dispatch, wpa, NULL, NULL);
318
319         i = dhcpcd_wpa_if(wpa);
320         if (i == NULL) {
321                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
322                 return;
323         }
324         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
325         lerrno = errno;
326         errno = 0;
327         scans = dhcpcd_wi_scans(i);
328         if (scans == NULL && errno)
329                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
330         errno = lerrno;
331         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
332                 if (wi->interface == i)
333                         break;
334         }
335         if (wi == NULL) {
336                 wi = malloc(sizeof(*wi));
337                 wi->interface = i;
338                 wi->scans = scans;
339                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
340         } else {
341                 const char *title;
342                 char *msgs, *nmsg;
343                 size_t msgs_len, mlen;
344
345                 title = NULL;
346                 msgs = NULL;
347                 for (s1 = scans; s1; s1 = s1->next) {
348                         for (s2 = wi->scans; s2; s2 = s2->next)
349                                 if (strcmp(s1->ssid, s2->ssid) == 0)
350                                         break;
351                         if (s2 == NULL) {
352                                 if (msgs == NULL) {
353                                         msgs = strdup(s1->ssid);
354                                         msgs_len = strlen(msgs) + 1;
355                                 } else {
356                                         if (title == NULL)
357                                                 title = _("New Access Points");
358                                         mlen = strlen(s1->ssid) + 1;
359                                         nmsg = realloc(msgs, msgs_len + mlen);
360                                         if (nmsg) {
361                                                 msgs = nmsg;
362                                                 msgs[msgs_len - 1] = '\n';
363                                                 memcpy(msgs + msgs_len,
364                                                     s1->ssid, mlen);
365                                                 msgs_len += mlen;
366                                         } else
367                                                 warn("realloc");
368                                 }
369                         }
370                 }
371                 if (msgs) {
372                         if (title == NULL)
373                                 title = _("New Access Point");
374                         mlen = strlen(title) + 1;
375                         nmsg = realloc(msgs, msgs_len + mlen);
376                         if (nmsg) {
377                                 msgs = nmsg;
378                                 memmove(msgs + mlen, msgs, msgs_len);
379                                 memcpy(msgs, title, mlen);
380                                 msgs[mlen - 1] = '\n';
381                         } else
382                                 warn("realloc");
383                         notify(ctx, "%s", msgs);
384                         free(msgs);
385                 }
386
387                 dhcpcd_wi_scans_free(wi->scans);
388                 wi->scans = scans;
389         }
390 }
391
392 static void
393 wpa_status_cb(DHCPCD_WPA *wpa,
394     unsigned int status, const char *status_msg, void *arg)
395 {
396         struct ctx *ctx = arg;
397         DHCPCD_IF *i;
398         WI_SCAN *w, *wn;
399
400         i = dhcpcd_wpa_if(wpa);
401         debug(ctx, _("%s: WPA status %s"), i->ifname, status_msg);
402         if (status == DHC_DOWN) {
403                 int fd;
404
405                 fd = dhcpcd_wpa_get_fd(wpa);
406                 eloop_event_delete(ctx->eloop, fd, 0);
407                 dhcpcd_wpa_close(wpa);
408                 TAILQ_FOREACH_SAFE(w, &ctx->wi_scans, next, wn) {
409                         if (w->interface == i) {
410                                 TAILQ_REMOVE(&ctx->wi_scans, w, next);
411                                 dhcpcd_wi_scans_free(w->scans);
412                                 free(w);
413                         }
414                 }
415         }
416 }
417
418 static void
419 bg_scan(void *arg)
420 {
421         struct ctx *ctx = arg;
422         WI_SCAN *w;
423         DHCPCD_WPA *wpa;
424
425         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
426                 if (w->interface->wireless& w->interface->up) {
427                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
428                         if (wpa &&
429                             (!w->interface->up ||
430                             dhcpcd_wpa_can_background_scan(wpa)))
431                                 dhcpcd_wpa_scan(wpa);
432                 }
433         }
434
435         eloop_timeout_add_msec(ctx->eloop, DHCPCD_WPA_SCAN_SHORT,
436             bg_scan, ctx);
437 }
438
439 static void
440 signal_cb(int sig, void *arg)
441 {
442         struct ctx *ctx = arg;
443         struct winsize ws;
444
445         switch(sig) {
446         case SIGWINCH:
447                 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
448                         resizeterm(ws.ws_row, ws.ws_col);
449                 break;
450         case SIGINT:
451                 debug(ctx, _("SIGINT caught, exiting"));
452                 eloop_exit(ctx->eloop, EXIT_FAILURE);
453                 break;
454         case SIGTERM:
455                 debug(ctx, _("SIGTERM caught, exiting"));
456                 eloop_exit(ctx->eloop, EXIT_FAILURE);
457                 break;
458         case SIGHUP:
459                 debug(ctx, ("SIGHUP caught, ignoring"));
460                 break;
461         case SIGPIPE:
462                 /* ignore and don't report */
463                 break;
464         }
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         sigset_t sigmask;
507
508         memset(&ctx, 0, sizeof(ctx));
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 }