1/*	$NetBSD: xsyslog.c,v 1.7 2020/03/02 15:30:25 christos Exp $	*/
2
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#if defined(LIBC_SCCS) && !defined(lint)
34#if 0
35static char sccsid[] = "@(#)syslog.c	8.5 (Berkeley) 4/29/95";
36#else
37__RCSID("$NetBSD: xsyslog.c,v 1.7 2020/03/02 15:30:25 christos Exp $");
38#endif
39#endif /* LIBC_SCCS and not lint */
40
41#include "namespace.h"
42#include <sys/types.h>
43#include <sys/param.h>
44#include <sys/socket.h>
45#include <sys/syslog.h>
46#include <sys/uio.h>
47#include <sys/un.h>
48
49#include <errno.h>
50#include <stdio.h>
51#include <stdarg.h>
52#include <string.h>
53#include <fcntl.h>
54#include <unistd.h>
55#include <stdlib.h>
56#include <paths.h>
57#include "syslog_private.h"
58#include "reentrant.h"
59#include "extern.h"
60
61static void
62disconnectlog_r(struct syslog_data *data)
63{
64	/*
65	 * If the user closed the FD and opened another in the same slot,
66	 * that's their problem.  They should close it before calling on
67	 * system services.
68	 */
69	if (data->log_file != -1) {
70		(void)close(data->log_file);
71		data->log_file = -1;
72	}
73	data->log_connected = 0;		/* retry connect */
74}
75
76static void
77connectlog_r(struct syslog_data *data)
78{
79	/* AF_UNIX address of local logger */
80	static const struct sockaddr_un sun = {
81		.sun_family = AF_LOCAL,
82		.sun_len = sizeof(sun),
83		.sun_path = _PATH_LOG,
84	};
85
86	if (data->log_file == -1 || fcntl(data->log_file, F_GETFL, 0) == -1) {
87		if ((data->log_file = socket(AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC,
88		    0)) == -1)
89			return;
90		data->log_connected = 0;
91	}
92	if (!data->log_connected) {
93		if (connect(data->log_file,
94		    (const struct sockaddr *)(const void *)&sun,
95		    (socklen_t)sizeof(sun)) == -1) {
96			(void)close(data->log_file);
97			data->log_file = -1;
98		} else
99			data->log_connected = 1;
100	}
101}
102
103void
104_openlog_unlocked_r(const char *ident, int logstat, int logfac,
105    struct syslog_data *data)
106{
107	if (ident != NULL)
108		data->log_tag = ident;
109	data->log_stat = logstat;
110	if (logfac != 0 && (logfac &~ LOG_FACMASK) == 0)
111		data->log_fac = logfac;
112
113	if (data->log_stat & LOG_NDELAY)	/* open immediately */
114		connectlog_r(data);
115
116	data->log_opened = 1;
117}
118
119void
120_closelog_unlocked_r(struct syslog_data *data)
121{
122	(void)close(data->log_file);
123	data->log_file = -1;
124	data->log_connected = 0;
125}
126
127static __sysloglike(6, 7) void
128_xsyslogp_r(int pri, struct syslog_fun *fun,
129    struct syslog_data *data, const char *msgid,
130    const char *sdfmt, const char *msgfmt, ...)
131{
132	va_list ap;
133	va_start(ap, msgfmt);
134	_vxsyslogp_r(pri, fun, data, msgid, sdfmt, msgfmt, ap);
135	va_end(ap);
136}
137
138void
139_vxsyslogp_r(int pri, struct syslog_fun *fun,
140    struct syslog_data *data, const char *msgid,
141    const char *sdfmt, const char *msgfmt, va_list ap)
142{
143	static const char BRCOSP[] = "]: ";
144	static const char CRLF[] = "\r\n";
145	size_t cnt, prlen, tries;
146	char ch, *p, *t;
147	int fd, saved_errno;
148#define TBUF_LEN	2048
149#define FMT_LEN		1024
150#define MAXTRIES	10
151	char tbuf[TBUF_LEN], fmt_cpy[FMT_LEN], fmt_cat[FMT_LEN] = "";
152	size_t tbuf_left, fmt_left, msgsdlen;
153	char *fmt = fmt_cat;
154	struct iovec iov[7];	/* prog + [ + pid + ]: + fmt + crlf */
155	int opened, iovcnt;
156
157	iovcnt = opened = 0;
158
159#define INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
160	/* Check for invalid bits. */
161	if (pri & ~(LOG_PRIMASK|LOG_FACMASK)) {
162		_xsyslogp_r(INTERNALLOG, &_syslog_ss_fun, data, NULL, NULL,
163		    "Unknown facility/priority: %#x", pri);
164		pri &= LOG_PRIMASK|LOG_FACMASK;
165	}
166
167	/* Check priority against setlogmask values. */
168	if (!(LOG_MASK(LOG_PRI(pri)) & data->log_mask))
169		return;
170
171	saved_errno = errno;
172
173	/* Set default facility if none specified. */
174	if ((pri & LOG_FACMASK) == 0)
175		pri |= data->log_fac;
176
177	/* Build the message. */
178	p = tbuf;
179	tbuf_left = TBUF_LEN;
180
181	prlen = snprintf_ss(p, tbuf_left, "<%d>1 ", pri);
182	DEC();
183
184	prlen = (*fun->timefun)(p, tbuf_left);
185
186	(*fun->lock)(data);
187
188	if (data->log_hostname[0] == '\0' && gethostname(data->log_hostname,
189	    sizeof(data->log_hostname)) == -1) {
190		/* can this really happen? */
191		data->log_hostname[0] = '-';
192		data->log_hostname[1] = '\0';
193	}
194
195	DEC();
196	prlen = snprintf_ss(p, tbuf_left, " %s ", data->log_hostname);
197
198	if (data->log_tag == NULL)
199		data->log_tag = getprogname();
200
201	DEC();
202	prlen = snprintf_ss(p, tbuf_left, "%s ",
203	    data->log_tag ? data->log_tag : "-");
204
205	(*fun->unlock)(data);
206
207	if (data->log_stat & (LOG_PERROR|LOG_CONS)) {
208		iov[iovcnt].iov_base = p;
209		iov[iovcnt].iov_len = prlen - 1;
210		iovcnt++;
211	}
212	DEC();
213
214	if (data->log_stat & LOG_PID) {
215		prlen = snprintf_ss(p, tbuf_left, "%d ", getpid());
216		if (data->log_stat & (LOG_PERROR|LOG_CONS)) {
217			iov[iovcnt].iov_base = __UNCONST("[");
218			iov[iovcnt].iov_len = 1;
219			iovcnt++;
220			iov[iovcnt].iov_base = p;
221			iov[iovcnt].iov_len = prlen - 1;
222			iovcnt++;
223			iov[iovcnt].iov_base = __UNCONST(BRCOSP);
224			iov[iovcnt].iov_len = 3;
225			iovcnt++;
226		}
227	} else {
228		prlen = snprintf_ss(p, tbuf_left, "- ");
229		if (data->log_stat & (LOG_PERROR|LOG_CONS)) {
230			iov[iovcnt].iov_base = __UNCONST(BRCOSP + 1);
231			iov[iovcnt].iov_len = 2;
232			iovcnt++;
233		}
234	}
235	DEC();
236
237	/*
238	 * concat the format strings, then use one vsnprintf()
239	 */
240	if (msgid != NULL && *msgid != '\0') {
241		strlcat(fmt_cat, msgid, FMT_LEN);
242		strlcat(fmt_cat, " ", FMT_LEN);
243	} else
244		strlcat(fmt_cat, "- ", FMT_LEN);
245
246	if (sdfmt != NULL && *sdfmt != '\0') {
247		strlcat(fmt_cat, sdfmt, FMT_LEN);
248	} else
249		strlcat(fmt_cat, "-", FMT_LEN);
250
251	if (data->log_stat & (LOG_PERROR|LOG_CONS))
252		msgsdlen = strlen(fmt_cat) + 1;
253	else
254		msgsdlen = 0;	/* XXX: GCC */
255
256	if (msgfmt != NULL && *msgfmt != '\0') {
257		strlcat(fmt_cat, " ", FMT_LEN);
258		strlcat(fmt_cat, msgfmt, FMT_LEN);
259	}
260
261	/*
262	 * We wouldn't need this mess if printf handled %m, or if
263	 * strerror() had been invented before syslog().
264	 */
265	for (t = fmt_cpy, fmt_left = FMT_LEN; (ch = *fmt) != '\0'; ++fmt) {
266		if (ch == '%' && fmt[1] == 'm') {
267			char buf[256];
268
269			if ((*fun->errfun)(saved_errno, buf, sizeof(buf)) != 0)
270				prlen = snprintf_ss(t, fmt_left, "Error %d",
271				    saved_errno);
272			else
273				prlen = strlcpy(t, buf, fmt_left);
274			if (prlen >= fmt_left)
275				prlen = fmt_left - 1;
276			t += prlen;
277			fmt++;
278			fmt_left -= prlen;
279		} else if (ch == '%' && fmt[1] == '%' && fmt_left > 2) {
280			*t++ = '%';
281			*t++ = '%';
282			fmt++;
283			fmt_left -= 2;
284		} else {
285			if (fmt_left > 1) {
286				*t++ = ch;
287				fmt_left--;
288			}
289		}
290	}
291	*t = '\0';
292
293	prlen = (*fun->prfun)(p, tbuf_left, fmt_cpy, ap);
294	if (data->log_stat & (LOG_PERROR|LOG_CONS)) {
295		iov[iovcnt].iov_base = p + msgsdlen;
296		iov[iovcnt].iov_len = prlen - msgsdlen;
297		iovcnt++;
298	}
299
300	DEC();
301	cnt = p - tbuf;
302
303	/* Output to stderr if requested. */
304	if (data->log_stat & LOG_PERROR) {
305		struct iovec *piov;
306		int piovcnt;
307
308		iov[iovcnt].iov_base = __UNCONST(CRLF + 1);
309		iov[iovcnt].iov_len = 1;
310		if (data->log_stat & LOG_PTRIM) {
311			piov = &iov[iovcnt - 1];
312			piovcnt = 2;
313		} else {
314			piov = iov;
315			piovcnt = iovcnt + 1;
316		}
317		(void)writev(STDERR_FILENO, piov, piovcnt);
318	}
319
320	if (data->log_stat & LOG_NLOG)
321		goto out;
322
323	/* Get connected, output the message to the local logger. */
324	(*fun->lock)(data);
325	opened = !data->log_opened;
326	if (opened)
327		_openlog_unlocked_r(data->log_tag, data->log_stat, 0, data);
328	connectlog_r(data);
329
330	/*
331	 * If the send() failed, there are two likely scenarios:
332	 *  1) syslogd was restarted
333	 *  2) /dev/log is out of socket buffer space
334	 * We attempt to reconnect to /dev/log to take care of
335	 * case #1 and keep send()ing data to cover case #2
336	 * to give syslogd a chance to empty its socket buffer.
337	 */
338	for (tries = 0; tries < MAXTRIES; tries++) {
339		if (send(data->log_file, tbuf, cnt, 0) != -1)
340			break;
341		if (errno != ENOBUFS) {
342			disconnectlog_r(data);
343			connectlog_r(data);
344		} else
345			(void)usleep(1);
346	}
347
348	/*
349	 * Output the message to the console; try not to block
350	 * as a blocking console should not stop other processes.
351	 * Make sure the error reported is the one from the syslogd failure.
352	 */
353	if (tries == MAXTRIES && (data->log_stat & LOG_CONS) &&
354	    (fd = open(_PATH_CONSOLE,
355		O_WRONLY | O_NONBLOCK | O_CLOEXEC, 0)) >= 0) {
356		iov[iovcnt].iov_base = __UNCONST(CRLF);
357		iov[iovcnt].iov_len = 2;
358		(void)writev(fd, iov, iovcnt + 1);
359		(void)close(fd);
360	}
361
362out:
363	if (!(*fun->unlock)(data) && opened)
364		_closelog_unlocked_r(data);
365}
366
367int
368setlogmask_r(int pmask, struct syslog_data *data)
369{
370	int omask;
371
372	omask = data->log_mask;
373	if (pmask != 0)
374		data->log_mask = pmask;
375	return omask;
376}
377