OpenBSD-4.6/sbin/isakmpd/isakmpd.c

Compare this file to the similar file:
Show the results in this format:

/* $OpenBSD: isakmpd.c,v 1.97 2008/05/12 19:15:02 pyr 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/param.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 "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);

/* 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;
#if defined(INSECURE_RAND)
	char           *ep;
#endif
	int             cls, level;
	int             do_packetlog = 0;

	while ((ch = getopt(argc, argv, "46ac:dD:f:i:KnN:p:Ll:r: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++;
			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':
#if defined(INSECURE_RAND)
			seed = strtoul(optarg, &ep, 0);
			srandom(seed);
			if (*ep != '\0')
				log_fatal("parse_args: invalid numeric arg "
				    "to -r (%s)", optarg);
			regrand = 1;
#else
			usage();
#endif
			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;

		case '?':
		default:
			usage();
		}
	}
	argc -= optind;
	argv += optind;

	if (argc > 0)	
		usage();

	if (do_packetlog && !pcap_file)
		pcap_file = PCAP_FILE_DEFAULT;
}

/* ARGSUSED */
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().  */
/* ARGSUSED */
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);
}

int
main(int argc, char *argv[])
{
	fd_set         *rfds, *wfds;
	int             n, m;
	size_t          mask_size;
	struct timeval  tv, *timeout;

	closefrom(STDERR_FILENO + 1);

	/*
	 * Make sure init() won't alloc fd 0, 1 or 2, as daemon() will close
	 * them.
	 */
	for (n = 0; n <= 2; n++)
		if (fcntl(n, F_GETFL, 0) == -1 && errno == EBADF)
			(void) open("/dev/null", n ? O_WRONLY : O_RDONLY, 0);

	/* Log cmd line parsing and initialization errors to stderr.  */
	log_to(stderr);
	parse_args(argc, argv);
	log_init(debug);

	/* 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 = (fd_set *) malloc(mask_size);
	if (!rfds)
		log_fatal("main: malloc (%lu) failed",
		    (unsigned long)mask_size);
	wfds = (fd_set *) 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 = &tv;
		timer_next_event(&timeout);

		n = select(n, rfds, wfds, 0, timeout);
		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();
	}
}