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