/*++ /* NAME /* maillog_client 3 /* SUMMARY /* choose between syslog client and postlog client /* SYNOPSIS /* #include /* /* int maillog_client_init( /* const char *progname, /* int flags) /* DESCRIPTION /* maillog_client_init() chooses between logging to the syslog /* service or to the internal postlog service. /* /* maillog_client_init() may be called before configuration /* parameters are initialized. During this time, logging is /* controlled by the presence or absence of POSTLOG_SERVICE /* in the process environment (this is ignored if a program /* runs with set-uid or set-gid permissions). /* /* maillog_client_init() may also be called after configuration /* parameters are initialized. During this time, logging is /* controlled by the "maillog_file" parameter value. /* /* Arguments: /* .IP progname /* The program name that will be prepended to logfile records. /* .IP flags /* Specify one of the following: /* .RS /* .IP MAILLOG_CLIENT_FLAG_NONE /* No special processing. /* .IP MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK /* Try to fall back to writing the "maillog_file" directly, /* if logging to the internal postlog service is enabled, but /* the postlog service is unavailable. If the fallback fails, /* die with a fatal error. /* .RE /* ENVIRONMENT /* .ad /* .fi /* When logging to the internal postlog service is enabled, /* each process exports the following information, to help /* initialize the logging in a child process, before the child /* has initialized its configuration parameters. /* .IP POSTLOG_SERVICE /* The pathname of the public postlog service endpoint, usually /* "$queue_directory/public/$postlog_service_name". /* .IP POSTLOG_HOSTNAME /* The hostname to prepend to information that is sent to the /* internal postlog logging service, usually "$myhostname". /* CONFIGURATION PARAMETERS /* .ad /* .fi /* .IP "maillog_file (empty)" /* The name of an optional logfile. If the value is empty, or /* unitialized and the process environment does not specify /* POSTLOG_SERVICE, the program will log to the syslog service /* instead. /* .IP "myhostname (default: see 'postconf -d' output)" /* The internet hostname of this mail system. /* .IP "postlog_service_name (postlog)" /* The name of the internal postlog logging service. /* SEE ALSO /* msg_syslog(3) syslog client /* msg_logger(3) internal logger /* 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 library. */ #include #include #include /* * Utility library. */ #include #include #include #include #include #include /* * Global library. */ #include #include #include #include /* * Using logging to debug logging is painful. */ #define MAILLOG_CLIENT_DEBUG 0 /* * Application-specific. */ static int maillog_client_flags; #define POSTLOG_SERVICE_ENV "POSTLOG_SERVICE" #define POSTLOG_HOSTNAME_ENV "POSTLOG_HOSTNAME" /* maillog_client_logwriter_fallback - fall back to logfile writer or bust */ static void maillog_client_logwriter_fallback(const char *text) { static int fallback_guard = 0; static VSTREAM *fp; /* * Guard against recursive calls. * * If an error happened before the maillog_file parameter was initialized, * or if maillog_file logging is disabled, then we cannot fall back to a * logfile. All we can do is to hope that stderr logging will bring out * the bad news. */ if (fallback_guard++ == 0 && var_maillog_file && *var_maillog_file) { if (text == 0 && fp != 0) { (void) vstream_fclose(fp); fp = 0; } if (fp == 0) { fp = logwriter_open_or_die(var_maillog_file); close_on_exec(vstream_fileno(fp), CLOSE_ON_EXEC); } if (text && (logwriter_write(fp, text, strlen(text)) != 0 || vstream_fflush(fp) != 0)) { msg_fatal("logfile '%s' write error: %m", var_maillog_file); } fallback_guard = 0; } } /* maillog_client_init - set up syslog or internal log client */ void maillog_client_init(const char *progname, int flags) { char *import_service_path; char *import_hostname; /* * Crucially, only one logger mode can be in effect at any time, * otherwise postlogd(8) may go into a loop. */ enum { MAILLOG_CLIENT_MODE_SYSLOG, MAILLOG_CLIENT_MODE_POSTLOG, } logger_mode; /* * Security: this code may run before the import_environment setting has * taken effect. It has to guard against privilege escalation attacks on * setgid programs, using malicious environment settings. * * Import the postlog service name and hostname from the environment. * * - These will be used and kept if the process has not yet initialized its * configuration parameters. * * - These will be set or updated if the configuration enables postlog * logging. * * - These will be removed if the configuration does not enable postlog * logging. */ if ((import_service_path = safe_getenv(POSTLOG_SERVICE_ENV)) != 0 && *import_service_path == 0) import_service_path = 0; if ((import_hostname = safe_getenv(POSTLOG_HOSTNAME_ENV)) != 0 && *import_hostname == 0) import_hostname = 0; #if MAILLOG_CLIENT_DEBUG #define STRING_OR_NULL(s) ((s) ? (s) : "(null)") msg_syslog_init(progname, LOG_PID, LOG_FACILITY); msg_info("import_service_path=%s", STRING_OR_NULL(import_service_path)); msg_info("import_hostname=%s", STRING_OR_NULL(import_hostname)); #endif /* * Before configuration parameters are initialized, the logging mode is * controlled by the presence or absence of POSTLOG_SERVICE in the * process environment. After configuration parameters are initialized, * the logging mode is controlled by the "maillog_file" parameter value. * * The configured mode may change after a process is started. The * postlogd(8) server will proxy logging to syslogd where needed. */ if (var_maillog_file ? *var_maillog_file == 0 : import_service_path == 0) { logger_mode = MAILLOG_CLIENT_MODE_SYSLOG; } else { /* var_maillog_file ? *var_maillog_file : import_service_path != 0 */ logger_mode = MAILLOG_CLIENT_MODE_POSTLOG; } /* * Postlog logging is enabled. Update the 'progname' as that may have * changed since an earlier call, and update the environment settings if * they differ from configuration settings. This blends two code paths, * one code path where configuration parameters are initialized (the * preferred path), and one code path that uses imports from environment. */ if (logger_mode == MAILLOG_CLIENT_MODE_POSTLOG) { char *myhostname; char *service_path; if (var_maillog_file && *var_maillog_file) { ARGV *good_prefixes = argv_split(var_maillog_file_pfxs, CHARS_COMMA_SP); char **cpp; for (cpp = good_prefixes->argv; /* see below */ ; cpp++) { if (*cpp == 0) msg_fatal("%s value '%s' does not match any prefix in %s", VAR_MAILLOG_FILE, var_maillog_file, VAR_MAILLOG_FILE_PFXS); if (strncmp(var_maillog_file, *cpp, strlen(*cpp)) == 0) break; } argv_free(good_prefixes); } if (var_myhostname && *var_myhostname) { myhostname = var_myhostname; } else if ((myhostname = import_hostname) == 0) { myhostname = "amnesiac"; } #if MAILLOG_CLIENT_DEBUG msg_info("myhostname=%s", STRING_OR_NULL(myhostname)); #endif if (var_postlog_service) { service_path = concatenate(var_queue_dir, "/", MAIL_CLASS_PUBLIC, "/", var_postlog_service, (char *) 0); } else { /* * var_postlog_service == 0, therefore var_maillog_file == 0. * logger_mode == MAILLOG_CLIENT_MODE_POSTLOG && var_maillog_file == * 0, therefore import_service_path != 0. */ service_path = import_service_path; } maillog_client_flags = flags; msg_logger_init(progname, myhostname, service_path, (flags & MAILLOG_CLIENT_FLAG_LOGWRITER_FALLBACK) ? maillog_client_logwriter_fallback : (MSG_LOGGER_FALLBACK_FN) 0); /* * Export or update the exported postlog service pathname and the * hostname, so that a child process can bootstrap postlog logging * before it has processed main.cf and command-line options. */ if (import_service_path == 0 || strcmp(service_path, import_service_path) != 0) { #if MAILLOG_CLIENT_DEBUG msg_info("export %s=%s", POSTLOG_SERVICE_ENV, service_path); #endif if (setenv(POSTLOG_SERVICE_ENV, service_path, 1) < 0) msg_fatal("setenv: %m"); } if (import_hostname == 0 || strcmp(myhostname, import_hostname) != 0) { #if MAILLOG_CLIENT_DEBUG msg_info("export %s=%s", POSTLOG_HOSTNAME_ENV, myhostname); #endif if (setenv(POSTLOG_HOSTNAME_ENV, myhostname, 1) < 0) msg_fatal("setenv: %m"); } if (service_path != import_service_path) myfree(service_path); msg_logger_control(CA_MSG_LOGGER_CTL_CONNECT_NOW, CA_MSG_LOGGER_CTL_END); } /* * Postlog logging is disabled. Silence the msg_logger client, and remove * the environment settings that bootstrap postlog logging in a child * process. */ else { msg_logger_control(CA_MSG_LOGGER_CTL_DISABLE, CA_MSG_LOGGER_CTL_END); if ((import_service_path && unsetenv(POSTLOG_SERVICE_ENV)) || (import_hostname && unsetenv(POSTLOG_HOSTNAME_ENV))) msg_fatal("unsetenv: %m"); } /* * Syslog logging is enabled. Update the 'progname' as that may have * changed since an earlier call. */ if (logger_mode == MAILLOG_CLIENT_MODE_SYSLOG) { msg_syslog_init(progname, LOG_PID, LOG_FACILITY); } /* * Syslog logging is disabled, silence the syslog client. */ else { msg_syslog_disable(); } }