Home | History | Annotate | Line # | Download | only in global
      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