533 lines
12 KiB
C
533 lines
12 KiB
C
/* $OpenBSD: isakmpd.c,v 1.109 2023/03/08 04:43:06 guenther Exp $ */
|
|
/* $EOM: isakmpd.c,v 1.54 2000/10/05 09:28:22 niklas Exp $ */
|
|
|
|
/*
|
|
* Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist. All rights reserved.
|
|
* Copyright (c) 1999, 2000 Angelos D. Keromytis. All rights reserved.
|
|
* Copyright (c) 1999, 2000, 2001 Håkan Olsson. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
/*
|
|
* This code was written under funding by Ericsson Radio Systems.
|
|
*/
|
|
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <netdb.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <paths.h>
|
|
|
|
#include "app.h"
|
|
#include "conf.h"
|
|
#include "connection.h"
|
|
#include "init.h"
|
|
#include "libcrypto.h"
|
|
#include "log.h"
|
|
#include "message.h"
|
|
#include "monitor.h"
|
|
#include "nat_traversal.h"
|
|
#include "sa.h"
|
|
#include "timer.h"
|
|
#include "transport.h"
|
|
#include "udp.h"
|
|
#include "udp_encap.h"
|
|
#include "ui.h"
|
|
#include "util.h"
|
|
#include "cert.h"
|
|
|
|
#include "policy.h"
|
|
|
|
static void usage(void);
|
|
|
|
/*
|
|
* Set if -d is given, currently just for running in the foreground and log
|
|
* to stderr instead of syslog.
|
|
*/
|
|
int debug = 0;
|
|
|
|
/* Set when no policy file shall be used. */
|
|
int acquire_only = 0;
|
|
|
|
/* Set when SAs shall be deleted on shutdown. */
|
|
int delete_sas = 1;
|
|
|
|
/*
|
|
* If we receive a SIGHUP signal, this flag gets set to show we need to
|
|
* reconfigure ASAP.
|
|
*/
|
|
volatile sig_atomic_t sighupped = 0;
|
|
|
|
/*
|
|
* If we receive a USR1 signal, this flag gets set to show we need to dump
|
|
* a report over our internal state ASAP. The file to report to is settable
|
|
* via the -R parameter.
|
|
*/
|
|
volatile sig_atomic_t sigusr1ed = 0;
|
|
static char *report_file = "/var/run/isakmpd.report";
|
|
|
|
/*
|
|
* If we receive a TERM signal, perform a "clean shutdown" of the daemon.
|
|
* This includes to send DELETE notifications for all our active SAs.
|
|
* Also on recv of an INT signal (Ctrl-C out of an '-d' session, typically).
|
|
*/
|
|
volatile sig_atomic_t sigtermed = 0;
|
|
void daemon_shutdown_now(int);
|
|
void set_slave_signals(void);
|
|
void sanitise_stdfd(void);
|
|
|
|
/* The default path of the PID file. */
|
|
char *pid_file = "/var/run/isakmpd.pid";
|
|
|
|
/* The path of the IKE packet capture log file. */
|
|
static char *pcap_file = 0;
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
extern char *__progname;
|
|
|
|
fprintf(stderr,
|
|
"usage: %s [-46adKLnSTv] [-c config-file] [-D class=level] [-f fifo]\n"
|
|
" [-i pid-file] [-l packetlog-file] [-N udpencap-port]\n"
|
|
" [-p listen-port] [-R report-file]\n",
|
|
__progname);
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
parse_args(int argc, char *argv[])
|
|
{
|
|
int ch;
|
|
int cls, level;
|
|
int do_packetlog = 0;
|
|
|
|
while ((ch = getopt(argc, argv, "46ac:dD:f:i:KnN:p:Ll:R:STv")) != -1) {
|
|
switch (ch) {
|
|
case '4':
|
|
bind_family |= BIND_FAMILY_INET4;
|
|
break;
|
|
|
|
case '6':
|
|
bind_family |= BIND_FAMILY_INET6;
|
|
break;
|
|
|
|
case 'a':
|
|
acquire_only = 1;
|
|
break;
|
|
|
|
case 'c':
|
|
conf_path = optarg;
|
|
break;
|
|
|
|
case 'd':
|
|
debug++;
|
|
break;
|
|
|
|
case 'D':
|
|
if (sscanf(optarg, "%d=%d", &cls, &level) != 2) {
|
|
if (sscanf(optarg, "A=%d", &level) == 1) {
|
|
for (cls = 0; cls < LOG_ENDCLASS;
|
|
cls++)
|
|
log_debug_cmd(cls, level);
|
|
} else
|
|
log_print("parse_args: -D argument "
|
|
"unparseable: %s", optarg);
|
|
} else
|
|
log_debug_cmd(cls, level);
|
|
break;
|
|
|
|
case 'f':
|
|
ui_fifo = optarg;
|
|
break;
|
|
|
|
case 'i':
|
|
pid_file = optarg;
|
|
break;
|
|
|
|
case 'K':
|
|
ignore_policy++;
|
|
break;
|
|
|
|
case 'n':
|
|
app_none++;
|
|
break;
|
|
|
|
case 'N':
|
|
udp_encap_default_port = optarg;
|
|
break;
|
|
|
|
case 'p':
|
|
udp_default_port = optarg;
|
|
break;
|
|
|
|
case 'l':
|
|
pcap_file = optarg;
|
|
/* FALLTHROUGH */
|
|
|
|
case 'L':
|
|
do_packetlog++;
|
|
break;
|
|
|
|
case 'R':
|
|
report_file = optarg;
|
|
break;
|
|
|
|
case 'S':
|
|
delete_sas = 0;
|
|
ui_daemon_passive = 1;
|
|
break;
|
|
|
|
case 'T':
|
|
disable_nat_t = 1;
|
|
break;
|
|
|
|
case 'v':
|
|
verbose_logging = 1;
|
|
break;
|
|
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if (argc > 0)
|
|
usage();
|
|
|
|
if (do_packetlog && !pcap_file)
|
|
pcap_file = PCAP_FILE_DEFAULT;
|
|
}
|
|
|
|
static void
|
|
sighup(int sig)
|
|
{
|
|
sighupped = 1;
|
|
}
|
|
|
|
/* Report internal state on SIGUSR1. */
|
|
static void
|
|
report(void)
|
|
{
|
|
FILE *rfp, *old;
|
|
mode_t old_umask;
|
|
|
|
old_umask = umask(S_IRWXG | S_IRWXO);
|
|
rfp = monitor_fopen(report_file, "w");
|
|
umask(old_umask);
|
|
|
|
if (!rfp) {
|
|
log_error("report: fopen (\"%s\", \"w\") failed", report_file);
|
|
return;
|
|
}
|
|
/* Divert the log channel to the report file during the report. */
|
|
old = log_current();
|
|
log_to(rfp);
|
|
ui_report("r");
|
|
log_to(old);
|
|
fclose(rfp);
|
|
}
|
|
|
|
static void
|
|
sigusr1(int sig)
|
|
{
|
|
sigusr1ed = 1;
|
|
}
|
|
|
|
static int
|
|
phase2_sa_check(struct sa *sa, void *arg)
|
|
{
|
|
return sa->phase == 2;
|
|
}
|
|
|
|
static int
|
|
phase1_sa_check(struct sa *sa, void *arg)
|
|
{
|
|
return sa->phase == 1;
|
|
}
|
|
|
|
void
|
|
set_slave_signals(void)
|
|
{
|
|
int n;
|
|
|
|
for (n = 1; n < _NSIG; n++)
|
|
signal(n, SIG_DFL);
|
|
|
|
/*
|
|
* Do a clean daemon shutdown on TERM/INT. These signals must be
|
|
* initialized before monitor_init(). INT is only used with '-d'.
|
|
*/
|
|
signal(SIGTERM, daemon_shutdown_now);
|
|
if (debug == 1) /* i.e '-dd' will skip this. */
|
|
signal(SIGINT, daemon_shutdown_now);
|
|
|
|
/* Reinitialize on HUP reception. */
|
|
signal(SIGHUP, sighup);
|
|
|
|
/* Report state on USR1 reception. */
|
|
signal(SIGUSR1, sigusr1);
|
|
}
|
|
|
|
static void
|
|
daemon_shutdown(void)
|
|
{
|
|
/* Perform a (protocol-wise) clean shutdown of the daemon. */
|
|
struct sa *sa;
|
|
|
|
if (sigtermed == 1) {
|
|
log_print("isakmpd: shutting down...");
|
|
|
|
if (delete_sas &&
|
|
strncmp("no", conf_get_str("General", "Delete-SAs"), 2)) {
|
|
/*
|
|
* Delete all active SAs. First IPsec SAs, then
|
|
* ISAKMPD. Each DELETE is another (outgoing) message.
|
|
*/
|
|
while ((sa = sa_find(phase2_sa_check, NULL)))
|
|
sa_delete(sa, 1);
|
|
|
|
while ((sa = sa_find(phase1_sa_check, NULL)))
|
|
sa_delete(sa, 1);
|
|
}
|
|
|
|
/* We only want to do this once. */
|
|
sigtermed++;
|
|
}
|
|
if (transport_prio_sendqs_empty()) {
|
|
/*
|
|
* When the prioritized transport sendq:s are empty, i.e all
|
|
* the DELETE notifications have been sent, we can shutdown.
|
|
*/
|
|
|
|
log_packet_stop();
|
|
log_print("isakmpd: exit");
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
/* Called on SIGTERM, SIGINT or by ui_shutdown_daemon(). */
|
|
void
|
|
daemon_shutdown_now(int sig)
|
|
{
|
|
sigtermed = 1;
|
|
}
|
|
|
|
/* Write pid file. */
|
|
static void
|
|
write_pid_file(void)
|
|
{
|
|
FILE *fp;
|
|
|
|
unlink(pid_file);
|
|
|
|
fp = fopen(pid_file, "w");
|
|
if (fp != NULL) {
|
|
if (fprintf(fp, "%ld\n", (long) getpid()) < 0)
|
|
log_error("write_pid_file: failed to write PID to "
|
|
"\"%.100s\"", pid_file);
|
|
fclose(fp);
|
|
} else
|
|
log_fatal("write_pid_file: fopen (\"%.100s\", \"w\") failed",
|
|
pid_file);
|
|
}
|
|
|
|
void
|
|
sanitise_stdfd(void)
|
|
{
|
|
int nullfd, dupfd;
|
|
|
|
if ((nullfd = dupfd = open(_PATH_DEVNULL, O_RDWR)) == -1) {
|
|
fprintf(stderr, "Couldn't open /dev/null: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
while (++dupfd <= STDERR_FILENO) {
|
|
/* Only populate closed fds */
|
|
if (fcntl(dupfd, F_GETFL) == -1 && errno == EBADF) {
|
|
if (dup2(nullfd, dupfd) == -1) {
|
|
fprintf(stderr, "dup2: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
if (nullfd > STDERR_FILENO)
|
|
close(nullfd);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
fd_set *rfds, *wfds;
|
|
int n, m;
|
|
size_t mask_size;
|
|
struct timespec ts, *timeout;
|
|
|
|
closefrom(STDERR_FILENO + 1);
|
|
|
|
/*
|
|
* Make sure init() won't alloc fd 0, 1 or 2, as daemon() will close
|
|
* them.
|
|
*/
|
|
sanitise_stdfd();
|
|
|
|
/* Log cmd line parsing and initialization errors to stderr. */
|
|
log_to(stderr);
|
|
parse_args(argc, argv);
|
|
log_init(debug);
|
|
log_print("isakmpd: starting");
|
|
|
|
/* Open protocols and services databases. */
|
|
setprotoent(1);
|
|
setservent(1);
|
|
|
|
/* Open command fifo */
|
|
ui_init();
|
|
|
|
set_slave_signals();
|
|
/* Daemonize before forking unpriv'ed child */
|
|
if (!debug)
|
|
if (daemon(0, 0))
|
|
log_fatal("main: daemon (0, 0) failed");
|
|
|
|
/* Set timezone before priv'separation */
|
|
tzset();
|
|
|
|
write_pid_file();
|
|
|
|
if (monitor_init(debug)) {
|
|
/* The parent, with privileges enters infinite monitor loop. */
|
|
monitor_loop(debug);
|
|
exit(0); /* Never reached. */
|
|
}
|
|
/* Child process only from this point on, no privileges left. */
|
|
|
|
init();
|
|
|
|
/* If we wanted IKE packet capture to file, initialize it now. */
|
|
if (pcap_file != 0)
|
|
log_packet_init(pcap_file);
|
|
|
|
/* Allocate the file descriptor sets just big enough. */
|
|
n = getdtablesize();
|
|
mask_size = howmany(n, NFDBITS) * sizeof(fd_mask);
|
|
rfds = malloc(mask_size);
|
|
if (!rfds)
|
|
log_fatal("main: malloc (%lu) failed",
|
|
(unsigned long)mask_size);
|
|
wfds = malloc(mask_size);
|
|
if (!wfds)
|
|
log_fatal("main: malloc (%lu) failed",
|
|
(unsigned long)mask_size);
|
|
|
|
monitor_init_done();
|
|
|
|
while (1) {
|
|
/* If someone has sent SIGHUP to us, reconfigure. */
|
|
if (sighupped) {
|
|
sighupped = 0;
|
|
log_print("SIGHUP received");
|
|
reinit();
|
|
}
|
|
/* and if someone sent SIGUSR1, do a state report. */
|
|
if (sigusr1ed) {
|
|
sigusr1ed = 0;
|
|
log_print("SIGUSR1 received");
|
|
report();
|
|
}
|
|
/*
|
|
* and if someone set 'sigtermed' (SIGTERM, SIGINT or via the
|
|
* UI), this indicates we should start a controlled shutdown
|
|
* of the daemon.
|
|
*
|
|
* Note: Since _one_ message is sent per iteration of this
|
|
* enclosing while-loop, and we want to send a number of
|
|
* DELETE notifications, we must loop atleast this number of
|
|
* times. The daemon_shutdown() function starts by queueing
|
|
* the DELETEs, all other calls just increments the
|
|
* 'sigtermed' variable until it reaches a "safe" value, and
|
|
* the daemon exits.
|
|
*/
|
|
if (sigtermed)
|
|
daemon_shutdown();
|
|
|
|
/* Setup the descriptors to look for incoming messages at. */
|
|
bzero(rfds, mask_size);
|
|
n = transport_fd_set(rfds);
|
|
FD_SET(ui_socket, rfds);
|
|
if (ui_socket + 1 > n)
|
|
n = ui_socket + 1;
|
|
|
|
/*
|
|
* XXX Some day we might want to deal with an abstract
|
|
* application class instead, with many instantiations
|
|
* possible.
|
|
*/
|
|
if (!app_none && app_socket >= 0) {
|
|
FD_SET(app_socket, rfds);
|
|
if (app_socket + 1 > n)
|
|
n = app_socket + 1;
|
|
}
|
|
/* Setup the descriptors that have pending messages to send. */
|
|
bzero(wfds, mask_size);
|
|
m = transport_pending_wfd_set(wfds);
|
|
if (m > n)
|
|
n = m;
|
|
|
|
/* Find out when the next timed event is. */
|
|
timeout = &ts;
|
|
timer_next_event(&timeout);
|
|
|
|
n = pselect(n, rfds, wfds, NULL, timeout, NULL);
|
|
if (n == -1) {
|
|
if (errno != EINTR) {
|
|
log_error("main: select");
|
|
|
|
/*
|
|
* In order to give the unexpected error
|
|
* condition time to resolve without letting
|
|
* this process eat up all available CPU
|
|
* we sleep for a short while.
|
|
*/
|
|
sleep(1);
|
|
}
|
|
} else if (n) {
|
|
transport_handle_messages(rfds);
|
|
transport_send_messages(wfds);
|
|
if (FD_ISSET(ui_socket, rfds))
|
|
ui_handler();
|
|
if (!app_none && app_socket >= 0 &&
|
|
FD_ISSET(app_socket, rfds))
|
|
app_handler();
|
|
}
|
|
timer_handle_expirations();
|
|
}
|
|
}
|