/*	$CoreSDI: ia_syslog.c,v 1.20 2001/10/05 19:38: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.
 */

/*
 * Syslog IA Module
 * Author: Claudio Castiglia
 */

#include <sys/param.h>

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

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.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 "ia_list.h"
#include "iaargs.h"
#include "log.h"

#define IA_SYSLOG	"ia syslog: "

#define WHITE_SPACE	" \f\n\r\t\v"
#define	PIDFILE0	"/var/run/syslog.pid"
#define	PIDFILE1	"/var/run/syslogd.pid"
#define	CONFILE		"/etc/syslog.conf"
#define INV_LOGFILE	"/dev/"
#define INV_LOGFILE_LEN	5
#define FORCELOG_SIGNAL	((is_msyslog) ? SIGUSR1 : SIGHUP)
#define STOP_SIGNAL	((is_msyslog) ? SIGTSTP : SIGSTOP)
#define CONT_SIGNAL	SIGCONT

static int is_msyslog = 0;

/*
 * _syslog_pid():
 *	Get syslogd pid and return it;
 *	return 0 if syslogd not running.
 */
static pid_t
_syslog_pid()
{
	char	buf[21];
	pid_t	pid;
	int	fd;

	pid = 0;
	if ( (fd = open(PIDFILE0, O_RDONLY)) < 0)
		fd = open(PIDFILE1, O_RDONLY);

	if (fd >= 0) {
		if (read(fd, buf, sizeof(buf)) > 0)
			pid = (pid_t) strtoul(buf, NULL, 10);
		close(fd);
	}

	return (pid);
}


/*
 * _remove_ending_junk():
 *	Remove ending string junk (white spaces).
 */
static void
_remove_ending_junk(char *p)
{
	size_t i;

	for (i = strlen(p) - 1; i >= 0 && isspace(p[i]); i--);
	p[i + 1] = '\0';
}


/*
 * _lock():
 *	Send STOP signal to [m]syslogd.
 */
static int
_lock(const char *logset)
{
	pid_t	pid;
	int	status;

	log_debug(IA_SYSLOG "Locking '%s'.", logset);
	if ( (pid = _syslog_pid()) != 0)
		status = kill(pid, STOP_SIGNAL);
	else
		errno = ESRCH;

	if (pid == 0 || status < 0) {
		log_err(IA_SYSLOG "Can't send STOP signal to syslog daemon: "
		    "%s.", strerror(errno));
		return (-1);
	}

	return (0);
}


/*
 * _unlock():
 *	Send CONT signal to [m]syslogd.
 */
static int
_unlock(const char *logset)
{
	pid_t	pid;
	int	status;

	log_debug(IA_SYSLOG "Unlocking '%s'.", logset);
	if ( (pid = _syslog_pid()) != 0)
		status = kill(pid, CONT_SIGNAL);
	else
		errno = ESRCH;

	if (pid == 0 || status < 0) {
		log_err(IA_SYSLOG "Can't send CONT signal to syslog daemon: "
		    "%s.", strerror(errno));
		return (-1);
	}

	return (0);
}


/*
 * _hup():
 *	Send FORCELOG signal to [m]syslogd.
 */
/* XXX
static void
_hup()
{
	pid_t pid;

	log_debug(IA_SYSLOG "Hup.");
	if ( (pid = _syslog_pid()) != 0)
		if (kill(pid, FORCELOG_SIGNAL) < 0)
			log_warn(IA_SYSLOG "Can't send HUP signal to syslog "
			    "daemon: %s. Ignoring.", strerror(errno));
}
*/


/*
 * init():
 *	Read configuration file and return a pointer to the IA context
 *	or NULL on error and set errno.
 *	NOTE: The returned logset context member is really a RAW_LOGSET
 *	      type. You must call to release_raw_logset() after use it.
 */
IACON *
init()
{
	FILE		*f;
	char		*p, *np, line[LINE_MAX];
	IACON		*iacon;
	CMD_LINE	*pcmd, *cmd;
	RAW_LOGSET	*plogset, *logset;
	int		 error;

	log_debug(IA_SYSLOG "Initializing.");

	/* Create ia context and add space for a RAW_LOGSET pointer */
	iacon = (IACON *) malloc(sizeof(IACON));
	if (iacon == NULL) {
		log_err(IA_SYSLOG "Can't create context: %s.", strerror(errno));
		return (NULL);
	}

	/* Open and parse syslog configuration file */
	if ( (f = fopen(CONFILE, "r")) == NULL) {
		log_err(IA_SYSLOG "Can't open '%s': %s.", CONFILE,
		    strerror(errno));
		return (NULL);
	}

	plogset = logset = NULL;
	error = 0;
	log_debug(IA_SYSLOG "Parsing '%s'.", CONFILE);
	while (1) {
		if (fgets(line, sizeof(line), f) == NULL)
			break;

		/* Remove beginning white spaces */
		p = line + strspn(line, WHITE_SPACE);
		if (*p == '\0' || *p == '#')
			continue;

		/* Jump to action field (see syslog.conf(5)) */
		if (strsep(&p, WHITE_SPACE) == NULL)
			continue;
		p += strspn(p, WHITE_SPACE);
		if (*p == '\0')
			continue;
		_remove_ending_junk(p);

		/* Parse standard syslog.conf file */
		if (*p != '%') {
			char buf[LINE_MAX];

			/* Detect a valid logfile */
			if (*p != '/')
				continue;
			if (!strncmp(p, INV_LOGFILE, INV_LOGFILE_LEN))
				continue;

			/* Simulate msyslog classic module */
			snprintf(buf, sizeof(buf), "classic %s", p);
			if ( (cmd = create_cmd_line(NULL, buf)) == NULL) {
				error = 1;
				break;
			}

			/* Create raw logset item and add it into the list */
			logset = create_raw_logset(logset, cmd);
			if (logset == NULL) {
				release_cmd_line(cmd);
				error = 1;
				break;
			}
			if (plogset == NULL)
				plogset = logset;
		} else {
			/* Parse modular syslog configuration file */
			is_msyslog = 1;
			p++;
			pcmd = cmd = NULL;
			while (p != NULL) {
				/* Detect module entry */
				if ( (np = strsep(&p, "%")) != NULL) {
					_remove_ending_junk(np);

					/*
					 * Create command line item and
					 * add it into the list.
					 */
					cmd = create_cmd_line(cmd, np);
					if (cmd == NULL) {
						error = 1;
						break;
					}
					if (pcmd == NULL)
						pcmd = cmd;
				}
			}

			if (!error) {
				/*
				 * Create raw logset item and
				 * add it into the list.
				 */
				logset = create_raw_logset(logset, pcmd);
				if (logset == NULL)
					error = 1;
				else if (plogset == NULL)
					plogset = logset;
			}

			if (error) {
				release_cmd_line(pcmd);
				break;
			}
		}
	}

	fclose(f);
	if (error) {
		log_err(IA_SYSLOG "Can't initialize: %s.", strerror(errno));
		free(iacon);
		release_raw_logset(plogset);
		return (NULL);
	}

	remove_raw_logset_dups(plogset);
	iacon->logset = (LOGSET_INFO *) plogset;
	return (iacon);
}


/*
 * proc_entry()
 */
int
proc_entry(int opcode, IACON *context, void *args)
{
	switch(opcode) {
	case IA_LOCK:
		return (_lock(args));
		break;
	case IA_UNLOCK:
		return (_unlock(args));
		break;
	default:
		errno = EINVAL;
		log_err(IA_SYSLOG "Invalid '%d' opcode.", opcode);
	}
	return (-1);
}

