/*	$CoreSDI: attr_peo.c,v 1.12 2001/11/16 22:52:59 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.
 */

/*
 * Peo attribute module.
 * Author: Claudio Castiglia
 */

#include <sys/param.h>
#include <sys/stat.h>
#include <sys/uio.h>

#ifdef __linux__
#include <netinet/in.h>		/* in_addr_t */
#endif

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

#include "sysdep.h"
#include "resource.h"
#include "packet.h"
#include "modtypes.h"
#include "iaargs.h"
#include "log.h"

#define ATTR_PEO	"attr peo: "
#define DEFAULT_KEYFILE	"/var/ssyslog/.var.log.messages.key"
#define DEFAULT_HASH	"sha1"
#define OFFSET_PREFIX	"PeoOffset_"		/* offset resource prefix */

typedef struct _peo_ctx {
	char		 hash[10];		/* hash method     */
	char		 keyfile[MAXPATHLEN];	/* keyfile         */
	char		 key[20];		/* last peo key    */
	ssize_t		 keysize;		/* peo key size    */
	char		 macfile[MAXPATHLEN];	/* macfile         */
	struct stat	 st;			/* macfile stat    */
	RESOURCE	*roffset;		/* offset resource */
	off_t		 offset;		/* macfile offset  */
	off_t		 pos;			/* internal variable */
	off_t		 prev_size;		/* internal variable */
	int		 fd;			/* internal variable */
} PEO_CTX;
#define PEO_l(x)	(x->macfile[0] != '\0')
#define GET_PEO_CTX(c)	(PEO_CTX *) (c + 1)


/*
 * _load_offset:
 *	Search offset resource data and update offset context member.
 */
static void
_load_offset(ATTRCON *context)
{
	char	rname[MAXPATHLEN];
	PEO_CTX *pc;

	pc = GET_PEO_CTX(context);
	if (pc->offset >= 0)
		return;

	snprintf(rname, sizeof(rname), OFFSET_PREFIX "%s", pc->macfile);

	/* Get peo-l offset resource */
	if (context->s->s_rlist != NULL)
		pc->roffset = res_find(context->s->s_rlist, rname);

	/* Convert resource to off_t */
	if (pc->roffset == NULL)
		pc->offset = 0;
	else {
		/* Prevent coredump */
		if (pc->roffset->dsize != sizeof(pc->offset))
			log_warn(ATTR_PEO "Invalid resource size: %s. "
			    "Ignoring.", rname);
	}
}

 
/*
 * _save_offset:
 *	Save current offset into the resource list.
 *	Return 0 on success and -1 on error and set errno.
 */
static int
_save_offset(ATTRCON *context)
{
	char	 rname[MAXPATHLEN];
	int	 error;
	PEO_CTX	*pc;

	pc = GET_PEO_CTX(context);
	if (!PEO_l(pc) || pc->offset < 0)
		return (0);

	error = 0;
	if (context->s->s_rlist != NULL) {
		/* If resource does not exists create one */
		if (pc->roffset == NULL) {
			snprintf(rname, sizeof(rname), OFFSET_PREFIX "%s",
			    pc->macfile);
			error = res_add(context->s->s_rlist, rname, 
			    &pc->offset, sizeof(pc->offset));
		} else 
			/* If resource exists replace current data */
			error = res_replace(context->s->s_rlist, pc->roffset,
			    &pc->offset, sizeof(pc->offset));
	}	
	return (error);
}


/*
 * _copy():
 *	Copy data from one fd to another or same fd.
 */
static int
_copy(int dstfd, int srcfd, off_t dstoff, off_t srcoff, off_t size)
{
	char	buf[BUFSIZ];
	ssize_t	to_read, to_write;
	
	if (lseek(dstfd, dstoff, SEEK_SET) < 0 ||
	    lseek(srcfd, srcoff, SEEK_SET) < 0)
		return (-1);

	while (size) {
		to_read = (size > sizeof(buf)) ? sizeof(buf) : size;
		to_write = read(srcfd, buf, to_read);
		if (to_write < 0)
			return (-1);

		if (dstfd == srcfd) {
			if (lseek(dstfd, dstoff, SEEK_SET) < 0)
				return (-1);
			srcoff += to_write;
		}

		if (write(dstfd, buf, to_write) < 0)
			return (-1);

		if (dstfd == srcfd) {
			if (lseek(srcfd, srcoff, SEEK_SET) < 0)
				return (-1);
			dstoff += to_write;
		}

		size -= to_write;
	}

	return (0);
}


/*
 * _get_logset_name():
 *	Return logset name.
 */
