xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/msg_logger.c (revision 67b9b338a7386232ac596b5fd0cd5a9cc8a03c71)
1*67b9b338Schristos /*	$NetBSD: msg_logger.c,v 1.3 2022/10/08 16:12:50 christos Exp $	*/
2f3bc92a4Schristos 
3f3bc92a4Schristos /*++
4f3bc92a4Schristos /* NAME
5f3bc92a4Schristos /*	msg_logger 3
6f3bc92a4Schristos /* SUMMARY
7f3bc92a4Schristos /*	direct diagnostics to logger service
8f3bc92a4Schristos /* SYNOPSIS
9f3bc92a4Schristos /*	#include <msg_logger.h>
10f3bc92a4Schristos /*
11f3bc92a4Schristos /*	void	msg_logger_init(
12f3bc92a4Schristos /*	const char *progname,
13f3bc92a4Schristos /*	const char *hostname,
14f3bc92a4Schristos /*	const char *unix_path,
15f3bc92a4Schristos /*	void	(*fallback)(const char *))
16f3bc92a4Schristos /*
17f3bc92a4Schristos /*	void	msg_logger_control(
18f3bc92a4Schristos /*	int	key,...)
19f3bc92a4Schristos /* DESCRIPTION
20f3bc92a4Schristos /*	This module implements support to report msg(3) diagnostics
21f3bc92a4Schristos /*	through a logger daemon, with an optional fallback mechanism.
22f3bc92a4Schristos /*	The log record format is like traditional syslog:
23f3bc92a4Schristos /*
24f3bc92a4Schristos /* .nf
25f3bc92a4Schristos /*	    Mmm dd host progname[pid]: text...
26f3bc92a4Schristos /* .fi
27f3bc92a4Schristos /*
28f3bc92a4Schristos /*	msg_logger_init() arranges that subsequent msg(3) calls
29f3bc92a4Schristos /*	will write to an internal logging service. This function
30f3bc92a4Schristos /*	may also be used to update msg_logger settings.
31f3bc92a4Schristos /*
32f3bc92a4Schristos /*	Arguments:
33f3bc92a4Schristos /* .IP progname
34f3bc92a4Schristos /*	The program name that is prepended to a log record.
35f3bc92a4Schristos /* .IP hostname
36f3bc92a4Schristos /*	The host name that is prepended to a log record. Only the
37f3bc92a4Schristos /*	first hostname label will be used.
38f3bc92a4Schristos /* .IP unix_path
39f3bc92a4Schristos /*	Pathname of a unix-domain datagram service endpoint. A
40f3bc92a4Schristos /*	typical use case is the pathname of the postlog socket.
41f3bc92a4Schristos /* .IP fallback
42f3bc92a4Schristos /*	Null pointer, or pointer to function that will be called
43f3bc92a4Schristos /*	with a formatted message when the logger service is not
44f3bc92a4Schristos /*	(yet) available. A typical use case is to pass the record
45f3bc92a4Schristos /*	to the logwriter(3) module.
46f3bc92a4Schristos /* .PP
47f3bc92a4Schristos /*	msg_logger_control() makes adjustments to the msg_logger
48f3bc92a4Schristos /*	client. These adjustments remain in effect until the next
49f3bc92a4Schristos /*	msg_logger_init() or msg_logger_control() call. The arguments
50f3bc92a4Schristos /*	are a list of macros with zero or more arguments, terminated
51f3bc92a4Schristos /*	with CA_MSG_LOGGER_CTL_END which has none. The following
52f3bc92a4Schristos /*	lists the names and the types of the corresponding value
53f3bc92a4Schristos /*	arguments.
54f3bc92a4Schristos /*
55f3bc92a4Schristos /*	Arguments:
56f3bc92a4Schristos /* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY
57f3bc92a4Schristos /*	Disable the logging socket, and use the fallback function
58f3bc92a4Schristos /*	only. This remains in effect until the next msg_logger_init()
59f3bc92a4Schristos /*	call.
60f3bc92a4Schristos /* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *))
61f3bc92a4Schristos /*	Override the fallback setting (see above) with the specified
62f3bc92a4Schristos /*	function pointer. This remains in effect until the next
63f3bc92a4Schristos /*	msg_logger_init() or msg_logger_control() call.
64f3bc92a4Schristos /* .IP CA_MSG_LOGGER_CTL_DISABLE
65f3bc92a4Schristos /*	Disable the msg_logger. This remains in effect until the
66f3bc92a4Schristos /*	next msg_logger_init() call.
67f3bc92a4Schristos /* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW
68f3bc92a4Schristos /*	Close the logging socket if it was already open, and open
69f3bc92a4Schristos /*	the logging socket now, if permitted by current settings.
70f3bc92a4Schristos /*	Otherwise, the open is delayed until a logging request.
71f3bc92a4Schristos /* SEE ALSO
72f3bc92a4Schristos /*	msg(3)  diagnostics module
73f3bc92a4Schristos /* BUGS
74f3bc92a4Schristos /*	Output records are truncated to ~2000 characters, because
75f3bc92a4Schristos /*	unlimited logging is a liability.
76f3bc92a4Schristos /* LICENSE
77f3bc92a4Schristos /* .ad
78f3bc92a4Schristos /* .fi
79f3bc92a4Schristos /*	The Secure Mailer license must be distributed with this software.
80f3bc92a4Schristos /* AUTHOR(S)
81f3bc92a4Schristos /*	Wietse Venema
82f3bc92a4Schristos /*	Google, Inc.
83f3bc92a4Schristos /*	111 8th Avenue
84f3bc92a4Schristos /*	New York, NY 10011, USA
85f3bc92a4Schristos /*--*/
86f3bc92a4Schristos 
87f3bc92a4Schristos  /*
88f3bc92a4Schristos   * System libraries.
89f3bc92a4Schristos   */
90f3bc92a4Schristos #include <sys_defs.h>
91f3bc92a4Schristos #include <sys/socket.h>
92f3bc92a4Schristos #include <stdlib.h>
93f3bc92a4Schristos #include <string.h>
94f3bc92a4Schristos #include <time.h>
95f3bc92a4Schristos #include <unistd.h>
96f3bc92a4Schristos 
97f3bc92a4Schristos  /*
98f3bc92a4Schristos   * Application-specific.
99f3bc92a4Schristos   */
100f3bc92a4Schristos #include <connect.h>
101f3bc92a4Schristos #include <logwriter.h>
102f3bc92a4Schristos #include <msg.h>
103f3bc92a4Schristos #include <msg_logger.h>
104f3bc92a4Schristos #include <msg_output.h>
105f3bc92a4Schristos #include <mymalloc.h>
106f3bc92a4Schristos #include <safe.h>
107f3bc92a4Schristos #include <vstream.h>
108f3bc92a4Schristos #include <vstring.h>
109f3bc92a4Schristos 
110f3bc92a4Schristos  /*
111f3bc92a4Schristos   * Saved state from msg_logger_init().
112f3bc92a4Schristos   */
113f3bc92a4Schristos static char *msg_logger_progname;
114f3bc92a4Schristos static char *msg_logger_hostname;
115f3bc92a4Schristos static char *msg_logger_unix_path;
116f3bc92a4Schristos static void (*msg_logger_fallback_fn) (const char *);
117f3bc92a4Schristos static int msg_logger_fallback_only_override = 0;
118f3bc92a4Schristos static int msg_logger_enable = 0;
119f3bc92a4Schristos 
120f3bc92a4Schristos #define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0)
121f3bc92a4Schristos 
122f3bc92a4Schristos  /*
123f3bc92a4Schristos   * Other state.
124f3bc92a4Schristos   */
125f3bc92a4Schristos #define MSG_LOGGER_SOCK_NONE	(-1)
126f3bc92a4Schristos 
127f3bc92a4Schristos static VSTRING *msg_logger_buf;
128f3bc92a4Schristos static int msg_logger_sock = MSG_LOGGER_SOCK_NONE;
129f3bc92a4Schristos 
130f3bc92a4Schristos  /*
131f3bc92a4Schristos   * Safety limit.
132f3bc92a4Schristos   */
133f3bc92a4Schristos #define MSG_LOGGER_RECLEN	2000
134f3bc92a4Schristos 
135f3bc92a4Schristos  /*
136f3bc92a4Schristos   * SLMs.
137f3bc92a4Schristos   */
138f3bc92a4Schristos #define STR(x)	vstring_str(x)
139f3bc92a4Schristos #define LEN(x)	VSTRING_LEN(x)
140f3bc92a4Schristos 
141f3bc92a4Schristos /* msg_logger_connect - connect to logger service */
142f3bc92a4Schristos 
msg_logger_connect(void)143f3bc92a4Schristos static void msg_logger_connect(void)
144f3bc92a4Schristos {
145f3bc92a4Schristos     if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) {
146f3bc92a4Schristos 	msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING);
147f3bc92a4Schristos 	if (msg_logger_sock >= 0)
148f3bc92a4Schristos 	    close_on_exec(msg_logger_sock, CLOSE_ON_EXEC);
149f3bc92a4Schristos     }
150f3bc92a4Schristos }
151f3bc92a4Schristos 
152f3bc92a4Schristos /* msg_logger_disconnect - disconnect from logger service */
153f3bc92a4Schristos 
msg_logger_disconnect(void)154f3bc92a4Schristos static void msg_logger_disconnect(void)
155f3bc92a4Schristos {
156f3bc92a4Schristos     if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
157f3bc92a4Schristos 	(void) close(msg_logger_sock);
158f3bc92a4Schristos 	msg_logger_sock = MSG_LOGGER_SOCK_NONE;
159f3bc92a4Schristos     }
160f3bc92a4Schristos }
161f3bc92a4Schristos 
162f3bc92a4Schristos /* msg_logger_print - log info to service or file */
163f3bc92a4Schristos 
msg_logger_print(int level,const char * text)164f3bc92a4Schristos static void msg_logger_print(int level, const char *text)
165f3bc92a4Schristos {
166f3bc92a4Schristos     time_t  now;
167f3bc92a4Schristos     struct tm *lt;
168f3bc92a4Schristos     ssize_t len;
169f3bc92a4Schristos 
170f3bc92a4Schristos     /*
171f3bc92a4Schristos      * TODO: this should be a reusable NAME_CODE table plus lookup function.
172f3bc92a4Schristos      */
173f3bc92a4Schristos     static int log_level[] = {
174f3bc92a4Schristos 	MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC,
175f3bc92a4Schristos     };
176f3bc92a4Schristos     static char *severity_name[] = {
177f3bc92a4Schristos 	"info", "warning", "error", "fatal", "panic",
178f3bc92a4Schristos     };
179f3bc92a4Schristos 
180f3bc92a4Schristos     /*
181*67b9b338Schristos      * This test is simple enough that we don't bother with unregistering the
182*67b9b338Schristos      * msg_logger_print() function.
183*67b9b338Schristos      */
184*67b9b338Schristos     if (msg_logger_enable == 0)
185*67b9b338Schristos 	return;
186*67b9b338Schristos 
187*67b9b338Schristos     /*
188f3bc92a4Schristos      * Note: there is code in postlogd(8) that attempts to strip off
189f3bc92a4Schristos      * information that is prepended here. If the formatting below is
190f3bc92a4Schristos      * changed, then postlogd needs to be updated as well.
191f3bc92a4Schristos      */
192f3bc92a4Schristos 
193f3bc92a4Schristos     /*
194f3bc92a4Schristos      * Format the time stamp.
195f3bc92a4Schristos      */
196f3bc92a4Schristos     if (time(&now) < 0)
197f3bc92a4Schristos 	msg_fatal("no time: %m");
198f3bc92a4Schristos     lt = localtime(&now);
199f3bc92a4Schristos     VSTRING_RESET(msg_logger_buf);
200f3bc92a4Schristos     if ((len = strftime(vstring_str(msg_logger_buf),
201f3bc92a4Schristos 			vstring_avail(msg_logger_buf),
202f3bc92a4Schristos 			"%b %d %H:%M:%S ", lt)) == 0)
203f3bc92a4Schristos 	msg_fatal("strftime: %m");
204f3bc92a4Schristos     vstring_set_payload_size(msg_logger_buf, len);
205f3bc92a4Schristos 
206f3bc92a4Schristos     /*
207f3bc92a4Schristos      * Format the host name (first name label only).
208f3bc92a4Schristos      */
209f3bc92a4Schristos     vstring_sprintf_append(msg_logger_buf, "%.*s ",
210f3bc92a4Schristos 			   (int) strcspn(msg_logger_hostname, "."),
211f3bc92a4Schristos 			   msg_logger_hostname);
212f3bc92a4Schristos 
213f3bc92a4Schristos     /*
214f3bc92a4Schristos      * Format the message.
215f3bc92a4Schristos      */
216f3bc92a4Schristos     if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0])))
217f3bc92a4Schristos 	msg_panic("msg_logger_print: invalid severity level: %d", level);
218f3bc92a4Schristos 
219f3bc92a4Schristos     if (level == MSG_INFO) {
220f3bc92a4Schristos 	vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s",
221f3bc92a4Schristos 			       msg_logger_progname, (long) getpid(),
222f3bc92a4Schristos 			       (int) MSG_LOGGER_RECLEN, text);
223f3bc92a4Schristos     } else {
224f3bc92a4Schristos 	vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s",
225f3bc92a4Schristos 			       msg_logger_progname, (long) getpid(),
226f3bc92a4Schristos 		       severity_name[level], (int) MSG_LOGGER_RECLEN, text);
227f3bc92a4Schristos     }
228f3bc92a4Schristos 
229f3bc92a4Schristos     /*
230f3bc92a4Schristos      * Connect to logging service, or fall back to direct log. Many systems
231f3bc92a4Schristos      * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no
232f3bc92a4Schristos      * server has opened the endpoint.
233f3bc92a4Schristos      */
234f3bc92a4Schristos     if (MSG_LOGGER_NEED_SOCKET())
235f3bc92a4Schristos 	msg_logger_connect();
236f3bc92a4Schristos     if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) {
237f3bc92a4Schristos 	send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0);
238f3bc92a4Schristos     } else if (msg_logger_fallback_fn) {
239f3bc92a4Schristos 	msg_logger_fallback_fn(STR(msg_logger_buf));
240f3bc92a4Schristos     }
241f3bc92a4Schristos }
242f3bc92a4Schristos 
243f3bc92a4Schristos /* msg_logger_init - initialize */
244f3bc92a4Schristos 
msg_logger_init(const char * progname,const char * hostname,const char * unix_path,void (* fallback)(const char *))245f3bc92a4Schristos void    msg_logger_init(const char *progname, const char *hostname,
246f3bc92a4Schristos 	             const char *unix_path, void (*fallback) (const char *))
247f3bc92a4Schristos {
248f3bc92a4Schristos     static int first_call = 1;
249f3bc92a4Schristos     extern char **environ;
250f3bc92a4Schristos 
251f3bc92a4Schristos     /*
252f3bc92a4Schristos      * XXX If this program is set-gid, then TZ must not be trusted. This
253f3bc92a4Schristos      * scrubbing code is in the wrong place.
254f3bc92a4Schristos      */
255f3bc92a4Schristos     if (first_call) {
256f3bc92a4Schristos 	if (unsafe())
257f3bc92a4Schristos 	    while (getenv("TZ"))		/* There may be multiple. */
258f3bc92a4Schristos 		if (unsetenv("TZ") < 0) {	/* Desperate measures. */
259f3bc92a4Schristos 		    environ[0] = 0;
260f3bc92a4Schristos 		    msg_fatal("unsetenv: %m");
261f3bc92a4Schristos 		}
262f3bc92a4Schristos 	tzset();
263f3bc92a4Schristos     }
264f3bc92a4Schristos 
265f3bc92a4Schristos     /*
266f3bc92a4Schristos      * Save the request info. Use free-after-update because this data will be
267f3bc92a4Schristos      * accessed when mystrdup() runs out of memory.
268f3bc92a4Schristos      */
269f3bc92a4Schristos #define UPDATE_AND_FREE(dst, src) do { \
270f3bc92a4Schristos 	if ((dst) == 0 || strcmp((dst), (src)) != 0) { \
271f3bc92a4Schristos 	    char *_bak = (dst); \
272f3bc92a4Schristos 	    (dst) = mystrdup(src); \
273f3bc92a4Schristos 	    if ((_bak)) myfree(_bak); \
274f3bc92a4Schristos 	} \
275f3bc92a4Schristos     } while (0)
276f3bc92a4Schristos 
277f3bc92a4Schristos     UPDATE_AND_FREE(msg_logger_progname, progname);
278f3bc92a4Schristos     UPDATE_AND_FREE(msg_logger_hostname, hostname);
279f3bc92a4Schristos     UPDATE_AND_FREE(msg_logger_unix_path, unix_path);
280f3bc92a4Schristos     msg_logger_fallback_fn = fallback;
281f3bc92a4Schristos 
282f3bc92a4Schristos     /*
283f3bc92a4Schristos      * One-time activity: register the output handler, and allocate a buffer.
284f3bc92a4Schristos      */
285f3bc92a4Schristos     if (first_call) {
286f3bc92a4Schristos 	first_call = 0;
287f3bc92a4Schristos 	msg_output(msg_logger_print);
288f3bc92a4Schristos 	msg_logger_buf = vstring_alloc(2048);
289f3bc92a4Schristos     }
290f3bc92a4Schristos 
291f3bc92a4Schristos     /*
292f3bc92a4Schristos      * Always.
293f3bc92a4Schristos      */
294f3bc92a4Schristos     msg_logger_enable = 1;
295f3bc92a4Schristos     msg_logger_fallback_only_override = 0;
296f3bc92a4Schristos }
297f3bc92a4Schristos 
298f3bc92a4Schristos /* msg_logger_control - tweak the client */
299f3bc92a4Schristos 
msg_logger_control(int name,...)300f3bc92a4Schristos void    msg_logger_control(int name,...)
301f3bc92a4Schristos {
302f3bc92a4Schristos     const char *myname = "msg_logger_control";
303f3bc92a4Schristos     va_list ap;
304f3bc92a4Schristos 
305f3bc92a4Schristos     /*
306f3bc92a4Schristos      * Overrides remain in effect until the next msg_logger_init() or
307f3bc92a4Schristos      * msg_logger_control() call,
308f3bc92a4Schristos      */
309f3bc92a4Schristos     for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) {
310f3bc92a4Schristos 	switch (name) {
311f3bc92a4Schristos 	case MSG_LOGGER_CTL_FALLBACK_ONLY:
312f3bc92a4Schristos 	    msg_logger_fallback_only_override = 1;
313f3bc92a4Schristos 	    msg_logger_disconnect();
314f3bc92a4Schristos 	    break;
315f3bc92a4Schristos 	case MSG_LOGGER_CTL_FALLBACK_FN:
316f3bc92a4Schristos 	    msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN);
317f3bc92a4Schristos 	    break;
318f3bc92a4Schristos 	case MSG_LOGGER_CTL_DISABLE:
319f3bc92a4Schristos 	    msg_logger_enable = 0;
320f3bc92a4Schristos 	    break;
321f3bc92a4Schristos 	case MSG_LOGGER_CTL_CONNECT_NOW:
322f3bc92a4Schristos 	    msg_logger_disconnect();
323f3bc92a4Schristos 	    if (MSG_LOGGER_NEED_SOCKET())
324f3bc92a4Schristos 		msg_logger_connect();
325f3bc92a4Schristos 	    break;
326f3bc92a4Schristos 	default:
327f3bc92a4Schristos 	    msg_panic("%s: bad name %d", myname, name);
328f3bc92a4Schristos 	}
329f3bc92a4Schristos     }
330f3bc92a4Schristos     va_end(ap);
331f3bc92a4Schristos }
332f3bc92a4Schristos 
333f3bc92a4Schristos #ifdef TEST
334f3bc92a4Schristos 
335f3bc92a4Schristos  /*
336f3bc92a4Schristos   * Proof-of-concept program to test the msg_logger module.
337f3bc92a4Schristos   *
338f3bc92a4Schristos   * Usage: msg_logger hostname unix_path fallback_path text...
339f3bc92a4Schristos   */
340f3bc92a4Schristos static char *fallback_path;
341f3bc92a4Schristos 
fallback(const char * msg)342f3bc92a4Schristos static void fallback(const char *msg)
343f3bc92a4Schristos {
344f3bc92a4Schristos     if (logwriter_one_shot(fallback_path, msg) != 0)
345f3bc92a4Schristos 	msg_fatal("unable to fall back to directly write %s: %m",
346f3bc92a4Schristos 		  fallback_path);
347f3bc92a4Schristos }
348f3bc92a4Schristos 
main(int argc,char ** argv)349f3bc92a4Schristos int     main(int argc, char **argv)
350f3bc92a4Schristos {
351f3bc92a4Schristos     VSTRING *vp = vstring_alloc(256);
352f3bc92a4Schristos 
353f3bc92a4Schristos     if (argc < 4)
354f3bc92a4Schristos 	msg_fatal("usage: %s host port path text to log", argv[0]);
355f3bc92a4Schristos     msg_logger_init(argv[0], argv[1], argv[2], fallback);
356f3bc92a4Schristos     fallback_path = argv[3];
357f3bc92a4Schristos     argc -= 3;
358f3bc92a4Schristos     argv += 3;
359f3bc92a4Schristos     while (--argc && *++argv) {
360f3bc92a4Schristos 	vstring_strcat(vp, *argv);
361f3bc92a4Schristos 	if (argv[1])
362f3bc92a4Schristos 	    vstring_strcat(vp, " ");
363f3bc92a4Schristos     }
364f3bc92a4Schristos     msg_warn("static text");
365f3bc92a4Schristos     msg_warn("dynamic text: >%s<", vstring_str(vp));
366f3bc92a4Schristos     msg_warn("dynamic numeric: >%d<", 42);
367f3bc92a4Schristos     msg_warn("error text: >%m<");
368f3bc92a4Schristos     msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp));
369f3bc92a4Schristos     vstring_free(vp);
370f3bc92a4Schristos     return (0);
371f3bc92a4Schristos }
372f3bc92a4Schristos 
373f3bc92a4Schristos #endif
374