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