static void
_get_logset_name(ATTRCON *context, struct attrargs_ret *args)
{
	/* Peo attribute doesnt't know about logset name */
	args->size = 0;
}


/*
 * _freeze():
 *	Freeze peo status; return 0 on success or -1 on error
 *	and set errno.
 */
static int
_freeze(ATTRCON *context)
{
	PEO_CTX *pc;
	int	 fd;

	pc = GET_PEO_CTX(context);
	if ( (fd = open(pc->keyfile, O_RDONLY, 0)) < 0) {
		log_err(ATTR_PEO "Can't open '%s': %s.", pc->keyfile,
		    strerror(errno));
		return (-1);
	}

	pc->keysize = read(fd, pc->key, sizeof(pc->key));
	close(fd);
	if (pc->keysize < 0) {
		log_err(ATTR_PEO "Can't read '%s': %s.", pc->keyfile,
		    strerror(errno));
		return (-1);
	}

	if (PEO_l(pc))
		if (stat(pc->macfile, &pc->st) < 0) {
			log_err(ATTR_PEO "Can't stat '%s': %s.", pc->macfile,
			    strerror(errno));
			return (-1);
		}

	return (0);
}


/*
 * _info():
 *	Return peo information coded as a string:
 *	"peo
 *		hash    <hash_method>
 *		keyfile <file>
 *	      [ macfile <macfile>
 *	        offset  <offset> ]"
 *	XXX: add file perms
 */
static void
_info(ATTRCON *context, struct attrargs_ret *args)
{
	PEO_CTX *pc;
	size_t	 len;

	pc = GET_PEO_CTX(context);
	len = snprintf(args->data, args->size,
	    "peo\n\thash\t%s\n\tkeyfile\t%s\n", pc->hash, pc->keyfile);

	/* If using PEO-l load offset and add more info */
	if (PEO_l(pc)) {
		_load_offset(context);
		snprintf(args->data + len, args->size - len,
		    "\tmacfile\t%s\n\toffset\t%li\n",
		    pc->macfile, (unsigned long int) pc->offset);
	}
}


/*
 * _get():
 *	Send keyfile and macfile;
 *	return 0 on success or -1 on error and set errno.
 */
static int
_get(ATTRCON *context)
{
	PEO_CTX	*pc;
	int	 fd;

	pc = GET_PEO_CTX(context);
	log_debug(ATTR_PEO "Getting %s.", pc->keyfile);

	/* Send freezed key */
	packet_put_string(context->s->s_packet, pc->keyfile);
	packet_put_int32(context->s->s_packet, sizeof(pc->key));
	packet_put_raw(context->s->s_packet, pc->key, sizeof(pc->key));

	/* Send macfile */
	if (PEO_l(pc)) {
		if ( (fd = open(pc->macfile, O_RDONLY, 0)) < 0) {
			log_err(ATTR_PEO "Can't open '%s': %s.", pc->macfile,
			    strerror(errno));
			return (-1);
		}

		packet_put_string(context->s->s_packet, pc->macfile);

		/*
		 * If size < offset something wrong occurred,
		 * autidor would detect it.
		 */
		_load_offset(context);
		if (pc->st.st_size < pc->offset)
			pc->offset = 0;

		/* Send macfile from current offset */
		pc->offset = packet_write(context->s->s_packet, fd, pc->offset);
		close(fd);
	}

	return (0);
}


/*
 * _zap():
 *	Zap macfile and reset peo offset.
 */
static int
_zap(ATTRCON *context, int last_call)
{
	PEO_CTX *pc;
	off_t	 len;

	pc = GET_PEO_CTX(context);
	log_debug(ATTR_PEO "Zapping %s, pass %d.", pc->keyfile, last_call); 

	/* First time ? */
	if (pc->prev_size < 0) {

		/* Return 0 if user can't truncate or trucation not needed */
		if (!PEO_l(pc))
			return (0);
		_load_offset(context);
		if (pc->offset == 0)
			return (0);

		/* Open macfile */
		if ( (pc->fd = open(pc->macfile, O_RDWR, 0)) < 0) {
			log_err(ATTR_PEO "Can't open '%s': %s.", pc->macfile,
			    strerror(errno));
			return (-1);
		}

		/* Actualize internal variables */
		pc->prev_size = pc->offset;
		pc->offset = pc->pos = 0;
	}

	if (!PEO_l(pc));

	/* Move new unreadaed data to the beginning */
	len = pc->st.st_size - pc->prev_size;
	if (len >= 0) {
		if (_copy(pc->fd, pc->fd, pc->pos, pc->prev_size, len) < 0) {
			log_err(ATTR_PEO "Can't zap '%s': %s.", pc->macfile,
			    strerror(errno));
			pc->prev_size = -1;
			close(pc->fd);
			return (-1);
		}

		pc->pos += len;
		if (last_call) {
			pc->prev_size = -1;
			close(pc->fd);
			if (truncate(pc->macfile, pc->pos) < 0) {
				log_err(ATTR_PEO "Can't truncate '%s': %s.",
				    pc->macfile, strerror(errno));
				return (-1);
			}
		}

		pc->prev_size = pc->st.st_size;
	}

	/* Call me again */
	return (1);	//XXX
}


