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