1 /* $NetBSD: maillog_client.c,v 1.4 2025/02/25 19:15:45 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* maillog_client 3 6 /* SUMMARY 7 /* choose between syslog client and postlog client 8 /* SYNOPSIS 9 /* #include <maillog_client.h> 10 /* 11 /* int maillog_client_init( 12 /* const char *progname, 13 /* int flags) 14 /* DESCRIPTION 15 /* maillog_client_init() chooses between logging to the syslog 16 /* service or to the internal postlog service. 17 /* 18 /* maillog_client_init() may be called before configuration 19 /* parameters are initialized. During this time, logging is 20 /* controlled by the presence or absence of POSTLOG_SERVICE 21 /* in the process environment (this is ignored if a program 22 /* runs with set-uid or set-gid permissions). 23 /* 24 /* maillog_client_init() may also be called after configuration 25 /* parameters are initialized. During this time, logging is 26 /* controlled by the "maillog_file" parameter value. 27 /* 28 /* Arguments: 29 /* .IP progname 30 /* The program name that will be prepended to logfile records. 31 /* .IP flags 32 /* Specify one of the following: 33 /* .RS 34 /* .IP MAILLOG_CLIENT_FLAG_NONE 35 /* No special processing. 36 /* .IP MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK 37 /* Try to fall back to writing the "maillog_file" directly, 38 /* if logging to the internal postlog service is enabled, but 39 /* the postlog service is unavailable. If the fallback fails, 40 /* die with a fatal error. 41 /* .RE 42 /* ENVIRONMENT 43 /* .ad 44 /* .fi 45 /* When logging to the internal postlog service is enabled, 46 /* each process exports the following information, to help 47 /* initialize the logging in a child process, before the child 48 /* has initialized its configuration parameters. 49 /* .IP POSTLOG_SERVICE 50 /* The pathname of the public postlog service endpoint, usually 51 /* "$queue_directory/public/$postlog_service_name". 52 /* .IP POSTLOG_HOSTNAME 53 /* The hostname to prepend to information that is sent to the 54 /* internal postlog logging service, usually "$myhostname". 55 /* CONFIGURATION PARAMETERS 56 /* .ad 57 /* .fi 58 /* .IP "maillog_file (empty)" 59 /* The name of an optional logfile. If the value is empty, or 60 /* unitialized and the process environment does not specify 61 /* POSTLOG_SERVICE, the program will log to the syslog service 62 /* instead. 63 /* .IP "myhostname (default: see 'postconf -d' output)" 64 /* The internet hostname of this mail system. 65 /* .IP "postlog_service_name (postlog)" 66 /* The name of the internal postlog logging service. 67 /* SEE ALSO 68 /* msg_syslog(3) syslog client 69 /* msg_logger(3) internal logger 70 /* LICENSE 71 /* .ad 72 /* .fi 73 /* The Secure Mailer license must be distributed with this 74 /* software. 75 /* AUTHOR(S) 76 /* Wietse Venema 77 /* Google, Inc. 78 /* 111 8th Avenue 79 /* New York, NY 10011, USA 80 /* 81 /* Wietse Venema 82 /* porcupine.org 83 /*--*/ 84 85 /* 86 * System library. 87 */ 88 #include <sys_defs.h> 89 #include <stdlib.h> 90 #include <string.h> 91 92 /* 93 * Utility library. 94 */ 95 #include <argv.h> 96 #include <logwriter.h> 97 #include <msg_logger.h> 98 #include <msg_syslog.h> 99 #include <safe.h> 100 #include <stringops.h> 101 102 /* 103 * Global library. 104 */ 105 #include <mail_params.h> 106 #include <mail_proto.h> 107 #include <maillog_client.h> 108 #include <msg.h> 109 110 /* 111 * Using logging to debug logging is painful. 112 */ 113 #define MAILLOG_CLIENT_DEBUG 0 114 115 /* 116 * Application-specific. 117 */ 118 static int maillog_client_flags; 119 120 #define POSTLOG_SERVICE_ENV "POSTLOG_SERVICE" 121 #define POSTLOG_HOSTNAME_ENV "POSTLOG_HOSTNAME" 122 123 /* maillog_client_logwriter_fallback - fall back to logfile writer or bust */ 124 125 static void maillog_client_logwriter_fallback(const char *text) 126 { 127 static int fallback_guard = 0; 128 static VSTREAM *fp; 129 130 /* 131 * Guard against recursive calls. 132 * 133 * If an error happened before the maillog_file parameter was initialized, 134 * or if maillog_file logging is disabled, then we cannot fall back to a 135 * logfile. All we can do is to hope that stderr logging will bring out 136 * the bad news. 137 */ 138 if (fallback_guard++ == 0 && var_maillog_file && *var_maillog_file) { 139 if (text == 0 && fp != 0) { 140 (void) vstream_fclose(fp); 141 fp = 0; 142 } 143 if (fp == 0) { 144 fp = logwriter_open_or_die(var_maillog_file); 145 close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); 146 } 147 if (text && (logwriter_write(fp, text, strlen(text)) != 0 || 148 vstream_fflush(fp) != 0)) { 149 msg_fatal("logfile '%s' write error: %m", var_maillog_file); 150 } 151 fallback_guard = 0; 152 } 153 } 154 155 /* maillog_client_init - set up syslog or internal log client */ 156 157 void maillog_client_init(const char *progname, int flags) 158 { 159 char *import_service_path; 160 char *import_hostname; 161 162 /* 163 * Crucially, only one logger mode can be in effect at any time, 164 * otherwise postlogd(8) may go into a loop. 165 */ 166 enum { 167 MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG, 168 } logger_mode; 169 170 /* 171 * Security: this code may run before the import_environment setting has 172 * taken effect. It has to guard against privilege escalation attacks on 173 * setgid programs, using malicious environment settings. 174 * 175 * Import the postlog service name and hostname from the environment. 176 * 177 * - These will be used and kept if the process has not yet initialized its 178 * configuration parameters. 179 * 180 * - These will be set or updated if the configuration enables postlog 181 * logging. 182 * 183 * - These will be removed if the configuration does not enable postlog 184 * logging. 185 */ 186 if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0 187 && *import_service_path == 0) 188 import_service_path = 0; 189 if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0 190 && *import_hostname == 0) 191 import_hostname = 0; 192 193 #if MAILLOG_CLIENT_DEBUG 194 #define STRING_OR_NULL(s) ((s) ? (s) : "(null)") 195 msg_syslog_init(progname, LOG_PID, LOG_FACILITY); 196 msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path)); 197 msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname)); 198 #endif 199 200 /* 201 * Before configuration parameters are initialized, the logging mode is 202 * controlled by the presence or absence of POSTLOG_SERVICE in the 203 * process environment. After configuration parameters are initialized, 204 * the logging mode is controlled by the "maillog_file" parameter value. 205 * 206 * The configured mode may change after a process is started. The 207 * postlogd(8) server will proxy logging to syslogd where needed. 208 */ 209 if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) { 210 logger_mode = MAILLOG_CLIENT_MODE_SYSLOG; 211 } else { 212 /* var_maillog_file ? *var_maillog_file : import_service_path != 0 */ 213 logger_mode = MAILLOG_CLIENT_MODE_POSTLOG; 214 } 215 216 /* 217 * Postlog logging is enabled. Update the 'progname' as that may have 218 * changed since an earlier call, and update the environment settings if 219 * they differ from configuration settings. This blends two code paths, 220 * one code path where configuration parameters are initialized (the 221 * preferred path), and one code path that uses imports from environment. 222 */ 223 if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) { 224 char *myhostname; 225 char *service_path; 226 227 if (var_maillog_file && *var_maillog_file) { 228 ARGV *good_prefixes = argv_split(var_maillog_file_pfxs, 229 CHARS_COMMA_SP); 230 char **cpp; 231 232 for (cpp = good_prefixes->argv; /* see below */ ; cpp++) { 233 if (*cpp == 0) 234 msg_fatal("%s value '%s' does not match any prefix in %s", 235 VAR_MAILLOG_FILE, var_maillog_file, 236 VAR_MAILLOG_FILE_PFXS); 237 if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0) 238 break; 239 } 240 argv_free(good_prefixes); 241 } 242 if (var_myhostname && *var_myhostname) { 243 myhostname = var_myhostname; 244 } else if ((myhostname = import_hostname) == 0) { 245 myhostname = "amnesiac"; 246 } 247 #if MAILLOG_CLIENT_DEBUG 248 msg_info("myhostname=%s", STRING_OR_NULL(myhostname)); 249 #endif 250 if (var_postlog_service) { 251 service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC, 252 "/", var_postlog_service, (char *) 0); 253 } else { 254 255 /* 256 * var_postlog_service == 0, therefore var_maillog_file == 0. 257 * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file 258 * == 0, therefore import_service_path != 0. 259 */ 260 service_path = import_service_path; 261 } 262 maillog_client_flags = flags; 263 msg_logger_init(progname, myhostname, service_path, 264 (flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ? 265 maillog_client_logwriter_fallback : 266 (MSG_LOGGER_FALLBACK_FN) 0); 267 268 /* 269 * Export or update the exported postlog service pathname and the 270 * hostname, so that a child process can bootstrap postlog logging 271 * before it has processed main.cf and command-line options. 272 */ 273 if (import_service_path == 0 274 || strcmp(service_path, import_service_path) != 0) { 275 #if MAILLOG_CLIENT_DEBUG 276 msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path); 277 #endif 278 if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0) 279 msg_fatal("setenv: %m"); 280 } 281 if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) { 282 #if MAILLOG_CLIENT_DEBUG 283 msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname); 284 #endif 285 if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0) 286 msg_fatal("setenv: %m"); 287 } 288 if (service_path != import_service_path) 289 myfree(service_path); 290 msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW, 291 CA_MSG_LOGGER_CTL_END); 292 } 293 294 /* 295 * Postlog logging is disabled. Silence the msg_logger client, and remove 296 * the environment settings that bootstrap postlog logging in a child 297 * process. 298 */ 299 else { 300 msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END); 301 if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV)) 302 || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV))) 303 msg_fatal("unsetenv: %m"); 304 } 305 306 /* 307 * Syslog logging is enabled. Update the 'progname' as that may have 308 * changed since an earlier call. 309 */ 310 if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) { 311 msg_syslog_init(progname, LOG_PID, LOG_FACILITY); 312 } 313 314 /* 315 * Syslog logging is disabled, silence the syslog client. 316 */ 317 else { 318 msg_syslog_disable(); 319 } 320 } 321