6b7bd0cd90bfc69ba1bb4c6a9475e7b0d8eedb09
[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         if (dhcpcd_get_fd(ctx->con) == -1) {
196                 warning(ctx, _("dhcpcd connection lost"));
197                 eloop_event_delete(ctx->eloop, ctx->fd, 0);
198                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
199                     try_open, ctx);
200                 return;
201         }
202
203         dhcpcd_dispatch(ctx->con);
204 }
205
206 static void
207 try_open(void *arg)
208 {
209         struct ctx *ctx = arg;
210         static int last_error;
211
212         ctx->fd = dhcpcd_open(ctx->con, true);
213         if (ctx->fd == -1) {
214                 if (errno == EACCES || errno == EPERM) {
215                         ctx->fd = dhcpcd_open(ctx->con, false);
216                         if (ctx->fd != -1)
217                                 goto unprived;
218                 }
219                 if (errno != last_error) {
220                         last_error = errno;
221                         set_status(ctx, strerror(errno));
222                 }
223                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
224                     try_open, ctx);
225                 return;
226         }
227
228 unprived:
229         last_error = 0;
230
231         /* Start listening to WPA events */
232         dhcpcd_wpa_start(ctx->con);
233
234         eloop_event_add(ctx->eloop, ctx->fd, dispatch, ctx, NULL, NULL);
235 }
236
237 static void
238 status_cb(DHCPCD_CONNECTION *con,
239     unsigned int status, const char *status_msg, void *arg)
240 {
241         struct ctx *ctx = arg;
242
243         debug(ctx, _("Status changed to %s"), status_msg);
244         set_status(ctx, status_msg);
245
246         if (status == DHC_DOWN) {
247                 eloop_event_delete(ctx->eloop, ctx->fd, 0);
248                 ctx->fd = -1;
249                 ctx->online = ctx->carrier = false;
250                 eloop_timeout_delete(ctx->eloop, NULL, ctx);
251                 set_summary(ctx, NULL);
252                 eloop_timeout_add_msec(ctx->eloop, DHCPCD_RETRYOPEN,
253                     try_open, ctx);
254         } else {
255                 bool refresh;
256
257                 if (ctx->last_status == DHC_UNKNOWN ||
258                     ctx->last_status == DHC_DOWN)
259                 {
260                         debug(ctx, _("Connected to dhcpcd-%s"),
261                             dhcpcd_version(con));
262                         refresh = true;
263                 } else
264                         refresh =
265                             ctx->last_status == DHC_OPENED ? true : false;
266                 update_online(ctx, refresh);
267         }
268
269         ctx->last_status = status;
270 }
271
272 static void
273 if_cb(DHCPCD_IF *i, void *arg)
274 {
275         struct ctx *ctx = arg;
276
277         if (i->state == DHS_RENEW ||
278             i->state == DHS_STOP || i->state == DHS_STOPPED)
279         {
280                 char *msg;
281                 bool new_msg;
282
283                 msg = dhcpcd_if_message(i, &new_msg);
284                 if (msg) {
285                         if (i->up)
286                                 warning(ctx, "%s", msg);
287                         else
288                                 notify(ctx, "%s", msg);
289                         free(msg);
290                 }
291         }
292
293         update_online(ctx, false);
294
295         if (i->wireless) {
296                 /* PROCESS SCANS */
297         }
298 }
299
300 static void
301 wpa_dispatch(void *arg)
302 {
303         DHCPCD_WPA *wpa = arg;
304
305         dhcpcd_wpa_dispatch(wpa);
306 }
307
308 static void
309 wpa_scan_cb(DHCPCD_WPA *wpa, void *arg)
310 {
311         struct ctx *ctx = arg;
312         DHCPCD_IF *i;
313         WI_SCAN *wi;
314         DHCPCD_WI_SCAN *scans, *s1, *s2;
315         int fd, lerrno;
316
317         /* This could be a new WPA so watch it */
318         if ((fd = dhcpcd_wpa_get_fd(wpa)) == -1) {
319                 debug(ctx, "%s (%p)", _("no fd for WPA"), wpa);
320                 return;
321         }
322         eloop_event_add(ctx->eloop,
323             dhcpcd_wpa_get_fd(wpa), wpa_dispatch, wpa, NULL, NULL);
324
325         i = dhcpcd_wpa_if(wpa);
326         if (i == NULL) {
327                 debug(ctx, "%s (%p)", _("No interface for WPA"), wpa);
328                 return;
329         }
330         debug(ctx, "%s: %s", i->ifname, _("Received scan results"));
331         lerrno = errno;
332         errno = 0;
333         scans = dhcpcd_wi_scans(i);
334         if (scans == NULL && errno)
335                 debug(ctx, "%s: %s", i->ifname, strerror(errno));
336         errno = lerrno;
337         TAILQ_FOREACH(wi, &ctx->wi_scans, next) {
338                 if (wi->interface == i)
339                         break;
340         }
341         if (wi == NULL) {
342                 wi = malloc(sizeof(*wi));
343                 wi->interface = i;
344                 wi->scans = scans;
345                 TAILQ_INSERT_TAIL(&ctx->wi_scans, wi, next);
346         } else {
347                 const char *title;
348                 char *msgs, *nmsg;
349                 size_t msgs_len, mlen;
350
351                 title = NULL;
352                 msgs = NULL;
353                 for (s1 = scans; s1; s1 = s1->next) {
354                         for (s2 = wi->scans; s2; s2 = s2->next)
355                                 if (strcmp(s1->ssid, s2->ssid) == 0)
356                                         break;
357                         if (s2 == NULL) {
358                                 if (msgs == NULL) {
359                                         msgs = strdup(s1->ssid);
360                                         msgs_len = strlen(msgs) + 1;
361                                 } else {
362                                         if (title == NULL)
363                                                 title = _("New Access Points");
364                                         mlen = strlen(s1->ssid) + 1;
365                                         nmsg = realloc(msgs, msgs_len + mlen);
366                                         if (nmsg) {
367                                                 msgs = nmsg;
368                                                 msgs[msgs_len - 1] = '\n';
369                                                 memcpy(msgs + msgs_len,
370                                                     s1->ssid, mlen);
371                                                 msgs_len += mlen;
372                                         } else
373                                                 warn("realloc");
374                                 }
375                         }
376                 }
377                 if (msgs) {
378                         if (title == NULL)
379                                 title = _("New Access Point");
380                         mlen = strlen(title) + 1;
381                         nmsg = realloc(msgs, msgs_len + mlen);
382                         if (nmsg) {
383                                 msgs = nmsg;
384                                 memmove(msgs + mlen, msgs, msgs_len);
385                                 memcpy(msgs, title, mlen);
386                                 msgs[mlen - 1] = '\n';
387                         } else
388                                 warn("realloc");
389                         notify(ctx, "%s", msgs);
390                         free(msgs);
391                 }
392
393                 dhcpcd_wi_scans_free(wi->scans);
394                 wi->scans = scans;
395         }
396 }
397
398 /*
399  * XXXX Fixme, ideally in the wpa_dispatch
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
423 static void
424 bg_scan(void *arg)
425 {
426         struct ctx *ctx = arg;
427         WI_SCAN *w;
428         DHCPCD_WPA *wpa;
429
430         TAILQ_FOREACH(w, &ctx->wi_scans, next) {
431                 if (w->interface->wireless& w->interface->up) {
432                         wpa = dhcpcd_wpa_find(ctx->con, w->interface->ifname);
433                         if (wpa &&
434                             (!w->interface->up ||
435                             dhcpcd_wpa_can_background_scan(wpa)))
436                                 dhcpcd_wpa_scan(wpa);
437                 }
438         }
439
440         eloop_timeout_add_msec(ctx->eloop, DHCPCD_WPA_SCAN_SHORT,
441             bg_scan, ctx);
442 }
443
444 static void
445 signal_cb(int sig, void *arg)
446 {
447         struct ctx *ctx = arg;
448         struct winsize ws;
449
450         switch(sig) {
451         case SIGWINCH:
452                 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0)
453                         resizeterm(ws.ws_row, ws.ws_col);
454                 break;
455         case SIGINT:
456                 debug(ctx, _("SIGINT caught, exiting"));
457                 eloop_exit(ctx->eloop, EXIT_FAILURE);
458                 break;
459         case SIGTERM:
460                 debug(ctx, _("SIGTERM caught, exiting"));
461                 eloop_exit(ctx->eloop, EXIT_FAILURE);
462                 break;
463         case SIGHUP:
464                 debug(ctx, ("SIGHUP caught, ignoring"));
465                 break;
466         case SIGPIPE:
467                 debug(ctx, ("SIGPIPE caught, ignoring"));
468                 break;
469         }
470 }
471
472 static int
473 create_windows(struct ctx *ctx)
474 {
475         int h, w;
476
477         getmaxyx(ctx->stdscr, h, w);
478
479         if ((ctx->win_status = newwin(1, w, 0, 0)) == NULL)
480                 return -1;
481
482         if ((ctx->win_summary_border = newwin(10, w - 2, 2, 1)) == NULL)
483                 return -1;
484         box(ctx->win_summary_border, 0, 0);
485         mvwprintw(ctx->win_summary_border, 0, 5, " %s ",
486             _("Connection Summary"));
487         wrefresh(ctx->win_summary_border);
488         if ((ctx->win_summary = newwin(8, w - 4, 3, 2)) == NULL)
489                 return -1;
490         scrollok(ctx->win_summary, TRUE);
491
492 #if 1
493         if ((ctx->win_debug_border = newwin(8, w - 2, h - 10, 1)) == NULL)
494                 return -1;
495         box(ctx->win_debug_border, 0, 0);
496         mvwprintw(ctx->win_debug_border, 0, 5, " %s ",
497             _("Event Log"));
498         wrefresh(ctx->win_debug_border);
499         if ((ctx->win_debug = newwin(6, w - 4, h - 9, 2)) == NULL)
500                 return -1;
501         scrollok(ctx->win_debug, TRUE);
502 #endif
503         return 0;
504 }
505
506 int
507 main(void)
508 {
509         struct ctx ctx;
510         WI_SCAN *wi;
511         sigset_t sigmask;
512
513         memset(&ctx, 0, sizeof(ctx));
514         ctx.fd = -1;
515         TAILQ_INIT(&ctx.wi_scans);
516
517         if ((ctx.eloop = eloop_new()) == NULL)
518                 err(EXIT_FAILURE, "eloop_new");
519         if (eloop_signal_set_cb(ctx.eloop, sigs, signal_cb, &ctx) == -1)
520                 err(EXIT_FAILURE, "eloop_signal_set_cb");
521         if (eloop_signal_mask(ctx.eloop, &sigmask) == -1)
522                 err(EXIT_FAILURE, "eloop_signal_mask");
523
524         if ((ctx.con = dhcpcd_new()) == NULL)
525                 err(EXIT_FAILURE, "dhcpcd_new");
526
527         if ((ctx.stdscr = initscr()) == NULL)
528                 err(EXIT_FAILURE, "initscr");
529
530         if (create_windows(&ctx) == -1)
531                 err(EXIT_FAILURE, "create_windows");
532
533         curs_set(0);
534         noecho();
535         keypad(ctx.stdscr, TRUE);
536
537         wprintw(ctx.win_status, "%s %s", _("dhcpcd Curses Interface"), VERSION);
538         dhcpcd_set_progname(ctx.con, "dhcpcd-curses");
539         dhcpcd_set_status_callback(ctx.con, status_cb, &ctx);
540         dhcpcd_set_if_callback(ctx.con, if_cb, &ctx);
541         dhcpcd_wpa_set_scan_callback(ctx.con, wpa_scan_cb, &ctx);
542         //dhcpcd_wpa_set_status_callback(ctx.con, wpa_status_cb, &ctx);
543
544         eloop_timeout_add_sec(ctx.eloop, 0, try_open, &ctx);
545         eloop_timeout_add_msec(ctx.eloop, DHCPCD_WPA_SCAN_SHORT,
546             bg_scan, &ctx);
547         eloop_start(ctx.eloop, &sigmask);
548
549         /* Un-resgister the callbacks to avoid spam on close */
550         dhcpcd_set_status_callback(ctx.con, NULL, NULL);
551         dhcpcd_set_if_callback(ctx.con, NULL, NULL);
552         dhcpcd_wpa_set_scan_callback(ctx.con, NULL, NULL);
553         dhcpcd_wpa_set_status_callback(ctx.con, NULL, NULL);
554         dhcpcd_close(ctx.con);
555         dhcpcd_free(ctx.con);
556
557         /* Free our saved scans */
558         while ((wi = TAILQ_FIRST(&ctx.wi_scans))) {
559                 TAILQ_REMOVE(&ctx.wi_scans, wi, next);
560                 dhcpcd_wi_scans_free(wi->scans);
561                 free(wi);
562         }
563
564         /* Free everything else */
565         eloop_free(ctx.eloop);
566         endwin();
567
568 #ifdef HAVE_NC_FREE_AND_EXIT
569         /* undefined ncurses function to allow valgrind debugging */
570         _nc_free_and_exit();
571 #endif
572
573         return EXIT_SUCCESS;
574 }