/*	$CoreSDI: auditd.c,v 1.41 2001/11/01 22:22:31 claudio Exp $	*/

/*
 *   Copyright (c) 2000, 2001, Core SDI S.A., Argentina
 *   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.
 *   3. Neither name of the Core SDI S.A. nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *  
 *   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.
 */

/*
 * Auditd - Audit Server Daemon
 * Author: Claudio Castiglia
 */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <openssl/evp.h>

#include "sysdep.h"
#include "packet.h"
#include "version.h"
#include "audit.h"
#include "audconf.h"
#include "commands.h"
#include "log.h"
#include "resource.h"
#include "modtypes.h"
#include "modules.h"
#include "ia_list.h"
#include "resadmin.h"
#include "authargs.h"
#include "ia.h"

#define OPTARGS	"f:M:p:t:T:ldvqh?"

AUDITD_OPTIONS	options;		/* Configuration 		*/
int		sfd;			/* Listening socket		*/
int		sighup = 0;		/* 1 if SIGHUP received		*/


/*
 * create_pidfile():
 *	Create pidfile and write pid.
 */
void
create_pidfile()
{
	int	 fd;
	char	 cpid[21];

	fd = open(options.pidfile, O_RDWR | O_CREAT | O_TRUNC,
	    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
	if (fd < 0) {
		fatal(EX_OSERR, "%s: %s.", options.pidfile, strerror(errno));
		/* NOTREACHED */
	}

	snprintf(cpid, sizeof(cpid), "%d", getpid());
	if (write(fd, cpid, strlen(cpid)) <= 0) {
		fatal(EX_OSERR, "%s: %s.", options.pidfile, strerror(errno));
		/* NOTREACHED */
	}

	close(fd);
}


/*
 * signal_handler()
 */
void
signal_handler(int signo)
{
	int old_errno, status;

	old_errno = errno;
	switch(signo) {
	case SIGCHLD:
		waitpid(-1, &status, WNOHANG);
		break;
	case SIGHUP:
		sighup = 1;
		break;
	case SIGPIPE:
		errno = EPIPE;
		log_msg(LOG_INFO, "Connection closed: %s", strerror(errno));
		exit(EX_IOERR);
	case SIGALRM:
		log_msg(LOG_INFO, "Connection closed: timeout.");
bye:		auditd_release_options(&options); /* XXX cleanup(), shutdown(2)
						   */
		exit(-1);
	case SIGABRT:
	case SIGINT:
	case SIGQUIT:
	case SIGTERM:
		log_msg(LOG_INFO, "Exiting on signal %d.", signo);
		/*
		 * XXX: It should always cleanup for every exit(3) and
		 * not only on fatal().
		 */
		goto bye;
	}
	errno = old_errno;
}


/*
 * set_parent_signals():
 *	Set parent signal handler and its stack.
 */
int
set_parent_signals()
{
	struct sigaction	 sa;
	struct sigaltstack	 alt_stack;

	alt_stack.ss_sp = malloc(SIGSTKSZ);
	alt_stack.ss_size = SIGSTKSZ;
	alt_stack.ss_flags = 0;
	if (alt_stack.ss_sp == NULL)
		return (-1);
	sigemptyset(&sa.sa_mask);
	sigaddset(&sa.sa_mask, SIGCHLD);
	sigaddset(&sa.sa_mask, SIGHUP);
	sigaddset(&sa.sa_mask, SIGINT);
	sigaddset(&sa.sa_mask, SIGQUIT);
	sigaddset(&sa.sa_mask, SIGTERM);
	sigaddset(&sa.sa_mask, SIGABRT);
	sa.sa_handler = signal_handler;
	sa.sa_flags = SA_NOCLDSTOP | SA_RESTART | SA_ONSTACK;
	return (sigaltstack(&alt_stack, NULL) |
	    sigaction(SIGCHLD, &sa, NULL) |
	    sigaction(SIGHUP,  &sa, NULL) |
	    sigaction(SIGINT, &sa, NULL)  |
	    sigaction(SIGQUIT, &sa, NULL) |
	    sigaction(SIGTERM, &sa, NULL) |
	    sigaction(SIGABRT, &sa, NULL));
}


/*
 * set_child_signals():
 *	Set childs signal handler.
 */
int
set_child_signals()
{
	struct sigaction sa;

	sigemptyset(&sa.sa_mask);
	sa.sa_handler = signal_handler;
	sa.sa_flags = 0;
	sigaddset(&sa.sa_mask, SIGPIPE);
	sigaddset(&sa.sa_mask, SIGALRM);
	sigaction(SIGPIPE, &sa, NULL);
	sigaction(SIGALRM, &sa, NULL);
	sa.sa_handler = SIG_IGN;
	return (sigaction(SIGCHLD, &sa, NULL) |
	    sigaction(SIGHUP, &sa, NULL) |
	    sigaction(SIGINT, &sa, NULL));
}


/*
 * _restart():
 *	Restart function, called after SIGHUP; executes a new
 *	daemon and terminates current one; on error the current
 *	daemon is terminated and the error message logged.
 */
static void
_restart(char **argv)
{
	log_msg(LOG_INFO, "SIGHUP received, restarting.");
	if (sfd >= 0)
		close(sfd);
	execv(argv[0], argv);
	log_err("Restart failed: %s: %s.", argv[0], strerror(errno));
	exit(-1);
}


/*
 * send_error():
 *	Send error message to the client.
 */
void
send_error(PACKET *packet, const char *fmt, ...)
{
	char	msg[LINE_MAX];
	va_list	ap;

	va_start(ap, fmt);
	vsnprintf(msg, sizeof(msg), fmt, ap);
	va_end(ap);
	packet_put_int32(packet, CMD_ERROR);
	packet_put_string(packet, msg);
}


/*
 * internal_error():
 *      Send "Command aborted." message to the client.
 */
void
internal_error(PACKET *packet)
{
        send_error(packet, "Command aborted.");
} 


/*
 * auth_error():
 *	Log authentication error message and send "Authentication failed"
 *	to the client.
 */
void
auth_error(PACKET *packet, const char *fmt, ...)
{
	va_list ap;
	char	msg[LINE_MAX];

	va_start(ap, fmt);
	vsnprintf(msg, sizeof(msg), fmt, ap);
	va_end(ap);
	log_err(msg);
	packet_put_int32(packet, CMD_ERROR);
	packet_put_string(packet, "Authentication failed.");
}


/*
 * send_string_list():
 *	Send given string list to the client.
 */
void
send_string_list(PACKET *packet, int items, const char **list)
{
	int	i;

	for (i = 0; i < items; i++)
		packet_put_string(packet, list[i]);
	packet_put_string(packet, "");
}


/*
 * auth_get_module_list():
 *	Send authentication module list to the client.
 */
void
auth_get_module_list(PACKET *packet)
{
	log_debug("Sending authentication module list.");
	packet_put_int32(packet, CMD_OK);
	send_string_list(packet, options.auth_modules,
	    (const char **) options.auth_list);
	log_cmd("unknown user", "Ok", "AUTH MODULE LIST");
}


/*
 * get_auth():
 * 	Set specified authentication module, authenticate user,
 *	and return authentication context; on error NULL is
 *	returned (user authentication failure is considered an error).
 */
AUTHCON *
get_auth(PACKET *packet)
{
	AUTHCON	*authcon;
	char	 buf[MAXPATHLEN];
	int32_t	 command;
	int	 i;

	/* Receive command */
	packet_get_int32(packet, &command);

	/* List auth module names */
	if (command == CMD_IA_GET_MODULE_LIST) {
		auth_get_module_list(packet);
		return (NULL);
	} else {
		if (command != CMD_IA_SET_MODULE)
			return (NULL);
	}

	/* Receive authentication module name and check if it exists */
	packet_get_string(packet, buf, sizeof(buf));
	if (strlen(buf) == 0)
		return (NULL);

	for (i = 0; i < options.auth_modules; i++)
		if (!strcmp(buf, options.auth_list[i]))
			break;

	if (i == options.auth_modules) {
		auth_error(packet, "Incorrect '%s' authentication module.",
		    buf);
		return (NULL);
	}

 	/* Load authentication module */
	if ( (authcon = load_auth(options.auth_list[i], packet)) == NULL) {
		auth_error(packet,
		    "Unable to load '%s' authentication module.", buf);
		return (NULL);
	}

	/* Authentication module successfully loaded */
	packet_put_int32(packet, CMD_OK);

	/* Authentication process */
	if (authcon->mod.proc_entry(AUTH_SERVER, authcon, NULL) < 0) {
		auth_error(packet, "Login for %s failed.",
		    (authcon->peername[0] != '\0') ?
		     authcon->peername : "unknown user");
		release_auth(authcon);
		return (NULL);
	}

	/* Load user resources (if not loaded by authentication module) */
	if (authcon->rlist == NULL) {
		authcon->rlist =
		    res_open_list(authcon->peername, RES_READ);
		if (authcon->rlist == NULL) {
			auth_error(packet, "Can't load resources for user '%s':"
			    " %s.", authcon->peername, strerror(errno));
			release_auth(authcon);
			return (NULL);
		}
	}

	log_msg(LOG_INFO, "Starting session for %s.", authcon->peername);
	return (authcon);
}


/*
 * Talk w/client
 */
void
talk(PACKET *packet, struct sockaddr_in *caddr)
{
	SESSION		 session;
	struct hostent	*h;
	char	 	 cip[INET_ADDRSTRLEN],
			 msg[LINE_MAX];
	int32_t		 c;

	/* Get peer address */
	inet_ntop(AF_INET, &caddr->sin_addr, cip, sizeof(cip));
	h = gethostbyaddr((char *) &caddr->sin_addr, sizeof(caddr->sin_addr),
	    AF_INET);
	log_msg(LOG_INFO, "Connection from %s [ %s ].",
	    (h == NULL) ? "" : h->h_name, cip);

	bzero((void *) &session, sizeof(session));
	alarm(options.timeout);
	if ( (session.aucon = get_auth(packet)) != NULL) {
		alarm(options.command_timeout);
		while (1) {
			packet_get_int32(packet, &c);
			if (c == CMD_QUIT)
				break;
			alarm(0);
			switch (c) {
			case CMD_IA_GET_MODULE_LIST:
				ia_get_module_list(&session);
				break;
			case CMD_IA_SET_MODULE:
				ia_set_module(&session);
				break;
			case CMD_IA_GET_LIST:
				ia_get_list(&session);
				break;
			case CMD_IA_GET_INFO:
				ia_get_info(&session);
				break;
			case CMD_IA_GET:
				ia_get(&session);
				break;
			case CMD_IA_ZAP:
				ia_zap(&session);
				break;
			case CMD_IA_ROTATE:
				ia_rotate(&session);
				break;
			case CMD_IA_SIGN:
				ia_sign(&session);
				break;
			case CMD_RES_ADDUSER:
				resadmin_adduser(&session);
				break;
			case CMD_RES_RMUSER:
				resadmin_rmuser(&session);
				break;
			case CMD_RES_SET:
				resadmin_set(&session);
				break;
			case CMD_RES_FIND:
				resadmin_find(&session);
				break;
			case CMD_RES_REMOVE:
				resadmin_remove(&session);
				break;
			default:
				snprintf(msg, sizeof(msg), "Command [ %04X ] "
				    "not supported.", c);
				log_err(msg);
				packet_put_int32(packet, CMD_EOPNOTSUPP);
				packet_put_string(packet, msg);
			}
			alarm(options.command_timeout);
		}
		release_ia(session.iacon);	/* first IA, then AUTH */
		release_auth(session.aucon);
	}
	alarm(0);
	log_msg(LOG_INFO, "Connection closed.");
}


/*
 * usage()
 */
void
usage()
{
	extern char *__progname;

	fprintf(stderr,
	    "Auditd version %s\n"
	    "Usage: %s [options]\n"
	    "Options:\n"
	    "  -f file       Config file (default is %s).\n"
	    "  -M path       Modules path (default is %s).\n"
	    "  -p [add:]port Address and port to listen (default is\n"
	    "                to listen on all addresses and port %d).\n"
	    "  -t seconds    Connection timeout (default is %d).\n"
	    "  -T seconds    Command timeout (default is %d).\n"
	    "  -l            Log each audit(1) session.\n"
	    "  -d            Log debug information.\n"
	    "  -v            Verbose mode: don't become a daemon and log\n"
	    "                messages on stderr instead of syslog(3).\n"
	    "  -q            Quiet mode (disables -l, -d, and -v options).\n"
	    "  -h            This help.\n",
	    AUDIT_VERSION,
	    __progname,
	    DEFAULT_CONFIG_FILE,
	    DEFAULT_MODULES_PATH,
	    DEFAULT_AUDIT_PORT,
	    DEFAULT_TIMEOUT,
	    DEFAULT_COMMAND_TIMEOUT);
	exit(EX_USAGE);
}


/*
 * main()
 */
int
main(int argc, char **argv)
{
	int			 c, old_optind, cfd;
	struct pollfd		 pfd;
	pid_t	 		 pid;
	struct sockaddr_in	 caddr;
	socklen_t		 caddr_len;
	char			*p, laddress[INET_ADDRSTRLEN];
	extern int		 optind;
	extern char		*optarg;

	/* Load default options */
	auditd_set_default_options(&options);
	old_optind = optind;
	while ( (c = getopt(argc, argv, OPTARGS)) != -1)
		switch(c) {
		case 'f': /* Config file */
			if (auditd_set_option(&options, AOC_CONFIG_FILE,
			    optarg) < 0)
				errx(-1, "Invalid -f argument.");
			break;
		case 'M': /* Modules path */
		case 'p': /* Listen port */
		case 't': /* Login timeout */
		case 'T': /* Command timeout */
		case 'l': /* Log session messages */
			break;
		case 'd': /* Log debug messages */
			auditd_set_option(&options, AOC_LOG_DEBUG, "yes");
			break;
		case 'v': /* Verbose mode: no daemon */
			auditd_set_option(&options, AOC_LOG_VERBOSE, "yes");
			break;
		case 'q': /* Quiet mode */
			auditd_set_option(&options, AOC_LOG_QUIET, "yes");
			break;
		case 'h': /* Help */
		case '?':
		default:
			usage();
		}
	if (argc - optind < 0)
		usage();

	/* Init log system for messages from auditd_load_configuration() */
	log_init(options.flags, options.sysfac);
	if (auditd_load_configuration(&options) < 0)
		exit(EX_CONFIG);

	optind = old_optind;
	while ( (c = getopt(argc, argv, OPTARGS)) != -1)
		switch (c) {
		case 'f': /* Config file */
			break;
		case 'M': /* Modules path */
			if (auditd_set_option(&options, AOC_MODULES_PATH,
			    optarg) < 0)
				errx(-1, "Invalid -M argument.");
			break;
		case 'p': /* Listen address and port "-p [address:]port" */
			p = optarg;
			strsep(&p, ":");
			if (p == NULL) 
				p = optarg;
			else {
				if (auditd_set_option(&options, AOC_ADDRESS,
				    optarg) < 0)
					errx(-1, "Invalid -p argument.");
			}
			if (auditd_set_option(&options, AOC_PORT, p) < 0)
				errx(-1, "Invalid -p argument.");
			break;
		case 't': /* Login timeout */
			if (auditd_set_option(&options, AOC_TIMEOUT, optarg)
			    < 0)
				errx(-1, "Ivalid -t argument.");
			break;
		case 'T': /* Command timeout */
			if (auditd_set_option(&options, AOC_COMMAND_TIMEOUT,
			    optarg) < 0)
				errx(-1, "Invalid -T argument.");
			break;
		case 'l': /* Log session messages */
			auditd_set_option(&options, AOC_LOG_SESSION, "yes");
			break;
		case 'd': /* Log debug messages */
			auditd_set_option(&options, AOC_LOG_DEBUG, "yes");
			break;
		case 'v': /* Verbose mode: do not daemonize */
			auditd_set_option(&options, AOC_LOG_VERBOSE, "yes");
			break;
		case 'q': /* Quiet mode */
			auditd_set_option(&options, AOC_LOG_QUIET, "yes");
			break;
		case 'h': /* Help */
		case '?':
		default:
			usage();
		}

	if (set_parent_signals() != 0)
		fatal(EX_OSERR, "Can't set signal handlers: %s.",
		    strerror(errno));

	/* Become a daemon */
	if (!(options.flags & LOGF_STDERR))
		daemon(0, 0);

	/* Init log system */
	log_init(options.flags, options.sysfac);

	/* Load resources module */
	if (res_module_init(options.resmodule) < 0)
		fatal(EX_SOFTWARE, "");

	/* Load libcrypto lookup table and error strings */
	init_cryptostrings();

	/* Start auditd */
	if ( (sfd = init_socket()) < 0)
		fatal(EX_OSERR, "Can't initialize socket: %s.",
		    strerror(errno));

	c = 1;
	setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &c, sizeof(c));

	if (bind(sfd, (struct sockaddr *) &options.addr,
	    sizeof(options.addr)) < 0)
		fatal(EX_OSERR, "Can't bind: %s.", strerror(errno));

	if (listen(sfd, 5) < 0)
		fatal(EX_OSERR, "Can't listen: %s.", strerror(errno));

	pfd.fd = sfd;
	pfd.events = POLLIN;

	create_pidfile();

	/* Log start message */
	log_msg(LOG_INFO, "Starting auditd, listening on %s, port %d.",
	    inet_ntop(options.addr.sin_family, &options.addr.sin_addr.s_addr,
	    laddress, sizeof(laddress)), ntohs(options.addr.sin_port));

	/* Daemon */
	while (1) {
		if (sighup)
			_restart(argv);
	
		if (poll(&pfd, 1, -1) < 0)
			switch(errno) {
			case EINTR:
			case EAGAIN:
				continue;
			default:
				fatal(-1, "Can't poll: %s.", strerror(errno));
				/* NOTREACHED */
			}

		caddr_len = sizeof(caddr);
		cfd = accept(sfd, (struct sockaddr *) &caddr, &caddr_len);
		if (cfd < 0) {
			/* XXX: ECONNABORTED (solaris) */
			if (errno != EINTR && errno != ECONNABORTED &&
			    errno != EWOULDBLOCK && errno != EAGAIN) {
				fatal(EX_OSERR, "Can't accept: %s.",
				    strerror(errno));
				/* NOTREACHED */
			} else
				continue;
		}

		/* Do not fork on verbose mode */
		if (!(options.flags & LOGF_STDERR)) {
			if ( (pid = fork()) < 0)
				fatal(EX_OSERR, "Can't fork: %s.",
				    strerror(errno));
			else
				cleanup_rm_all(); /* remove parent cleanups */
		} else
			pid = 0;

		if (pid == 0) {
			PACKET *packet;

			close(sfd);
			if (set_child_signals() != 0) {
				fatal(EX_OSERR, "Can't set child signal "
				    "handlers: %s.", strerror(errno));
				/* NOTREACHED */
			}
			packet = packet_init(cfd, cfd);
			if (packet == NULL) {
				fatal(EX_OSERR, "Packet initialization: %s.",
				    strerror(errno));
				/* NOTREACHED */
			}
			if (!compatible_peer(packet)) {
				fatal(EX_PROTOCOL,
				    "Incompatible client version.");
				/* NOTREACHED */
			}
			
			talk(packet, &caddr);
			packet_release(packet);
			close(cfd);
			release_cryptostrings();
			exit(EX_OK);
		}

		close(cfd);
		{
		int i;
		wait(&i);	/*
				 * XXX:	avoid race condition 
				 * when accessing same logset:
				 * Simultaneous auditors not supported yet.
				 */
		}
	}

	return (0);
}


