1/*	$OpenBSD: message.c,v 1.30 2021/06/22 20:19:28 jmc Exp $	*/
2
3/*
4 * Copyright (c) 1983 Regents of the University of California.
5 * 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 <errno.h>
33#include <limits.h>
34#include <paths.h>
35#include <stdarg.h>
36#include <stdio.h>
37#include <stdlib.h>
38#include <string.h>
39#include <syslog.h>
40#include <unistd.h>
41
42#include "defs.h"
43
44/*
45 * Message handling functions for both rdist and rdistd.
46 */
47
48
49#define MSGBUFSIZ	32*1024
50
51int			debug = 0;		/* Debugging level */
52int			nerrs = 0;		/* Number of errors */
53
54/*
55 * Message Types
56 */
57struct msgtype {
58	int		mt_type;		/* Type (bit) */
59	char	       *mt_name;		/* Name of message type */
60} msgtypes[] = {
61	{ MT_CHANGE,	"change" },
62	{ MT_INFO,	"info" },
63	{ MT_NOTICE,	"notice" },
64	{ MT_NERROR,	"nerror" },
65	{ MT_FERROR,	"ferror" },
66	{ MT_WARNING,	"warning" },
67	{ MT_VERBOSE,	"verbose" },
68	{ MT_ALL,	"all" },
69	{ MT_DEBUG,	"debug" },
70	{ 0 },
71};
72
73/*
74 * Description of message facilities
75 */
76struct msgfacility {
77	/* compile time initialized data */
78	int		mf_msgfac;		/* One of MF_* from below */
79	char	       *mf_name;		/* Name of this facility */
80	void	      (*mf_sendfunc)		/* Function to send msg */
81			(struct msgfacility *, int, int, char *);
82	/* run time initialized data */
83	int		mf_msgtypes;		/* Bitmask of MT_* from above*/
84	char	       *mf_filename;		/* Name of file */
85	FILE	       *mf_fptr;		/* File pointer to output to */
86};
87
88/*
89 * Message Facilities
90 */
91#define MF_STDOUT	1			/* Standard Output */
92#define MF_NOTIFY	2			/* Notify mail service */
93#define MF_FILE		3			/* A normal file */
94#define MF_SYSLOG	4			/* syslog() */
95
96static void msgsendstdout(struct msgfacility *, int, int, char *);
97static void msgsendsyslog(struct msgfacility *, int, int, char *);
98static void msgsendfile(struct msgfacility *, int, int, char *);
99static void msgsendnotify(struct msgfacility *, int, int, char *);
100
101/*
102 * Message Facilities
103 */
104struct msgfacility msgfacility[] = {
105	{ MF_STDOUT,	"stdout",	msgsendstdout },
106	{ MF_FILE,	"file",		msgsendfile },
107	{ MF_SYSLOG,	"syslog",	msgsendsyslog },
108	{ MF_NOTIFY,	"notify",	msgsendnotify },
109	{ 0 },
110};
111
112static struct msgfacility *getmsgfac(char *);
113static struct msgtype *getmsgtype(char *);
114static char *setmsgtypes(struct msgfacility *, char *);
115static void _message(int, char *);
116static void _debugmsg(int, char *);
117static void _error(const char *);
118static void _fatalerr(const char *);
119
120/*
121 * Print enabled message logging info
122 */
123void
124msgprconfig(void)
125{
126	int i, x;
127	static char buf[MSGBUFSIZ];
128
129	debugmsg(DM_MISC, "Current message logging config:");
130	for (i = 0; msgfacility[i].mf_name; ++i) {
131		(void) snprintf(buf, sizeof(buf), "    %.*s=",
132			       (int)(sizeof(buf) - 7), msgfacility[i].mf_name);
133		for (x = 0; msgtypes[x].mt_name; ++x)
134			if (IS_ON(msgfacility[i].mf_msgtypes,
135				  msgtypes[x].mt_type)) {
136				if (x > 0)
137					(void) strlcat(buf, ",", sizeof(buf));
138				(void) strlcat(buf, msgtypes[x].mt_name,
139				    sizeof(buf));
140			}
141		debugmsg(DM_MISC, "%s", buf);
142	}
143
144}
145
146/*
147 * Get the Message Facility entry "name"
148 */
149static struct msgfacility *
150getmsgfac(char *name)
151{
152	int i;
153
154	for (i = 0; msgfacility[i].mf_name; ++i)
155		if (strcasecmp(name, msgfacility[i].mf_name) == 0)
156			return(&msgfacility[i]);
157
158	return(NULL);
159}
160
161/*
162 * Get the Message Type entry named "name"
163 */
164static struct msgtype *
165getmsgtype(char *name)
166{
167	int i;
168
169	for (i = 0; msgtypes[i].mt_name; ++i)
170		if (strcasecmp(name, msgtypes[i].mt_name) == 0)
171			return(&msgtypes[i]);
172
173	return(NULL);
174}
175
176/*
177 * Set Message Type information for Message Facility "msgfac" as
178 * indicated by string "str".
179 */
180static char *
181setmsgtypes(struct msgfacility *msgfac, char *str)
182{
183	static char ebuf[BUFSIZ];
184	char *cp;
185	char *strptr, *word;
186	struct msgtype *mtp;
187
188	/*
189	 * MF_SYSLOG is the only supported message facility for the server
190	 */
191	if (isserver && (msgfac->mf_msgfac != MF_SYSLOG &&
192			 msgfac->mf_msgfac != MF_FILE)) {
193		(void) snprintf(ebuf, sizeof(ebuf),
194		"The \"%.*s\" message facility cannot be used by the server.",
195			        100, msgfac->mf_name);
196		return(ebuf);
197	}
198
199	strptr = str;
200
201	/*
202	 * Do any necessary Message Facility preparation
203	 */
204	switch(msgfac->mf_msgfac) {
205	case MF_FILE:
206		/*
207		 * The MF_FILE string should look like "<file>=<types>".
208		 */
209		if ((cp = strchr(strptr, '=')) == NULL)
210			return(
211			   "No file name found for \"file\" message facility");
212		*cp++ = CNULL;
213
214		if ((msgfac->mf_fptr = fopen(strptr, "w")) == NULL)
215			fatalerr("Cannot open log file for writing: %s: %s.",
216				 strptr, SYSERR);
217		msgfac->mf_filename = xstrdup(strptr);
218
219		strptr = cp;
220		break;
221
222	case MF_NOTIFY:
223		break;
224
225	case MF_STDOUT:
226		msgfac->mf_fptr = stdout;
227		break;
228
229	case MF_SYSLOG:
230		openlog(progname, LOG_PID, LOG_DAEMON);
231		break;
232	}
233
234	/*
235	 * Parse each type word
236	 */
237	msgfac->mf_msgtypes = 0;	/* Start from scratch */
238	while (strptr) {
239		word = strptr;
240		if ((cp = strchr(strptr, ',')) != NULL)
241			*cp++ = CNULL;
242		strptr = cp;
243
244		if ((mtp = getmsgtype(word)) != NULL) {
245			msgfac->mf_msgtypes |= mtp->mt_type;
246			/*
247			 * XXX This is really a kludge until we add real
248			 * control over debugging.
249			 */
250			if (!debug && isserver &&
251			    strcasecmp(word, "debug") == 0)
252				debug = DM_ALL;
253		} else {
254			(void) snprintf(ebuf, sizeof(ebuf),
255				        "Message type \"%.*s\" is invalid.",
256				        100, word);
257			return(ebuf);
258		}
259	}
260
261	return(NULL);
262}
263
264/*
265 * Parse a message logging option string
266 */
267char *
268msgparseopts(char *msgstr, int doset)
269{
270	static char ebuf[BUFSIZ], msgbuf[MSGBUFSIZ];
271	char *cp, *optstr;
272	char *word;
273	struct msgfacility *msgfac;
274
275	if (msgstr == NULL)
276		return("NULL message string");
277
278	/* strtok() is harmful */
279	(void) strlcpy(msgbuf, msgstr, sizeof(msgbuf));
280
281	/*
282	 * Each <facility>=<types> list is separated by ":".
283	 */
284	for (optstr = strtok(msgbuf, ":"); optstr;
285	     optstr = strtok(NULL, ":")) {
286
287		if ((cp = strchr(optstr, '=')) == NULL)
288			return("No '=' found");
289
290		*cp++ = CNULL;
291		word = optstr;
292		if ((int)strlen(word) <= 0)
293			return("No message facility specified");
294		if ((int)strlen(cp) <= 0)
295			return("No message type specified");
296
297		if ((msgfac = getmsgfac(word)) == NULL) {
298			(void) snprintf(ebuf, sizeof(ebuf),
299				        "%.*s is not a valid message facility",
300				        100, word);
301			return(ebuf);
302		}
303
304		if (doset) {
305			char *mcp;
306
307			if ((mcp = setmsgtypes(msgfac, cp)) != NULL)
308				return(mcp);
309		}
310	}
311
312	if (isserver && debug) {
313		debugmsg(DM_MISC, "%s", getversion());
314		msgprconfig();
315	}
316
317	return(NULL);
318}
319
320/*
321 * Send a message to facility "stdout".
322 * For rdistd, this is really the rdist client.
323 */
324static void
325msgsendstdout(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
326{
327	char cmd;
328
329	if (isserver) {
330		if (rem_w < 0 || IS_ON(flags, MT_NOREMOTE))
331			return;
332
333		cmd = CNULL;
334
335		switch(mtype) {
336		case MT_NERROR:		cmd = C_ERRMSG;		break;
337		case MT_FERROR:		cmd = C_FERRMSG;	break;
338		case MT_NOTICE:		cmd = C_NOTEMSG;	break;
339		case MT_REMOTE:		cmd = C_LOGMSG;		break;
340		}
341
342		if (cmd != CNULL)
343			(void) sendcmd(cmd, "%s", msgbuf);
344	} else {
345		switch(mtype) {
346		case MT_FERROR:
347		case MT_NERROR:
348			if (msgbuf && *msgbuf) {
349				(void) fprintf(stderr, "%s\n", msgbuf);
350				(void) fflush(stderr);
351			}
352			break;
353
354		case MT_DEBUG:
355			/*
356			 * Only things that are strictly MT_DEBUG should
357			 * be shown.
358			 */
359			if (flags != MT_DEBUG)
360				return;
361		case MT_NOTICE:
362		case MT_CHANGE:
363		case MT_INFO:
364		case MT_VERBOSE:
365		case MT_WARNING:
366			if (msgbuf && *msgbuf) {
367				(void) printf("%s\n", msgbuf);
368				(void) fflush(stdout);
369			}
370			break;
371		}
372	}
373}
374
375/*
376 * Send a message to facility "syslog"
377 */
378static void
379msgsendsyslog(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
380{
381	int syslvl = 0;
382
383	if (!msgbuf || !*msgbuf)
384		return;
385
386	switch(mtype) {
387#if	defined(SL_NERROR)
388	case MT_NERROR:		syslvl = SL_NERROR;	break;
389#endif
390#if	defined(SL_FERROR)
391	case MT_FERROR:		syslvl = SL_FERROR;	break;
392#endif
393#if	defined(SL_WARNING)
394	case MT_WARNING:	syslvl = SL_WARNING;	break;
395#endif
396#if	defined(SL_CHANGE)
397	case MT_CHANGE:		syslvl = SL_CHANGE;	break;
398#endif
399#if	defined(SL_INFO)
400	case MT_SYSLOG:
401	case MT_VERBOSE:
402	case MT_INFO:		syslvl = SL_INFO;	break;
403#endif
404#if	defined(SL_NOTICE)
405	case MT_NOTICE:		syslvl = SL_NOTICE;	break;
406#endif
407#if	defined(SL_DEBUG)
408	case MT_DEBUG:		syslvl = SL_DEBUG;	break;
409#endif
410	}
411
412	if (syslvl)
413		syslog(syslvl, "%s", msgbuf);
414}
415
416/*
417 * Send a message to a "file" facility.
418 */
419static void
420msgsendfile(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
421{
422	if (msgfac->mf_fptr == NULL)
423		return;
424
425	if (!msgbuf || !*msgbuf)
426		return;
427
428	(void) fprintf(msgfac->mf_fptr, "%s\n", msgbuf);
429	(void) fflush(msgfac->mf_fptr);
430}
431
432/*
433 * Same method as msgsendfile()
434 */
435static void
436msgsendnotify(struct msgfacility *msgfac, int mtype, int flags, char *msgbuf)
437{
438	char *tempfile;
439
440	if (IS_ON(flags, MT_DEBUG))
441		return;
442
443	if (!msgbuf || !*msgbuf)
444		return;
445
446	if (!msgfac->mf_fptr) {
447		char *cp;
448		int fd;
449		size_t len;
450
451		/*
452		 * Create and open a new temporary file
453		 */
454		if ((cp = getenv("TMPDIR")) == NULL || *cp == '\0')
455			cp = _PATH_TMP;
456		len = strlen(cp) + 1 + sizeof(_RDIST_TMP);
457		tempfile = xmalloc(len);
458		(void) snprintf(tempfile, len, "%s/%s", cp, _RDIST_TMP);
459
460		msgfac->mf_filename = tempfile;
461		if ((fd = mkstemp(msgfac->mf_filename)) == -1 ||
462		    (msgfac->mf_fptr = fdopen(fd, "w")) == NULL)
463		    fatalerr("Cannot open notify file for writing: %s: %s.",
464			msgfac->mf_filename, SYSERR);
465		debugmsg(DM_MISC, "Created notify temp file '%s'",
466			 msgfac->mf_filename);
467	}
468
469	if (msgfac->mf_fptr == NULL)
470		return;
471
472	(void) fprintf(msgfac->mf_fptr, "%s\n", msgbuf);
473	(void) fflush(msgfac->mf_fptr);
474}
475
476/*
477 * Insure currenthost is set to something reasonable.
478 */
479void
480checkhostname(void)
481{
482	static char mbuf[HOST_NAME_MAX+1];
483	char *cp;
484
485	if (!currenthost) {
486		if (gethostname(mbuf, sizeof(mbuf)) == 0) {
487			if ((cp = strchr(mbuf, '.')) != NULL)
488				*cp = CNULL;
489			currenthost = xstrdup(mbuf);
490		} else
491			currenthost = "(unknown)";
492	}
493}
494
495/*
496 * Print a message contained in "msgbuf" if a level "lvl" is set.
497 */
498static void
499_message(int flags, char *msgbuf)
500{
501	int i, x;
502	static char mbuf[2048];
503
504	if (msgbuf && *msgbuf) {
505		/*
506		 * Ensure no stray newlines are present
507		 */
508		msgbuf[strcspn(msgbuf, "\n")] = CNULL;
509
510		checkhostname();
511		if (strncmp(currenthost, msgbuf, strlen(currenthost)) == 0)
512			(void) strlcpy(mbuf, msgbuf, sizeof(mbuf));
513		else
514			(void) snprintf(mbuf, sizeof(mbuf),
515					"%s: %s", currenthost, msgbuf);
516	} else
517		mbuf[0] = '\0';
518
519	/*
520	 * Special case for messages that only get
521	 * logged to the system log facility
522	 */
523	if (IS_ON(flags, MT_SYSLOG)) {
524		msgsendsyslog(NULL, MT_SYSLOG, flags, mbuf);
525		return;
526	}
527
528	/*
529	 * Special cases
530	 */
531	if (isserver && IS_ON(flags, MT_NOTICE)) {
532		msgsendstdout(NULL, MT_NOTICE, flags, mbuf);
533		return;
534	} else if (isserver && IS_ON(flags, MT_REMOTE))
535		msgsendstdout(NULL, MT_REMOTE, flags, mbuf);
536	else if (isserver && IS_ON(flags, MT_NERROR))
537		msgsendstdout(NULL, MT_NERROR, flags, mbuf);
538	else if (isserver && IS_ON(flags, MT_FERROR))
539		msgsendstdout(NULL, MT_FERROR, flags, mbuf);
540
541	/*
542	 * For each Message Facility, check each Message Type to see
543	 * if the bits in "flags" are set.  If so, call the appropriate
544	 * Message Facility to dispatch the message.
545	 */
546	for (i = 0; msgfacility[i].mf_name; ++i)
547		for (x = 0; msgtypes[x].mt_name; ++x)
548			/*
549			 * XXX MT_ALL should not be used directly
550			 */
551			if (msgtypes[x].mt_type != MT_ALL &&
552			    IS_ON(flags, msgtypes[x].mt_type) &&
553			    IS_ON(msgfacility[i].mf_msgtypes,
554				  msgtypes[x].mt_type))
555				(*msgfacility[i].mf_sendfunc)(&msgfacility[i],
556							   msgtypes[x].mt_type,
557							      flags,
558							      mbuf);
559}
560
561/*
562 * Front-end to _message()
563 */
564void
565message(int lvl, const char *fmt, ...)
566{
567	static char buf[MSGBUFSIZ];
568	va_list args;
569
570	if (fmt != NULL) {
571		va_start(args, fmt);
572		(void) vsnprintf(buf, sizeof(buf), fmt, args);
573		va_end(args);
574	}
575
576	_message(lvl, fmt ? buf : NULL);
577}
578
579/*
580 * Display a debugging message
581 */
582static void
583_debugmsg(int lvl, char *buf)
584{
585	if (IS_ON(debug, lvl))
586		_message(MT_DEBUG, buf);
587}
588
589/*
590 * Front-end to _debugmsg()
591 */
592void
593debugmsg(int lvl, const char *fmt, ...)
594{
595	static char buf[MSGBUFSIZ];
596	va_list args;
597
598	va_start(args, fmt);
599	(void) vsnprintf(buf, sizeof(buf), fmt, args);
600	va_end(args);
601
602	_debugmsg(lvl, buf);
603}
604
605/*
606 * Print an error message
607 */
608static void
609_error(const char *msg)
610{
611	static char buf[MSGBUFSIZ];
612
613	nerrs++;
614	buf[0] = CNULL;
615
616	if (msg) {
617		if (isserver)
618			(void) snprintf(buf, sizeof(buf),
619					"REMOTE ERROR: %s", msg);
620		else
621			(void) snprintf(buf, sizeof(buf),
622					"LOCAL ERROR: %s", msg);
623	}
624
625	_message(MT_NERROR, (buf[0]) ? buf : NULL);
626}
627
628/*
629 * Frontend to _error()
630 */
631void
632error(const char *fmt, ...)
633{
634	static char buf[MSGBUFSIZ];
635	va_list args;
636
637	buf[0] = CNULL;
638	va_start(args, fmt);
639	if (fmt)
640		(void) vsnprintf(buf, sizeof(buf), fmt, args);
641	va_end(args);
642
643	_error((buf[0]) ? buf : NULL);
644}
645
646/*
647 * Display a fatal message
648 */
649static void
650_fatalerr(const char *msg)
651{
652	static char buf[MSGBUFSIZ];
653
654	++nerrs;
655
656	if (isserver)
657		(void) snprintf(buf, sizeof(buf), "REMOTE ERROR: %s", msg);
658	else
659		(void) snprintf(buf, sizeof(buf), "LOCAL ERROR: %s", msg);
660
661	_message(MT_FERROR, buf);
662
663	exit(nerrs);
664}
665
666/*
667 * Front-end to _fatalerr()
668 */
669void
670fatalerr(const char *fmt, ...)
671{
672	static char buf[MSGBUFSIZ];
673	va_list args;
674
675	va_start(args, fmt);
676	(void) vsnprintf(buf, sizeof(buf), fmt, args);
677	va_end(args);
678
679	_fatalerr(buf);
680}
681
682/*
683 * Get the name of the file used for notify.
684 * A side effect is that the file pointer to the file
685 * is closed.  We assume this function is only called when
686 * we are ready to read the file.
687 */
688char *
689getnotifyfile(void)
690{
691	int i;
692
693	for (i = 0; msgfacility[i].mf_name; i++)
694		if (msgfacility[i].mf_msgfac == MF_NOTIFY &&
695		    msgfacility[i].mf_fptr) {
696			(void) fclose(msgfacility[i].mf_fptr);
697			msgfacility[i].mf_fptr = NULL;
698			return(msgfacility[i].mf_filename);
699		}
700
701	return(NULL);
702}
703