/*	$CoreSDI: audconf.c,v 1.14 2001/08/15 00:59:40 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 configuration functions
 * Author: Claudio Castiglia
 */

#include <sys/param.h>
#include <sys/socket.h>

#ifndef __linux__
#include <machine/endian.h>
#endif

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "sysdep.h"
#include "packet.h"
#include "resource.h"
#include "modtypes.h"
#include "modules.h"
#include "audit.h"
#include "audconf.h"
#include "log.h"

#define WHITE_SPACE	" \r\n\t"
#define YES(arg)	(!strcasecmp(arg, "yes"))
#define NO(arg)		(!strcasecmp(arg, "no"))


/* 
 * Misc. name-code struct
 */
struct namecode {
	char *name;
	int code;
};


/*
 * Auditd Options List
 */
struct namecode auditd_options[] = {
	{ "LogSession", AOC_LOG_SESSION },
	{ "LogDebugInfo", AOC_LOG_DEBUG },
	{ "QuietMode", AOC_LOG_QUIET },
	{ "Port", AOC_PORT },
	{ "ListenAddress", AOC_ADDRESS },
	{ "ModulesPath", AOC_MODULES_PATH },
	{ "SyslogFacility", AOC_SYSLOG_FACILITY },
	{ "Timeout", AOC_TIMEOUT },
	{ "CommandTimeout", AOC_COMMAND_TIMEOUT },
	{ "AuthModule", AOC_AUTH_MODULE },
	{ "IaModule", AOC_IA_MODULE },
	{ "ResModule", AOC_RESOURCE_MODULE },
	{ NULL, 0 }
};


/*
 * Allowed Auditd Syslog Facility Options
 */
struct namecode auditd_sysfac_options[] = {
	{ "AUTH", LOG_AUTH },
	{ "AUTHPRIV", LOG_AUTHPRIV },
	{ "DAEMON", LOG_DAEMON },
	{ "USER", LOG_USER },
	{ NULL, 0 }
};


/*
 * Add module_name into auth/ia list
 *	list == 0: auth list
 *	list != 0: ia list
 *	Return 0 on success and -1 on error
 */
int
auditd_add_modulename(AUDITD_OPTIONS *options,
		      int list, const char *module_name)
{
	char ***dst;
	char **p;
	char *d;
	int  *n;

	if (list) {
		dst = &options->ia_list;
		n = &options->ia_modules;
	} else {
		dst = &options->auth_list;
		n = &options->auth_modules;
	}

	if ( (d = strdup(module_name)) == NULL)
		return (-1);
	
	if ( (p = (char **) realloc(*dst, sizeof(char *) * (*n)+1)) == NULL) {
		free(d);
		return (-1);
	}

	p[*n] = d;
	*dst = p;
	(*n)++;

	return (0);
}


/*
 * Release auditd options dynamic memory
 */
void
auditd_release_options(AUDITD_OPTIONS *options)
{
	if (options->auth_list != NULL) {
		while (options->auth_modules--)
			free(options->auth_list[options->auth_modules]);
		free(options->auth_list);
		options->auth_list = NULL;
	}
	if (options->ia_list != NULL) {
		while (options->ia_modules--)
			free(options->ia_list[options->ia_modules]);
		free(options->ia_list);
		options->ia_list = NULL;
	}
}


/*
 * Set default auditd options
 */
void
auditd_set_default_options(AUDITD_OPTIONS *options)
{
	options->flags = LOGF_ERROR;
	options->sysfac = DEFAULT_FACILITY;
	options->timeout = DEFAULT_TIMEOUT;
	options->command_timeout = DEFAULT_COMMAND_TIMEOUT;
	options->auth_modules = 0;
	options->ia_modules = 0;
	options->auth_list = NULL;
	options->ia_list = NULL;
	bzero(&options->addr, sizeof(options->addr));
	options->addr.sin_family = AF_INET;
	options->addr.sin_port = htons(DEFAULT_AUDIT_PORT);
	options->addr.sin_addr.s_addr = htonl(DEFAULT_ADDRESS);

	strlcpy(options->pidfile, AUDITD_PID_FILE, sizeof(options->pidfile));

	strlcpy(options->confile,
	    DEFAULT_CONFIG_FILE, sizeof(options->confile));

	strlcpy(options->modpath,
	    DEFAULT_MODULES_PATH, sizeof(options->modpath));

	strlcpy(options->resmodule,
	    DEFAULT_RESOURCES_MODULE, sizeof(options->resmodule));
}


/*
 * auditd_set_option:
 *	Set user auditd option, return 0 on success and -1 on fail
 */
