diff options
Diffstat (limited to 'src/util/msg_logger.c')
-rw-r--r-- | src/util/msg_logger.c | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/src/util/msg_logger.c b/src/util/msg_logger.c new file mode 100644 index 0000000..07c9e92 --- /dev/null +++ b/src/util/msg_logger.c @@ -0,0 +1,371 @@ +/*++ +/* NAME +/* msg_logger 3 +/* SUMMARY +/* direct diagnostics to logger service +/* SYNOPSIS +/* #include <msg_logger.h> +/* +/* void msg_logger_init( +/* const char *progname, +/* const char *hostname, +/* const char *unix_path, +/* void (*fallback)(const char *)) +/* +/* void msg_logger_control( +/* int key,...) +/* DESCRIPTION +/* This module implements support to report msg(3) diagnostics +/* through a logger daemon, with an optional fallback mechanism. +/* The log record format is like traditional syslog: +/* +/* .nf +/* Mmm dd host progname[pid]: text... +/* .fi +/* +/* msg_logger_init() arranges that subsequent msg(3) calls +/* will write to an internal logging service. This function +/* may also be used to update msg_logger settings. +/* +/* Arguments: +/* .IP progname +/* The program name that is prepended to a log record. +/* .IP hostname +/* The host name that is prepended to a log record. Only the +/* first hostname label will be used. +/* .IP unix_path +/* Pathname of a unix-domain datagram service endpoint. A +/* typical use case is the pathname of the postlog socket. +/* .IP fallback +/* Null pointer, or pointer to function that will be called +/* with a formatted message when the logger service is not +/* (yet) available. A typical use case is to pass the record +/* to the logwriter(3) module. +/* .PP +/* msg_logger_control() makes adjustments to the msg_logger +/* client. These adjustments remain in effect until the next +/* msg_logger_init() or msg_logger_control() call. The arguments +/* are a list of macros with zero or more arguments, terminated +/* with CA_MSG_LOGGER_CTL_END which has none. The following +/* lists the names and the types of the corresponding value +/* arguments. +/* +/* Arguments: +/* .IP CA_MSG_LOGGER_CTL_FALLBACK_ONLY +/* Disable the logging socket, and use the fallback function +/* only. This remains in effect until the next msg_logger_init() +/* call. +/* .IP CA_MSG_LOGGER_CTL_FALLBACK(void (*)(const char *)) +/* Override the fallback setting (see above) with the specified +/* function pointer. This remains in effect until the next +/* msg_logger_init() or msg_logger_control() call. +/* .IP CA_MSG_LOGGER_CTL_DISABLE +/* Disable the msg_logger. This remains in effect until the +/* next msg_logger_init() call. +/* .IP CA_MSG_LOGGER_CTL_CONNECT_NOW +/* Close the logging socket if it was already open, and open +/* the logging socket now, if permitted by current settings. +/* Otherwise, the open is delayed until a logging request. +/* SEE ALSO +/* msg(3) diagnostics module +/* BUGS +/* Output records are truncated to ~2000 characters, because +/* unlimited logging is a liability. +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* AUTHOR(S) +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + + /* + * System libraries. + */ +#include <sys_defs.h> +#include <sys/socket.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + + /* + * Application-specific. + */ +#include <connect.h> +#include <logwriter.h> +#include <msg.h> +#include <msg_logger.h> +#include <msg_output.h> +#include <mymalloc.h> +#include <safe.h> +#include <vstream.h> +#include <vstring.h> + + /* + * Saved state from msg_logger_init(). + */ +static char *msg_logger_progname; +static char *msg_logger_hostname; +static char *msg_logger_unix_path; +static void (*msg_logger_fallback_fn) (const char *); +static int msg_logger_fallback_only_override = 0; +static int msg_logger_enable = 0; + +#define MSG_LOGGER_NEED_SOCKET() (msg_logger_fallback_only_override == 0) + + /* + * Other state. + */ +#define MSG_LOGGER_SOCK_NONE (-1) + +static VSTRING *msg_logger_buf; +static int msg_logger_sock = MSG_LOGGER_SOCK_NONE; + + /* + * Safety limit. + */ +#define MSG_LOGGER_RECLEN 2000 + + /* + * SLMs. + */ +#define STR(x) vstring_str(x) +#define LEN(x) VSTRING_LEN(x) + +/* msg_logger_connect - connect to logger service */ + +static void msg_logger_connect(void) +{ + if (msg_logger_sock == MSG_LOGGER_SOCK_NONE) { + msg_logger_sock = unix_dgram_connect(msg_logger_unix_path, BLOCKING); + if (msg_logger_sock >= 0) + close_on_exec(msg_logger_sock, CLOSE_ON_EXEC); + } +} + +/* msg_logger_disconnect - disconnect from logger service */ + +static void msg_logger_disconnect(void) +{ + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + (void) close(msg_logger_sock); + msg_logger_sock = MSG_LOGGER_SOCK_NONE; + } +} + +/* msg_logger_print - log info to service or file */ + +static void msg_logger_print(int level, const char *text) +{ + time_t now; + struct tm *lt; + ssize_t len; + + /* + * TODO: this should be a reusable NAME_CODE table plus lookup function. + */ + static int log_level[] = { + MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, + }; + static char *severity_name[] = { + "info", "warning", "error", "fatal", "panic", + }; + + /* + * This test is simple enough that we don't bother with unregistering the + * msg_logger_print() function. + */ + if (msg_logger_enable == 0) + return; + + /* + * Note: there is code in postlogd(8) that attempts to strip off + * information that is prepended here. If the formatting below is + * changed, then postlogd needs to be updated as well. + */ + + /* + * Format the time stamp. + */ + if (time(&now) < 0) + msg_fatal("no time: %m"); + lt = localtime(&now); + VSTRING_RESET(msg_logger_buf); + if ((len = strftime(vstring_str(msg_logger_buf), + vstring_avail(msg_logger_buf), + "%b %d %H:%M:%S ", lt)) == 0) + msg_fatal("strftime: %m"); + vstring_set_payload_size(msg_logger_buf, len); + + /* + * Format the host name (first name label only). + */ + vstring_sprintf_append(msg_logger_buf, "%.*s ", + (int) strcspn(msg_logger_hostname, "."), + msg_logger_hostname); + + /* + * Format the message. + */ + if (level < 0 || level >= (int) (sizeof(log_level) / sizeof(log_level[0]))) + msg_panic("msg_logger_print: invalid severity level: %d", level); + + if (level == MSG_INFO) { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %.*s", + msg_logger_progname, (long) getpid(), + (int) MSG_LOGGER_RECLEN, text); + } else { + vstring_sprintf_append(msg_logger_buf, "%s[%ld]: %s: %.*s", + msg_logger_progname, (long) getpid(), + severity_name[level], (int) MSG_LOGGER_RECLEN, text); + } + + /* + * Connect to logging service, or fall back to direct log. Many systems + * will report ENOENT if the endpoint does not exist, ECONNREFUSED if no + * server has opened the endpoint. + */ + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + if (msg_logger_sock != MSG_LOGGER_SOCK_NONE) { + send(msg_logger_sock, STR(msg_logger_buf), LEN(msg_logger_buf), 0); + } else if (msg_logger_fallback_fn) { + msg_logger_fallback_fn(STR(msg_logger_buf)); + } +} + +/* msg_logger_init - initialize */ + +void msg_logger_init(const char *progname, const char *hostname, + const char *unix_path, void (*fallback) (const char *)) +{ + static int first_call = 1; + extern char **environ; + + /* + * XXX If this program is set-gid, then TZ must not be trusted. This + * scrubbing code is in the wrong place. + */ + if (first_call) { + if (unsafe()) + while (getenv("TZ")) /* There may be multiple. */ + if (unsetenv("TZ") < 0) { /* Desperate measures. */ + environ[0] = 0; + msg_fatal("unsetenv: %m"); + } + tzset(); + } + + /* + * Save the request info. Use free-after-update because this data will be + * accessed when mystrdup() runs out of memory. + */ +#define UPDATE_AND_FREE(dst, src) do { \ + if ((dst) == 0 || strcmp((dst), (src)) != 0) { \ + char *_bak = (dst); \ + (dst) = mystrdup(src); \ + if ((_bak)) myfree(_bak); \ + } \ + } while (0) + + UPDATE_AND_FREE(msg_logger_progname, progname); + UPDATE_AND_FREE(msg_logger_hostname, hostname); + UPDATE_AND_FREE(msg_logger_unix_path, unix_path); + msg_logger_fallback_fn = fallback; + + /* + * One-time activity: register the output handler, and allocate a buffer. + */ + if (first_call) { + first_call = 0; + msg_output(msg_logger_print); + msg_logger_buf = vstring_alloc(2048); + } + + /* + * Always. + */ + msg_logger_enable = 1; + msg_logger_fallback_only_override = 0; +} + +/* msg_logger_control - tweak the client */ + +void msg_logger_control(int name,...) +{ + const char *myname = "msg_logger_control"; + va_list ap; + + /* + * Overrides remain in effect until the next msg_logger_init() or + * msg_logger_control() call, + */ + for (va_start(ap, name); name != MSG_LOGGER_CTL_END; name = va_arg(ap, int)) { + switch (name) { + case MSG_LOGGER_CTL_FALLBACK_ONLY: + msg_logger_fallback_only_override = 1; + msg_logger_disconnect(); + break; + case MSG_LOGGER_CTL_FALLBACK_FN: + msg_logger_fallback_fn = va_arg(ap, MSG_LOGGER_FALLBACK_FN); + break; + case MSG_LOGGER_CTL_DISABLE: + msg_logger_enable = 0; + break; + case MSG_LOGGER_CTL_CONNECT_NOW: + msg_logger_disconnect(); + if (MSG_LOGGER_NEED_SOCKET()) + msg_logger_connect(); + break; + default: + msg_panic("%s: bad name %d", myname, name); + } + } + va_end(ap); +} + +#ifdef TEST + + /* + * Proof-of-concept program to test the msg_logger module. + * + * Usage: msg_logger hostname unix_path fallback_path text... + */ +static char *fallback_path; + +static void fallback(const char *msg) +{ + if (logwriter_one_shot(fallback_path, msg) != 0) + msg_fatal("unable to fall back to directly write %s: %m", + fallback_path); +} + +int main(int argc, char **argv) +{ + VSTRING *vp = vstring_alloc(256); + + if (argc < 4) + msg_fatal("usage: %s host port path text to log", argv[0]); + msg_logger_init(argv[0], argv[1], argv[2], fallback); + fallback_path = argv[3]; + argc -= 3; + argv += 3; + while (--argc && *++argv) { + vstring_strcat(vp, *argv); + if (argv[1]) + vstring_strcat(vp, " "); + } + msg_warn("static text"); + msg_warn("dynamic text: >%s<", vstring_str(vp)); + msg_warn("dynamic numeric: >%d<", 42); + msg_warn("error text: >%m<"); + msg_warn("dynamic: >%s<: error: >%m<", vstring_str(vp)); + vstring_free(vp); + return (0); +} + +#endif |