/*	$CoreSDI: res_local.c,v 1.20 2001/12/08 00:12:30 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.
 */

/*
 * Local Resources Module
 * Author: Claudio Castiglia
 */

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

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

#include "log.h"
#include "resource.h"

#define LOCAL_RM		"resmod local: "

#ifndef LOCAL_RESOURCES_UMASK
#define LOCAL_RESOURCES_UMASK	(S_IRWXG | S_IRWXO | S_IXUSR)
#endif

#ifndef LOCAL_RESOURCES_PATH
#define LOCAL_RESOURCES_PATH	"/var/audit/resources"
#endif

#define WHITE_SPACE    		" \f\n\r\t\v"
#define NAME_BEGIN		'['
#define NAME_END		']'
#define INCLUDE_COMMAND		"include"
#define INCLUDE_COMMAND_LEN	7
#define MAX_LRM_CODES		20 /* Max. codes on a resource line */


/*
 * _decode():
 *	Decode (binarize) resource line;
 *	return no. of data bytes on the decoded line or -1 on error.
 */
static ssize_t
_decode(char *line)
{
	char	 asc[3], *pd;
	ssize_t	 datasize;

	for (datasize = 0, pd = line;
	    *line != '\0' && *line != '\n' && *line !='\r';
	    line++, pd++, datasize++)
		if (*line == '\\')
			switch (*(++line)) {
			case 'n':
				*pd = '\n';
				break;
			case 't':
				*pd = '\t';
				break;
			case '[':
			case '\\':
				*pd = *line;
				break;
			default: /* Valid only two hex digits */
				if (isxdigit(*line) && isxdigit(*(line+1))) {
					asc[0] = *line;
					asc[1] = *(++line);
					asc[2] = '\0';
					*pd = (char) strtoul(asc, NULL, 16);
					break;
				}
				errno = EINVAL;
				return (-1);
			}
		else
			*pd = *line;
	*pd = '\0';
	return (datasize);
}


/*
 * _encode():
 *	Encode binary data to ascii (lrm file convention, see NOTE 2 below)
 *	and return a pointer to a encoded string.
 *	On error NULL is returned.
 *	NOTE 1: After use, the buffer must be deallocated using free(3).
 *	NOTE 2: Local Resource Module (lrm) convention:
 *		Every character between 0x20 and 0x7E are printed as is;
 *		others are encoded as "\xx" string, where xx is the
 *		character hex code represeted in ascii.
 */
static char *hexset = "0123456789ABCDEF";
#define LOWER_PRINTABLE	0x20
#define UPPER_PRINTABLE	0x7E
#define UPPER_DIGIT(a)	*(hexset + (a >> 4))
#define LOWER_DIGIT(a)	*(hexset + (a & 0x0F))

static char *
_encode(const unsigned char *data, ssize_t size)
{
	char	*encoded;
	int	 e, i;

	encoded = (char *) malloc(size * 3 + 1); /* byte ab -> ascii "\ab" */
	if (encoded != NULL) {
		for (e = i = 0; i < size; i++, e++)
			if (data[i] < LOWER_PRINTABLE ||
			    data[i] > UPPER_PRINTABLE ||
			    data[i] == '[' || data[i] == '\\') {
				encoded[e++] = '\\';
				encoded[e++] = UPPER_DIGIT(data[i]);
				encoded[e] = LOWER_DIGIT(data[i]);
			} else
				encoded[e] = data[i];
		encoded[e] = '\0';
	}
	return (encoded);
}


/*
 * _add_resource():
 *	Add a new resource into the resource list;
 *	return 0 on success and -1 on error.
 *	NOTE: The *name value is free(3)d and reset to NULL;
 *	      *size is reset to 0.
 */
static int
_add_resource(RESLIST *list, char **name, const void *data, ssize_t *size)
{
	int error;

	error = 0;
	if (*name != NULL && data != NULL && *size > 0) {
		log_debug(LOCAL_RM "Creating '%s'.", *name);
		error = res_add(list, *name, data, *size);
		free(*name);
		*name = NULL;
		*size = 0;
	}
	return (error);
}