int
auditd_set_option(AUDITD_OPTIONS *options,
		  AUDITD_OPTIONS_CODES code, char *value)
{
	unsigned long int i;
	char *err;

	if (value == NULL)
		return (-1);

	switch (code) {
	case AOC_LOG_SESSION:
		if (YES(value))
			options->flags |= LOGF_SESSION;
		else if (NO(value))
			options->flags &= ~LOGF_SESSION;
		else 
			break;
		return (0);
	case AOC_LOG_DEBUG:
		if (YES(value))
			options->flags |= LOGF_DEBUG;
		else if (NO(value))
			options->flags &= ~LOGF_DEBUG;
		else
			break;
		return (0);
	case AOC_LOG_VERBOSE:
		if (YES(value))
			options->flags |= LOGF_STDERR;
		else if (NO(value))
			options->flags &= ~LOGF_STDERR;
		else 
			break;
		return (0);
	case AOC_LOG_QUIET:
		if (YES(value))
			options->flags |= LOGF_NOLOG;
		else if (NO(value))
			options->flags &= ~LOGF_NOLOG;
		else 
			break;
		return (0);
	case AOC_SYSLOG_FACILITY:
		for (i = 0; auditd_sysfac_options[i].name; i++)
			if (!strcasecmp(auditd_sysfac_options[i].name, value)) {
				options->sysfac = auditd_sysfac_options[i].code;
				return (0);;
			}
		if (auditd_sysfac_options[i].name == NULL)
			break;
		return (0);
	case AOC_PORT:
		i = strtoul(value, &err, 0);
		if (*err != '\0' || i > 65535)
			break;
		options->addr.sin_port = htons((u_int16_t) i);
		return (0);
	case AOC_ADDRESS:
		switch(inet_pton(options->addr.sin_family, value,
		    &options->addr.sin_addr.s_addr)) {
		case 1:
			break;
		case 0:
			errno = EAFNOSUPPORT;
		default:
			return (-1);
		}
		return (0);
	case AOC_CONFIG_FILE:
		return ((realpath(value, options->confile) == NULL) ? -1 : 0);
	case AOC_MODULES_PATH:
		return ((realpath(value, options->modpath) == NULL) ? -1 : 0);
	case AOC_TIMEOUT:
		i = strtoul(value, &err, 0);
		if (*err != '\0')
			break;
		options->timeout = i;
		return (0);
	case AOC_COMMAND_TIMEOUT:
		i = strtoul(value, &err, 0);
		if (*err != '\0')
			break;
		options->command_timeout = i;
		return (0);
	case AOC_AUTH_MODULE:
		return (auditd_add_modulename(options, 0, value));
	case AOC_IA_MODULE:
		return (auditd_add_modulename(options, 1, value));
	case AOC_RESOURCE_MODULE:
		strlcpy(options->resmodule, value, sizeof(options->resmodule));
		return (0);
	default:
	}

	errno = EINVAL;
	return (-1);
}


/*
 * auditd_load_configuration():
 * 	Open, parse configuration file, and configure auditd.
 */
int
auditd_load_configuration(AUDITD_OPTIONS *options)
{
	char  buf[128];
	char *pc;
	char *pn;
	int   line;
	int   errors;
	int   i;
	FILE *f;
	AUDITD_OPTIONS o;

	o = *options;
	o.auth_modules = 0;
	o.ia_modules = 0;
	o.auth_list = NULL;
	o.ia_list = NULL; 
	if (o.confile[0] != '\0') {
		if ( (f = fopen(o.confile, "r")) == NULL) {
			log_err("Can't open '%s': %s", o.confile,
			    strerror(errno));
			return (-1);
		}
		line = 0;
		errors = 0;
		while (fgets(buf, sizeof(buf), f) != NULL) {
			line++;
			pc = buf + strspn(buf, WHITE_SPACE);
			if (*pc == '\0' || *pc == '\n' || *pc == '#')
				continue;
			pn = pc;
			if (strsep(&pn, WHITE_SPACE) == NULL)
				continue;
			pn += strspn(buf, WHITE_SPACE);
			if (pn[( i = strlen(pn) - 1)] == '\n')
				pn[i] = '\0';
			for (i = 0; auditd_options[i].name != NULL; i++)
				if (!strcasecmp(auditd_options[i].name, pc)) {
					if (auditd_set_option(&o,
					    auditd_options[i].code, pn) < 0) {
						errors++;
						log_err("%s: Invalid \"%s\" "
						    "argument on line %d.",
						    options->confile, pn,
						    line);
					}
				break;
				}
			if (auditd_options[i].name == NULL) {
				errors++;
				log_err("%s: Invalid \"%s\" "
				    "option on line %d.", o.confile, pc, line);
			}
		}
		fclose(f);
		if (errors) {
			log_err("%s: Found %d errors.", o.confile, errors);
			auditd_release_options(&o);
			return (-1);
		} else {
			auditd_release_options(options);
			*options = o;
		}
	set_modules_path(options->modpath);
	}
	return (0);
}


