/*	$CoreSDI: ia.c,v 1.17 2001/10/10 00:30:11 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.
 */

/*
 * Information Accessing commands
 * 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 <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 "ia_perm.h"
#include "iaargs.h"

extern AUDITD_OPTIONS options;
extern void send_error(PACKET *, const char *, ...);
extern void internal_error(PACKET *);
extern void send_string_list(PACKET *, int, const char **);


/*
 * _sync_attrs():
 *	Synchronize attributes status.
 *	Before calling this function you MUST lock IA module;
 *	After this function returns you MUST unlock IA module.
 */
static int
_sync_attrs(LOGSET_INFO *li, const char *logset_name)
{
	ATTR *at;

	log_debug("Synchronizing attributes.");
	for (at = li->attr; at != NULL; at = at->next)
		if (at->a_proc(ATTR_FREEZE, at->atcon, NULL) < 0) {
			log_debug("Synchronization aborted.");
			return (-1);
		}
	return (0);
}


/*
 * _lock():
 *	Locks ia system; returns 0 in success and -1 on error (the error
 *	is loggued in the server host and into the other side).
 */
static int
_lock(SESSION *s, char *logset_name)
{
	if (s->s_iaproc(IA_LOCK, s->iacon, logset_name) < 0) {
		send_error(s->s_packet, "Can't lock '%s': %s.", logset_name,
		    strerror(errno));
		return (-1);
	}
	return (0);
}


/*
 * _unlock():
 *	Unlocks ia system; returns 0 in success and -1 on error (the error
 *	is loggued in the server host and into the other side).
 */
static int
_unlock(SESSION *s, char *logset_name)
{
	if (s->s_iaproc(IA_UNLOCK, s->iacon, logset_name) < 0) {
		send_error(s->s_packet, "Can't unlock '%s': %s.", logset_name,
		    strerror(errno));
		return (-1);
	}
	return (0);
}


/*
 * _ia_loaded():
 *	Return a ero value if IA module loaded;
 *	If the logset does not exits, an error message is sent to the
 *	client and -1 is returned.
 */
static int
_ia_loaded(SESSION *s)
{
	if (s->iacon != NULL) {
		if (s->s_logset != NULL)
			return (0);
	}
	send_error(s->s_packet, "IA module not set.");
	return (-1);
}


/*
 * ia_get_module_list():
 *	Send IA module list to the client.
 */
void
ia_get_module_list(SESSION *session)
{
	log_debug("Sending IA module list.");
	packet_put_int32(session->s_packet, CMD_OK);
	send_string_list(session->s_packet, options.ia_modules,
	    (const char **) options.ia_list);
	log_cmd(session->s_peername, "Ok", "GET MODULE LIST");
}


/*
 * ia_set_module():
 *	Set specified IA module:
 *	Load IA module and its associated LOGSET_INFO list.
 *
 *	XXX: check permissions on AllowedIAModules resource
 */
void
ia_set_module(SESSION *s)
{
	char		 module_name[MAXPATHLEN];
	int		 i;
	IACON		*iacon;
	RAW_LOGSET	*rawset, *r;
	LOGSET_INFO	*logset, *l;
	unsigned int	 generic_perms;

	/* Get IA module name and search it on server ia modules list */
	packet_get_string(s->s_packet, module_name, sizeof(module_name));
	for (i = 0; i < options.ia_modules; i++)
		if (!strcmp(options.ia_list[i], module_name))
			break;

	if (i == options.ia_modules) {
		log_cmd(s->s_peername, "Error: Invalid module name",
		    "SET '%s' IA MODULE", module_name);
		send_error(s->s_packet, "");
		return;
	}

	/* Load module */
	iacon = load_ia(options.ia_list[i]);
	if (iacon == NULL) {
		log_cmd(s->s_peername, "Error: Can't load module",
		    "SET '%s' IA MODULE", options.ia_list[i]);
		send_error(s->s_packet, "Can't set '%s'.", options.ia_list[i]);
		return;
	}

	/* Generate LOGSET_INFO list based on RAW_LOGSET list */
	rawset = (RAW_LOGSET *) iacon->logset;
	logset = l = NULL;
	generic_perms = PERM_INVALID;
	for (r = rawset; r != NULL; r = r->next) {
		if (create_logset_info(l, r, s, &generic_perms, &l) < 0) {
			log_debug("Can't initialize IA module: %s: %s.",
			    options.ia_list[i], r->cmd->data);
			log_cmd(s->s_peername, "Error",
			    "SET '%s' IA MODULE", options.ia_list[i]);
			internal_error(s->s_packet);
			release_logset_info(logset);
			release_raw_logset(rawset);
			release_module((void *) iacon);
			return;
		}
		if (logset == NULL)
			logset = l;
	}

	/* Release previous ia module and set the new one */
	release_raw_logset(rawset);
	iacon->logset = logset;
	release_ia(s->iacon);
	s->iacon = iacon;
	packet_put_int32(s->s_packet, CMD_OK);
	log_cmd(s->s_peername, "Ok", "SET '%s' IA MODULE", options.ia_list[i]);
}


