/*
 * Copyright (c) 1983, 1988, 1993
 *	The Regents of the University of California.  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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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.
 */

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)syslog.c	8.4 (Berkeley) 3/18/94";
#endif /* LIBC_SCCS and not lint */

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/syslog.h>
#include <sys/uio.h>
#include <netdb.h>

#include <errno.h>
#include <fcntl.h>
#include <paths.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#if __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif

#if defined(sun) && defined(__svr4__)
#include <sys/stropts.h>
#include <sys/strlog.h>
#endif /* SOLARIS */

/* NeXT needs this, sez Luke Howard <lukeh@auswired.net> */
#ifndef STDERR_FILENO
#define STDERR_FILENO 2
#endif /* !STDERR_FILENO */


static int	LogFile = -1;		/* fd for log */
static int	connected;		/* have done connect */
static int	LogStat = 0;		/* status bits, set by openlog() */
static const char *LogTag = NULL;	/* string to tag the entry with */
static int	LogFacility = LOG_USER;	/* default facility code */
static int	LogMask = 0xff;		/* mask of priorities to be logged */
extern char	*__progname;		/* Program name, from crt0. */

/*
 * syslog, vsyslog --
 *	print message on log file; output is intended for syslogd(8).
 */
void
#if __STDC__
syslog(int pri, const char *fmt, ...)
#else
syslog(pri, fmt, va_alist)
	int pri;
	char *fmt;
	va_dcl
#endif
{
	va_list ap;

#if __STDC__
	va_start(ap, fmt);
#else
	va_start(ap);
#endif
	vsyslog(pri, fmt, ap);
	va_end(ap);
}

void
vsyslog(pri, fmt, ap)
	int pri;
	register const char *fmt;
	va_list ap;
{
	register int cnt;
	register char ch, *p, *t;
	time_t now;
	int fd, saved_errno;

#define TBUFLEN 1024
#define FMT_CPYLEN 768
#define SYSLOG_MIN(a,b) ((a)<(b)?(a):(b))

#if 0				/* Original syslog(3) numbers */
#define TBUFLEN 2048
#define FMT_CPYLEN 1024
#endif /* ORIG_SYSLOG */

	int tbuf_len, fmt_cpy_len, prlen, WANT_TRUNCATE_MESSAGE;
	char *stdp, tbuf[TBUFLEN], fmt_cpy[FMT_CPYLEN];

	tbuf_len = TBUFLEN;
	fmt_cpy_len = FMT_CPYLEN;
	p = tbuf;
	WANT_TRUNCATE_MESSAGE = 0;

#define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
	/* Check for invalid bits. */
	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
		syslog(INTERNALLOG,
		    "syslog: unknown facility/priority: %x", pri);
		pri &= LOG_PRIMASK|LOG_FACMASK;
	}

	/* Check priority against setlogmask values. */
	if (!LOG_MASK(LOG_PRI(pri)) & LogMask)
		return;

	saved_errno = errno;

	/* Set default facility if none specified. */
	if ((pri & LOG_FACMASK) == 0)
		pri |= LogFacility;

	/* Build the message. */
	(void)time(&now);
#if !(defined(sun) && defined(__svr4__))
	prlen = snprintf(tbuf, tbuf_len, "<%d>", pri);
	if (prlen >= tbuf_len)
	  prlen = tbuf_len - 1;
	tbuf_len -= prlen;
	p += prlen;
#endif /* SOLARIS */

