11573Srgrimes/*
21573Srgrimes * Copyright (c) 1983, 1988, 1993
31573Srgrimes *	The Regents of the University of California.  All rights reserved.
41573Srgrimes *
51573Srgrimes * Redistribution and use in source and binary forms, with or without
61573Srgrimes * modification, are permitted provided that the following conditions
71573Srgrimes * are met:
81573Srgrimes * 1. Redistributions of source code must retain the above copyright
91573Srgrimes *    notice, this list of conditions and the following disclaimer.
101573Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111573Srgrimes *    notice, this list of conditions and the following disclaimer in the
121573Srgrimes *    documentation and/or other materials provided with the distribution.
131573Srgrimes * 4. Neither the name of the University nor the names of its contributors
141573Srgrimes *    may be used to endorse or promote products derived from this software
151573Srgrimes *    without specific prior written permission.
161573Srgrimes *
171573Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181573Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191573Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201573Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211573Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221573Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231573Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241573Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251573Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261573Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271573Srgrimes * SUCH DAMAGE.
281573Srgrimes */
291573Srgrimes
301573Srgrimes#if defined(LIBC_SCCS) && !defined(lint)
3123668Speterstatic char sccsid[] = "@(#)syslog.c	8.5 (Berkeley) 4/29/95";
321573Srgrimes#endif /* LIBC_SCCS and not lint */
3390045Sobrien#include <sys/cdefs.h>
3490045Sobrien__FBSDID("$FreeBSD$");
351573Srgrimes
3671579Sdeischen#include "namespace.h"
371573Srgrimes#include <sys/types.h>
381573Srgrimes#include <sys/socket.h>
391573Srgrimes#include <sys/syslog.h>
401573Srgrimes#include <sys/uio.h>
4134059Sbrian#include <sys/un.h>
421573Srgrimes#include <netdb.h>
431573Srgrimes
441573Srgrimes#include <errno.h>
451573Srgrimes#include <fcntl.h>
461573Srgrimes#include <paths.h>
47139440Sglebius#include <pthread.h>
481573Srgrimes#include <stdio.h>
4993399Smarkm#include <stdlib.h>
501573Srgrimes#include <string.h>
511573Srgrimes#include <time.h>
521573Srgrimes#include <unistd.h>
531573Srgrimes
541573Srgrimes#include <stdarg.h>
5571579Sdeischen#include "un-namespace.h"
561573Srgrimes
5793399Smarkm#include "libc_private.h"
5893399Smarkm
591573Srgrimesstatic int	LogFile = -1;		/* fd for log */
60137233Sglebiusstatic int	status;			/* connection status */
6114332Speterstatic int	opened;			/* have done openlog() */
621573Srgrimesstatic int	LogStat = 0;		/* status bits, set by openlog() */
631573Srgrimesstatic const char *LogTag = NULL;	/* string to tag the entry with */
641573Srgrimesstatic int	LogFacility = LOG_USER;	/* default facility code */
651573Srgrimesstatic int	LogMask = 0xff;		/* mask of priorities to be logged */
66139440Sglebiusstatic pthread_mutex_t	syslog_mutex = PTHREAD_MUTEX_INITIALIZER;
671573Srgrimes
68139440Sglebius#define	THREAD_LOCK()							\
69139440Sglebius	do { 								\
70139440Sglebius		if (__isthreaded) _pthread_mutex_lock(&syslog_mutex);	\
71139440Sglebius	} while(0)
72139440Sglebius#define	THREAD_UNLOCK()							\
73139440Sglebius	do {								\
74139440Sglebius		if (__isthreaded) _pthread_mutex_unlock(&syslog_mutex);	\
75139440Sglebius	} while(0)
76139440Sglebius
7790045Sobrienstatic void	disconnectlog(void); /* disconnect from syslogd */
7890045Sobrienstatic void	connectlog(void);	/* (re)connect to syslogd */
79139440Sglebiusstatic void	openlog_unlocked(const char *, int, int);
8014332Speter
81137233Sglebiusenum {
82137233Sglebius	NOCONN = 0,
83137233Sglebius	CONNDEF,
84137233Sglebius	CONNPRIV,
85137233Sglebius};
86137233Sglebius
871573Srgrimes/*
8810794Speter * Format of the magic cookie passed through the stdio hook
8910794Speter */
9010794Speterstruct bufcookie {
9110794Speter	char	*base;	/* start of buffer */
9210794Speter	int	left;
9310794Speter};
9410794Speter
9510794Speter/*
9610794Speter * stdio write hook for writing to a static string buffer
9710794Speter * XXX: Maybe one day, dynamically allocate it so that the line length
9810794Speter *      is `unlimited'.
9910794Speter */
100144815Sstefanfstatic int
101144815Sstefanfwritehook(void *cookie, const char *buf, int len)
10210794Speter{
10310794Speter	struct bufcookie *h;	/* private `handle' */
10410794Speter
10510794Speter	h = (struct bufcookie *)cookie;
10610794Speter	if (len > h->left) {
10710794Speter		/* clip in case of wraparound */
10810794Speter		len = h->left;
10910794Speter	}
11010794Speter	if (len > 0) {
11110794Speter		(void)memcpy(h->base, buf, len); /* `write' it. */
11210794Speter		h->base += len;
11310794Speter		h->left -= len;
11410794Speter	}
115161053Sphk	return len;
11610794Speter}
11710794Speter
11810794Speter/*
1191573Srgrimes * syslog, vsyslog --
1201573Srgrimes *	print message on log file; output is intended for syslogd(8).
1211573Srgrimes */
1221573Srgrimesvoid
1231573Srgrimessyslog(int pri, const char *fmt, ...)
1241573Srgrimes{
1251573Srgrimes	va_list ap;
1261573Srgrimes
1271573Srgrimes	va_start(ap, fmt);
1281573Srgrimes	vsyslog(pri, fmt, ap);
1291573Srgrimes	va_end(ap);
1301573Srgrimes}
1311573Srgrimes
1321573Srgrimesvoid
133144815Sstefanfvsyslog(int pri, const char *fmt, va_list ap)
1341573Srgrimes{
13590045Sobrien	int cnt;
13690045Sobrien	char ch, *p;
1371573Srgrimes	time_t now;
1381573Srgrimes	int fd, saved_errno;
139139440Sglebius	char *stdp, tbuf[2048], fmt_cpy[1024], timbuf[26], errstr[64];
14010794Speter	FILE *fp, *fmt_fp;
14110794Speter	struct bufcookie tbuf_cookie;
14210794Speter	struct bufcookie fmt_cookie;
1431573Srgrimes
1441573Srgrimes#define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
1451573Srgrimes	/* Check for invalid bits. */
1461573Srgrimes	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
1471573Srgrimes		syslog(INTERNALLOG,
1481573Srgrimes		    "syslog: unknown facility/priority: %x", pri);
1491573Srgrimes		pri &= LOG_PRIMASK|LOG_FACMASK;
1501573Srgrimes	}
1511573Srgrimes
152158426Sdavidxu	saved_errno = errno;
153158426Sdavidxu
154139440Sglebius	THREAD_LOCK();
155139440Sglebius
1561573Srgrimes	/* Check priority against setlogmask values. */
157139440Sglebius	if (!(LOG_MASK(LOG_PRI(pri)) & LogMask)) {
158139440Sglebius		THREAD_UNLOCK();
1591573Srgrimes		return;
160139440Sglebius	}
1611573Srgrimes
1621573Srgrimes	/* Set default facility if none specified. */
1631573Srgrimes	if ((pri & LOG_FACMASK) == 0)
1641573Srgrimes		pri |= LogFacility;
1651573Srgrimes
16610794Speter	/* Create the primary stdio hook */
16710794Speter	tbuf_cookie.base = tbuf;
16810794Speter	tbuf_cookie.left = sizeof(tbuf);
16910794Speter	fp = fwopen(&tbuf_cookie, writehook);
170139440Sglebius	if (fp == NULL) {
171139440Sglebius		THREAD_UNLOCK();
17210794Speter		return;
173139440Sglebius	}
17410794Speter
1751573Srgrimes	/* Build the message. */
1761573Srgrimes	(void)time(&now);
17710794Speter	(void)fprintf(fp, "<%d>", pri);
17889805Sdwmalone	(void)fprintf(fp, "%.15s ", ctime_r(&now, timbuf) + 4);
17910794Speter	if (LogStat & LOG_PERROR) {
18010794Speter		/* Transfer to string buffer */
18110794Speter		(void)fflush(fp);
18210794Speter		stdp = tbuf + (sizeof(tbuf) - tbuf_cookie.left);
18310794Speter	}
1841573Srgrimes	if (LogTag == NULL)
18593399Smarkm		LogTag = _getprogname();
1861573Srgrimes	if (LogTag != NULL)
18710794Speter		(void)fprintf(fp, "%s", LogTag);
1881573Srgrimes	if (LogStat & LOG_PID)
18910794Speter		(void)fprintf(fp, "[%d]", getpid());
1901573Srgrimes	if (LogTag != NULL) {
19110794Speter		(void)fprintf(fp, ": ");
1921573Srgrimes	}
1931573Srgrimes
19410794Speter	/* Check to see if we can skip expanding the %m */
19510794Speter	if (strstr(fmt, "%m")) {
1961573Srgrimes
19710794Speter		/* Create the second stdio hook */
19810794Speter		fmt_cookie.base = fmt_cpy;
19910794Speter		fmt_cookie.left = sizeof(fmt_cpy) - 1;
20010794Speter		fmt_fp = fwopen(&fmt_cookie, writehook);
20110794Speter		if (fmt_fp == NULL) {
20210794Speter			fclose(fp);
203139440Sglebius			THREAD_UNLOCK();
20410794Speter			return;
20510794Speter		}
2061573Srgrimes
207110635Salfred		/*
208110635Salfred		 * Substitute error message for %m.  Be careful not to
209110635Salfred		 * molest an escaped percent "%%m".  We want to pass it
210110635Salfred		 * on untouched as the format is later parsed by vfprintf.
211110635Salfred		 */
212110635Salfred		for ( ; (ch = *fmt); ++fmt) {
21310794Speter			if (ch == '%' && fmt[1] == 'm') {
21410794Speter				++fmt;
215139440Sglebius				strerror_r(saved_errno, errstr, sizeof(errstr));
216139440Sglebius				fputs(errstr, fmt_fp);
217110635Salfred			} else if (ch == '%' && fmt[1] == '%') {
218110635Salfred				++fmt;
21910794Speter				fputc(ch, fmt_fp);
220110635Salfred				fputc(ch, fmt_fp);
221110635Salfred			} else {
222110635Salfred				fputc(ch, fmt_fp);
223110635Salfred			}
224110635Salfred		}
22510794Speter
22610794Speter		/* Null terminate if room */
22710794Speter		fputc(0, fmt_fp);
22810794Speter		fclose(fmt_fp);
22910794Speter
23010794Speter		/* Guarantee null termination */
23110794Speter		fmt_cpy[sizeof(fmt_cpy) - 1] = '\0';
23210794Speter
23310794Speter		fmt = fmt_cpy;
23410794Speter	}
23510794Speter
23610794Speter	(void)vfprintf(fp, fmt, ap);
23710794Speter	(void)fclose(fp);
23810794Speter
23910794Speter	cnt = sizeof(tbuf) - tbuf_cookie.left;
24010794Speter
241129091Sdds	/* Remove a trailing newline */
242129091Sdds	if (tbuf[cnt - 1] == '\n')
243129091Sdds		cnt--;
244129091Sdds
2451573Srgrimes	/* Output to stderr if requested. */
2461573Srgrimes	if (LogStat & LOG_PERROR) {
2471573Srgrimes		struct iovec iov[2];
24890045Sobrien		struct iovec *v = iov;
2491573Srgrimes
2501573Srgrimes		v->iov_base = stdp;
2511573Srgrimes		v->iov_len = cnt - (stdp - tbuf);
2521573Srgrimes		++v;
2531573Srgrimes		v->iov_base = "\n";
2541573Srgrimes		v->iov_len = 1;
25571579Sdeischen		(void)_writev(STDERR_FILENO, iov, 2);
2561573Srgrimes	}
2571573Srgrimes
2581573Srgrimes	/* Get connected, output the message to the local logger. */
25914332Speter	if (!opened)
260139440Sglebius		openlog_unlocked(LogTag, LogStat | LOG_NDELAY, 0);
26114332Speter	connectlog();
2621573Srgrimes
2631573Srgrimes	/*
264136283Sglebius	 * If the send() failed, there are two likely scenarios:
265136283Sglebius	 *  1) syslogd was restarted
266137233Sglebius	 *  2) /var/run/log is out of socket buffer space, which
267137233Sglebius	 *     in most cases means local DoS.
268228193Sobrien	 * We attempt to reconnect to /var/run/log[priv] to take care of
269136283Sglebius	 * case #1 and keep send()ing data to cover case #2
270136283Sglebius	 * to give syslogd a chance to empty its socket buffer.
271137233Sglebius	 *
272137233Sglebius	 * If we are working with a priveleged socket, then take
273137233Sglebius	 * only one attempt, because we don't want to freeze a
274137233Sglebius	 * critical application like su(1) or sshd(8).
275137233Sglebius	 *
27614332Speter	 */
27714332Speter
278136283Sglebius	if (send(LogFile, tbuf, cnt, 0) < 0) {
279136283Sglebius		if (errno != ENOBUFS) {
280136283Sglebius			disconnectlog();
281136283Sglebius			connectlog();
282136283Sglebius		}
283136283Sglebius		do {
284228193Sobrien			if (status == CONNPRIV)
285228193Sobrien				break;
286148657Sdeischen			_usleep(1);
287139440Sglebius			if (send(LogFile, tbuf, cnt, 0) >= 0) {
288139440Sglebius				THREAD_UNLOCK();
289139256Sglebius				return;
290139440Sglebius			}
291136283Sglebius		} while (errno == ENOBUFS);
292139440Sglebius	} else {
293139440Sglebius		THREAD_UNLOCK();
294139256Sglebius		return;
295139440Sglebius	}
296136283Sglebius
29714332Speter	/*
29891940Sdwmalone	 * Output the message to the console; try not to block
29991940Sdwmalone	 * as a blocking console should not stop other processes.
30091940Sdwmalone	 * Make sure the error reported is the one from the syslogd failure.
3011573Srgrimes	 */
3021573Srgrimes	if (LogStat & LOG_CONS &&
303241046Sjilles	    (fd = _open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK|O_CLOEXEC, 0)) >=
304241046Sjilles	    0) {
30511192Speter		struct iovec iov[2];
30690045Sobrien		struct iovec *v = iov;
30711192Speter
30811192Speter		p = strchr(tbuf, '>') + 1;
30911192Speter		v->iov_base = p;
31011192Speter		v->iov_len = cnt - (p - tbuf);
31111192Speter		++v;
31211192Speter		v->iov_base = "\r\n";
31311192Speter		v->iov_len = 2;
31471579Sdeischen		(void)_writev(fd, iov, 2);
31556698Sjasone		(void)_close(fd);
3161573Srgrimes	}
317139440Sglebius
318139440Sglebius	THREAD_UNLOCK();
3191573Srgrimes}
320139440Sglebius
321139440Sglebius/* Should be called with mutex acquired */
32214332Speterstatic void
323144815Sstefanfdisconnectlog(void)
3241573Srgrimes{
32514332Speter	/*
32614332Speter	 * If the user closed the FD and opened another in the same slot,
32714332Speter	 * that's their problem.  They should close it before calling on
32814332Speter	 * system services.
32914332Speter	 */
33014332Speter	if (LogFile != -1) {
33156698Sjasone		_close(LogFile);
33214332Speter		LogFile = -1;
33314332Speter	}
334137233Sglebius	status = NOCONN;			/* retry connect */
33514332Speter}
3361573Srgrimes
337139440Sglebius/* Should be called with mutex acquired */
33814332Speterstatic void
339144815Sstefanfconnectlog(void)
34014332Speter{
34134059Sbrian	struct sockaddr_un SyslogAddr;	/* AF_UNIX address of local logger */
34224068Sjdp
3431573Srgrimes	if (LogFile == -1) {
34471579Sdeischen		if ((LogFile = _socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
34514332Speter			return;
346220157Spluknet		(void)_fcntl(LogFile, F_SETFD, FD_CLOEXEC);
3471573Srgrimes	}
348137233Sglebius	if (LogFile != -1 && status == NOCONN) {
34934059Sbrian		SyslogAddr.sun_len = sizeof(SyslogAddr);
35034059Sbrian		SyslogAddr.sun_family = AF_UNIX;
351137233Sglebius
352137233Sglebius		/*
353137233Sglebius		 * First try priveleged socket. If no success,
354137233Sglebius		 * then try default socket.
355137233Sglebius		 */
356137233Sglebius		(void)strncpy(SyslogAddr.sun_path, _PATH_LOG_PRIV,
35734075Sbrian		    sizeof SyslogAddr.sun_path);
358137233Sglebius		if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
359137233Sglebius		    sizeof(SyslogAddr)) != -1)
360137233Sglebius			status = CONNPRIV;
36124068Sjdp
362137233Sglebius		if (status == NOCONN) {
363137233Sglebius			(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
364137233Sglebius			    sizeof SyslogAddr.sun_path);
365137233Sglebius			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
366137233Sglebius			    sizeof(SyslogAddr)) != -1)
367137233Sglebius				status = CONNDEF;
368137233Sglebius		}
369137233Sglebius
370137233Sglebius		if (status == NOCONN) {
37124068Sjdp			/*
37224068Sjdp			 * Try the old "/dev/log" path, for backward
37324068Sjdp			 * compatibility.
37424068Sjdp			 */
37534059Sbrian			(void)strncpy(SyslogAddr.sun_path, _PATH_OLDLOG,
37634075Sbrian			    sizeof SyslogAddr.sun_path);
377137233Sglebius			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
378137233Sglebius			    sizeof(SyslogAddr)) != -1)
379137233Sglebius				status = CONNDEF;
38024068Sjdp		}
38124068Sjdp
382137233Sglebius		if (status == NOCONN) {
38356698Sjasone			(void)_close(LogFile);
3841573Srgrimes			LogFile = -1;
38524068Sjdp		}
38614332Speter	}
3871573Srgrimes}
3881573Srgrimes
389139440Sglebiusstatic void
390144815Sstefanfopenlog_unlocked(const char *ident, int logstat, int logfac)
39114332Speter{
39214332Speter	if (ident != NULL)
39314332Speter		LogTag = ident;
39414332Speter	LogStat = logstat;
39514332Speter	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
39614332Speter		LogFacility = logfac;
39714332Speter
39814332Speter	if (LogStat & LOG_NDELAY)	/* open immediately */
39914332Speter		connectlog();
40014332Speter
40114332Speter	opened = 1;	/* ident and facility has been set */
40214332Speter}
40314332Speter
40414332Spetervoid
405144815Sstefanfopenlog(const char *ident, int logstat, int logfac)
406139440Sglebius{
407139440Sglebius	THREAD_LOCK();
408139440Sglebius	openlog_unlocked(ident, logstat, logfac);
409139440Sglebius	THREAD_UNLOCK();
410139440Sglebius}
411139440Sglebius
412139440Sglebius
413139440Sglebiusvoid
414144815Sstefanfcloselog(void)
4151573Srgrimes{
416139440Sglebius	THREAD_LOCK();
417237286Seadler	if (LogFile != -1) {
418237286Seadler		(void)_close(LogFile);
419237286Seadler		LogFile = -1;
420237286Seadler	}
421106911Sru	LogTag = NULL;
422137233Sglebius	status = NOCONN;
423139440Sglebius	THREAD_UNLOCK();
4241573Srgrimes}
4251573Srgrimes
4261573Srgrimes/* setlogmask -- set the log mask level */
4271573Srgrimesint
428144815Sstefanfsetlogmask(int pmask)
4291573Srgrimes{
4301573Srgrimes	int omask;
4311573Srgrimes
432139440Sglebius	THREAD_LOCK();
4331573Srgrimes	omask = LogMask;
4341573Srgrimes	if (pmask != 0)
4351573Srgrimes		LogMask = pmask;
436139440Sglebius	THREAD_UNLOCK();
4371573Srgrimes	return (omask);
4381573Srgrimes}
439