/*
 * ia_get_list():
 *	If client has LIST permissions, send logset list.
 */
void
ia_get_list(SESSION *s)
{
	int	     i;
	LOGSET_INFO *l;

	log_debug("Sending logset list.");

	/* Send Ok to indicate command accepted */
	packet_put_int32(s->s_packet, CMD_OK);

	/* Send first name of each logset (with LIST permissions) */
	for (l = s->s_logset; l != NULL; l = l->next) {
		for (i = 0; i < l->names; i++)
			if (l->names_str[i] != '\0')
				break;
		if (i < l->names && (l->perms & PERM_LIST) == PERM_LIST) 
			packet_put_string(s->s_packet, l->names_str[i]);
	}

	/* End of name list */
	packet_put_string(s->s_packet, "");
	log_cmd(s->s_peername, "Ok", "GET LOGSET LIST");
}


/*
 * ia_get_info():
 *	If client has INFO permissions, send logset info.
 */
void
ia_get_info(SESSION *s)
{
	char			 logset_name[MAXPATHLEN],
				 info[LINE_MAX];
	LOGSET_INFO		*li;
	struct attrargs_ret	 info_args;
	ATTR			*at;
	int	     		 status;

	log_debug("Sending logset information.");

	/* Get logset name */
	packet_get_string(s->s_packet, logset_name, sizeof(logset_name));

	if (_ia_loaded(s) < 0) {
		log_cmd(s->s_peername, "IA module not loaded", "GET INFO '%s'",
		    logset_name);
		return;
	}

	li = find_logset_info(s->s_logset, logset_name);
	if (li == NULL) {
		log_cmd(s->s_peername, "Not found", "GET '%s' INFO",
		    logset_name);
		send_error(s->s_packet, "%s: Not found.", logset_name);
		return;
	}

	/* Test INFO perms */
	if (!((li->perms & PERM_INFO) == PERM_INFO)) {
		log_cmd(s->s_peername, strerror(EACCES), "GET '%s' INFO",
		    logset_name);
		send_error(s->s_packet, "%s: %s", logset_name,
		    strerror(EACCES));
		return;
	}

	/* Synchronize attributes */
	if (_lock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "GET '%s' INFO",
		    logset_name);
		return;
	}
	status = _sync_attrs(li, logset_name);
	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "GET '%s' INFO",
		    logset_name);
		return;
	}
	if (status < 0) {
		internal_error(s->s_packet);
		return;
	}

	/* Accept command and send each attribute information */
	packet_put_int32(s->s_packet, CMD_OK);
	for (at = li->attr; at != NULL; at = at->next) {
		info_args.data = info;
		info_args.size = sizeof(info);
		at->a_proc(ATTR_INFO, at->atcon, &info_args);
		if (info_args.size)
			packet_put_string(s->s_packet, info);
	}

	/* End of string list */
	packet_put_string(s->s_packet, "");
	log_cmd(s->s_peername, "Ok", "GET '%s' INFO", logset_name);
}


/*
 * ia_get():
 *	Send logset to the client (information and logs).
 */
