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