xref: /freebsd/lib/libc/gen/syslog.c (revision 924226fba12cc9a228c73b956e1b7fa24c60b055)
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 <stdbool.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <time.h>
54 #include <unistd.h>
55 
56 #include <stdarg.h>
57 #include "un-namespace.h"
58 
59 #include "libc_private.h"
60 
61 /* Maximum number of characters of syslog message */
62 #define	MAXLINE		8192
63 
64 static int	LogFile = -1;		/* fd for log */
65 static bool	connected;		/* have done connect */
66 static int	opened;			/* have done openlog() */
67 static int	LogStat = 0;		/* status bits, set by openlog() */
68 static const char *LogTag = NULL;	/* string to tag the entry with */
69 static int	LogFacility = LOG_USER;	/* default facility code */
70 static int	LogMask = 0xff;		/* mask of priorities to be logged */
71 static pthread_mutex_t	syslog_mutex = PTHREAD_MUTEX_INITIALIZER;
72 
73 #define	THREAD_LOCK()							\
74 	do { 								\
75 		if (__isthreaded) _pthread_mutex_lock(&syslog_mutex);	\
76 	} while(0)
77 #define	THREAD_UNLOCK()							\
78 	do {								\
79 		if (__isthreaded) _pthread_mutex_unlock(&syslog_mutex);	\
80 	} while(0)
81 
82 /* RFC5424 defined value. */
83 #define NILVALUE "-"
84 
85 static void	disconnectlog(void); /* disconnect from syslogd */
86 static void	connectlog(void);	/* (re)connect to syslogd */
87 static void	openlog_unlocked(const char *, int, int);
88 
89 /*
90  * Format of the magic cookie passed through the stdio hook
91  */
92 struct bufcookie {
93 	char	*base;	/* start of buffer */
94 	int	left;
95 };
96 
97 /*
98  * stdio write hook for writing to a static string buffer
99  * XXX: Maybe one day, dynamically allocate it so that the line length
100  *      is `unlimited'.
101  */
102 static int
103 writehook(void *cookie, const char *buf, int len)
104 {
105 	struct bufcookie *h;	/* private `handle' */
106 
107 	h = (struct bufcookie *)cookie;
108 	if (len > h->left) {
109 		/* clip in case of wraparound */
110 		len = h->left;
111 	}
112 	if (len > 0) {
113 		(void)memcpy(h->base, buf, len); /* `write' it. */
114 		h->base += len;
115 		h->left -= len;
116 	}
117 	return len;
118 }
119 
120 /*
121  * syslog, vsyslog --
122  *	print message on log file; output is intended for syslogd(8).
123  */
124 void
125 syslog(int pri, const char *fmt, ...)
126 {
127 	va_list ap;
128 
129 	va_start(ap, fmt);
130 	vsyslog(pri, fmt, ap);
131 	va_end(ap);
132 }
133 
134 static void
135 vsyslog1(int pri, const char *fmt, va_list ap)
136 {
137 	struct timeval now;
138 	struct tm tm;
139 	char ch, *p;
140 	long tz_offset;
141 	int cnt, fd, saved_errno;
142 	char hostname[MAXHOSTNAMELEN], *stdp, tbuf[MAXLINE], fmt_cpy[MAXLINE],
143 	    errstr[64], tz_sign;
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 	saved_errno = errno;
157 
158 	/* Check priority against setlogmask values. */
159 	if (!(LOG_MASK(LOG_PRI(pri)) & LogMask))
160 		return;
161 
162 	/* Set default facility if none specified. */
163 	if ((pri & LOG_FACMASK) == 0)
164 		pri |= LogFacility;
165 
166 	/* Create the primary stdio hook */
167 	tbuf_cookie.base = tbuf;
168 	tbuf_cookie.left = sizeof(tbuf);
169 	fp = fwopen(&tbuf_cookie, writehook);
170 	if (fp == NULL)
171 		return;
172 
173 	/* Build the message according to RFC 5424. Tag and version. */
174 	(void)fprintf(fp, "<%d>1 ", pri);
175 	/* Timestamp similar to RFC 3339. */
176 	if (gettimeofday(&now, NULL) == 0 &&
177 	    localtime_r(&now.tv_sec, &tm) != NULL) {
178 		if (tm.tm_gmtoff < 0) {
179 			tz_sign = '-';
180 			tz_offset = -tm.tm_gmtoff;
181 		} else {
182 			tz_sign = '+';
183 			tz_offset = tm.tm_gmtoff;
184 		}
185 
186 		(void)fprintf(fp,
187 		    "%04d-%02d-%02d"		/* Date. */
188 		    "T%02d:%02d:%02d.%06ld"	/* Time. */
189 		    "%c%02ld:%02ld ",		/* Time zone offset. */
190 		    tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
191 		    tm.tm_hour, tm.tm_min, tm.tm_sec, now.tv_usec,
192 		    tz_sign, tz_offset / 3600, (tz_offset % 3600) / 60);
193 	} else
194 		(void)fputs(NILVALUE " ", fp);
195 	/* Hostname. */
196 	(void)gethostname(hostname, sizeof(hostname));
197 	(void)fprintf(fp, "%s ",
198 	    hostname[0] == '\0' ? NILVALUE : hostname);
199 	if (LogStat & LOG_PERROR) {
200 		/* Transfer to string buffer */
201 		(void)fflush(fp);
202 		stdp = tbuf + (sizeof(tbuf) - tbuf_cookie.left);
203 	}
204 	/* Application name. */
205 	if (LogTag == NULL)
206 		LogTag = _getprogname();
207 	(void)fprintf(fp, "%s ", LogTag == NULL ? NILVALUE : LogTag);
208 	/*
209 	 * Provide the process ID regardless of whether LOG_PID has been
210 	 * specified, as it provides valuable information. Many
211 	 * applications tend not to use this, even though they should.
212 	 */
213 	(void)fprintf(fp, "%d ", getpid());
214 	/* Message ID. */
215 	(void)fputs(NILVALUE " ", fp);
216 	/* Structured data. */
217 	(void)fputs(NILVALUE " ", fp);
218 
219 	/* Check to see if we can skip expanding the %m */
220 	if (strstr(fmt, "%m")) {
221 
222 		/* Create the second stdio hook */
223 		fmt_cookie.base = fmt_cpy;
224 		fmt_cookie.left = sizeof(fmt_cpy) - 1;
225 		fmt_fp = fwopen(&fmt_cookie, writehook);
226 		if (fmt_fp == NULL) {
227 			fclose(fp);
228 			return;
229 		}
230 
231 		/*
232 		 * Substitute error message for %m.  Be careful not to
233 		 * molest an escaped percent "%%m".  We want to pass it
234 		 * on untouched as the format is later parsed by vfprintf.
235 		 */
236 		for ( ; (ch = *fmt); ++fmt) {
237 			if (ch == '%' && fmt[1] == 'm') {
238 				++fmt;
239 				strerror_r(saved_errno, errstr, sizeof(errstr));
240 				fputs(errstr, fmt_fp);
241 			} else if (ch == '%' && fmt[1] == '%') {
242 				++fmt;
243 				fputc(ch, fmt_fp);
244 				fputc(ch, fmt_fp);
245 			} else {
246 				fputc(ch, fmt_fp);
247 			}
248 		}
249 
250 		/* Null terminate if room */
251 		fputc(0, fmt_fp);
252 		fclose(fmt_fp);
253 
254 		/* Guarantee null termination */
255 		fmt_cpy[sizeof(fmt_cpy) - 1] = '\0';
256 
257 		fmt = fmt_cpy;
258 	}
259 
260 	/* Message. */
261 	(void)vfprintf(fp, fmt, ap);
262 	(void)fclose(fp);
263 
264 	cnt = sizeof(tbuf) - tbuf_cookie.left;
265 
266 	/* Remove a trailing newline */
267 	if (tbuf[cnt - 1] == '\n')
268 		cnt--;
269 
270 	/* Output to stderr if requested. */
271 	if (LogStat & LOG_PERROR) {
272 		struct iovec iov[2];
273 		struct iovec *v = iov;
274 
275 		v->iov_base = stdp;
276 		v->iov_len = cnt - (stdp - tbuf);
277 		++v;
278 		v->iov_base = "\n";
279 		v->iov_len = 1;
280 		(void)_writev(STDERR_FILENO, iov, 2);
281 	}
282 
283 	/* Get connected, output the message to the local logger. */
284 	if (!opened)
285 		openlog_unlocked(LogTag, LogStat | LOG_NDELAY, 0);
286 	connectlog();
287 
288 	/*
289 	 * If the send() failed, there are two likely scenarios:
290 	 * 1) syslogd was restarted.  In this case make one (only) attempt
291 	 *    to reconnect.
292 	 * 2) We filled our buffer due to syslogd not being able to read
293 	 *    as fast as we write.  In this case prefer to lose the current
294 	 *    message rather than whole buffer of previously logged data.
295 	 */
296 	if (send(LogFile, tbuf, cnt, 0) < 0) {
297 		if (errno != ENOBUFS) {
298 			disconnectlog();
299 			connectlog();
300 			if (send(LogFile, tbuf, cnt, 0) >= 0)
301 				return;
302 		}
303 	} else
304 		return;
305 
306 	/*
307 	 * Output the message to the console; try not to block
308 	 * as a blocking console should not stop other processes.
309 	 * Make sure the error reported is the one from the syslogd failure.
310 	 */
311 	if (LogStat & LOG_CONS &&
312 	    (fd = _open(_PATH_CONSOLE, O_WRONLY|O_NONBLOCK|O_CLOEXEC, 0)) >=
313 	    0) {
314 		struct iovec iov[2];
315 		struct iovec *v = iov;
316 
317 		p = strchr(tbuf, '>') + 3;
318 		v->iov_base = p;
319 		v->iov_len = cnt - (p - tbuf);
320 		++v;
321 		v->iov_base = "\r\n";
322 		v->iov_len = 2;
323 		(void)_writev(fd, iov, 2);
324 		(void)_close(fd);
325 	}
326 }
327 
328 static void
329 syslog_cancel_cleanup(void *arg __unused)
330 {
331 
332 	THREAD_UNLOCK();
333 }
334 
335 void
336 vsyslog(int pri, const char *fmt, va_list ap)
337 {
338 
339 	THREAD_LOCK();
340 	pthread_cleanup_push(syslog_cancel_cleanup, NULL);
341 	vsyslog1(pri, fmt, ap);
342 	pthread_cleanup_pop(1);
343 }
344 
345 /* Should be called with mutex acquired */
346 static void
347 disconnectlog(void)
348 {
349 	/*
350 	 * If the user closed the FD and opened another in the same slot,
351 	 * that's their problem.  They should close it before calling on
352 	 * system services.
353 	 */
354 	if (LogFile != -1) {
355 		_close(LogFile);
356 		LogFile = -1;
357 	}
358 	connected = false;			/* retry connect */
359 }
360 
361 /* Should be called with mutex acquired */
362 static void
363 connectlog(void)
364 {
365 	struct sockaddr_un SyslogAddr;	/* AF_UNIX address of local logger */
366 
367 	if (LogFile == -1) {
368 		socklen_t len;
369 
370 		if ((LogFile = _socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC,
371 		    0)) == -1)
372 			return;
373 		if (_getsockopt(LogFile, SOL_SOCKET, SO_SNDBUF, &len,
374 		    &(socklen_t){sizeof(len)}) == 0) {
375 			if (len < MAXLINE) {
376 				len = MAXLINE;
377 				(void)_setsockopt(LogFile, SOL_SOCKET, SO_SNDBUF,
378 				    &len, sizeof(len));
379 			}
380 		}
381 	}
382 	if (!connected) {
383 		SyslogAddr.sun_len = sizeof(SyslogAddr);
384 		SyslogAddr.sun_family = AF_UNIX;
385 
386 		(void)strncpy(SyslogAddr.sun_path, _PATH_LOG,
387 		    sizeof SyslogAddr.sun_path);
388 		if (_connect(LogFile, (struct sockaddr *)&SyslogAddr,
389 		    sizeof(SyslogAddr)) != -1)
390 			connected = true;
391 		else {
392 			(void)_close(LogFile);
393 			LogFile = -1;
394 		}
395 	}
396 }
397 
398 static void
399 openlog_unlocked(const char *ident, int logstat, int logfac)
400 {
401 	if (ident != NULL)
402 		LogTag = ident;
403 	LogStat = logstat;
404 	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
405 		LogFacility = logfac;
406 
407 	if (LogStat & LOG_NDELAY)	/* open immediately */
408 		connectlog();
409 
410 	opened = 1;	/* ident and facility has been set */
411 }
412 
413 void
414 openlog(const char *ident, int logstat, int logfac)
415 {
416 
417 	THREAD_LOCK();
418 	pthread_cleanup_push(syslog_cancel_cleanup, NULL);
419 	openlog_unlocked(ident, logstat, logfac);
420 	pthread_cleanup_pop(1);
421 }
422 
423 
424 void
425 closelog(void)
426 {
427 	THREAD_LOCK();
428 	if (LogFile != -1) {
429 		(void)_close(LogFile);
430 		LogFile = -1;
431 	}
432 	LogTag = NULL;
433 	connected = false;
434 	THREAD_UNLOCK();
435 }
436 
437 /* setlogmask -- set the log mask level */
438 int
439 setlogmask(int pmask)
440 {
441 	int omask;
442 
443 	THREAD_LOCK();
444 	omask = LogMask;
445 	if (pmask != 0)
446 		LogMask = pmask;
447 	THREAD_UNLOCK();
448 	return (omask);
449 }
450