/*-
 * Copyright (c) 2003 Andrey Simonenko
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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.
 */

#include "config.h"

#ifndef lint
static const char rcsid[] ATTR_UNUSED =
  "@(#)$Id: ipa_log.c,v 1.1.4.9 2007/05/11 16:29:59 simon Exp $";
#endif /* !lint */

#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/uio.h>
#include <sys/queue.h>
#include <unistd.h>

#include "ipa_mod.h"

#include "dlapi.h"
#include "confcommon.h"
#include "memfunc.h"

#include "ipa_ac.h"
#include "ipa_db.h"
#include "ipa_cmd.h"
#include "ipa_ctl.h"
#include "ipa_time.h"

#include "ipa_log.h"
#include "ipa_main.h"
#include "ipa_rules.h"
#include "ipa_autorules.h"

/*
 * To prevent possible recursion from mem_* functions,
 * libc memory allocation function are used in this function.
 */

/*
 * open_log(), logmsg() and mod_logmsg() save errno on enter
 * and restore errno on exit, to allow external function to check
 * value of errno, changed by some function, which caused error.
 */

#define LOG_STREAM_BUF_SIZE	1024

/*
 * Don't call read for caught standard output and error stream
 * more than LOG_STREAM_READ_CALLS, to get other code a chance
 * to be executed.
 */
#define LOG_STREAM_READ_CALLS	10

/* vlogmsgx() or something like this. */
void		(*xvlogmsgx)(int, const char *, va_list);

const char	*log_ident = IPA_LOG_IDENT;	/* -i <log-ident> */
const char	*log_file = NULL;		/* -o <log-file> */

static int	syslog_logopt;			/* Syslog log options. */

static long	mypid;				/* PID of current process. */
static int	log_fd = -1;			/* File descriptor of log file. */

#ifndef LOG_FILE_PERM
# define LOG_FILE_PERM (S_IRUSR|S_IWUSR|S_IRGRP)	/* 0640 */
#endif

static const char *const priomsg[] = {	/* IPA_LOG_xxx -> Message */
	"", "W: ", "E: "
};

static const int log_priority[] = {	/* IPA_LOG_xxx -> LOG_xxx */
	LOG_INFO, LOG_WARNING, LOG_ERR
};

#ifdef WITH_PTHREAD
# define STRERRBUF_SIZE		128	/* Size of buffer for strerror_r(). */
#endif

/*
 * Init settings for syslog only.
 */
void
init_log(void)
{
	if (log_file == NULL)
		syslog_logopt = LOG_PID |
#ifdef LOG_ODELAY
		    LOG_ODELAY |
#endif
#ifdef LOG_NOWAIT
		    LOG_NOWAIT |
#endif
		    (debug ?
#ifdef LOG_PERROR
			LOG_PERROR
#else
			0
#endif
			: LOG_CONS);
}

/*
 * This function is called by modules to output their
 * log messages.
 */
void
mod_logmsg(const char *mod_name, int priority, int code, const char *format,
    va_list ap)
{
	int	rv, allocated = 0;
	int	errno_save;
	char	buf[LOG_BUF_SIZE];	/* Try to use buffer in stack. */
	char	*ptr;

	errno_save = errno;	/* Save errno. */

	if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0)
		ptr = "(mod_logmsg: vsnprintf failed)";
	else if (rv >= sizeof buf) {
		if ( (ptr = malloc(++rv)) == NULL)
			ptr = "(mod_logmsg: malloc failed)";
		else {
			if (vsnprintf(ptr, rv, format, ap) < 0) {
				free(ptr);
				ptr = "(mod_logmsg: vsnprintf failed again)";
			} else
				allocated = 1;
		}
	} else
		ptr = buf;

	if (code != 0) {
		errno = code;
		logmsg(priority, "MOD %s: %s", mod_name, ptr);
	} else
		logmsgx(priority, "MOD %s: %s", mod_name, ptr);

	if (allocated)
		free(ptr);

	errno = errno_save;	/* Restore errno. */
}

/*
 * Write a message with a timestamp to the log file and to
 * standard error if needed.  If stat(2) for the opened
 * log file failed, then try to open/create it again.
 */
