syslog.c revision 148657
1/*
2 * Copyright (c) 1983, 1988, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 *    must display the following acknowledgement:
15 *	This product includes software developed by the University of
16 *	California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 *    may be used to endorse or promote products derived from this software
19 *    without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 * SUCH DAMAGE.
32 */
33
34#if defined(LIBC_SCCS) && !defined(lint)
35static char sccsid[] = "@(#)syslog.c	8.5 (Berkeley) 4/29/95";
36#endif /* LIBC_SCCS and not lint */
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/lib/libc/gen/syslog.c 148657 2005-08-03 00:45:58Z deischen $");
39
40#include "namespace.h"
41#include <sys/types.h>
42#include <sys/socket.h>
43#include <sys/syslog.h>
44#include <sys/uio.h>
45#include <sys/un.h>
46#include <netdb.h>
47
48#include <errno.h>
49#include <fcntl.h>
50#include <paths.h>
51#include <pthread.h>
52#include <stdio.h>
53#include <stdlib.h>
54#include <string.h>
55#include <time.h>
56#include <unistd.h>
57
58#include <stdarg.h>
59#include "un-namespace.h"
60
61#include "libc_private.h"
62
63static int	LogFile = -1;		/* fd for log */
64static int	status;			/* connection status */
65static int	opened;			/* have done openlog() */
66static int	LogStat = 0;		/* status bits, set by openlog() */
67static const char *LogTag = NULL;	/* string to tag the entry with */
68static int	LogFacility = LOG_USER;	/* default facility code */
69static int	LogMask = 0xff;		/* mask of priorities to be logged */
70static pthread_mutex_t	syslog_mutex = PTHREAD_MUTEX_INITIALIZER;
71
72#define	THREAD_LOCK()							\
73	do { 								\
74		if (__isthreaded) _pthread_mutex_lock(&syslog_mutex);	\
75	} while(0)
76#define	THREAD_UNLOCK()							\
77	do {								\
78		if (__isthreaded) _pthread_mutex_unlock(&syslog_mutex);	\
79	} while(0)
80
81static void	disconnectlog(void); /* disconnect from syslogd */
82static void	connectlog(void);	/* (re)connect to syslogd */
83static void	openlog_unlocked(const char *, int, int);
84
85enum {
86	NOCONN = 0,
87	CONNDEF,
88	CONNPRIV,
89};
90
91/*
92 * Format of the magic cookie passed through the stdio hook
93 */
94struct bufcookie {
95	char	*base;	/* start of buffer */
96	int	left;
97};
98
99/*
100 * stdio write hook for writing to a static string buffer
101 * XXX: Maybe one day, dynamically allocate it so that the line length
102 *      is `unlimited'.
103 */
104static int
105writehook(void *cookie, const char *buf, int len)
106{
107	struct bufcookie *h;	/* private `handle' */
108
109	h = (struct bufcookie *)cookie;
110	if (len > h->left) {
111		/* clip in case of wraparound */
112		len = h->left;
113	}
114	if (len > 0) {
115		(void)memcpy(h->base, buf, len); /* `write' it. */
116		h->base += len;
117		h->left -= len;
118	}
119	return 0;
120}
121
122/*
123 * syslog, vsyslog --
124 *	print message on log file; output is intended for syslogd(8).
125 */
126void
127syslog(int pri, const char *fmt, ...)
128{
129	va_list ap;
130
131	va_start(ap, fmt);
132	vsyslog(pri, fmt, ap);
133	va_end(ap);
134}
135
136void
137vsyslog(int pri, const char *fmt, va_list ap)
138{
139	int cnt;
140	char ch, *p;
141	time_t now;
142	int fd, saved_errno;
143	char *stdp, tbuf[2048], fmt_cpy[1024], timbuf[26], errstr[64];
144	FILE *fp, *fmt_fp;
145	struct bufcookie tbuf_cookie;
146	struct bufcookie fmt_cookie;
147
148#define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
149	/* Check for invalid bits. */
150	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
151		syslog(INTERNALLOG,
152		    "syslog: unknown facility/priority: %x", pri);
153		pri &= LOG_PRIMASK|LOG_FACMASK;
154	}
155
156	THREAD_LOCK();
157
158	/* Check priority against setlogmask values. */
159	if (!(LOG_MASK(LOG_PRI(pri)) & LogMask)) {
160		THREAD_UNLOCK();
161		return;
162	}
163
164	saved_errno = errno;
165
166	/* Set default facility if none specified. */
167	if ((pri & LOG_FACMASK) == 0)
168		pri |= LogFacility;
169
170	/* Create the primary stdio hook */
171	tbuf_cookie.base = tbuf;
172	tbuf_cookie.left = sizeof(tbuf);
173	fp = fwopen(&tbuf_cookie, writehook);
174	if (fp == NULL) {
175		THREAD_UNLOCK();
176		return;
177	}
178
179	/* Build the message. */
180	(void)time(&now);
181	(void)fprintf(fp, "<%d>", pri);
182	(void)fprintf(fp, "%.15s ", ctime_r(&now, timbuf) + 4);
183	if (LogStat & LOG_PERROR) {
184		/* Transfer to string buffer */
185		(void)fflush(fp);
186		stdp = tbuf + (sizeof(tbuf) - tbuf_cookie.left);
187	}
188	if (LogTag == NULL)
189		LogTag = _getprogname();
190	if (LogTag != NULL)
191		(void)fprintf(fp, "%s", LogTag);
192	if (LogStat & LOG_PID)
193		(void)fprintf(fp, "[%d]", getpid());
194	if (LogTag != NULL) {
195		(void)fprintf(fp, ": ");
196	}
197
198	/* Check to see if we can skip expanding the %m */
199	if (strstr(fmt, "%m")) {
200
201		/* Create the second stdio hook */
202		fmt_cookie.base = fmt_cpy;
203		fmt_cookie.left = sizeof(fmt_cpy) - 1;
204		fmt_fp = fwopen(&fmt_cookie, writehook);
205		if (fmt_fp == NULL) {
206			fclose(fp);
207			THREAD_UNLOCK();
208			return;
209		}
210
211		/*
212		 * Substitute error message for %m.  Be careful not to
213		 * molest an escaped percent "%%m".  We want to pass it
214		 * on untouched as the format is later parsed by vfprintf.
215		 */
216		for ( ; (ch = *fmt); ++fmt) {
217			if (ch == '%' && fmt[1] == 'm') {
218				++fmt;
219				strerror_r(saved_errno, errstr, sizeof(errstr));
220				fputs(errstr, fmt_fp);
221			} else if (ch == '%' && fmt[1] == '%') {
222				++fmt;
223				fputc(ch, fmt_fp);
224				fputc(ch, fmt_fp);
225			} else {
226				fputc(ch, fmt_fp);
227			}
228		}
229
230		/* Null terminate if room */
231		fputc(0, fmt_fp);
232		fclose(fmt_fp);
233
234		/* Guarantee null termination */
235		fmt_cpy[sizeof(fmt_cpy) - 1] = '\0';
236
237		fmt = fmt_cpy;
238	}
239
240	(void)vfprintf(fp, fmt, ap);
241	(void)fclose(fp);
242
243	cnt = sizeof(tbuf) - tbuf_cookie.left;
244
245	/* Remove a trailing newline */
246	if (tbuf[cnt - 1] == '\n')
247		cnt--;
248
249	/* Output to stderr if requested. */
250	if (LogStat & LOG_PERROR) {
251		struct iovec iov[2];
252		struct iovec *v = iov;
253
254		v->iov_base = stdp;
255		v->iov_len = cnt - (stdp - tbuf);
256		++v;
257		v->iov_base = "\n";
258		v->iov_len = 1;
259		(void)_writev(STDERR_FILENO, iov, 2);
260	}
261
262	/* Get connected, output the message to the local logger. */
263	if (!opened)
264		openlog_unlocked(LogTag, LogStat | LOG_NDELAY, 0);
265	connectlog();
266
267	/*
268	 * If the send() failed, there are two likely scenarios:
269	 *  1) syslogd was restarted
270	 *  2) /var/run/log is out of socket buffer space, which
271	 *     in most cases means local DoS.
272	 * We attempt to reconnect to /var/run/log to take care of
273	 * case #1 and keep send()ing data to cover case #2
274	 * to give syslogd a chance to empty its socket buffer.
275	 *
276	 * If we are working with a priveleged socket, then take
277	 * only one attempt, because we don't want to freeze a
278	 * critical application like su(1) or sshd(8).
279	 *
280	 */
281
282	if (send(LogFile, tbuf, cnt, 0) < 0) {
283		if (errno != ENOBUFS) {
284			disconnectlog();
285			connectlog();
286		}
287		do {
288			_usleep(1);
289			if (send(LogFile, tbuf, cnt, 0) >= 0) {
290				THREAD_UNLOCK();
291				return;
292			}
293			if (status == CONNPRIV)
294				break;
295		} while (errno == ENOBUFS);
296	} else {
297		THREAD_UNLOCK();
298		return;
299	}
300
301	/*
302	 * Output the message to the console; try not to block
303	 * as a blocking console should not stop other processes.
304	 * Make sure the error reported is the one from the syslogd failure.
305	 */
306	if (LogStat & LOG_CONS &&
307	    (fd = _open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK, 0)) >= 0) {
308		struct iovec iov[2];
309		struct iovec *v = iov;
310
311		p = strchr(tbuf, '>') + 1;
312		v->iov_base = p;
313		v->iov_len = cnt - (p - tbuf);
314		++v;
315		v->iov_base = "\r\n";
316		v->iov_len = 2;
317		(void)_writev(fd, iov, 2);
318		(void)_close(fd);
319	}
320
321	THREAD_UNLOCK();
322}
323
324/* Should be called with mutex acquired */
325static void
326disconnectlog(void)
327{
328	/*
329	 * If the user closed the FD and opened another in the same slot,
330	 * that's their problem.  They should close it before calling on
331	 * system services.
332	 */
333	if (LogFile != -1) {
334		_close(LogFile);
335		LogFile = -1;
336	}
337	status = NOCONN;			/* retry connect */
338}
339
340/* Should be called with mutex acquired */
341static void
342connectlog(void)
343{
344	struct sockaddr_un SyslogAddr;	/* AF_UNIX address of local logger */
345
346	if (LogFile == -1) {
347		if ((LogFile = _socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
348			return;
349		(void)_fcntl(LogFile, F_SETFD, 1);
350	}
351	if (LogFile != -1 && status == NOCONN) {
352		SyslogAddr.sun_len = sizeof(SyslogAddr);
353		SyslogAddr.sun_family = AF_UNIX;
354
355		/*
356		 * First try priveleged socket. If no success,
357		 * then try default socket.
358		 */
359		(void)strncpy(SyslogAddr.sun_path, _PATH_LOG_PRIV,
360		    sizeof SyslogAddr.sun_path);
361		if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
362		    sizeof(SyslogAddr)) != -1)
363			status = CONNPRIV;
364
365		if (status == NOCONN) {
366			(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
367			    sizeof SyslogAddr.sun_path);
368			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
369			    sizeof(SyslogAddr)) != -1)
370				status = CONNDEF;
371		}
372
373		if (status == NOCONN) {
374			/*
375			 * Try the old "/dev/log" path, for backward
376			 * compatibility.
377			 */
378			(void)strncpy(SyslogAddr.sun_path, _PATH_OLDLOG,
379			    sizeof SyslogAddr.sun_path);
380			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
381			    sizeof(SyslogAddr)) != -1)
382				status = CONNDEF;
383		}
384
385		if (status == NOCONN) {
386			(void)_close(LogFile);
387			LogFile = -1;
388		}
389	}
390}
391
392static void
393openlog_unlocked(const char *ident, int logstat, int logfac)
394{
395	if (ident != NULL)
396		LogTag = ident;
397	LogStat = logstat;
398	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
399		LogFacility = logfac;
400
401	if (LogStat & LOG_NDELAY)	/* open immediately */
402		connectlog();
403
404	opened = 1;	/* ident and facility has been set */
405}
406
407void
408openlog(const char *ident, int logstat, int logfac)
409{
410	THREAD_LOCK();
411	openlog_unlocked(ident, logstat, logfac);
412	THREAD_UNLOCK();
413}
414
415
416void
417closelog(void)
418{
419	THREAD_LOCK();
420	(void)_close(LogFile);
421	LogFile = -1;
422	LogTag = NULL;
423	status = NOCONN;
424	THREAD_UNLOCK();
425}
426
427/* setlogmask -- set the log mask level */
428int
429setlogmask(int pmask)
430{
431	int omask;
432
433	THREAD_LOCK();
434	omask = LogMask;
435	if (pmask != 0)
436		LogMask = pmask;
437	THREAD_UNLOCK();
438	return (omask);
439}
440