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