void
ia_get(SESSION *s)
{
	char			 logset_name[MAXPATHLEN],
				 info[LINE_MAX];
	LOGSET_INFO		*li;
	struct attrargs_ret	 get_args;
	ATTR			*at;
	int			 status;

	/* Get logset name */
	packet_get_string(s->s_packet, logset_name, sizeof(logset_name));
	log_debug("Sending logset '%s'.", logset_name);

	if (_ia_loaded(s) < 0) {
		log_cmd(s->s_peername, "IA module not loaded", "GET '%s'",
		    logset_name);
		return;
	}

	li = find_logset_info(s->s_logset, logset_name);
	if (li == NULL) {
		log_cmd(s->s_peername, "Not found", "GET '%s'", logset_name);
		send_error(s->s_packet, "%s: Not found.", logset_name);
		return;
	}

	/* Test GET perms */
	if (!((li->perms & PERM_GET) == PERM_GET)) {
		log_cmd(s->s_peername, strerror(EACCES), "GET '%s'",
		    logset_name);
		send_error(s->s_packet, "%s: %s.", logset_name,
		    strerror(EACCES));
		return;
	}

	/* Synchronize attributes */
	if (_lock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "GET '%s'",
		    logset_name);
		return;
	}
	status = _sync_attrs(li, logset_name);
	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "GET '%s'",
		    logset_name);
		return;
	}
	if (status < 0) {
		internal_error(s->s_packet);
		return;
	}

	/* Accept command and send information */
	packet_put_int32(s->s_packet, CMD_OK);
	for (at = li->attr; at != NULL; at = at->next) {
		get_args.data = info;
		get_args.size = sizeof(info);
		at->a_proc(ATTR_INFO, at->atcon, &get_args);
		if (get_args.size)
			packet_put_string(s->s_packet, info);
	}

	/* End of info string list */
	packet_put_string(s->s_packet, "");

	/* Send logs */
	for (at = li->attr; at != NULL; at = at->next) {
		if ( (status = at->a_proc(ATTR_GET, at->atcon, NULL)) != 0) {
			log_cmd(s->s_peername, "Error", "GET '%s'",
			    logset_name);
			internal_error(s->s_packet);
			return;
		}
	}

	/* End of log list */
	packet_put_string(s->s_packet, "");
	packet_put_int32(s->s_packet, CMD_OK);
	log_cmd(s->s_peername, "Ok", "GET '%s'", logset_name);
}


/*
 * ia_zap():
 *	Zap logset.
 */
void
ia_zap(SESSION *s)
{
	char	     logset_name[MAXPATHLEN];
	LOGSET_INFO *li;
	ATTR	    *at;
	int	     status;

	/* Get logset name */
	packet_get_string(s->s_packet, logset_name, sizeof(logset_name));
	log_debug("Zapping logset '%s'.", logset_name);

	if (_ia_loaded(s) < 0) {
		log_cmd(s->s_peername, "IA module not loaded", "ZAP '%s'",
		    logset_name);
		return;
	}

	li = find_logset_info(s->s_logset, logset_name);
	if (li == NULL) {
		log_cmd(s->s_peername, "Not found", "ZAP '%s'", logset_name);
		send_error(s->s_packet, "%s: Not found.", logset_name);
		return;
	}

	/* Test ZAP perms */
	if (!((li->perms & PERM_ZAP) == PERM_ZAP)) {
		log_cmd(s->s_peername, strerror(EACCES), "ZAP '%s'",
		    logset_name);
		send_error(s->s_packet, "%s: %s.", logset_name,
		    strerror(EACCES));
		return;
	}

	/* Sync attributes stat */
	if (_lock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ZAP '%s'",
		    logset_name);
		return;
	}
	status = _sync_attrs(li, logset_name);
	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ZAP '%s'",
		    logset_name);
		return;
	}

	if (status < 0) {
		internal_error(s->s_packet);
		return;
	}

	/* First zap step */
	for (at = li->attr; at != NULL; at = at->next) {
		status = at->a_proc(ATTR_ZAP, at->atcon, (void *) 0);
		if (status < 0) {
			log_cmd(s->s_peername, "Error", "ZAP '%s'",
			    logset_name);
			internal_error(s->s_packet);
			return;
		}
	}

	/* Sync attributes stat and last zap step */
	if (_lock(s, logset_name) < 0) {
		/* Can't complete command */
		log_cmd(s->s_peername, strerror(errno), "ZAP '%s'",
		    logset_name);
		fatal(EX_SOFTWARE, "Command ZAP '%s' aborted.", logset_name);
		/* NOTREACHED */
	}
	_sync_attrs(li, logset_name);
	for (at = li->attr; at != NULL; at = at->next) {
		status = at->a_proc(ATTR_ZAP, at->atcon, (void *) 1);
		if (status < 0) {
			if (_unlock(s, logset_name)) {
				log_cmd(s->s_peername, strerror(errno),
				    "ZAP '%s'", logset_name);
				return;
			}
			log_cmd(s->s_peername, "Error" , "ZAP '%s'",
			    logset_name);
			internal_error(s->s_packet);
			return;
		}
	}

	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ZAP '%s'",
		    logset_name);
	} else {
		log_cmd(s->s_peername, "Ok", "ZAP '%s'", logset_name);
		packet_put_int32(s->s_packet, CMD_OK);
	}
}


