xref: /freebsd/lib/libc/gen/syslog.c (revision 63f9a4cb2684a303e3eb2ffed39c03a2e2b28ae0)
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)
35 static 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$");
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 <stdio.h>
52 #include <stdlib.h>
53 #include <string.h>
54 #include <time.h>
55 #include <unistd.h>
56 
57 #include <stdarg.h>
58 #include "un-namespace.h"
59 
60 #include "libc_private.h"
61 
62 static int	LogFile = -1;		/* fd for log */
63 static int	status;			/* connection status */
64 static int	opened;			/* have done openlog() */
65 static int	LogStat = 0;		/* status bits, set by openlog() */
66 static const char *LogTag = NULL;	/* string to tag the entry with */
67 static int	LogFacility = LOG_USER;	/* default facility code */
68 static int	LogMask = 0xff;		/* mask of priorities to be logged */
69 
70 static void	disconnectlog(void); /* disconnect from syslogd */
71 static void	connectlog(void);	/* (re)connect to syslogd */
72 
73 enum {
74 	NOCONN = 0,
75 	CONNDEF,
76 	CONNPRIV,
77 };
78 
79 /*
80  * Format of the magic cookie passed through the stdio hook
81  */
82 struct bufcookie {
83 	char	*base;	/* start of buffer */
84 	int	left;
85 };
86 
87 /*
88  * stdio write hook for writing to a static string buffer
89  * XXX: Maybe one day, dynamically allocate it so that the line length
90  *      is `unlimited'.
91  */
92 static
93 int writehook(cookie, buf, len)
94 	void	*cookie;	/* really [struct bufcookie *] */
95 	char	*buf;		/* characters to copy */
96 	int	len;		/* length to copy */
97 {
98 	struct bufcookie *h;	/* private `handle' */
99 
100 	h = (struct bufcookie *)cookie;
101 	if (len > h->left) {
102 		/* clip in case of wraparound */
103 		len = h->left;
104 	}
105 	if (len > 0) {
106 		(void)memcpy(h->base, buf, len); /* `write' it. */
107 		h->base += len;
108 		h->left -= len;
109 	}
110 	return 0;
111 }
112 
113 /*
114  * syslog, vsyslog --
115  *	print message on log file; output is intended for syslogd(8).
116  */
117 void
118 syslog(int pri, const char *fmt, ...)
119 {
120 	va_list ap;
121 
122 	va_start(ap, fmt);
123 	vsyslog(pri, fmt, ap);
124 	va_end(ap);
125 }
126 
127 void
128 vsyslog(pri, fmt, ap)
129 	int pri;
130 	const char *fmt;
131 	va_list ap;
132 {
133 	int cnt;
134 	char ch, *p;
135 	time_t now;
136 	int fd, saved_errno;
137 	char *stdp, tbuf[2048], fmt_cpy[1024], timbuf[26];
138 	FILE *fp, *fmt_fp;
139 	struct bufcookie tbuf_cookie;
140 	struct bufcookie fmt_cookie;
141 
142 #define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
143 	/* Check for invalid bits. */
144 	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
145 		syslog(INTERNALLOG,
146 		    "syslog: unknown facility/priority: %x", pri);
147 		pri &= LOG_PRIMASK|LOG_FACMASK;
148 	}
149 
150 	/* Check priority against setlogmask values. */
151 	if (!(LOG_MASK(LOG_PRI(pri)) & LogMask))
152 		return;
153 
154 	saved_errno = errno;
155 
156 	/* Set default facility if none specified. */
157 	if ((pri & LOG_FACMASK) == 0)
158 		pri |= LogFacility;
159 
160 	/* Create the primary stdio hook */
161 	tbuf_cookie.base = tbuf;
162 	tbuf_cookie.left = sizeof(tbuf);
163 	fp = fwopen(&tbuf_cookie, writehook);
164 	if (fp == NULL)
165 		return;
166 
167 	/* Build the message. */
168 	(void)time(&now);
169 	(void)fprintf(fp, "<%d>", pri);
170 	(void)fprintf(fp, "%.15s ", ctime_r(&now, timbuf) + 4);
171 	if (LogStat & LOG_PERROR) {
172 		/* Transfer to string buffer */
173 		(void)fflush(fp);
174 		stdp = tbuf + (sizeof(tbuf) - tbuf_cookie.left);
175 	}
176 	if (LogTag == NULL)
177 		LogTag = _getprogname();
178 	if (LogTag != NULL)
179 		(void)fprintf(fp, "%s", LogTag);
180 	if (LogStat & LOG_PID)
181 		(void)fprintf(fp, "[%d]", getpid());
182 	if (LogTag != NULL) {
183 		(void)fprintf(fp, ": ");
184 	}
185 
186 	/* Check to see if we can skip expanding the %m */
187 	if (strstr(fmt, "%m")) {
188 
189 		/* Create the second stdio hook */
190 		fmt_cookie.base = fmt_cpy;
191 		fmt_cookie.left = sizeof(fmt_cpy) - 1;
192 		fmt_fp = fwopen(&fmt_cookie, writehook);
193 		if (fmt_fp == NULL) {
194 			fclose(fp);
195 			return;
196 		}
197 
198 		/*
199 		 * Substitute error message for %m.  Be careful not to
200 		 * molest an escaped percent "%%m".  We want to pass it
201 		 * on untouched as the format is later parsed by vfprintf.
202 		 */
203 		for ( ; (ch = *fmt); ++fmt) {
204 			if (ch == '%' && fmt[1] == 'm') {
205 				++fmt;
206 				fputs(strerror(saved_errno), fmt_fp);
207 			} else if (ch == '%' && fmt[1] == '%') {
208 				++fmt;
209 				fputc(ch, fmt_fp);
210 				fputc(ch, fmt_fp);
211 			} else {
212 				fputc(ch, fmt_fp);
213 			}
214 		}
215 
216 		/* Null terminate if room */
217 		fputc(0, fmt_fp);
218 		fclose(fmt_fp);
219 
220 		/* Guarantee null termination */
221 		fmt_cpy[sizeof(fmt_cpy) - 1] = '\0';
222 
223 		fmt = fmt_cpy;
224 	}
225 
226 	(void)vfprintf(fp, fmt, ap);
227 	(void)fclose(fp);
228 
229 	cnt = sizeof(tbuf) - tbuf_cookie.left;
230 
231 	/* Remove a trailing newline */
232 	if (tbuf[cnt - 1] == '\n')
233 		cnt--;
234 
235 	/* Output to stderr if requested. */
236 	if (LogStat & LOG_PERROR) {
237 		struct iovec iov[2];
238 		struct iovec *v = iov;
239 
240 		v->iov_base = stdp;
241 		v->iov_len = cnt - (stdp - tbuf);
242 		++v;
243 		v->iov_base = "\n";
244 		v->iov_len = 1;
245 		(void)_writev(STDERR_FILENO, iov, 2);
246 	}
247 
248 	/* Get connected, output the message to the local logger. */
249 	if (!opened)
250 		openlog(LogTag, LogStat | LOG_NDELAY, 0);
251 	connectlog();
252 
253 	/*
254 	 * If the send() failed, there are two likely scenarios:
255 	 *  1) syslogd was restarted
256 	 *  2) /var/run/log is out of socket buffer space, which
257 	 *     in most cases means local DoS.
258 	 * We attempt to reconnect to /var/run/log to take care of
259 	 * case #1 and keep send()ing data to cover case #2
260 	 * to give syslogd a chance to empty its socket buffer.
261 	 *
262 	 * If we are working with a priveleged socket, then take
263 	 * only one attempt, because we don't want to freeze a
264 	 * critical application like su(1) or sshd(8).
265 	 *
266 	 */
267 
268 	if (send(LogFile, tbuf, cnt, 0) < 0) {
269 		if (errno != ENOBUFS) {
270 			disconnectlog();
271 			connectlog();
272 		}
273 		do {
274 			usleep(1);
275 			if (send(LogFile, tbuf, cnt, 0) >= 0)
276 				break;
277 			if (status == CONNPRIV)
278 				break;
279 		} while (errno == ENOBUFS);
280 	}
281 
282 	/*
283 	 * Output the message to the console; try not to block
284 	 * as a blocking console should not stop other processes.
285 	 * Make sure the error reported is the one from the syslogd failure.
286 	 */
287 	if (LogStat & LOG_CONS &&
288 	    (fd = _open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK, 0)) >= 0) {
289 		struct iovec iov[2];
290 		struct iovec *v = iov;
291 
292 		p = strchr(tbuf, '>') + 1;
293 		v->iov_base = p;
294 		v->iov_len = cnt - (p - tbuf);
295 		++v;
296 		v->iov_base = "\r\n";
297 		v->iov_len = 2;
298 		(void)_writev(fd, iov, 2);
299 		(void)_close(fd);
300 	}
301 }
302 static void
303 disconnectlog()
304 {
305 	/*
306 	 * If the user closed the FD and opened another in the same slot,
307 	 * that's their problem.  They should close it before calling on
308 	 * system services.
309 	 */
310 	if (LogFile != -1) {
311 		_close(LogFile);
312 		LogFile = -1;
313 	}
314 	status = NOCONN;			/* retry connect */
315 }
316 
317 static void
318 connectlog()
319 {
320 	struct sockaddr_un SyslogAddr;	/* AF_UNIX address of local logger */
321 
322 	if (LogFile == -1) {
323 		if ((LogFile = _socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
324 			return;
325 		(void)_fcntl(LogFile, F_SETFD, 1);
326 	}
327 	if (LogFile != -1 && status == NOCONN) {
328 		SyslogAddr.sun_len = sizeof(SyslogAddr);
329 		SyslogAddr.sun_family = AF_UNIX;
330 
331 		/*
332 		 * First try priveleged socket. If no success,
333 		 * then try default socket.
334 		 */
335 		(void)strncpy(SyslogAddr.sun_path, _PATH_LOG_PRIV,
336 		    sizeof SyslogAddr.sun_path);
337 		if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
338 		    sizeof(SyslogAddr)) != -1)
339 			status = CONNPRIV;
340 
341 		if (status == NOCONN) {
342 			(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
343 			    sizeof SyslogAddr.sun_path);
344 			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
345 			    sizeof(SyslogAddr)) != -1)
346 				status = CONNDEF;
347 		}
348 
349 		if (status == NOCONN) {
350 			/*
351 			 * Try the old "/dev/log" path, for backward
352 			 * compatibility.
353 			 */
354 			(void)strncpy(SyslogAddr.sun_path, _PATH_OLDLOG,
355 			    sizeof SyslogAddr.sun_path);
356 			if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
357 			    sizeof(SyslogAddr)) != -1)
358 				status = CONNDEF;
359 		}
360 
361 		if (status == NOCONN) {
362 			(void)_close(LogFile);
363 			LogFile = -1;
364 		}
365 	}
366 }
367 
368 void
369 openlog(ident, logstat, logfac)
370 	const char *ident;
371 	int logstat, logfac;
372 {
373 	if (ident != NULL)
374 		LogTag = ident;
375 	LogStat = logstat;
376 	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
377 		LogFacility = logfac;
378 
379 	if (LogStat & LOG_NDELAY)	/* open immediately */
380 		connectlog();
381 
382 	opened = 1;	/* ident and facility has been set */
383 }
384 
385 void
386 closelog()
387 {
388 	(void)_close(LogFile);
389 	LogFile = -1;
390 	LogTag = NULL;
391 	status = NOCONN;
392 }
393 
394 /* setlogmask -- set the log mask level */
395 int
396 setlogmask(pmask)
397 	int pmask;
398 {
399 	int omask;
400 
401 	omask = LogMask;
402 	if (pmask != 0)
403 		LogMask = pmask;
404 	return (omask);
405 }
406