1 /* $NetBSD: msg_logger.c,v 1.2 2020/03/18 19:05:21 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 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 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 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 * This test is simple enough that we don't bother with unregistering the 172 * msg_logger_print() function. 173 */ 174 if (msg_logger_enable == 0) 175 return; 176 177 /* 178 * TODO: this should be a reusable NAME_CODE table plus lookup function. 179 */ 180 static int log_level[] = { 181 MSG_INFO, MSG_WARN, MSG_ERROR, MSG_FATAL, MSG_PANIC, 182 }; 183 static char *severity_name[] = { 184 "info", "warning", "error", "fatal", "panic", 185 }; 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 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 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 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 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