/*
 * ia_rotate():
 *	Rotate logset.
 */
void
ia_rotate(SESSION *s)
{
	char	     logset_name[MAXPATHLEN];
	LOGSET_INFO *li;
	ATTR	    *at;
	int	     status;

	/* Get logset name */
	packet_get_string(s->s_packet, logset_name, sizeof(logset_name));
	log_debug("Rotating logset '%s'.", logset_name);

	if (_ia_loaded(s) < 0) {
		log_cmd(s->s_peername, "IA module not loaded", "ROTATE '%s'",
		    logset_name);
		return;
	}

	li = find_logset_info(s->s_logset, logset_name);
	if (li == NULL) {
		log_cmd(s->s_peername, "Not found", "ROTATE '%s'", logset_name);
		send_error(s->s_packet, "%s: Not found.", logset_name);
		return;
	}

	/* Test ROTATE perms */
	if (!((li->perms & PERM_ROTATE) == PERM_ROTATE)) {
		log_cmd(s->s_peername, strerror(EACCES), "ROTATE '%s'",
		    logset_name);
		send_error(s->s_packet, "%s: %s.", logset_name,
		    strerror(EACCES));
		return;
	}

	/* Sync attributes stat */
	if (_lock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ROTATE '%s'",
		    logset_name);
		return;
	}
	status = _sync_attrs(li, logset_name);
	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ROTATE '%s'",
		    logset_name);
		return;
	}
	if (status < 0) {
		internal_error(s->s_packet);
		return;
	}

	/* First rotate step */
	for (at = li->attr; at != NULL; at = at->next) {
		status = at->a_proc(ATTR_ROTATE, at->atcon, (void *) 0);
		if (status < 0) {
			log_cmd(s->s_peername, "Error", "ROTATE '%s'",
			    logset_name);
			internal_error(s->s_packet);
			return;
		}
	}

	/* Sync attributes status and last rotate step */
	if (_lock(s, logset_name) < 0) {
		/* Can't complete command */
		log_cmd(s->s_peername, strerror(errno), "ROTATE '%s'",
		    logset_name);
		fatal(EX_SOFTWARE, "Command ROTATE '%s' aborted.", logset_name);
		/* NOTREACHED */
	}
	_sync_attrs(li, logset_name);
	for (at = li->attr; at != NULL; at = at->next) {
		status = at->a_proc(ATTR_ROTATE, at->atcon, (void *) 1);
		if (status < 0) {
			if (_unlock(s, logset_name) < 0) {
				log_cmd(s->s_peername, strerror(errno),
				    "ROTATE '%s'", logset_name);
				return;
			}
			log_cmd(s->s_peername, "Error", "ROTATE '%s'",
			    logset_name);
			internal_error(s->s_packet);
			return;
		}
	}
	s->s_iaproc(IA_UNLOCK, s->iacon, logset_name);
	if (_unlock(s, logset_name) < 0) {
		log_cmd(s->s_peername, strerror(errno), "ROTATE '%s'",
		    logset_name);
	} else {
		log_cmd(s->s_peername, "Ok", "ROTATE '%s'", logset_name);
		packet_put_int32(s->s_packet, CMD_OK);
	}
}


/*
 * ia_sign():
 *	Sign a logset.
 */
void
ia_sign(SESSION *s)
{
	/* XXX */
	log_cmd(s->s_peername, strerror(EOPNOTSUPP), "SIGN");
	packet_put_int32(s->s_packet, CMD_ERROR);
	packet_put_string(s->s_packet, strerror(EOPNOTSUPP));
}

