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