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