/*
 * _rotate():
 *	Rotate keyfile.
 */
static int
_rotate(ATTRCON *context, int last_call)
{
	PEO_CTX *pc;

	pc = GET_PEO_CTX(context);
	log_debug(ATTR_PEO "Rotating %s: pass %d, calling zap.",
	    pc->keyfile, last_call);
	return (_zap(context, last_call));
}


/*
 * _sign():
 *	Sign logfile.
 */
static int
_sign(ATTRCON *context)
{
	log_err(ATTR_PEO "SIGN: %s.", strerror(EOPNOTSUPP));
	return (-1);
}


/*
 * release():
 *	Release peo context (ATTRCON members are not released).
 */
void
release(ATTRCON *context)
{
	PEO_CTX *pc;

	if (context != NULL) {
		pc = GET_PEO_CTX(context);
		bzero(pc->key, sizeof(pc->key));
		if (_save_offset(context) < 0)
			log_err(ATTR_PEO "Can't release context: %s.",
			    strerror(errno));
		free(context);
	}
}


/*
 * init():
 *	Initialize peo module;
 *	on success 0 is returned and an internal context is created;
 *	on error -1 is returned and errno set.
 */
ATTRCON *
init(struct attrargs_init *args)
{
	static const char	*module_name = "peo";
	ATTRCON			*context;
	PEO_CTX			*pc;
	int			 i, err, peo_l;

	log_debug(ATTR_PEO "Initializing.");

	if (args->argc < 2) {
		errno = EINVAL;
		log_err(ATTR_PEO "Can't initialize %s.", strerror(errno));
		return (NULL);
	}

	context = (ATTRCON *) calloc(1, sizeof(ATTRCON) + sizeof(PEO_CTX));
	if (context == NULL) {
		log_err(ATTR_PEO "Can't create context: %s.", strerror(errno));
		return (NULL);
	}

	pc = GET_PEO_CTX(context);
	err = 0;
	peo_l = 0;
	for (i = 1; i < args->argc; i++) {
		if (!strcmp(args->argv[i], "-k")) {	/* keyfile */
			if (++i == args->argc) {
				errno = EINVAL;
				err = 1;
				break;
			}
			strlcpy(pc->keyfile, args->argv[i],
			    sizeof(pc->keyfile));
		} else if (!strcmp(args->argv[i], "-l")) {
			peo_l = 1;			/* PEO-l */
		} else if (!strcmp(args->argv[i], "-m")) {
			if (++i == args->argc) {	/* hash method */
				errno = EINVAL;
				err = 1;
				break;
			}
			strlcpy(pc->hash, args->argv[i], sizeof(pc->hash));
		}
	}

	if (!err && pc->keyfile[0] == '\0')
		strlcpy(pc->keyfile, DEFAULT_KEYFILE, sizeof(pc->keyfile));

	if (!err && pc->hash[0] == '\0')
		strlcpy(pc->hash, DEFAULT_HASH, sizeof(pc->hash));

	if (err) {
		log_err(ATTR_PEO "Can't initialize: %s.", strerror(errno));
		release(context);
		return (NULL);
	}

	if (peo_l)
		snprintf(pc->macfile, sizeof(pc->macfile), "%s.mac",
		    pc->keyfile);

	pc->offset = -1;
	pc->prev_size = -1;
	context->attr_name = module_name;
	return (context);
}


/*
 * proc_entry()
 */
int
proc_entry(int opcode, ATTRCON *context, void *args)
{
	switch(opcode) {
	case ATTR_GET_LOGSET_NAME:
		_get_logset_name(context, args);
		return (0);
	case ATTR_FREEZE:
		return (_freeze(context));
	case ATTR_INFO:
		_info(context, args);
		return (0);
	case ATTR_GET:
		return (_get(context));
	case ATTR_ZAP:
		return (_zap(context, (int) args));
	case ATTR_ROTATE:
		return (_rotate(context, (int) args));
	case ATTR_SIGN:
		return (_sign(context));
	default:
		errno = EINVAL;
	}

	log_err(ATTR_PEO "Invalid %d command.", opcode);
	return (-1);
}

