512 lines
12 KiB
C
512 lines
12 KiB
C
/* $OpenBSD: iked.c,v 1.69 2024/02/15 19:04:12 tobhe Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 2019 Tobias Heider <tobias.heider@stusta.de>
|
|
* Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
|
|
#include <sys/queue.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/uio.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <getopt.h>
|
|
#include <signal.h>
|
|
#include <syslog.h>
|
|
#include <errno.h>
|
|
#include <err.h>
|
|
#include <pwd.h>
|
|
#include <event.h>
|
|
|
|
#include "iked.h"
|
|
#include "ikev2.h"
|
|
#include "version.h"
|
|
|
|
__dead void usage(void);
|
|
|
|
void parent_shutdown(struct iked *);
|
|
void parent_sig_handler(int, short, void *);
|
|
int parent_dispatch_ca(int, struct privsep_proc *, struct imsg *);
|
|
int parent_dispatch_control(int, struct privsep_proc *, struct imsg *);
|
|
int parent_dispatch_ikev2(int, struct privsep_proc *, struct imsg *);
|
|
int parent_configure(struct iked *);
|
|
|
|
struct iked *iked_env;
|
|
|
|
static struct privsep_proc procs[] = {
|
|
{ "ca", PROC_CERT, parent_dispatch_ca, caproc, IKED_CA },
|
|
{ "control", PROC_CONTROL, parent_dispatch_control, control },
|
|
{ "ikev2", PROC_IKEV2, parent_dispatch_ikev2, ikev2 }
|
|
};
|
|
|
|
__dead void
|
|
usage(void)
|
|
{
|
|
extern char *__progname;
|
|
|
|
fprintf(stderr, "usage: %s [-dnSTtVv] [-D macro=value] "
|
|
"[-f file] [-p udpencap_port] [-s socket]\n", __progname);
|
|
exit(1);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
int debug = 0, verbose = 0;
|
|
int opts = 0;
|
|
enum natt_mode natt_mode = NATT_DEFAULT;
|
|
in_port_t port = IKED_NATT_PORT;
|
|
const char *conffile = IKED_CONFIG;
|
|
const char *sock = IKED_SOCKET;
|
|
const char *errstr, *title = NULL;
|
|
struct iked *env = NULL;
|
|
struct privsep *ps;
|
|
enum privsep_procid proc_id = PROC_PARENT;
|
|
int proc_instance = 0;
|
|
int argc0 = argc;
|
|
|
|
log_init(1, LOG_DAEMON);
|
|
|
|
while ((c = getopt(argc, argv, "6D:df:I:nP:p:Ss:TtvV")) != -1) {
|
|
switch (c) {
|
|
case '6':
|
|
log_warnx("the -6 option is ignored and will be "
|
|
"removed in the future.");
|
|
break;
|
|
case 'D':
|
|
if (cmdline_symset(optarg) < 0)
|
|
log_warnx("could not parse macro definition %s",
|
|
optarg);
|
|
break;
|
|
case 'd':
|
|
debug++;
|
|
break;
|
|
case 'f':
|
|
conffile = optarg;
|
|
break;
|
|
case 'I':
|
|
proc_instance = strtonum(optarg, 0,
|
|
PROC_MAX_INSTANCES, &errstr);
|
|
if (errstr)
|
|
fatalx("invalid process instance");
|
|
break;
|
|
case 'n':
|
|
debug = 1;
|
|
opts |= IKED_OPT_NOACTION;
|
|
break;
|
|
case 'P':
|
|
title = optarg;
|
|
proc_id = proc_getid(procs, nitems(procs), title);
|
|
if (proc_id == PROC_MAX)
|
|
fatalx("invalid process name");
|
|
break;
|
|
case 'p':
|
|
if (natt_mode == NATT_DISABLE)
|
|
errx(1, "-T and -p are mutually exclusive");
|
|
port = strtonum(optarg, 1, UINT16_MAX, &errstr);
|
|
if (errstr != NULL)
|
|
errx(1, "port is %s: %s", errstr, optarg);
|
|
natt_mode = NATT_FORCE;
|
|
break;
|
|
case 'S':
|
|
opts |= IKED_OPT_PASSIVE;
|
|
break;
|
|
case 's':
|
|
sock = optarg;
|
|
break;
|
|
case 'T':
|
|
if (natt_mode == NATT_FORCE)
|
|
errx(1, "-T and -t/-p are mutually exclusive");
|
|
natt_mode = NATT_DISABLE;
|
|
break;
|
|
case 't':
|
|
if (natt_mode == NATT_DISABLE)
|
|
errx(1, "-T and -t are mutually exclusive");
|
|
natt_mode = NATT_FORCE;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
opts |= IKED_OPT_VERBOSE;
|
|
break;
|
|
case 'V':
|
|
fprintf(stderr, "OpenIKED %s\n", IKED_VERSION);
|
|
return 0;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
/* log to stderr until daemonized */
|
|
log_init(debug ? debug : 1, LOG_DAEMON);
|
|
|
|
argc -= optind;
|
|
if (argc > 0)
|
|
usage();
|
|
|
|
if ((env = calloc(1, sizeof(*env))) == NULL)
|
|
fatal("calloc: env");
|
|
|
|
iked_env = env;
|
|
env->sc_opts = opts;
|
|
env->sc_nattmode = natt_mode;
|
|
env->sc_nattport = port;
|
|
|
|
ps = &env->sc_ps;
|
|
ps->ps_env = env;
|
|
|
|
if (strlcpy(env->sc_conffile, conffile, PATH_MAX) >= PATH_MAX)
|
|
errx(1, "config file exceeds PATH_MAX");
|
|
|
|
group_init();
|
|
policy_init(env);
|
|
|
|
/* check for root privileges */
|
|
if (geteuid())
|
|
errx(1, "need root privileges");
|
|
|
|
if ((ps->ps_pw = getpwnam(IKED_USER)) == NULL)
|
|
errx(1, "unknown user %s", IKED_USER);
|
|
|
|
/* Configure the control socket */
|
|
ps->ps_csock.cs_name = sock;
|
|
|
|
log_init(debug, LOG_DAEMON);
|
|
log_setverbose(verbose);
|
|
|
|
if (opts & IKED_OPT_NOACTION)
|
|
ps->ps_noaction = 1;
|
|
|
|
ps->ps_instance = proc_instance;
|
|
if (title != NULL)
|
|
ps->ps_title[proc_id] = title;
|
|
|
|
/* only the parent returns */
|
|
proc_init(ps, procs, nitems(procs), debug, argc0, argv, proc_id);
|
|
|
|
setproctitle("parent");
|
|
log_procinit("parent");
|
|
|
|
event_init();
|
|
|
|
signal_set(&ps->ps_evsigint, SIGINT, parent_sig_handler, ps);
|
|
signal_set(&ps->ps_evsigterm, SIGTERM, parent_sig_handler, ps);
|
|
signal_set(&ps->ps_evsigchld, SIGCHLD, parent_sig_handler, ps);
|
|
signal_set(&ps->ps_evsighup, SIGHUP, parent_sig_handler, ps);
|
|
signal_set(&ps->ps_evsigpipe, SIGPIPE, parent_sig_handler, ps);
|
|
signal_set(&ps->ps_evsigusr1, SIGUSR1, parent_sig_handler, ps);
|
|
|
|
signal_add(&ps->ps_evsigint, NULL);
|
|
signal_add(&ps->ps_evsigterm, NULL);
|
|
signal_add(&ps->ps_evsigchld, NULL);
|
|
signal_add(&ps->ps_evsighup, NULL);
|
|
signal_add(&ps->ps_evsigpipe, NULL);
|
|
signal_add(&ps->ps_evsigusr1, NULL);
|
|
|
|
proc_connect(ps);
|
|
|
|
vroute_init(env);
|
|
|
|
if (parent_configure(env) == -1)
|
|
fatalx("configuration failed");
|
|
|
|
event_dispatch();
|
|
|
|
log_debug("%d parent exiting", getpid());
|
|
parent_shutdown(env);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
parent_configure(struct iked *env)
|
|
{
|
|
struct sockaddr_storage ss;
|
|
|
|
if (parse_config(env->sc_conffile, env) == -1) {
|
|
proc_kill(&env->sc_ps);
|
|
exit(1);
|
|
}
|
|
|
|
if (env->sc_opts & IKED_OPT_NOACTION) {
|
|
fprintf(stderr, "configuration OK\n");
|
|
proc_kill(&env->sc_ps);
|
|
exit(0);
|
|
}
|
|
|
|
env->sc_pfkey = -1;
|
|
config_setpfkey(env);
|
|
|
|
/* Send private and public keys to cert after forking the children */
|
|
if (config_setkeys(env) == -1)
|
|
fatalx("%s: failed to send keys", __func__);
|
|
config_setreset(env, RESET_CA, PROC_CERT);
|
|
|
|
/* Now compile the policies and calculate skip steps */
|
|
config_setcompile(env, PROC_IKEV2);
|
|
|
|
bzero(&ss, sizeof(ss));
|
|
ss.ss_family = AF_INET;
|
|
|
|
/* see comment on config_setsocket() */
|
|
if (env->sc_nattmode != NATT_FORCE)
|
|
config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2);
|
|
if (env->sc_nattmode != NATT_DISABLE)
|
|
config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2);
|
|
|
|
bzero(&ss, sizeof(ss));
|
|
ss.ss_family = AF_INET6;
|
|
|
|
if (env->sc_nattmode != NATT_FORCE)
|
|
config_setsocket(env, &ss, htons(IKED_IKE_PORT), PROC_IKEV2);
|
|
if (env->sc_nattmode != NATT_DISABLE)
|
|
config_setsocket(env, &ss, htons(env->sc_nattport), PROC_IKEV2);
|
|
|
|
/*
|
|
* pledge in the parent process:
|
|
* It has to run fairly late to allow forking the processes and
|
|
* opening the PFKEY socket and the listening UDP sockets (once)
|
|
* that need the bypass ioctls that are never allowed by pledge.
|
|
*
|
|
* Other flags:
|
|
* stdio - for malloc and basic I/O including events.
|
|
* rpath - for reload to open and read the configuration files.
|
|
* proc - run kill to terminate its children safely.
|
|
* dns - for reload and ocsp connect.
|
|
* inet - for ocsp connect.
|
|
* route - for using interfaces in iked.conf (SIOCGIFGMEMB)
|
|
* wroute - for adding and removing addresses (SIOCAIFGMEMB)
|
|
* sendfd - for ocsp sockets.
|
|
*/
|
|
if (pledge("stdio rpath proc dns inet route wroute sendfd", NULL) == -1)
|
|
fatal("pledge");
|
|
|
|
config_setstatic(env);
|
|
config_setcoupled(env, env->sc_decoupled ? 0 : 1);
|
|
config_setocsp(env);
|
|
/* Must be last */
|
|
config_setmode(env, env->sc_passive ? 1 : 0);
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
parent_reload(struct iked *env, int reset, const char *filename)
|
|
{
|
|
/* Switch back to the default config file */
|
|
if (filename == NULL || *filename == '\0')
|
|
filename = env->sc_conffile;
|
|
|
|
log_debug("%s: level %d config file %s", __func__, reset, filename);
|
|
|
|
if (reset == RESET_RELOAD) {
|
|
config_setreset(env, RESET_POLICY, PROC_IKEV2);
|
|
if (config_setkeys(env) == -1)
|
|
fatalx("%s: failed to send keys", __func__);
|
|
config_setreset(env, RESET_CA, PROC_CERT);
|
|
|
|
if (parse_config(filename, env) == -1) {
|
|
log_debug("%s: failed to load config file %s",
|
|
__func__, filename);
|
|
}
|
|
|
|
/* Re-compile policies and skip steps */
|
|
config_setcompile(env, PROC_IKEV2);
|
|
|
|
config_setstatic(env);
|
|
config_setcoupled(env, env->sc_decoupled ? 0 : 1);
|
|
config_setocsp(env);
|
|
/* Must be last */
|
|
config_setmode(env, env->sc_passive ? 1 : 0);
|
|
} else {
|
|
config_setreset(env, reset, PROC_IKEV2);
|
|
config_setreset(env, reset, PROC_CERT);
|
|
}
|
|
}
|
|
|
|
void
|
|
parent_sig_handler(int sig, short event, void *arg)
|
|
{
|
|
struct privsep *ps = arg;
|
|
int die = 0, status, fail, id;
|
|
pid_t pid;
|
|
char *cause;
|
|
|
|
switch (sig) {
|
|
case SIGHUP:
|
|
log_info("%s: reload requested with SIGHUP", __func__);
|
|
|
|
/*
|
|
* This is safe because libevent uses async signal handlers
|
|
* that run in the event loop and not in signal context.
|
|
*/
|
|
parent_reload(ps->ps_env, 0, NULL);
|
|
break;
|
|
case SIGPIPE:
|
|
log_info("%s: ignoring SIGPIPE", __func__);
|
|
break;
|
|
case SIGUSR1:
|
|
log_info("%s: ignoring SIGUSR1", __func__);
|
|
break;
|
|
case SIGTERM:
|
|
case SIGINT:
|
|
die = 1;
|
|
/* FALLTHROUGH */
|
|
case SIGCHLD:
|
|
do {
|
|
int len = 0;
|
|
|
|
pid = waitpid(-1, &status, WNOHANG);
|
|
if (pid <= 0)
|
|
continue;
|
|
|
|
fail = 0;
|
|
if (WIFSIGNALED(status)) {
|
|
fail = 1;
|
|
len = asprintf(&cause, "terminated; signal %d",
|
|
WTERMSIG(status));
|
|
} else if (WIFEXITED(status)) {
|
|
if (WEXITSTATUS(status) != 0) {
|
|
fail = 1;
|
|
len = asprintf(&cause,
|
|
"exited abnormally");
|
|
} else
|
|
len = asprintf(&cause, "exited okay");
|
|
} else
|
|
fatalx("unexpected cause of SIGCHLD");
|
|
|
|
if (len == -1)
|
|
fatal("asprintf");
|
|
|
|
die = 1;
|
|
|
|
for (id = 0; id < PROC_MAX; id++)
|
|
if (pid == ps->ps_pid[id]) {
|
|
if (fail)
|
|
log_warnx("lost child: %s %s",
|
|
ps->ps_title[id], cause);
|
|
break;
|
|
}
|
|
|
|
free(cause);
|
|
} while (pid > 0 || (pid == -1 && errno == EINTR));
|
|
|
|
if (die)
|
|
parent_shutdown(ps->ps_env);
|
|
break;
|
|
default:
|
|
fatalx("unexpected signal");
|
|
}
|
|
}
|
|
|
|
int
|
|
parent_dispatch_ca(int fd, struct privsep_proc *p, struct imsg *imsg)
|
|
{
|
|
struct iked *env = iked_env;
|
|
|
|
switch (imsg->hdr.type) {
|
|
case IMSG_CTL_ACTIVE:
|
|
case IMSG_CTL_PASSIVE:
|
|
proc_forward_imsg(&env->sc_ps, imsg, PROC_IKEV2, -1);
|
|
break;
|
|
case IMSG_OCSP_FD:
|
|
ocsp_connect(env, imsg);
|
|
break;
|
|
default:
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
parent_dispatch_control(int fd, struct privsep_proc *p, struct imsg *imsg)
|
|
{
|
|
struct iked *env = iked_env;
|
|
int v;
|
|
char *str = NULL;
|
|
unsigned int type = imsg->hdr.type;
|
|
|
|
switch (type) {
|
|
case IMSG_CTL_RESET:
|
|
IMSG_SIZE_CHECK(imsg, &v);
|
|
memcpy(&v, imsg->data, sizeof(v));
|
|
parent_reload(env, v, NULL);
|
|
break;
|
|
case IMSG_CTL_COUPLE:
|
|
case IMSG_CTL_DECOUPLE:
|
|
case IMSG_CTL_ACTIVE:
|
|
case IMSG_CTL_PASSIVE:
|
|
proc_compose(&env->sc_ps, PROC_IKEV2, type, NULL, 0);
|
|
break;
|
|
case IMSG_CTL_RELOAD:
|
|
if (IMSG_DATA_SIZE(imsg) > 0)
|
|
str = get_string(imsg->data, IMSG_DATA_SIZE(imsg));
|
|
parent_reload(env, 0, str);
|
|
free(str);
|
|
break;
|
|
case IMSG_CTL_VERBOSE:
|
|
proc_forward_imsg(&env->sc_ps, imsg, PROC_IKEV2, -1);
|
|
proc_forward_imsg(&env->sc_ps, imsg, PROC_CERT, -1);
|
|
|
|
/* return 1 to let proc.c handle it locally */
|
|
return (1);
|
|
default:
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
parent_dispatch_ikev2(int fd, struct privsep_proc *p, struct imsg *imsg)
|
|
{
|
|
struct iked *env = iked_env;
|
|
|
|
switch (imsg->hdr.type) {
|
|
case IMSG_IF_ADDADDR:
|
|
case IMSG_IF_DELADDR:
|
|
return (vroute_getaddr(env, imsg));
|
|
case IMSG_VDNS_ADD:
|
|
case IMSG_VDNS_DEL:
|
|
return (vroute_getdns(env, imsg));
|
|
case IMSG_VROUTE_ADD:
|
|
case IMSG_VROUTE_DEL:
|
|
return (vroute_getroute(env, imsg));
|
|
case IMSG_VROUTE_CLONE:
|
|
return (vroute_getcloneroute(env, imsg));
|
|
default:
|
|
return (-1);
|
|
}
|
|
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
parent_shutdown(struct iked *env)
|
|
{
|
|
proc_kill(&env->sc_ps);
|
|
|
|
vroute_cleanup(env);
|
|
free(env->sc_vroute);
|
|
free(env);
|
|
|
|
log_warnx("parent terminating");
|
|
exit(0);
|
|
}
|