#if defined(NeXT)
	prlen = strftime(p, tbuf_len, "%b %d %H:%M:%S ",
#elif defined(ultrix)
	prlen = strftime(p, tbuf_len, "%h %d %T ",                         
#else /* strftime has %e */
	prlen = strftime(p, tbuf_len, "%h %e %T ",
#endif /* special cases where strftime lacks %e */
	    localtime(&now));
#if defined(ultrix)
	if (p[4] == '0')
	  p[4] = ' ';
#endif /* ultrix */
	if (prlen >= tbuf_len)
	  prlen = tbuf_len - 1;
	tbuf_len -= prlen;
	p += prlen;

	if (LogStat & LOG_PERROR)
		stdp = p;
	if (LogTag == NULL)
		LogTag = "generic";
	if (LogTag != NULL) {
	  prlen = snprintf(p, tbuf_len, "%s", LogTag);
	  if (prlen >= tbuf_len)
	    prlen = tbuf_len - 1;
	  tbuf_len -= prlen;
	  p += prlen;
	}
	if (LogStat & LOG_PID) {
	  prlen = snprintf(p, tbuf_len, "[%d]", getpid());
	  if (prlen >= tbuf_len)
	    prlen = tbuf_len - 1;
	  tbuf_len -= prlen;
	  p += prlen;
	}
	if (LogTag != NULL) {
	  prlen = snprintf(p, tbuf_len, ": ");
	  if (prlen >= tbuf_len)
	    prlen = tbuf_len - 1;
	  tbuf_len -= prlen;
	  p += prlen;
	}

	/* Substitute error message for %m. */
	for (t = fmt_cpy; ch = *fmt; ++fmt)
		if (ch == '%' && fmt[1] == 'm') {
			++fmt;
			prlen = snprintf(t, fmt_cpy_len,
					 "%s", strerror(saved_errno));
			if (prlen >= fmt_cpy_len)
			  prlen = fmt_cpy_len - 1;
			t += prlen;
			fmt_cpy_len -= prlen;
		} else {
			if (fmt_cpy_len-- > 0)
                          *t++ = ch;
		}

        if (fmt_cpy_len-- > 0)
          *t = '\0';
        else
	  {
	    fmt_cpy[FMT_CPYLEN-1] = '\0'; /* crock, but what else to do? */
	    WANT_TRUNCATE_MESSAGE=1;
	  }

        prlen = vsnprintf(p, tbuf_len, fmt_cpy, ap);
	if (prlen >= tbuf_len)
	  prlen = tbuf_len - 1;
	tbuf_len -= prlen;
	p += prlen;

	/*
	 * CERT requested a visible warning of the fact that something
	 * bad happened.
	 */
	if (tbuf_len <= 1 || WANT_TRUNCATE_MESSAGE)
	  {
	    char *truncate_msg = " [TRUNCATED]\n";
	    int truncate_len = strlen(truncate_msg);
	    char *tbuf_offset;

	    tbuf_offset = SYSLOG_MIN(tbuf+(TBUFLEN-truncate_len-1),p);

	    /* This should always be true, but... */
	    if (tbuf_offset+truncate_len <= tbuf + TBUFLEN - 1)
	      snprintf(tbuf_offset,truncate_len,truncate_msg);

	    /*
	     * Our manual hacking around with tbuf may have voided
	     * the proper values of tbuf_len and p.  Let's fix them
	     * up as if we were at TBUF_LEN--after all, that is
	     * probably true!
	     */
	    tbuf_len = 1;
	    p = tbuf + strlen(tbuf);
	  }

	/*
	 * Irix can do funky things if you are logging like crazy.
	 *
	 * Specifically, their use of a stream instead of a datagram
	 * means that multiple write()s may be accumulated into one
	 * syslogd read().  If you do not have a terminating return
	 * seperating the two, then the two lines will be printed as
	 * one with the inband <priority> stuff shown.
	 *
	 * Solution?  We always put a terminating return.  YUK!
	 *
	 * Of course this begs the question of what happens with the
	 * ``other'' syslog lines which followed that.  With IRIX's
	 * syslogd, they are discarded.  [ This is the same as what
	 * IRIX's syslog(3) and syslogd(8) do. ]
	 *
	 * We *can* be a little smarter that this with our syslogd
	 * by logging another line for the next stuff, but there is
	 * nothing we can do about this in syslog(3).
	 */
	prlen = snprintf(p, tbuf_len, "\n");
	if (prlen >= tbuf_len)
	  prlen = tbuf_len - 1;
	tbuf_len -= prlen;
	p += prlen;

	/* FAILSAFE--we always want terminating return */
	tbuf[TBUFLEN-2] = '\n';
	tbuf[TBUFLEN-1] = '\0';

	cnt = strlen(tbuf)+1;

	/* Output to stderr if requested. */
	if (LogStat & LOG_PERROR) {
		struct iovec iov[2];
		register struct iovec *v = iov;

		v->iov_base = stdp;
		v->iov_len = cnt - (stdp - tbuf);
		++v;
		v->iov_base = "\n";
		v->iov_len = 1;
		(void)writev(STDERR_FILENO, iov, 2);
	}

	/* Get connected, output the message to the local logger. */
	if (!connected)
		openlog(LogTag, LogStat | LOG_NDELAY, 0);

	/* (for OS who use unix devices instead of sockets for /dev/log) */
#if defined(sgi)
	if (write(LogFile, tbuf, cnt) >= 0)
		return;
#else /* sgi et al */
#if defined(sun) && defined(__svr4__)
	{
	  struct log_ctl ctlinfo;
	  struct strbuf ctl;
	  struct strbuf data;

	  /*
	   * Does anybody know what these do or why these
	   * magic numbers are required? (or for that matter
	   * why Sun is so enamored of streams when sockets
	   * are so much niced?)
	   */
	  ctlinfo.mid = 0;
	  ctlinfo.sid = 0;
	  ctlinfo.level = 0;
	  ctlinfo.flags = SL_CONSOLE;
	  ctlinfo.ltime = 0;
	  ctlinfo.ttime = 0;
	  ctlinfo.seq_no = 0;
	  ctlinfo.pri = pri;

	  ctl.len = ctl.maxlen = sizeof(struct log_ctl);
	  ctl.buf = (char *)&ctlinfo;

	  data.maxlen = TBUFLEN;
	  data.len = cnt;
	  data.buf = tbuf;

	  if (putmsg(LogFile, &ctl, &data, 0) >= 0)
	    return;
	}
#else /* Solaris */
	if (send(LogFile, tbuf, cnt, 0) >= 0)
		return;
#endif /* Solaris */
#endif /* sgi et al */

	/*
	 * Output the message to the console; don't worry about blocking,
	 * if console blocks everything will.  Make sure the error reported
	 * is the one from the syslogd failure.
	 */
	if (LogStat & LOG_CONS &&
	    (fd = open(_PATH_CONSOLE, O_WRONLY, 0)) >= 0) {
		(void)strcat(tbuf, "\r\n");
		cnt += 2;
		p = strchr(tbuf, '>') + 1;
		(void)write(fd, p, cnt - (p - tbuf));
		(void)close(fd);
	}
}

static struct sockaddr SyslogAddr;	/* AF_UNIX address of local logger */

void
openlog(ident, logstat, logfac)
	const char *ident;
	int logstat, logfac;
{
	if (ident != NULL)
		LogTag = ident;
	LogStat = logstat;
	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
		LogFacility = logfac;

#if defined(sgi) || (defined(sun) && defined(__svr4__))
	if ((LogFile == -1) && (LogStat & LOG_NDELAY)) {
		if ((LogFile = open(_PATH_LOG, O_WRONLY)) < 0)
			return;
		connected = 1;
	}
#else /* SGI et al */
	if (LogFile == -1) {
		SyslogAddr.sa_family = AF_UNIX;
		(void)strncpy(SyslogAddr.sa_data, _PATH_LOG,
		    sizeof(SyslogAddr.sa_data));
		if (LogStat & LOG_NDELAY) {
			if ((LogFile = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
				return;
			(void)fcntl(LogFile, F_SETFD, 1);
		}
	}
	if (LogFile != -1 && !connected)
		if (connect(LogFile, &SyslogAddr, sizeof(SyslogAddr)) == -1) {
			(void)close(LogFile);
			LogFile = -1;
		} else
			connected = 1;
#endif /* SGI et al */
}

void
closelog()
{
	(void)close(LogFile);
	LogFile = -1;
	connected = 0;
}

/* setlogmask -- set the log mask level */
int
setlogmask(pmask)
	int pmask;
{
	int omask;

	omask = LogMask;
	if (pmask != 0)
		LogMask = pmask;
	return (omask);
}
