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