static void
log_message(const char *message)
{
	int	fd;
	time_t	t;
	struct iovec iov[3];
	struct stat statbuf;
#ifdef WITH_PTHREAD
	char	buf[26];
	char	strerrbuf[STRERRBUF_SIZE];
#endif

	iov[2].iov_base = "\n";
	iov[2].iov_len = 1;

	for (;;) {
		if (log_fd < 0) {
			if ( (log_fd = open(log_file, O_WRONLY|O_APPEND|O_CREAT,
			    LOG_FILE_PERM)) < 0) {
				if (debug) {
					iov[0].iov_base = "log_message: open log file failed: ";
					iov[0].iov_len = 35;
#ifdef WITH_PTHREAD
					iov[1].iov_base =
					    strerror_r(errno, strerrbuf, sizeof strerrbuf) == 0 ?
					    strerrbuf : "<strerror_r failed>";
#else
					iov[1].iov_base = strerror(errno);
#endif
					iov[1].iov_len = strlen(iov[1].iov_base);
					(void)fflush(stdout);
					(void)writev(STDERR_FILENO, iov, 3);
				} else
					/* Can't open/create log file and can't log to stderr. */
					return;
			}
			break;
		} else {
			if (stat(log_file, &statbuf) < 0) {
				(void)close(log_fd);
				log_fd = -1;
			} else
				break;
		}
	}

	if (debug) {
		fd = STDERR_FILENO;
		(void)fflush(stdout);
	} else
		fd = log_fd;

	if (time(&t) == (time_t)-1) {
		iov[0].iov_base = "log_message: time failed: ";
		iov[0].iov_len = 26;
#ifdef WITH_PTHREAD
		iov[1].iov_base =
		    strerror_r(errno, strerrbuf, sizeof strerrbuf) == 0 ?
		    strerrbuf : "<strerror_r failed>";
#else
		iov[1].iov_base = strerror(errno);
#endif
		iov[1].iov_len = strlen(iov[1].iov_base);
	} else {
		/* DDD MMM dd hh:mm:ss YYYY\n\0 */
#ifdef WITH_PTHREAD
		iov[0].iov_base = ctime_r(&t, buf) + 4; /* Skip DDD. */
#else
		iov[0].iov_base = ctime(&t) + 4; /* Skip DDD. */
#endif
		iov[0].iov_len = 20; /* MMM...YYY */
		iov[1].iov_base = (char *)message;
		iov[1].iov_len = strlen(message);
	}

	for (;;) {
		if (writev(fd, iov, 3) < 0)
			if (debug && fd != STDERR_FILENO) {
				(void)close(log_fd);
				log_fd = -1;
				return;
			}
		if (debug && fd == STDERR_FILENO) {
			if ( (fd = log_fd) < 0)
				break;
		} else
			break;
	}
}

/*
 * Call openlog for syslog (real open of log descriptor is delayed).
 */
void
open_log(void)
{
	int errno_save;

	if (log_file == NULL) {
		errno_save = errno;	/* Save errno. */
		openlog(log_ident, syslog_logopt, LOG_USER);
		errno = errno_save;	/* Restore errno. */
	} else {
		mypid = (long)getpid();
		log_fd = -1;
	}
}

/*
 * Close log descriptor.
 */
void
close_log(void)
{
	if (log_file == NULL)
		closelog();
	else if (log_fd >= 0) {
		(void)close(log_fd);
		log_fd = -1;
	}
}

/*
 * Wrapper for log function from memfunc.c.
 */
void
mvlogmsgx_wrapper(const char *format, va_list ap)
{
	/*
	 * Since mxxx functions log messages when errors
	 * occurred, we simply use fix log priority here.
	 */
	vlogmsgx(IPA_LOG_ERR, format, ap);
}

/*
 * vprintf-like function, do not output message for errno.
 */
void
vlogmsgx(int priority, const char *format, va_list ap)
{

	if (log_file == NULL) {
#ifdef HAVE_VSYSLOG
		vsyslog(log_priority[priority], format, ap);
#else
		char buf[LOG_BUF_SIZE];

		if (vsnprintf(buf, sizeof buf, format, ap) >= 0)
			syslog(log_priority[priority], buf);
#endif
#ifdef LOG_PERROR
		return;
#endif
	}

	{
		int	rv, allocated1, allocated2;
		char	*ptr1, *ptr2;
		char	buf1[LOG_BUF_SIZE];
		char	buf2[LOG_BUF_SIZE + 128];

		allocated1 = allocated2 = 0;

		if ( (rv = vsnprintf(buf1, sizeof buf1, format, ap)) < 0)
			ptr1 = "(vlogmsgx: vsnprintf failed for ptr1)";
		else if (rv >= sizeof buf1) {
			if ( (ptr1 = malloc(++rv)) == NULL)
				ptr1 = "(vlogmsgx: malloc failed for ptr1)";
			else {
				if (vsnprintf(ptr1, rv, format, ap) < 0) {
					free(ptr1);
					ptr1 = "(vlogmsgx: vsnprintf failed again for ptr1)";
				} else
					allocated1 = 1;
			}
		} else
			ptr1 = buf1;

		if ( (rv = snprintf(buf2, sizeof buf2, " %s[%ld]: %s%s",
		    log_ident, mypid, priomsg[priority], ptr1)) < 0)
			ptr2 = "(vlogmsgx: vsnprintf failed for ptr2)";
		else if (rv >= sizeof buf2) {
			if ( (ptr2 = malloc(++rv)) == NULL)
				ptr2 = "(vlogmsgx: malloc failed for ptr2)";
			else {
				if (vsnprintf(ptr2, rv, format, ap) < 0) {
					free(ptr2);
					ptr2 = "(vlogmsgx: vsnprintf failed again for ptr2)";
				} else
					allocated2 = 1;
			}
		} else
			ptr2 = buf2;

		log_message(ptr2);

		if (allocated1)
			free(ptr1);
		if (allocated2)
			free(ptr2);
	}
}

/*
 * The same as vlogmsgx, but output everything on stderr.
 */
