xref: /openbsd/sbin/isakmpd/ui.c (revision b7041c07)
1 /* $OpenBSD: ui.c,v 1.58 2021/10/24 21:24:21 deraadt Exp $	 */
2 /* $EOM: ui.c,v 1.43 2000/10/05 09:25:12 niklas Exp $	 */
3 
4 /*
5  * Copyright (c) 1998, 1999, 2000 Niklas Hallqvist.  All rights reserved.
6  * Copyright (c) 1999, 2000, 2001, 2002 H�kan Olsson.  All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  * This code was written under funding by Ericsson Radio Systems.
31  */
32 
33 #include <sys/types.h>
34 #include <sys/socket.h>
35 #include <sys/stat.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <fcntl.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <errno.h>
43 
44 #include "conf.h"
45 #include "connection.h"
46 #include "doi.h"
47 #include "exchange.h"
48 #include "init.h"
49 #include "isakmp.h"
50 #include "log.h"
51 #include "monitor.h"
52 #include "sa.h"
53 #include "timer.h"
54 #include "transport.h"
55 #include "ui.h"
56 #include "util.h"
57 
58 #define BUF_SZ 256
59 
60 /* from isakmpd.c */
61 void		 daemon_shutdown_now(int);
62 
63 /* Report all SA configuration information. */
64 void		 ui_report_sa(char *);
65 
66 static FILE	*ui_open_result(void);
67 
68 char		*ui_fifo = FIFO;
69 int		 ui_socket;
70 struct event	*ui_cr_event = NULL;
71 int		 ui_daemon_passive = 0;
72 
73 /* Create and open the FIFO used for user control.  */
74 void
ui_init(void)75 ui_init(void)
76 {
77 	struct stat     st;
78 
79 	/* -f- means control messages comes in via stdin.  */
80 	if (strcmp(ui_fifo, "-") == 0) {
81 		ui_socket = 0;
82 		return;
83 	}
84 
85 	/* Don't overwrite a file, i.e '-f /etc/isakmpd/isakmpd.conf'.  */
86 	if (lstat(ui_fifo, &st) == 0) {
87 		if (S_ISREG(st.st_mode)) {
88 			errno = EEXIST;
89 			log_fatal("ui_init: could not create FIFO \"%s\"",
90 			    ui_fifo);
91 		}
92 	}
93 
94 	/* No need to know about errors.  */
95 	unlink(ui_fifo);
96 	if (mkfifo(ui_fifo, 0600) == -1)
97 		log_fatal("ui_init: mkfifo (\"%s\", 0600) failed", ui_fifo);
98 
99 	ui_socket = open(ui_fifo, O_RDWR | O_NONBLOCK);
100 	if (ui_socket == -1)
101 		log_fatal("ui_init: open (\"%s\", O_RDWR | O_NONBLOCK, 0) "
102 		    "failed", ui_fifo);
103 }
104 
105 /*
106  * Setup a phase 2 connection.
107  * XXX Maybe phase 1 works too, but teardown won't work then, fix?
108  */
109 static void
ui_connect(char * cmd)110 ui_connect(char *cmd)
111 {
112 	char	name[201];
113 
114 	if (sscanf(cmd, "c %200s", name) != 1) {
115 		log_print("ui_connect: command \"%s\" malformed", cmd);
116 		return;
117 	}
118 	LOG_DBG((LOG_UI, 10, "ui_connect: setup connection \"%s\"", name));
119 	connection_setup(name);
120 }
121 
122 /* Tear down a connection, can be phase 1 or 2.  */
123 static void
ui_teardown(char * cmd)124 ui_teardown(char *cmd)
125 {
126 	struct sockaddr_in	 addr;
127 	struct sockaddr_in6	 addr6;
128 	struct sa		*sa;
129 	int			 phase;
130 	char			 name[201];
131 
132 	/* If no phase is given, we default to phase 2. */
133 	phase = 2;
134 	if (sscanf(cmd, "t main %200s", name) == 1)
135 		phase = 1;
136 	else if (sscanf(cmd, "t quick %200s", name) == 1)
137 		phase = 2;
138 	else if (sscanf(cmd, "t %200s", name) != 1) {
139 		log_print("ui_teardown: command \"%s\" malformed", cmd);
140 		return;
141 	}
142 	LOG_DBG((LOG_UI, 10, "ui_teardown: teardown connection \"%s\", "
143 	    "phase %d", name, phase));
144 
145 	bzero(&addr, sizeof(addr));
146 	bzero(&addr6, sizeof(addr6));
147 
148 	if (inet_pton(AF_INET, name, &addr.sin_addr) == 1) {
149 		addr.sin_len = sizeof(addr);
150 		addr.sin_family = AF_INET;
151 
152 		while ((sa = sa_lookup_by_peer((struct sockaddr *)&addr,
153 		    SA_LEN((struct sockaddr *)&addr), phase)) != 0) {
154 			if (sa->name)
155 				connection_teardown(sa->name);
156 			sa_delete(sa, 1);
157 		}
158 	} else if (inet_pton(AF_INET6, name, &addr6.sin6_addr) == 1) {
159 		addr6.sin6_len = sizeof(addr6);
160 		addr6.sin6_family = AF_INET6;
161 
162 		while ((sa = sa_lookup_by_peer((struct sockaddr *)&addr6,
163 		    SA_LEN((struct sockaddr *)&addr6), phase)) != 0) {
164 			if (sa->name)
165 				connection_teardown(sa->name);
166 			sa_delete(sa, 1);
167 		}
168 	} else {
169 		if (phase == 2)
170 			connection_teardown(name);
171 		while ((sa = sa_lookup_by_name(name, phase)) != 0)
172 			sa_delete(sa, 1);
173 	}
174 }
175 
176 /* Tear down all phase 2 connections.  */
177 static void
ui_teardown_all(char * cmd)178 ui_teardown_all(char *cmd)
179 {
180 	/* Skip 'cmd' as arg. */
181 	sa_teardown_all();
182 }
183 
184 static void
ui_conn_reinit_event(void * v)185 ui_conn_reinit_event(void *v)
186 {
187 	/*
188 	 * This event is required for isakmpd to reinitialize the connection
189 	 * and passive-connection lists. Otherwise a change to the
190 	 * "[Phase 2]:Connections" tag will not have any effect.
191 	 */
192 	connection_reinit();
193 
194 	ui_cr_event = NULL;
195 }
196 
197 static void
ui_conn_reinit(void)198 ui_conn_reinit(void)
199 {
200 	struct timespec ts;
201 
202 	if (ui_cr_event)
203 		timer_remove_event(ui_cr_event);
204 
205 	clock_gettime(CLOCK_MONOTONIC, &ts);
206 	ts.tv_sec += 5;
207 
208 	ui_cr_event = timer_add_event("ui_conn_reinit", ui_conn_reinit_event,
209 	    0, &ts);
210 	if (!ui_cr_event)
211 		log_print("ui_conn_reinit: timer_add_event() failed. "
212 		    "Connections will not be updated.");
213 }
214 
215 /*
216  * Call the configuration API.
217  * XXX Error handling!  How to do multi-line transactions?
218  */
219 static void
ui_config(char * cmd)220 ui_config(char *cmd)
221 {
222 	struct conf_list *vlist;
223 	struct conf_list_node *vnode;
224 	char	 subcmd[201], section[201], tag[201], value[201], tmp[201];
225 	char	*v, *nv;
226 	int	 trans = 0, items, skip = 0, ret;
227 	FILE	*fp;
228 
229 	if (sscanf(cmd, "C %200s", subcmd) != 1)
230 		goto fail;
231 
232 	if (strcasecmp(subcmd, "get") == 0) {
233 		if (sscanf(cmd, "C %*s [%200[^]]]:%200s", section, tag) != 2)
234 			goto fail;
235 		v = conf_get_str(section, tag);
236 		fp = ui_open_result();
237 		if (fp) {
238 			if (v)
239 				fprintf(fp, "%s\n", v);
240 			fclose(fp);
241 		}
242 		LOG_DBG((LOG_UI, 30, "ui_config: \"%s\"", cmd));
243 		return;
244 	}
245 
246 	trans = conf_begin();
247 	if (strcasecmp(subcmd, "set") == 0) {
248 		items = sscanf(cmd, "C %*s [%200[^]]]:%200[^=]=%200s %200s",
249 		    section, tag, value, tmp);
250 		if (!(items == 3 || items == 4))
251 			goto fail;
252 		conf_set(trans, section, tag, value, items == 4 ? 1 : 0, 0);
253 		if (strcasecmp(section, "Phase 2") == 0 &&
254 		    (strcasecmp(tag, "Connections") == 0 ||
255 			strcasecmp(tag, "Passive-connections") == 0))
256 			ui_conn_reinit();
257 	} else if (strcasecmp(subcmd, "add") == 0) {
258 		items = sscanf(cmd, "C %*s [%200[^]]]:%200[^=]=%200s %200s",
259 		    section, tag, value, tmp);
260 		if (!(items == 3 || items == 4))
261 			goto fail;
262 		v = conf_get_str(section, tag);
263 		if (!v)
264 			conf_set(trans, section, tag, value, 1, 0);
265 		else {
266 			vlist = conf_get_list(section, tag);
267 			if (vlist) {
268 				for (vnode = TAILQ_FIRST(&vlist->fields);
269 				    vnode;
270 				    vnode = TAILQ_NEXT(vnode, link)) {
271 					if (strcmp(vnode->field, value) == 0) {
272 						skip = 1;
273 						break;
274 					}
275 				}
276 				conf_free_list(vlist);
277 			}
278 			/* Add the new value to the end of the 'v' list.  */
279 			if (skip == 0) {
280 				if (asprintf(&nv,
281 				    v[strlen(v) - 1] == ',' ? "%s%s" : "%s,%s",
282 				    v, value) == -1) {
283 					log_error("ui_config: malloc() failed");
284 					if (trans)
285 						conf_end(trans, 0);
286 					return;
287 				}
288 				conf_set(trans, section, tag, nv, 1, 0);
289 				free(nv);
290 			}
291 		}
292 		if (strcasecmp(section, "Phase 2") == 0 &&
293 		    (strcasecmp(tag, "Connections") == 0 ||
294 			strcasecmp(tag, "Passive-connections") == 0))
295 			ui_conn_reinit();
296 	} else if (strcasecmp(subcmd, "rmv") == 0) {
297 		items = sscanf(cmd, "C %*s [%200[^]]]:%200[^=]=%200s %200s",
298 		    section, tag, value, tmp);
299 		if (!(items == 3 || items == 4))
300 			goto fail;
301 		vlist = conf_get_list(section, tag);
302 		if (vlist) {
303 			nv = v = NULL;
304 			for (vnode = TAILQ_FIRST(&vlist->fields);
305 			    vnode;
306 			    vnode = TAILQ_NEXT(vnode, link)) {
307 				if (strcmp(vnode->field, value) == 0)
308 					continue;
309 				ret = v ?
310 				    asprintf(&nv, "%s,%s", v, vnode->field) :
311 				    asprintf(&nv, "%s", vnode->field);
312 				free(v);
313 				if (ret == -1) {
314 					log_error("ui_config: malloc() failed");
315 					if (trans)
316 						conf_end(trans, 0);
317 					return;
318 				}
319 				v = nv;
320 			}
321 			conf_free_list(vlist);
322 			if (nv) {
323 				conf_set(trans, section, tag, nv, 1, 0);
324 				free(nv);
325 			} else {
326 				conf_remove(trans, section, tag);
327 			}
328 		}
329 		if (strcasecmp(section, "Phase 2") == 0 &&
330 		    (strcasecmp(tag, "Connections") == 0 ||
331 			strcasecmp(tag, "Passive-connections") == 0))
332 			ui_conn_reinit();
333 	} else if (strcasecmp(subcmd, "rm") == 0) {
334 		if (sscanf(cmd, "C %*s [%200[^]]]:%200s", section, tag) != 2)
335 			goto fail;
336 		conf_remove(trans, section, tag);
337 	} else if (strcasecmp(subcmd, "rms") == 0) {
338 		if (sscanf(cmd, "C %*s [%200[^]]]", section) != 1)
339 			goto fail;
340 		conf_remove_section(trans, section);
341 	} else
342 		goto fail;
343 
344 	LOG_DBG((LOG_UI, 30, "ui_config: \"%s\"", cmd));
345 	conf_end(trans, 1);
346 	return;
347 
348 fail:
349 	if (trans)
350 		conf_end(trans, 0);
351 	log_print("ui_config: command \"%s\" malformed", cmd);
352 }
353 
354 static void
ui_delete(char * cmd)355 ui_delete(char *cmd)
356 {
357 	char            cookies_str[ISAKMP_HDR_COOKIES_LEN * 2 + 1];
358 	char            message_id_str[ISAKMP_HDR_MESSAGE_ID_LEN * 2 + 1];
359 	u_int8_t        cookies[ISAKMP_HDR_COOKIES_LEN];
360 	u_int8_t        message_id_buf[ISAKMP_HDR_MESSAGE_ID_LEN];
361 	u_int8_t       *message_id = message_id_buf;
362 	struct sa      *sa;
363 
364 	if (sscanf(cmd, "d %32s %8s", cookies_str, message_id_str) != 2) {
365 		log_print("ui_delete: command \"%s\" malformed", cmd);
366 		return;
367 	}
368 	if (strcmp(message_id_str, "-") == 0)
369 		message_id = 0;
370 
371 	if (hex2raw(cookies_str, cookies, ISAKMP_HDR_COOKIES_LEN) == -1 ||
372 	    (message_id && hex2raw(message_id_str, message_id_buf,
373 	    ISAKMP_HDR_MESSAGE_ID_LEN) == -1)) {
374 		log_print("ui_delete: command \"%s\" has bad arguments", cmd);
375 		return;
376 	}
377 	sa = sa_lookup(cookies, message_id);
378 	if (!sa) {
379 		log_print("ui_delete: command \"%s\" found no SA", cmd);
380 		return;
381 	}
382 	LOG_DBG((LOG_UI, 20,
383 	    "ui_delete: deleting SA for cookie \"%s\" msgid \"%s\"",
384 	    cookies_str, message_id_str));
385 	sa_delete(sa, 1);
386 }
387 
388 /* Parse the debug command found in CMD.  */
389 static void
ui_debug(char * cmd)390 ui_debug(char *cmd)
391 {
392 	int             cls, level;
393 	char            subcmd[3];
394 
395 	if (sscanf(cmd, "D %d %d", &cls, &level) == 2) {
396 		log_debug_cmd(cls, level);
397 		return;
398 	} else if (sscanf(cmd, "D %2s %d", subcmd, &level) == 2) {
399 		switch (subcmd[0]) {
400 		case 'A':
401 			for (cls = 0; cls < LOG_ENDCLASS; cls++)
402 				log_debug_cmd(cls, level);
403 			return;
404 		}
405 	} else if (sscanf(cmd, "D %2s", subcmd) == 1) {
406 		switch (subcmd[0]) {
407 		case 'T':
408 			log_debug_toggle();
409 			return;
410 		}
411 	}
412 	log_print("ui_debug: command \"%s\" malformed", cmd);
413 }
414 
415 static void
ui_packetlog(char * cmd)416 ui_packetlog(char *cmd)
417 {
418 	char	subcmd[201];
419 
420 	if (sscanf(cmd, "p %200s", subcmd) != 1)
421 		goto fail;
422 
423 	if (strncasecmp(subcmd, "on=", 3) == 0) {
424 		/* Start capture to a new file.  */
425 		if (subcmd[strlen(subcmd) - 1] == '\n')
426 			subcmd[strlen(subcmd) - 1] = 0;
427 		log_packet_restart(subcmd + 3);
428 	} else if (strcasecmp(subcmd, "on") == 0)
429 		log_packet_restart(NULL);
430 	else if (strcasecmp(subcmd, "off") == 0)
431 		log_packet_stop();
432 	return;
433 
434 fail:
435 	log_print("ui_packetlog: command \"%s\" malformed", cmd);
436 }
437 
438 static void
ui_shutdown_daemon(char * cmd)439 ui_shutdown_daemon(char *cmd)
440 {
441 	if (strlen(cmd) == 1) {
442 		log_print("ui_shutdown_daemon: received shutdown command");
443 		daemon_shutdown_now(0);
444 	} else
445 		log_print("ui_shutdown_daemon: command \"%s\" malformed", cmd);
446 }
447 
448 /* Report SAs and ongoing exchanges.  */
449 void
ui_report(char * cmd)450 ui_report(char *cmd)
451 {
452 	/* XXX Skip 'cmd' as arg? */
453 	sa_report();
454 	exchange_report();
455 	transport_report();
456 	connection_report();
457 	timer_report();
458 	conf_report();
459 }
460 
461 /* Report all SA configuration information.  */
462 void
ui_report_sa(char * cmd)463 ui_report_sa(char *cmd)
464 {
465 	FILE *fp = ui_open_result();
466 
467 	/* Skip 'cmd' as arg? */
468 	if (!fp)
469 		return;
470 
471 	sa_report_all(fp);
472 
473 	fclose(fp);
474 }
475 
476 static void
ui_setmode(char * cmd)477 ui_setmode(char *cmd)
478 {
479 	char	arg[11];
480 
481 	if (sscanf(cmd, "M %10s", arg) != 1)
482 		goto fail;
483 	if (strncmp(arg, "active", 6) == 0) {
484 		if (ui_daemon_passive)
485 			LOG_DBG((LOG_UI, 20,
486 			    "ui_setmode: switching to active mode"));
487 		ui_daemon_passive = 0;
488 	} else if (strncmp(arg, "passive", 7) == 0) {
489 		if (!ui_daemon_passive)
490 			LOG_DBG((LOG_UI, 20,
491 			    "ui_setmode: switching to passive mode"));
492 		ui_daemon_passive = 1;
493 	} else
494 		goto fail;
495 	return;
496 
497   fail:
498 	log_print("ui_setmode: command \"%s\" malformed", cmd);
499 }
500 
501 
502 /*
503  * Call the relevant command handler based on the first character of the
504  * line (the command).
505  */
506 static void
ui_handle_command(char * line)507 ui_handle_command(char *line)
508 {
509 	/* Find out what one-letter command was sent.  */
510 	switch (line[0]) {
511 	case 'c':
512 		ui_connect(line);
513 		break;
514 
515 	case 'C':
516 		ui_config(line);
517 		break;
518 
519 	case 'd':
520 		ui_delete(line);
521 		break;
522 
523 	case 'D':
524 		ui_debug(line);
525 		break;
526 
527 	case 'M':
528 		ui_setmode(line);
529 		break;
530 
531 	case 'p':
532 		ui_packetlog(line);
533 		break;
534 
535 	case 'Q':
536 		ui_shutdown_daemon(line);
537 		break;
538 
539 	case 'R':
540 		reinit();
541 		break;
542 
543 	case 'S':
544 		ui_report_sa(line);
545 		break;
546 
547 	case 'r':
548 		ui_report(line);
549 		break;
550 
551 	case 't':
552 		ui_teardown(line);
553 		break;
554 
555 	case 'T':
556 		ui_teardown_all(line);
557 		break;
558 
559 	default:
560 		log_print("ui_handle_messages: unrecognized command: '%c'",
561 		    line[0]);
562 	}
563 }
564 
565 /*
566  * A half-complex implementation of reading from a file descriptor
567  * line by line without resorting to stdio which apparently have
568  * troubles with non-blocking fifos.
569  */
570 void
ui_handler(void)571 ui_handler(void)
572 {
573 	static char    *buf = 0;
574 	static char    *p;
575 	static size_t   sz;
576 	static size_t   resid;
577 	ssize_t         n;
578 	char           *new_buf;
579 
580 	/* If no buffer, set it up.  */
581 	if (!buf) {
582 		sz = BUF_SZ;
583 		buf = malloc(sz);
584 		if (!buf) {
585 			log_print("ui_handler: malloc (%lu) failed",
586 			    (unsigned long)sz);
587 			return;
588 		}
589 		p = buf;
590 		resid = sz;
591 	}
592 	/* If no place left in the buffer reallocate twice as large.  */
593 	if (!resid) {
594 		new_buf = reallocarray(buf, sz, 2);
595 		if (!new_buf) {
596 			log_print("ui_handler: realloc (%p, %lu) failed", buf,
597 			    (unsigned long)sz * 2);
598 			free(buf);
599 			buf = 0;
600 			return;
601 		}
602 		buf = new_buf;
603 		p = buf + sz;
604 		resid = sz;
605 		sz *= 2;
606 	}
607 	n = read(ui_socket, p, resid);
608 	if (n == -1) {
609 		log_error("ui_handler: read (%d, %p, %lu)", ui_socket, p,
610 		    (unsigned long)resid);
611 		return;
612 	}
613 	if (!n)
614 		return;
615 	resid -= n;
616 	while (n--) {
617 		/*
618 		 * When we find a newline, cut off the line and feed it to the
619 		 * command processor.  Then move the rest up-front.
620 		 */
621 		if (*p == '\n') {
622 			*p = '\0';
623 			ui_handle_command(buf);
624 			memmove(buf, p + 1, n);
625 			p = buf;
626 			resid = sz - n;
627 			continue;
628 		}
629 		p++;
630 	}
631 }
632 
633 static FILE *
ui_open_result(void)634 ui_open_result(void)
635 {
636 	FILE *fp = monitor_fopen(RESULT_FILE, "w");
637 
638 	if (!fp)
639 		log_error("ui_open_result: fopen() failed");
640 	return fp;
641 }
642