xref: /netbsd/external/bsd/ntp/dist/libntp/msyslog.c (revision d539a741)
1 /*	$NetBSD: msyslog.c,v 1.7 2020/05/29 20:15:38 christos Exp $	*/
2 
3 /*
4  * msyslog - either send a message to the terminal or print it on
5  *	     the standard output.
6  *
7  * Converted to use varargs, much better ... jks
8  */
9 
10 #ifdef HAVE_CONFIG_H
11 # include <config.h>
12 #endif
13 
14 #include <sys/types.h>
15 #ifdef HAVE_UNISTD_H
16 # include <unistd.h>
17 #endif
18 #include <stdio.h>
19 
20 #include "ntp_string.h"
21 #include "ntp.h"
22 #include "ntp_debug.h"
23 #include "ntp_syslog.h"
24 
25 #ifdef SYS_WINNT
26 # include <stdarg.h>
27 # include "..\ports\winnt\libntp\messages.h"
28 #endif
29 
30 
31 int	syslogit = TRUE;
32 int	msyslog_term = FALSE;	/* duplicate to stdout/err */
33 int	msyslog_term_pid = TRUE;
34 int	msyslog_include_timestamp = TRUE;
35 FILE *	syslog_file;
36 char *	syslog_fname;
37 char *	syslog_abs_fname;
38 
39 /* libntp default ntp_syslogmask is all bits lit */
40 #define INIT_NTP_SYSLOGMASK	~(u_int32)0
41 u_int32 ntp_syslogmask = INIT_NTP_SYSLOGMASK;
42 
43 extern char const * progname;
44 
45 /* Declare the local functions */
46 void	addto_syslog	(int, const char *);
47 #ifdef VSNPRINTF_PERCENT_M
48 #define format_errmsg(buf, len, fmt, error) (fmt)
49 #else
50 static const char *format_errmsg(char *, size_t, const char *, int)
51     NTP_FORMAT_ARG(3);
52 
53 /* format_errmsg() is under #ifndef VSNPRINTF_PERCENT_M above */
54 static const char *
format_errmsg(char * nfmt,size_t lennfmt,const char * fmt,int errval)55 format_errmsg(
56 	char *		nfmt,
57 	size_t		lennfmt,
58 	const char *	fmt,
59 	int		errval
60 	)
61 {
62 	char errmsg[256];
63 	char c;
64 	char *n;
65 	const char *f;
66 	size_t len;
67 
68 	n = nfmt;
69 	f = fmt;
70 	while ((c = *f++) != '\0' && n < (nfmt + lennfmt - 1)) {
71 		if (c != '%') {
72 			*n++ = c;
73 			continue;
74 		}
75 		if ((c = *f++) != 'm') {
76 			*n++ = '%';
77 			if ('\0' == c)
78 				break;
79 			*n++ = c;
80 			continue;
81 		}
82 		errno_to_str(errval, errmsg, sizeof(errmsg));
83 		len = strlen(errmsg);
84 
85 		/* Make sure we have enough space for the error message */
86 		if ((n + len) < (nfmt + lennfmt - 1)) {
87 			memcpy(n, errmsg, len);
88 			n += len;
89 		}
90 	}
91 	*n = '\0';
92 	return nfmt;
93 }
94 #endif	/* VSNPRINTF_PERCENT_M */
95 
96 
97 /*
98  * errno_to_str() - a thread-safe strerror() replacement.
99  *		    Hides the varied signatures of strerror_r().
100  *		    For Windows, we have:
101  *			#define errno_to_str isc_strerror
102  */
103 #ifndef errno_to_str
104 void
errno_to_str(int err,char * buf,size_t bufsiz)105 errno_to_str(
106 	int	err,
107 	char *	buf,
108 	size_t	bufsiz
109 	)
110 {
111 # if defined(STRERROR_R_CHAR_P) || !HAVE_DECL_STRERROR_R
112 	char *	pstatic;
113 
114 	buf[0] = '\0';
115 #  ifdef STRERROR_R_CHAR_P
116 	pstatic = strerror_r(err, buf, bufsiz);
117 #  else
118 	pstatic = strerror(err);
119 #  endif
120 	if (NULL == pstatic && '\0' == buf[0])
121 		snprintf(buf, bufsiz, "%s(%d): errno %d",
122 #  ifdef STRERROR_R_CHAR_P
123 			 "strerror_r",
124 #  else
125 			 "strerror",
126 #  endif
127 			 err, errno);
128 	/* protect against believing an int return is a pointer */
129 	else if (pstatic != buf && pstatic > (char *)bufsiz)
130 		strlcpy(buf, pstatic, bufsiz);
131 # else
132 	int	rc;
133 
134 	rc = strerror_r(err, buf, bufsiz);
135 	if (rc < 0)
136 		snprintf(buf, bufsiz, "strerror_r(%d): errno %d",
137 			 err, errno);
138 # endif
139 }
140 #endif	/* errno_to_str */
141 
142 
143 /*
144  * addto_syslog()
145  * This routine adds the contents of a buffer to the syslog or an
146  * application-specific logfile.
147  */
148 void
addto_syslog(int level,const char * msg)149 addto_syslog(
150 	int		level,
151 	const char *	msg
152 	)
153 {
154 	static char const *	prevcall_progname;
155 	static char const *	prog;
156 	const char	nl[] = "\n";
157 	const char	empty[] = "";
158 	FILE *		term_file;
159 	int		log_to_term;
160 	int		log_to_file;
161 	int		pid;
162 	const char *	nl_or_empty;
163 	const char *	human_time;
164 
165 	/* setup program basename static var prog if needed */
166 	if (progname != prevcall_progname) {
167 		prevcall_progname = progname;
168 		prog = strrchr(progname, DIR_SEP);
169 		if (prog != NULL)
170 			prog++;
171 		else
172 			prog = progname;
173 	}
174 
175 	log_to_term = msyslog_term;
176 	log_to_file = FALSE;
177 #if !defined(VMS) && !defined(SYS_VXWORKS)
178 	if (syslogit)
179 		syslog(level, "%s", msg);
180 	else
181 #endif
182 		if (syslog_file != NULL)
183 			log_to_file = TRUE;
184 		else
185 			log_to_term = TRUE;
186 #if DEBUG
187 	if (debug > 0)
188 		log_to_term = TRUE;
189 #endif
190 	if (!(log_to_file || log_to_term))
191 		return;
192 
193 	/* syslog() adds the timestamp, name, and pid */
194 	if (msyslog_include_timestamp)
195 		human_time = humanlogtime();
196 	else	/* suppress gcc pot. uninit. warning */
197 		human_time = NULL;
198 	if (msyslog_term_pid || log_to_file)
199 		pid = getpid();
200 	else	/* suppress gcc pot. uninit. warning */
201 		pid = -1;
202 
203 	/* syslog() adds trailing \n if not present */
204 	if ('\n' != msg[strlen(msg) - 1])
205 		nl_or_empty = nl;
206 	else
207 		nl_or_empty = empty;
208 
209 	if (log_to_term) {
210 		term_file = (level <= LOG_ERR)
211 				? stderr
212 				: stdout;
213 		if (msyslog_include_timestamp)
214 			fprintf(term_file, "%s ", human_time);
215 		if (msyslog_term_pid)
216 			fprintf(term_file, "%s[%d]: ", prog, pid);
217 		fprintf(term_file, "%s%s", msg, nl_or_empty);
218 		fflush(term_file);
219 	}
220 
221 	if (log_to_file) {
222 		if (msyslog_include_timestamp)
223 			fprintf(syslog_file, "%s ", human_time);
224 		fprintf(syslog_file, "%s[%d]: %s%s", prog, pid, msg,
225 			nl_or_empty);
226 		fflush(syslog_file);
227 	}
228 }
229 
230 
231 int
mvsnprintf(char * buf,size_t bufsiz,const char * fmt,va_list ap)232 mvsnprintf(
233 	char *		buf,
234 	size_t		bufsiz,
235 	const char *	fmt,
236 	va_list		ap
237 	)
238 {
239 #ifndef VSNPRINTF_PERCENT_M
240 	char		fmtbuf[256];
241 #endif
242 	int		errval;
243 
244 	/*
245 	 * Save the error value as soon as possible
246 	 */
247 #ifdef SYS_WINNT
248 	errval = GetLastError();
249 	if (NO_ERROR == errval)
250 #endif /* SYS_WINNT */
251 		errval = errno;
252 
253 #ifdef VSNPRINTF_PERCENT_M
254 	errno = errval;
255 #endif
256 	return vsnprintf(buf, bufsiz,
257 	    format_errmsg(fmtbuf, sizeof(fmtbuf), fmt, errval), ap);
258 }
259 
260 
261 int
mvfprintf(FILE * fp,const char * fmt,va_list ap)262 mvfprintf(
263 	FILE *		fp,
264 	const char *	fmt,
265 	va_list		ap
266 	)
267 {
268 #ifndef VSNPRINTF_PERCENT_M
269 	char		fmtbuf[256];
270 #endif
271 	int		errval;
272 
273 	/*
274 	 * Save the error value as soon as possible
275 	 */
276 #ifdef SYS_WINNT
277 	errval = GetLastError();
278 	if (NO_ERROR == errval)
279 #endif /* SYS_WINNT */
280 		errval = errno;
281 
282 #ifdef VSNPRINTF_PERCENT_M
283 	errno = errval;
284 #endif
285 	return vfprintf(fp,
286 	    format_errmsg(fmtbuf, sizeof(fmtbuf), fmt, errval), ap);
287 }
288 
289 
290 int
mfprintf(FILE * fp,const char * fmt,...)291 mfprintf(
292 	FILE *		fp,
293 	const char *	fmt,
294 	...
295 	)
296 {
297 	va_list		ap;
298 	int		rc;
299 
300 	va_start(ap, fmt);
301 	rc = mvfprintf(fp, fmt, ap);
302 	va_end(ap);
303 
304 	return rc;
305 }
306 
307 
308 int
mprintf(const char * fmt,...)309 mprintf(
310 	const char *	fmt,
311 	...
312 	)
313 {
314 	va_list		ap;
315 	int		rc;
316 
317 	va_start(ap, fmt);
318 	rc = mvfprintf(stdout, fmt, ap);
319 	va_end(ap);
320 
321 	return rc;
322 }
323 
324 
325 int
msnprintf(char * buf,size_t bufsiz,const char * fmt,...)326 msnprintf(
327 	char *		buf,
328 	size_t		bufsiz,
329 	const char *	fmt,
330 	...
331 	)
332 {
333 	va_list	ap;
334 	int	rc;
335 
336 	va_start(ap, fmt);
337 	rc = mvsnprintf(buf, bufsiz, fmt, ap);
338 	va_end(ap);
339 
340 	return rc;
341 }
342 
343 
344 void
msyslog(int level,const char * fmt,...)345 msyslog(
346 	int		level,
347 	const char *	fmt,
348 	...
349 	)
350 {
351 	char	buf[1024];
352 	va_list	ap;
353 
354 	va_start(ap, fmt);
355 	mvsnprintf(buf, sizeof(buf), fmt, ap);
356 	va_end(ap);
357 	addto_syslog(level, buf);
358 }
359 
360 void
mvsyslog(int level,const char * fmt,va_list ap)361 mvsyslog(
362 	int		level,
363 	const char *	fmt,
364 	va_list		ap
365 	)
366 {
367 	char	buf[1024];
368 	mvsnprintf(buf, sizeof(buf), fmt, ap);
369 	addto_syslog(level, buf);
370 }
371 
372 
373 /*
374  * Initialize the logging
375  *
376  * Called once per process, including forked children.
377  */
378 void
init_logging(const char * name,u_int32 def_syslogmask,int is_daemon)379 init_logging(
380 	const char *	name,
381 	u_int32		def_syslogmask,
382 	int		is_daemon
383 	)
384 {
385 	static int	was_daemon;
386 	char *		cp;
387 	const char *	pname;
388 
389 	/*
390 	 * ntpd defaults to only logging sync-category events, when
391 	 * NLOG() is used to conditionalize.  Other libntp clients
392 	 * leave it alone so that all NLOG() conditionals will fire.
393 	 * This presumes all bits lit in ntp_syslogmask can't be
394 	 * configured via logconfig and all lit is thereby a sentinel
395 	 * that ntp_syslogmask is still at its default from libntp,
396 	 * keeping in mind this function is called in forked children
397 	 * where it has already been called in the parent earlier.
398 	 * Forked children pass 0 for def_syslogmask.
399 	 */
400 	if (INIT_NTP_SYSLOGMASK == ntp_syslogmask &&
401 	    0 != def_syslogmask)
402 		ntp_syslogmask = def_syslogmask; /* set more via logconfig */
403 
404 	/*
405 	 * Logging.  This may actually work on the gizmo board.  Find a name
406 	 * to log with by using the basename
407 	 */
408 	cp = strrchr(name, DIR_SEP);
409 	if (NULL == cp)
410 		pname = name;
411 	else
412 		pname = 1 + cp;	/* skip DIR_SEP */
413 	progname = estrdup(pname);
414 #ifdef SYS_WINNT			/* strip ".exe" */
415 	cp = strrchr(progname, '.');
416 	if (NULL != cp && !strcasecmp(cp, ".exe"))
417 		*cp = '\0';
418 #endif
419 
420 #if !defined(VMS)
421 
422 	if (is_daemon)
423 		was_daemon = TRUE;
424 # ifndef LOG_DAEMON
425 	openlog(progname, LOG_PID);
426 # else /* LOG_DAEMON */
427 
428 #  ifndef LOG_NTP
429 #	define	LOG_NTP LOG_DAEMON
430 #  endif
431 	openlog(progname, LOG_PID | LOG_NDELAY, (was_daemon)
432 						    ? LOG_NTP
433 						    : 0);
434 #  ifdef DEBUG
435 	if (debug)
436 		setlogmask(LOG_UPTO(LOG_DEBUG));
437 	else
438 #  endif /* DEBUG */
439 		setlogmask(LOG_UPTO(LOG_DEBUG)); /* @@@ was INFO */
440 # endif /* LOG_DAEMON */
441 #endif	/* !VMS */
442 }
443 
444 
445 /*
446  * change_logfile()
447  *
448  * Used to change from syslog to a logfile, or from one logfile to
449  * another, and to reopen logfiles after forking.  On systems where
450  * ntpd forks, deals with converting relative logfile paths to
451  * absolute (root-based) because we reopen logfiles after the current
452  * directory has changed.
453  */
454 int
change_logfile(const char * fname,int leave_crumbs)455 change_logfile(
456 	const char *	fname,
457 	int		leave_crumbs
458 	)
459 {
460 	FILE *		new_file;
461 	const char *	log_fname;
462 	char *		abs_fname;
463 #if !defined(SYS_WINNT) && !defined(SYS_VXWORKS) && !defined(VMS)
464 	char		curdir[512];
465 	size_t		cd_octets;
466 	size_t		octets;
467 #endif	/* POSIX */
468 
469 	REQUIRE(fname != NULL);
470 	log_fname = fname;
471 
472 	/*
473 	 * In a forked child of a parent which is logging to a file
474 	 * instead of syslog, syslog_file will be NULL and both
475 	 * syslog_fname and syslog_abs_fname will be non-NULL.
476 	 * If we are given the same filename previously opened
477 	 * and it's still open, there's nothing to do here.
478 	 */
479 	if (syslog_file != NULL && syslog_fname != NULL &&
480 	    0 == strcmp(syslog_fname, log_fname))
481 		return 0;
482 
483 	if (0 == strcmp(log_fname, "stderr")) {
484 		new_file = stderr;
485 		abs_fname = estrdup(log_fname);
486 	} else if (0 == strcmp(log_fname, "stdout")) {
487 		new_file = stdout;
488 		abs_fname = estrdup(log_fname);
489 	} else {
490 		if (syslog_fname != NULL &&
491 		    0 == strcmp(log_fname, syslog_fname))
492 			log_fname = syslog_abs_fname;
493 #if !defined(SYS_WINNT) && !defined(SYS_VXWORKS) && !defined(VMS)
494 		if (log_fname != syslog_abs_fname &&
495 		    DIR_SEP != log_fname[0] &&
496 		    0 != strcmp(log_fname, "stderr") &&
497 		    0 != strcmp(log_fname, "stdout") &&
498 		    NULL != getcwd(curdir, sizeof(curdir))) {
499 			cd_octets = strlen(curdir);
500 			/* trim any trailing '/' */
501 			if (cd_octets > 1 &&
502 			    DIR_SEP == curdir[cd_octets - 1])
503 				cd_octets--;
504 			octets = cd_octets;
505 			octets += 1;	/* separator '/' */
506 			octets += strlen(log_fname);
507 			octets += 1;	/* NUL terminator */
508 			abs_fname = emalloc(octets);
509 			snprintf(abs_fname, octets, "%.*s%c%s",
510 				 (int)cd_octets, curdir, DIR_SEP,
511 				 log_fname);
512 		} else
513 #endif
514 			abs_fname = estrdup(log_fname);
515 		TRACE(1, ("attempting to open log %s\n", abs_fname));
516 		new_file = fopen(abs_fname, "a");
517 	}
518 
519 	if (NULL == new_file) {
520 		free(abs_fname);
521 		return -1;
522 	}
523 
524 	/* leave a pointer in the old log */
525 	if (leave_crumbs && (syslogit || log_fname != syslog_abs_fname))
526 		msyslog(LOG_NOTICE, "switching logging to file %s",
527 			abs_fname);
528 
529 	if (syslog_file != NULL &&
530 	    syslog_file != stderr && syslog_file != stdout &&
531 	    fileno(syslog_file) != fileno(new_file))
532 		fclose(syslog_file);
533 	syslog_file = new_file;
534 	if (log_fname == syslog_abs_fname) {
535 		free(abs_fname);
536 	} else {
537 		if (syslog_abs_fname != NULL &&
538 		    syslog_abs_fname != syslog_fname)
539 			free(syslog_abs_fname);
540 		if (syslog_fname != NULL)
541 			free(syslog_fname);
542 		syslog_fname = estrdup(log_fname);
543 		syslog_abs_fname = abs_fname;
544 	}
545 	syslogit = FALSE;
546 
547 	return 0;
548 }
549 
550 
551 /*
552  * setup_logfile()
553  *
554  * Redirect logging to a file if requested with -l/--logfile or via
555  * ntp.conf logfile directive.
556  *
557  * This routine is invoked three different times in the sequence of a
558  * typical daemon ntpd with DNS lookups to do.  First it is invoked in
559  * the original ntpd process, then again in the daemon after closing
560  * all descriptors.  In both of those cases, ntp.conf has not been
561  * processed, so only -l/--logfile will trigger logfile redirection in
562  * those invocations.  Finally, if DNS names are resolved, the worker
563  * child invokes this routine after its fork and close of all
564  * descriptors.  In this case, ntp.conf has been processed and any
565  * "logfile" directive needs to be honored in the child as well.
566  */
567 void
setup_logfile(const char * name)568 setup_logfile(
569 	const char *	name
570 	)
571 {
572 	if (NULL == syslog_fname && NULL != name) {
573 		if (-1 == change_logfile(name, TRUE))
574 			msyslog(LOG_ERR, "Cannot open log file %s, %m",
575 				name);
576 		return ;
577 	}
578 	if (NULL == syslog_fname)
579 		return;
580 
581 	if (-1 == change_logfile(syslog_fname, FALSE))
582 		msyslog(LOG_ERR, "Cannot reopen log file %s, %m",
583 			syslog_fname);
584 }
585 
586 /* Helper for unit tests, where stdout + stderr are piped to the same
587  * stream.  This works moderately reliable only if both streams are
588  * unbuffered or line buffered.  Unfortunately stdout can be fully
589  * buffered on pipes or files...
590  */
591 int
change_iobufs(int how)592 change_iobufs(
593 	int how
594 	)
595 {
596 	int	retv = 0;
597 
598 #   ifdef HAVE_SETVBUF
599 
600 	int mode;
601 
602 	switch (how) {
603 	case 0 : mode = _IONBF; break; /* no buffering   */
604 	case 1 : mode = _IOLBF; break; /* line buffering */
605 	case 2 : mode = _IOFBF; break; /* full buffering */
606 	default: mode = _IOLBF; break; /* line buffering */
607 	}
608 
609 	retv = 1;
610 	if (setvbuf(stdout, NULL, mode, BUFSIZ) != 0)
611 		retv = -1;
612 	if (setvbuf(stderr, NULL, mode, BUFSIZ) != 0)
613 		retv = -1;
614 
615 #   else
616 
617 	UNUSED_ARG(how);
618 
619 #   endif
620 
621 	return retv;
622 }
623