void
vlogmsgx_stderr(int priority, const char *format, va_list ap)
{
	fflush(stdout);
	switch (priority) {
	case IPA_LOG_ERR:
		fprintf(stderr, "Error: ");
		break;
	case IPA_LOG_WARNING:
		fprintf(stderr, "Warning: ");
		break;
	default: /* IPA_LOG_INFO */
		fprintf(stderr, "Info: ");
	}
	vfprintf(stderr, format, ap);
	fprintf(stderr, "\n");
}

/*
 * printf-like function, do not output message for errno.
 */
void
logmsgx(int priority, const char *format, ...)
{
	va_list	ap;

	va_start(ap, format);
	vlogmsgx(priority, format, ap);
	va_end(ap);
}

/*
 * printf-like function, also output message for errno.
 */
void
logmsg(int priority, const char *format, ...)
{
	va_list	ap;

	if (errno == 0) {
		va_start(ap, format);
		vlogmsgx(priority, format, ap);
		va_end(ap);
		return;
	}

	{
		int	rv, allocated = 0;
		int	errno_save;
		char	buf[LOG_BUF_SIZE];	/* Try to use buffer in stack. */
		char	*ptr;
#ifdef WITH_PTHREAD
		char	strerrbuf[STRERRBUF_SIZE];
#endif

		errno_save = errno;	/* Save errno. */

		va_start(ap, format);

		if ( (rv = vsnprintf(buf, sizeof buf, format, ap)) < 0)
			ptr = "(logmsg: vsnprintf failed)";
		else if (rv >= sizeof buf) {
			if ( (ptr = malloc(++rv)) == NULL)
				ptr = "(logmsg: malloc failed)";
			else {
				if (vsnprintf(ptr, rv, format, ap) < 0) {
					free(ptr);
					ptr = "(logmsg: vsnprintf failed again)";
				} else
					allocated = 1;
			}
		} else
			ptr = buf;

		va_end(ap);

#ifdef WITH_PTHREAD
		if (strerror_r(errno_save, strerrbuf, sizeof strerrbuf) == 0)
			logmsgx(priority, "%s: %s", ptr, strerrbuf);
		else
			logmsgx(priority, "%s: error code %d", ptr, errno_save);
#else
		logmsgx(priority, "%s: %s", ptr, strerror(errno_save));
#endif

		if (allocated)
			free(ptr);

		errno = errno_save;	/* Restore errno. */
	}
}

/*
 * Call xvlomsgx() which is a pointer to some vlogmsgx-like function.
 */
void
xlogmsgx(int priority, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	xvlogmsgx(priority, format, ap);
	va_end(ap);
}

/*
 * Read data from nonblockable fd and log them prepending with
 * stream name string, interpret '\0' and '\n' as new lines, call
 * read() syscall for fd no more than LOG_STREAM_READ_CALLS times.
 * This function is used only by log_stdout and log_stderr.
 */
void
log_stream(int fd, const char *stream_name)
{
	char	string[LOG_STREAM_BUF_SIZE], *ptr1, *ptr2;
	int	i;
	u_int	ncalls = 0;	/* Number of read() invocations. */
	ssize_t	nread;		/* Number of read bytes from last read(). */
	size_t	ngot;		/* Number of valid bytes in string. */
	size_t	nleft;		/* Number of not used bytes in string. */

	ptr1 = string;
	nleft = sizeof(string) - 1;
	ngot = 0;
	for (;;) {
		if ( (nread = read(fd, ptr1, nleft)) < 0) {
			if (errno == EWOULDBLOCK) {
				if (ptr1 != string)
					logmsgx(IPA_LOG_WARNING, "*%s: %s",
					    stream_name, string);
				break;
			} else {
				logmsg(IPA_LOG_ERR, "log_stream(%s): read",
				    stream_name);
				break;
			}
		}
		ngot += nread;
		for (i = 0, ptr1 = ptr2 = string; i < ngot; ++ptr2, ++i)
			switch (*ptr2) {
			case '\n':
				*ptr2 = '\0';
				/* FALLTHROUGH */
			case '\0':
				logmsgx(IPA_LOG_WARNING, "*%s: %s",
				    stream_name, ptr1);
				ptr1 = ptr2 + 1;
			}
		if (ptr1 == string) {
			ptr1[ngot] = '\0';
			ngot = 0;
			nleft = sizeof(string) - 1;
			logmsgx(IPA_LOG_WARNING, "*%s: %s", stream_name, ptr1);
		} else if (ptr1 != ptr2) {
			ngot = ptr2 - ptr1;
			memcpy(string, ptr1, ngot);
			ptr1 = string + ngot;
			*ptr1 = '\0';
			nleft = sizeof(string) - ngot - 1;
		} else {
			ptr1 = string;
			ngot = 0;
			nleft = sizeof(string) - 1;
		}
		if (++ncalls == LOG_STREAM_READ_CALLS) {
			if (ngot != 0) /* and has '\0' in string. */
				logmsgx(IPA_LOG_WARNING, "*%s: %s",
				    stream_name, string);
			break;
		}
	}
}


syntax highlighted by Code2HTML, v. 0.9.1