/*
 * _get_include_name():
 *	Get an include file name from include command string and
 *	return a pointer to it.
 *	Return NULL if the string does not contain an include command.
 */
static char *
_get_include_name(const char *command)
{
	char *file;

	if (!strncmp(command, INCLUDE_COMMAND, INCLUDE_COMMAND_LEN)) {
		file = (char *) command + INCLUDE_COMMAND_LEN;
		if (isspace(*file++)) {
			while (*file != '\0' && isspace(*file))
				file++;
			if (*file != '\0')
				return (file);
		}
	}
	return (NULL);
}


/*
 * _save_resource():
 * 	Add a resource into the specified resource file.
 *	Return 0 on success or -1 on error.
 */
static int
_save_resource(FILE *f, RESOURCE *r)
{
	char	*encoded, line[LINE_MAX];
	ssize_t	 count, i, j;

	if (r->name == NULL)
		return (-1);
	if (*r->name == '\0')
		return (-1);

	/* Write resource name */
	if ( (encoded = _encode(r->name, strlen(r->name))) == NULL)
		return (-1);
	i = snprintf(line, sizeof(line),
	    "%c %s %c\n", NAME_BEGIN, encoded, NAME_END);
	free(encoded);
	if (fwrite(line, 1, i, f) != i)
		return (-1);

	/* Write resource data */
	if (r->data != NULL && r->dsize > 0) {
		if ( (encoded = _encode(r->data, r->dsize)) == NULL)
			return (-1);

		/* Print MAX_LRM_CODEs per line */
		j = 0;
		line[0] = '\t';
		while (encoded[j] != '\0') {
			for (i = 0, count = 0; count < MAX_LRM_CODES;
			    count++, i++) {
				if (encoded[j + i] == '\0')
					break;
				if (encoded[j + i] == '\\') {
					line[i + 1] = encoded[j + i++];
					line[i + 1] = encoded[j + i++];
				}
				line[i + 1] = encoded[j + i];
			}
			if (count) {
				line[i + 1] = '\n';
				if (fwrite(line, 1, i + 2, f) != i + 2) {
					free(encoded);
					return (-1);
				}
			}
			j += i;
		}
		free(encoded);
	}
	return (0);
}


/*
 * check():
 *	Return true if a given resource file exists.
 */
int
check(const char *name)
{
	char pathname[MAXPATHLEN];
	
	log_debug(LOCAL_RM "Checking '%s'.", name);
	snprintf(pathname, sizeof(pathname), "%s/%s", LOCAL_RESOURCES_PATH,
	    name);
	return (access(pathname, F_OK) == 0);
}


/*
 * load():
 *	Parse a resource file specified on list->lname member and put
 *	data into the list. Include files are detected and parsed.
 *	Return 0 on success and -1 on error.
 *	NOTE: Empty resources are not included into the list and it is
 *	      not considered an error.
 */
int
load(RESLIST *list)
{
	int	 line, status;
	FILE	*f;
	char	 buf[LINE_MAX], pathname[MAXPATHLEN], *p, *rn, *rname, *iname;
	void	*rdata;
	ssize_t	 rsize, bufsize, nlsize;

	log_debug(LOCAL_RM "Loading '%s'.", list->lname);
	
	snprintf(pathname, sizeof(pathname), "%s/%s",
	    LOCAL_RESOURCES_PATH, list->lname);
	if ( (f = fopen(pathname, "r+")) == NULL)
		return (-1);

	line = 0;
	rname = NULL;
	rdata = NULL;
	rsize = 0;
	bufsize = 0;
	status = -1;
	while (1) {
		if (fgets(buf, sizeof(buf), f) == NULL) {
			status = 0;
			break;
		}
		line++;

		/* Remove beginning white spaces */
		p = buf + strspn(buf, WHITE_SPACE);
		if (*p == '\0' || *p == '\n')
			continue;
		
		/* Detect resource name: [ name ] */
		if (*p == NAME_BEGIN) {
			/* Add previously detected resource into the list */
			if (_add_resource(list, &rname, rdata, &rsize) < 0)
				break;

			/* Get new resource name */
			p++;
			rn = p + strspn(p, WHITE_SPACE);
			if ( (p = strrchr(p, NAME_END)) == NULL)
				break;
			do p--;
			while (isspace(*p) && p > rn);
			if (p <= rn)
				break;
			*(p + 1) = '\0';
			if (_decode(rn) < 0)
				break;
			if ( (rname = strdup(rn)) == NULL)
				break;

			/* Detect include command */
			if ( (iname = _get_include_name(rname)) != NULL) {
				list->lnext = res_open_list(iname, 0);
				if (list->lnext == NULL)
					break;
				if (load(list->lnext) < 0)
					break;
				free(rname);
				rname = NULL;
				continue;
			}
			
			continue;
		}

		/* Get resource data */
		if (rname == NULL) {
			errno = EINVAL;
			break;
		}
		if ( (nlsize = _decode(p)) < 0)
			break;
		if (bufsize < nlsize + rsize) {
			void *np;
			while (bufsize < nlsize + rsize)
				bufsize += LINE_MAX;
			if ( (np = realloc(rdata, bufsize)) == NULL)
				break;
			rdata = np;
		}
		memcpy((char *) rdata + rsize, p, nlsize);
		rsize += nlsize;
	}

	if (ferror(f))
		status = -1;
	else if (rname != NULL && rdata != NULL && rsize > 0)
		status = _add_resource(list, &rname, rdata, &rsize);
	fclose(f);
	if (rname != NULL)
		free(rname);
	if (rdata != NULL)
		free(rdata);

	return (status);
}


/*
 * save():
 *	Save the specified resources file list.
 *	Return 0 on success and -1 on error.
 *	XXX: do a backup before replacing an existing file,
 *	     then remove the backup if no errors.
 */
int
save(const RESLIST *list)
{
	RESOURCE *r;
	FILE	 *f;
	char	  pathname[MAXPATHLEN];

	log_debug(LOCAL_RM "Saving '%s'.", list->lname);
	if (list == NULL) {
		errno = EINVAL;
		return (-1);
	}

	/* XXX save only if modified flag is on */
	for (; list != NULL; list = list->lnext) {
		snprintf(pathname, sizeof(pathname), "%s/%s",
		    LOCAL_RESOURCES_PATH, list->lname);
		umask(LOCAL_RESOURCES_UMASK);
		if ( (f = fopen(pathname, "w")) == NULL)
			return (-1);
		/* Include file */
		if (list->lnext != NULL)
			if (list->lnext->lname[0] != '\0') {
				fprintf(f, "%c %s %s %c\n", NAME_BEGIN,
				    INCLUDE_COMMAND, list->lnext->lname,
				    NAME_END);
				if (save(list->lnext) < 0) {
					fclose(f);
					return (-1);
				}
			}
		for (r = list->first_res; r != NULL; r = r->next)
			if (_save_resource(f, r) < 0)
				break;
		fclose(f);
		if (r != NULL) /* _save_resource() error */
			return (-1);
	}
	return (0);
}


/*
 * remove():
 *	Remove the specified resource list.
 *	Return 0 on success and -1 on error.
 */
int
remove(const char *lname)
{
	char pathname[MAXPATHLEN];

	log_debug(LOCAL_RM "Removing '%s'.", lname);
	if (lname == NULL) {
		errno = EINVAL;
		return (-1);
	}

	snprintf(pathname, sizeof(pathname), "%s/%s",
	    LOCAL_RESOURCES_PATH, lname);
	return ((access(pathname, F_OK) < 0) ? -1 : unlink(pathname));
}

