Home | History | Annotate | Line # | Download | only in master
      1 /*	$NetBSD: multi_server.c,v 1.5 2026/05/09 18:49:17 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	multi_server 3
      6 /* SUMMARY
      7 /*	skeleton multi-threaded mail subsystem
      8 /* SYNOPSIS
      9 /*	#include <mail_server.h>
     10 /*
     11 /*	NORETURN multi_server_main(argc, argv, service, key, value, ...)
     12 /*	int	argc;
     13 /*	char	**argv;
     14 /*	void	(*service)(VSTREAM *stream, char *service_name, char **argv);
     15 /*	int	key;
     16 /*
     17 /*	void	multi_server_disconnect(stream)
     18 /*	VSTREAM *stream;
     19 /*
     20 /*	void	multi_server_drain()
     21 /* DESCRIPTION
     22 /*	This module implements a skeleton for multi-threaded
     23 /*	mail subsystems: mail subsystem programs that service multiple
     24 /*	clients at the same time. The resulting program expects to be run
     25 /*	from the \fBmaster\fR process.
     26 /*
     27 /*	multi_server_main() is the skeleton entry point. It should be
     28 /*	called from the application main program.  The skeleton does all
     29 /*	the generic command-line processing, initialization of
     30 /*	configurable parameters, and connection management.
     31 /*	The skeleton never returns.
     32 /*
     33 /*	Arguments:
     34 /* .IP "void (*service)(VSTREAM *stream, char *service_name, char **argv)"
     35 /*	A pointer to a function that is called by the skeleton each
     36 /*	time a client sends data to the program's service port. The
     37 /*	function is run after the program has optionally dropped its
     38 /*	privileges. This function should not attempt to preserve state
     39 /*	across calls. The stream initial state is non-blocking mode.
     40 /*	The service name argument corresponds to the service name in the
     41 /*	master.cf file.
     42 /*	The argv argument specifies command-line arguments left over
     43 /*	after options processing.
     44 /* .PP
     45 /*	Optional arguments are specified as a null-terminated list
     46 /*	with macros that have zero or more arguments:
     47 /* .IP "CA_MAIL_SERVER_INT_TABLE(CONFIG_INT_TABLE *)"
     48 /*	A table with configurable parameters, to be loaded from the
     49 /*	global Postfix configuration file. Tables are loaded in the
     50 /*	order as specified, and multiple instances of the same type
     51 /*	are allowed.
     52 /* .IP "CA_MAIL_SERVER_LONG_TABLE(CONFIG_LONG_TABLE *)"
     53 /*	A table with configurable parameters, to be loaded from the
     54 /*	global Postfix configuration file. Tables are loaded in the
     55 /*	order as specified, and multiple instances of the same type
     56 /*	are allowed.
     57 /* .IP "CA_MAIL_SERVER_STR_TABLE(CONFIG_STR_TABLE *)"
     58 /*	A table with configurable parameters, to be loaded from the
     59 /*	global Postfix configuration file. Tables are loaded in the
     60 /*	order as specified, and multiple instances of the same type
     61 /*	are allowed.
     62 /* .IP "CA_MAIL_SERVER_BOOL_TABLE(CONFIG_BOOL_TABLE *)"
     63 /*	A table with configurable parameters, to be loaded from the
     64 /*	global Postfix configuration file. Tables are loaded in the
     65 /*	order as specified, and multiple instances of the same type
     66 /*	are allowed.
     67 /* .IP "CA_MAIL_SERVER_TIME_TABLE(CONFIG_TIME_TABLE *)"
     68 /*	A table with configurable parameters, to be loaded from the
     69 /*	global Postfix configuration file. Tables are loaded in the
     70 /*	order as specified, and multiple instances of the same type
     71 /*	are allowed.
     72 /* .IP "CA_MAIL_SERVER_RAW_TABLE(CONFIG_RAW_TABLE *)"
     73 /*	A table with configurable parameters, to be loaded from the
     74 /*	global Postfix configuration file. Tables are loaded in the
     75 /*	order as specified, and multiple instances of the same type
     76 /*	are allowed. Raw parameters are not subjected to $name
     77 /*	evaluation.
     78 /* .IP "CA_MAIL_SERVER_NINT_TABLE(CONFIG_NINT_TABLE *)"
     79 /*	A table with configurable parameters, to be loaded from the
     80 /*	global Postfix configuration file. Tables are loaded in the
     81 /*	order as specified, and multiple instances of the same type
     82 /*	are allowed.
     83 /* .IP "CA_MAIL_SERVER_NBOOL_TABLE(CONFIG_NBOOL_TABLE *)"
     84 /*	A table with configurable parameters, to be loaded from the
     85 /*	global Postfix configuration file. Tables are loaded in the
     86 /*	order as specified, and multiple instances of the same type
     87 /*	are allowed.
     88 /* .IP "CA_MAIL_SERVER_PRE_INIT(void *(char *service_name, char **argv))"
     89 /*	A pointer to a function that is called once
     90 /*	by the skeleton after it has read the global configuration file
     91 /*	and after it has processed command-line arguments, but before
     92 /*	the skeleton has optionally relinquished the process privileges.
     93 /* .sp
     94 /*	Only the last instance of this parameter type is remembered.
     95 /* .IP "CA_MAIL_SERVER_POST_INIT(void *(char *service_name, char **argv))"
     96 /*	A pointer to a function that is called once
     97 /*	by the skeleton after it has optionally relinquished the process
     98 /*	privileges, but before servicing client connection requests.
     99 /* .sp
    100 /*	Only the last instance of this parameter type is remembered.
    101 /* .IP "CA_MAIL_SERVER_LOOP(int *(char *service_name, char **argv))"
    102 /*	A pointer to function that is executed from
    103 /*	within the event loop, whenever an I/O or timer event has happened,
    104 /*	or whenever nothing has happened for a specified amount of time.
    105 /*	The result value of the function specifies how long to wait until
    106 /*	the next event. Specify -1 to wait for "as long as it takes".
    107 /* .sp
    108 /*	Only the last instance of this parameter type is remembered.
    109 /* .IP "CA_MAIL_SERVER_EXIT(void *(char *service_name, char **argv))"
    110 /*	A pointer to function that is executed immediately before normal
    111 /*	process termination.
    112 /* .IP "CA_MAIL_SERVER_PRE_ACCEPT(void *(char *service_name, char **argv))"
    113 /*	Function to be executed prior to accepting a new connection.
    114 /* .sp
    115 /*	Only the last instance of this parameter type is remembered.
    116 /* .IP "CA_MAIL_SERVER_POST_ACCEPT(void *(VSTREAM *stream, char *service_name, char **argv, HTABLE *attr))"
    117 /*	Function to be executed after accepting a new connection.
    118 /*	The stream, service_name and argv arguments are the same
    119 /*	as with the "service" argument. The attr argument is null
    120 /*	or a pointer to a table with 'pass' connection attributes.
    121 /*	The table is destroyed after the function returns.
    122 /* .sp
    123 /*	Only the last instance of this parameter type is remembered.
    124 /* .IP "CA_MAIL_SERVER_PRE_DISCONN(VSTREAM *, char *service_name, char **argv)"
    125 /*	A pointer to a function that is called
    126 /*	by the multi_server_disconnect() function (see below).
    127 /* .sp
    128 /*	Only the last instance of this parameter type is remembered.
    129 /* .IP CA_MAIL_SERVER_IN_FLOW_DELAY
    130 /*	Pause $in_flow_delay seconds when no "mail flow control token"
    131 /*	is available. A token is consumed for each connection request.
    132 /* .IP CA_MAIL_SERVER_SOLITARY
    133 /*	This service must be configured with process limit of 1.
    134 /* .IP CA_MAIL_SERVER_UNLIMITED
    135 /*	This service must be configured with process limit of 0.
    136 /* .IP CA_MAIL_SERVER_PRIVILEGED
    137 /*	This service must be configured as privileged.
    138 /* .IP "CA_MAIL_SERVER_BOUNCE_INIT(const char *, const char **)"
    139 /*	Initialize the DSN filter for the bounce/defer service
    140 /*	clients with the specified map source and map names.
    141 /* .PP
    142 /*	multi_server_disconnect() should be called by the application
    143 /*	to close a client connection.
    144 /*
    145 /*	multi_server_drain() should be called when the application
    146 /*	no longer wishes to accept new client connections. Existing
    147 /*	clients are handled in a background process, and the process
    148 /*	terminates when the last client is disconnected. A non-zero
    149 /*	result means this call should be tried again later.
    150 /*
    151 /*	The var_use_limit variable limits the number of clients that
    152 /*	a server can service before it commits suicide.
    153 /*	This value is taken from the global \fBmain.cf\fR configuration
    154 /*	file. Setting \fBvar_use_limit\fR to zero disables the client limit.
    155 /*
    156 /*	The var_idle_limit variable limits the time that a service
    157 /*	receives no client connection requests before it commits suicide.
    158 /*	This value is taken from the global \fBmain.cf\fR configuration
    159 /*	file. Setting \fBvar_idle_limit\fR to zero disables the idle limit.
    160 /* DIAGNOSTICS
    161 /*	Problems and transactions are logged to \fBsyslogd\fR(8)
    162 /*	or \fBpostlogd\fR(8).
    163 /* SEE ALSO
    164 /*	master(8), master process
    165 /*	postlogd(8), Postfix logging
    166 /*	syslogd(8), system logging
    167 /* LICENSE
    168 /* .ad
    169 /* .fi
    170 /*	The Secure Mailer license must be distributed with this software.
    171 /* AUTHOR(S)
    172 /*	Wietse Venema
    173 /*	IBM T.J. Watson Research
    174 /*	P.O. Box 704
    175 /*	Yorktown Heights, NY 10598, USA
    176 /*
    177 /*	Wietse Venema
    178 /*	Google, Inc.
    179 /*	111 8th Avenue
    180 /*	New York, NY 10011, USA
    181 /*--*/
    182 
    183 /* System library. */
    184 
    185 #include <sys_defs.h>
    186 #include <sys/socket.h>
    187 #include <sys/time.h>			/* select() */
    188 #include <unistd.h>
    189 #include <signal.h>
    190 #include <stdlib.h>
    191 #include <limits.h>
    192 #include <string.h>
    193 #include <errno.h>
    194 #include <fcntl.h>
    195 #include <stdarg.h>
    196 #ifdef STRCASECMP_IN_STRINGS_H
    197 #include <strings.h>
    198 #endif
    199 #include <time.h>
    200 
    201 #ifdef USE_SYS_SELECT_H
    202 #include <sys/select.h>			/* select() */
    203 #endif
    204 
    205 /* Utility library. */
    206 
    207 #include <msg.h>
    208 #include <msg_vstream.h>
    209 #include <chroot_uid.h>
    210 #include <listen.h>
    211 #include <events.h>
    212 #include <vstring.h>
    213 #include <vstream.h>
    214 #include <msg_vstream.h>
    215 #include <mymalloc.h>
    216 #include <iostuff.h>
    217 #include <stringops.h>
    218 #include <sane_accept.h>
    219 #include <myflock.h>
    220 #include <safe_open.h>
    221 #include <listen.h>
    222 #include <watchdog.h>
    223 #include <split_at.h>
    224 
    225 /* Global library. */
    226 
    227 #include <mail_task.h>
    228 #include <debug_process.h>
    229 #include <mail_params.h>
    230 #include <mail_conf.h>
    231 #include <mail_dict.h>
    232 #include <timed_ipc.h>
    233 #include <resolve_local.h>
    234 #include <mail_flow.h>
    235 #include <mail_version.h>
    236 #include <bounce.h>
    237 #include <maillog_client.h>
    238 
    239 /* Process manager. */
    240 
    241 #include "master_proto.h"
    242 
    243 /* Application-specific */
    244 
    245 #include "mail_server.h"
    246 
    247  /*
    248   * Global state.
    249   */
    250 static int client_count;
    251 static int use_count;
    252 static int socket_count = 1;
    253 
    254 static void (*multi_server_service) (VSTREAM *, char *, char **);
    255 static char *multi_server_name;
    256 static char **multi_server_argv;
    257 static void (*multi_server_accept) (int, void *);
    258 static void (*multi_server_onexit) (char *, char **);
    259 static void (*multi_server_pre_accept) (char *, char **);
    260 static void (*multi_server_post_accept) (VSTREAM *, char *, char **, HTABLE *);
    261 static VSTREAM *multi_server_lock;
    262 static int multi_server_in_flow_delay;
    263 static unsigned multi_server_generation;
    264 static void (*multi_server_pre_disconn) (VSTREAM *, char *, char **);
    265 static int multi_server_drain_was_called = 0;
    266 
    267 /* multi_server_exit - normal termination */
    268 
    269 static NORETURN multi_server_exit(void)
    270 {
    271     if (multi_server_onexit)
    272 	multi_server_onexit(multi_server_name, multi_server_argv);
    273     exit(0);
    274 }
    275 
    276 /* multi_server_abort - terminate after abnormal master exit */
    277 
    278 static void multi_server_abort(int unused_event, void *unused_context)
    279 {
    280     if (msg_verbose)
    281 	msg_info("master disconnect -- exiting");
    282     multi_server_exit();
    283 }
    284 
    285 /* multi_server_timeout - idle time exceeded */
    286 
    287 static void multi_server_timeout(int unused_event, void *unused_context)
    288 {
    289     if (msg_verbose)
    290 	msg_info("idle timeout -- exiting");
    291     multi_server_exit();
    292 }
    293 
    294 /* multi_server_drain - stop accepting new clients */
    295 
    296 int     multi_server_drain(void)
    297 {
    298     const char *myname = "multi_server_drain";
    299     int     fd;
    300 
    301     if (multi_server_drain_was_called)
    302 	return (0);
    303 
    304     switch (fork()) {
    305 	/* Try again later. */
    306     case -1:
    307 	return (-1);
    308 	/* Finish existing clients in the background, then terminate. */
    309     case 0:
    310 	(void) msg_cleanup((MSG_CLEANUP_FN) 0);
    311 	event_fork();
    312 	for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
    313 	    event_disable_readwrite(fd);
    314 	    (void) close(fd);
    315 	    /* Play safe - don't reuse this file number. */
    316 	    if (DUP2(STDIN_FILENO, fd) < 0)
    317 		msg_warn("%s: dup2(%d, %d): %m", myname, STDIN_FILENO, fd);
    318 	}
    319 	var_use_limit = 1;
    320 	multi_server_drain_was_called = 1;
    321 	return (0);
    322 	/* Let the master start a new process. */
    323     default:
    324 	exit(0);
    325     }
    326 }
    327 
    328 /* multi_server_disconnect - terminate client session */
    329 
    330 void    multi_server_disconnect(VSTREAM *stream)
    331 {
    332     if (msg_verbose)
    333 	msg_info("connection closed fd %d", vstream_fileno(stream));
    334     if (multi_server_pre_disconn)
    335 	multi_server_pre_disconn(stream, multi_server_name, multi_server_argv);
    336     event_disable_readwrite(vstream_fileno(stream));
    337     (void) vstream_fclose(stream);
    338     client_count--;
    339     /* Avoid integer wrap-around in a persistent process.  */
    340     if (use_count < INT_MAX)
    341 	use_count++;
    342     if (client_count == 0 && var_idle_limit > 0)
    343 	event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
    344 }
    345 
    346 /* multi_server_execute - in case (char *) != (struct *) */
    347 
    348 static void multi_server_execute(int unused_event, void *context)
    349 {
    350     VSTREAM *stream = (VSTREAM *) context;
    351 
    352     if (multi_server_lock != 0
    353 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
    354 		   MYFLOCK_OP_NONE) < 0)
    355 	msg_fatal("select unlock: %m");
    356 
    357     /*
    358      * Do not bother the application when the client disconnected. Don't drop
    359      * the already accepted client request after "postfix reload"; that would
    360      * be rude.
    361      */
    362     if (peekfd(vstream_fileno(stream)) > 0) {
    363 	if (master_notify(var_pid, multi_server_generation, MASTER_STAT_TAKEN) < 0)
    364 	     /* void */ ;
    365 	multi_server_service(stream, multi_server_name, multi_server_argv);
    366 	if (master_notify(var_pid, multi_server_generation, MASTER_STAT_AVAIL) < 0)
    367 	    multi_server_abort(EVENT_NULL_TYPE, EVENT_NULL_CONTEXT);
    368     } else {
    369 	multi_server_disconnect(stream);
    370     }
    371 }
    372 
    373 /* multi_server_enable_read - enable read events */
    374 
    375 static void multi_server_enable_read(int unused_event, void *context)
    376 {
    377     VSTREAM *stream = (VSTREAM *) context;
    378 
    379     event_enable_read(vstream_fileno(stream), multi_server_execute, (void *) stream);
    380 }
    381 
    382 /* multi_server_wakeup - wake up application */
    383 
    384 static void multi_server_wakeup(int fd, HTABLE *attr)
    385 {
    386     VSTREAM *stream;
    387     char   *tmp;
    388 
    389 #if defined(F_DUPFD) && (EVENTS_STYLE != EVENTS_STYLE_SELECT)
    390 #ifndef THRESHOLD_FD_WORKAROUND
    391 #define THRESHOLD_FD_WORKAROUND 128
    392 #endif
    393     int     new_fd;
    394 
    395     /*
    396      * Leave some handles < FD_SETSIZE for DBMS libraries, in the unlikely
    397      * case of a multi-server with a thousand clients.
    398      */
    399     if (fd < THRESHOLD_FD_WORKAROUND) {
    400 	if ((new_fd = fcntl(fd, F_DUPFD, THRESHOLD_FD_WORKAROUND)) < 0)
    401 	    msg_fatal("fcntl F_DUPFD: %m");
    402 	(void) close(fd);
    403 	fd = new_fd;
    404     }
    405 #endif
    406     if (msg_verbose)
    407 	msg_info("connection established fd %d", fd);
    408     non_blocking(fd, BLOCKING);
    409     close_on_exec(fd, CLOSE_ON_EXEC);
    410     client_count++;
    411     stream = vstream_fdopen(fd, O_RDWR);
    412     tmp = concatenate(multi_server_name, " socket", (char *) 0);
    413     vstream_control(stream,
    414 		    CA_VSTREAM_CTL_PATH(tmp),
    415 		    CA_VSTREAM_CTL_END);
    416     myfree(tmp);
    417     timed_ipc_setup(stream);
    418     if (multi_server_in_flow_delay && mail_flow_get(1) < 0)
    419 	event_request_timer(multi_server_enable_read, (void *) stream,
    420 			    var_in_flow_delay);
    421     else
    422 	multi_server_enable_read(0, (void *) stream);
    423     if (multi_server_post_accept)
    424 	multi_server_post_accept(stream, multi_server_name, multi_server_argv, attr);
    425     else if (attr)
    426 	msg_warn("service ignores 'pass' connection attributes");
    427     if (attr)
    428 	htable_free(attr, myfree);
    429 }
    430 
    431 /* multi_server_accept_local - accept client connection request */
    432 
    433 static void multi_server_accept_local(int unused_event, void *context)
    434 {
    435     int     listen_fd = CAST_ANY_PTR_TO_INT(context);
    436     int     time_left = -1;
    437     int     fd;
    438 
    439     if (multi_server_drain_was_called)
    440 	return;
    441 
    442     /*
    443      * Be prepared for accept() to fail because some other process already
    444      * got the connection (the number of processes competing for clients is
    445      * kept small, so this is not a "thundering herd" problem). If the
    446      * accept() succeeds, be sure to disable non-blocking I/O, in order to
    447      * minimize confusion.
    448      */
    449     if (client_count == 0 && var_idle_limit > 0)
    450 	time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
    451 
    452     if (multi_server_pre_accept)
    453 	multi_server_pre_accept(multi_server_name, multi_server_argv);
    454     if (multi_server_drain_was_called)
    455 	return;
    456     fd = LOCAL_ACCEPT(listen_fd);
    457     if (multi_server_lock != 0
    458 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
    459 		   MYFLOCK_OP_NONE) < 0)
    460 	msg_fatal("select unlock: %m");
    461     if (fd < 0) {
    462 	if (errno != EAGAIN)
    463 	    msg_error("accept connection: %m");
    464 	if (time_left >= 0)
    465 	    event_request_timer(multi_server_timeout, (void *) 0, time_left);
    466 	return;
    467     }
    468     multi_server_wakeup(fd, (HTABLE *) 0);
    469 }
    470 
    471 #ifdef MASTER_XPORT_NAME_PASS
    472 
    473 /* multi_server_accept_pass - accept descriptor */
    474 
    475 static void multi_server_accept_pass(int unused_event, void *context)
    476 {
    477     int     listen_fd = CAST_ANY_PTR_TO_INT(context);
    478     int     time_left = -1;
    479     int     fd;
    480     HTABLE *attr = 0;
    481 
    482     if (multi_server_drain_was_called)
    483 	return;
    484 
    485     /*
    486      * Be prepared for accept() to fail because some other process already
    487      * got the connection (the number of processes competing for clients is
    488      * kept small, so this is not a "thundering herd" problem). If the
    489      * accept() succeeds, be sure to disable non-blocking I/O, in order to
    490      * minimize confusion.
    491      */
    492     if (client_count == 0 && var_idle_limit > 0)
    493 	time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
    494 
    495     if (multi_server_pre_accept)
    496 	multi_server_pre_accept(multi_server_name, multi_server_argv);
    497     if (multi_server_drain_was_called)
    498 	return;
    499     fd = pass_accept_attr(listen_fd, &attr);
    500     if (multi_server_lock != 0
    501 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
    502 		   MYFLOCK_OP_NONE) < 0)
    503 	msg_fatal("select unlock: %m");
    504     if (fd < 0) {
    505 	if (errno != EAGAIN)
    506 	    msg_error("accept connection: %m");
    507 	if (time_left >= 0)
    508 	    event_request_timer(multi_server_timeout, (void *) 0, time_left);
    509 	return;
    510     }
    511     multi_server_wakeup(fd, attr);
    512 }
    513 
    514 #endif
    515 
    516 /* multi_server_accept_inet - accept client connection request */
    517 
    518 static void multi_server_accept_inet(int unused_event, void *context)
    519 {
    520     int     listen_fd = CAST_ANY_PTR_TO_INT(context);
    521     int     time_left = -1;
    522     int     fd;
    523 
    524     if (multi_server_drain_was_called)
    525 	return;
    526 
    527     /*
    528      * Be prepared for accept() to fail because some other process already
    529      * got the connection (the number of processes competing for clients is
    530      * kept small, so this is not a "thundering herd" problem). If the
    531      * accept() succeeds, be sure to disable non-blocking I/O, in order to
    532      * minimize confusion.
    533      */
    534     if (client_count == 0 && var_idle_limit > 0)
    535 	time_left = event_cancel_timer(multi_server_timeout, (void *) 0);
    536 
    537     if (multi_server_pre_accept)
    538 	multi_server_pre_accept(multi_server_name, multi_server_argv);
    539     if (multi_server_drain_was_called)
    540 	return;
    541     fd = inet_accept(listen_fd);
    542     if (multi_server_lock != 0
    543 	&& myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
    544 		   MYFLOCK_OP_NONE) < 0)
    545 	msg_fatal("select unlock: %m");
    546     if (fd < 0) {
    547 	if (errno != EAGAIN)
    548 	    msg_error("accept connection: %m");
    549 	if (time_left >= 0)
    550 	    event_request_timer(multi_server_timeout, (void *) 0, time_left);
    551 	return;
    552     }
    553     multi_server_wakeup(fd, (HTABLE *) 0);
    554 }
    555 
    556 /* multi_server_main - the real main program */
    557 
    558 NORETURN multi_server_main(int argc, char **argv, MULTI_SERVER_FN service,...)
    559 {
    560     const char *myname = "multi_server_main";
    561     VSTREAM *stream = 0;
    562     char   *root_dir = 0;
    563     char   *user_name = 0;
    564     int     debug_me = 0;
    565     int     daemon_mode = 1;
    566     char   *service_name = basename(argv[0]);
    567     int     delay;
    568     int     c;
    569     int     fd;
    570     va_list ap;
    571     MAIL_SERVER_INIT_FN pre_init = 0;
    572     MAIL_SERVER_INIT_FN post_init = 0;
    573     MAIL_SERVER_LOOP_FN loop = 0;
    574     int     key;
    575     char   *transport = 0;
    576 
    577 #if 0
    578     char   *lock_path;
    579     VSTRING *why;
    580 
    581 #endif
    582     int     alone = 0;
    583     int     zerolimit = 0;
    584     WATCHDOG *watchdog;
    585     char   *oname_val;
    586     char   *oname;
    587     char   *oval;
    588     const char *err;
    589     char   *generation;
    590     int     msg_vstream_needed = 0;
    591     const char *dsn_filter_title;
    592     const char **dsn_filter_maps;
    593 
    594     /*
    595      * Process environment options as early as we can.
    596      */
    597     if (getenv(CONF_ENV_VERB))
    598 	msg_verbose = 1;
    599     if (getenv(CONF_ENV_DEBUG))
    600 	debug_me = 1;
    601 
    602     /*
    603      * Don't die when a process goes away unexpectedly.
    604      */
    605     signal(SIGPIPE, SIG_IGN);
    606 
    607     /*
    608      * Don't die for frivolous reasons.
    609      */
    610 #ifdef SIGXFSZ
    611     signal(SIGXFSZ, SIG_IGN);
    612 #endif
    613 
    614     /*
    615      * May need this every now and then.
    616      */
    617     var_procname = mystrdup(basename(argv[0]));
    618     set_mail_conf_str(VAR_PROCNAME, var_procname);
    619 
    620     /*
    621      * Initialize logging and exit handler. Do the syslog first, so that its
    622      * initialization completes before we enter the optional chroot jail.
    623      */
    624     maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
    625     if (msg_verbose)
    626 	msg_info("daemon started");
    627 
    628     /*
    629      * Check the Postfix library version as soon as we enable logging.
    630      */
    631     MAIL_VERSION_CHECK;
    632 
    633     /*
    634      * Initialize from the configuration file. Allow command-line options to
    635      * override compiled-in defaults or configured parameter values.
    636      */
    637     mail_conf_suck();
    638 
    639     /*
    640      * After database open error, continue execution with reduced
    641      * functionality.
    642      */
    643     dict_allow_surrogate = 1;
    644 
    645     /*
    646      * Pick up policy settings from master process. Shut up error messages to
    647      * stderr, because no-one is going to see them.
    648      */
    649     opterr = 0;
    650     while ((c = GETOPT(argc, argv, "cdDi:lm:n:o:s:St:uvVz")) > 0) {
    651 	switch (c) {
    652 	case 'c':
    653 	    root_dir = "setme";
    654 	    break;
    655 	case 'd':
    656 	    daemon_mode = 0;
    657 	    break;
    658 	case 'D':
    659 	    debug_me = 1;
    660 	    break;
    661 	case 'i':
    662 	    mail_conf_update(VAR_MAX_IDLE, optarg);
    663 	    break;
    664 	case 'l':
    665 	    alone = 1;
    666 	    break;
    667 	case 'm':
    668 	    mail_conf_update(VAR_MAX_USE, optarg);
    669 	    break;
    670 	case 'n':
    671 	    service_name = optarg;
    672 	    break;
    673 	case 'o':
    674 	    oname_val = mystrdup(optarg);
    675 	    if ((err = split_nameval(oname_val, &oname, &oval)) != 0)
    676 		msg_fatal("invalid \"-o %s\" option value: %s", optarg, err);
    677 	    mail_conf_update(oname, oval);
    678 	    myfree(oname_val);
    679 	    break;
    680 	case 's':
    681 	    if ((socket_count = atoi(optarg)) <= 0)
    682 		msg_fatal("invalid socket_count: %s", optarg);
    683 	    break;
    684 	case 'S':
    685 	    stream = VSTREAM_IN;
    686 	    break;
    687 	case 'u':
    688 	    user_name = "setme";
    689 	    break;
    690 	case 't':
    691 	    transport = optarg;
    692 	    break;
    693 	case 'v':
    694 	    msg_verbose++;
    695 	    break;
    696 	case 'V':
    697 	    if (++msg_vstream_needed == 1)
    698 		msg_vstream_init(mail_task(var_procname), VSTREAM_ERR);
    699 	    break;
    700 	case 'z':
    701 	    zerolimit = 1;
    702 	    break;
    703 	default:
    704 	    msg_fatal("invalid option: %c", optopt);
    705 	    break;
    706 	}
    707     }
    708     set_mail_conf_str(VAR_SERVNAME, service_name);
    709 
    710     /*
    711      * Initialize generic parameters and re-initialize logging in case of a
    712      * non-default program name or logging destination.
    713      */
    714     mail_params_init();
    715     maillog_client_init(mail_task(var_procname), MAILLOG_CLIENT_FLAG_NONE);
    716 
    717     /*
    718      * Register higher-level dictionaries and initialize the support for
    719      * dynamically-loaded dictionaries.
    720      */
    721     mail_dict_init();
    722 
    723     /*
    724      * If not connected to stdin, stdin must not be a terminal.
    725      */
    726     if (daemon_mode && stream == 0 && isatty(STDIN_FILENO)) {
    727 	msg_vstream_init(var_procname, VSTREAM_ERR);
    728 	msg_fatal("do not run this command by hand");
    729     }
    730 
    731     /*
    732      * Application-specific initialization.
    733      */
    734     va_start(ap, service);
    735     while ((key = va_arg(ap, int)) != 0) {
    736 	switch (key) {
    737 	case MAIL_SERVER_INT_TABLE:
    738 	    get_mail_conf_int_table(va_arg(ap, CONFIG_INT_TABLE *));
    739 	    break;
    740 	case MAIL_SERVER_LONG_TABLE:
    741 	    get_mail_conf_long_table(va_arg(ap, CONFIG_LONG_TABLE *));
    742 	    break;
    743 	case MAIL_SERVER_STR_TABLE:
    744 	    get_mail_conf_str_table(va_arg(ap, CONFIG_STR_TABLE *));
    745 	    break;
    746 	case MAIL_SERVER_BOOL_TABLE:
    747 	    get_mail_conf_bool_table(va_arg(ap, CONFIG_BOOL_TABLE *));
    748 	    break;
    749 	case MAIL_SERVER_TIME_TABLE:
    750 	    get_mail_conf_time_table(va_arg(ap, CONFIG_TIME_TABLE *));
    751 	    break;
    752 	case MAIL_SERVER_RAW_TABLE:
    753 	    get_mail_conf_raw_table(va_arg(ap, CONFIG_RAW_TABLE *));
    754 	    break;
    755 	case MAIL_SERVER_NINT_TABLE:
    756 	    get_mail_conf_nint_table(va_arg(ap, CONFIG_NINT_TABLE *));
    757 	    break;
    758 	case MAIL_SERVER_NBOOL_TABLE:
    759 	    get_mail_conf_nbool_table(va_arg(ap, CONFIG_NBOOL_TABLE *));
    760 	    break;
    761 	case MAIL_SERVER_PRE_INIT:
    762 	    pre_init = va_arg(ap, MAIL_SERVER_INIT_FN);
    763 	    break;
    764 	case MAIL_SERVER_POST_INIT:
    765 	    post_init = va_arg(ap, MAIL_SERVER_INIT_FN);
    766 	    break;
    767 	case MAIL_SERVER_LOOP:
    768 	    loop = va_arg(ap, MAIL_SERVER_LOOP_FN);
    769 	    break;
    770 	case MAIL_SERVER_EXIT:
    771 	    multi_server_onexit = va_arg(ap, MAIL_SERVER_EXIT_FN);
    772 	    break;
    773 	case MAIL_SERVER_PRE_ACCEPT:
    774 	    multi_server_pre_accept = va_arg(ap, MAIL_SERVER_ACCEPT_FN);
    775 	    break;
    776 	case MAIL_SERVER_POST_ACCEPT:
    777 	    multi_server_post_accept = va_arg(ap, MAIL_SERVER_POST_ACCEPT_FN);
    778 	    break;
    779 	case MAIL_SERVER_PRE_DISCONN:
    780 	    multi_server_pre_disconn = va_arg(ap, MAIL_SERVER_DISCONN_FN);
    781 	    break;
    782 	case MAIL_SERVER_IN_FLOW_DELAY:
    783 	    multi_server_in_flow_delay = 1;
    784 	    break;
    785 	case MAIL_SERVER_SOLITARY:
    786 	    if (stream == 0 && !alone)
    787 		msg_fatal("service %s requires a process limit of 1",
    788 			  service_name);
    789 	    break;
    790 	case MAIL_SERVER_UNLIMITED:
    791 	    if (stream == 0 && !zerolimit)
    792 		msg_fatal("service %s requires a process limit of 0",
    793 			  service_name);
    794 	    break;
    795 	case MAIL_SERVER_PRIVILEGED:
    796 	    if (user_name)
    797 		msg_fatal("service %s requires privileged operation",
    798 			  service_name);
    799 	    break;
    800 	case MAIL_SERVER_BOUNCE_INIT:
    801 	    dsn_filter_title = va_arg(ap, const char *);
    802 	    dsn_filter_maps = va_arg(ap, const char **);
    803 	    bounce_client_init(dsn_filter_title, *dsn_filter_maps);
    804 	    break;
    805 	default:
    806 	    msg_panic("%s: unknown argument type: %d", myname, key);
    807 	}
    808     }
    809     va_end(ap);
    810 
    811     if (root_dir)
    812 	root_dir = var_queue_dir;
    813     if (user_name)
    814 	user_name = var_mail_owner;
    815 
    816     /*
    817      * Can options be required?
    818      */
    819     if (stream == 0) {
    820 	if (transport == 0)
    821 	    msg_fatal("no transport type specified");
    822 	if (strcasecmp(transport, MASTER_XPORT_NAME_INET) == 0)
    823 	    multi_server_accept = multi_server_accept_inet;
    824 	else if (strcasecmp(transport, MASTER_XPORT_NAME_UNIX) == 0)
    825 	    multi_server_accept = multi_server_accept_local;
    826 #ifdef MASTER_XPORT_NAME_PASS
    827 	else if (strcasecmp(transport, MASTER_XPORT_NAME_PASS) == 0)
    828 	    multi_server_accept = multi_server_accept_pass;
    829 #endif
    830 	else
    831 	    msg_fatal("unsupported transport type: %s", transport);
    832     }
    833 
    834     /*
    835      * Retrieve process generation from environment.
    836      */
    837     if ((generation = getenv(MASTER_GEN_NAME)) != 0) {
    838 	if (!alldig(generation))
    839 	    msg_fatal("bad generation: %s", generation);
    840 	OCTAL_TO_UNSIGNED(multi_server_generation, generation);
    841 	if (msg_verbose)
    842 	    msg_info("process generation: %s (%o)",
    843 		     generation, multi_server_generation);
    844     }
    845 
    846     /*
    847      * Optionally start the debugger on ourself.
    848      */
    849     if (debug_me)
    850 	debug_process();
    851 
    852     /*
    853      * Traditionally, BSD select() can't handle multiple processes selecting
    854      * on the same socket, and wakes up every process in select(). See TCP/IP
    855      * Illustrated volume 2 page 532. We avoid select() collisions with an
    856      * external lock file.
    857      */
    858 
    859     /*
    860      * XXX Can't compete for exclusive access to the listen socket because we
    861      * also have to monitor existing client connections for service requests.
    862      */
    863 #if 0
    864     if (stream == 0 && !alone) {
    865 	lock_path = concatenate(DEF_PID_DIR, "/", transport,
    866 				".", service_name, (char *) 0);
    867 	why = vstring_alloc(1);
    868 	if ((multi_server_lock = safe_open(lock_path, O_CREAT | O_RDWR, 0600,
    869 				      (struct stat *) 0, -1, -1, why)) == 0)
    870 	    msg_fatal("open lock file %s: %s", lock_path, vstring_str(why));
    871 	close_on_exec(vstream_fileno(multi_server_lock), CLOSE_ON_EXEC);
    872 	myfree(lock_path);
    873 	vstring_free(why);
    874     }
    875 #endif
    876 
    877     /*
    878      * Set up call-back info.
    879      */
    880     multi_server_service = service;
    881     multi_server_name = service_name;
    882     multi_server_argv = argv + optind;
    883 
    884     /*
    885      * Run pre-jail initialization.
    886      */
    887     if (chdir(var_queue_dir) < 0)
    888 	msg_fatal("chdir(\"%s\"): %m", var_queue_dir);
    889     if (pre_init)
    890 	pre_init(multi_server_name, multi_server_argv);
    891 
    892     /*
    893      * Optionally, restrict the damage that this process can do.
    894      */
    895     resolve_local_init();
    896     tzset();
    897     chroot_uid(root_dir, user_name);
    898 
    899     /*
    900      * Run post-jail initialization.
    901      */
    902     if (post_init)
    903 	post_init(multi_server_name, multi_server_argv);
    904 
    905     /*
    906      * Are we running as a one-shot server with the client connection on
    907      * standard input? If so, make sure the output is written to stdout so as
    908      * to satisfy common expectation.
    909      */
    910     if (stream != 0) {
    911 	vstream_control(stream,
    912 			CA_VSTREAM_CTL_DOUBLE,
    913 			CA_VSTREAM_CTL_WRITE_FD(STDOUT_FILENO),
    914 			CA_VSTREAM_CTL_END);
    915 	service(stream, multi_server_name, multi_server_argv);
    916 	vstream_fflush(stream);
    917 	multi_server_exit();
    918     }
    919 
    920     /*
    921      * Running as a semi-resident server. Service connection requests.
    922      * Terminate when we have serviced a sufficient number of clients, when
    923      * no-one has been talking to us for a configurable amount of time, or
    924      * when the master process terminated abnormally.
    925      */
    926     if (var_idle_limit > 0)
    927 	event_request_timer(multi_server_timeout, (void *) 0, var_idle_limit);
    928     for (fd = MASTER_LISTEN_FD; fd < MASTER_LISTEN_FD + socket_count; fd++) {
    929 	event_enable_read(fd, multi_server_accept, CAST_INT_TO_VOID_PTR(fd));
    930 	close_on_exec(fd, CLOSE_ON_EXEC);
    931     }
    932     event_enable_read(MASTER_STATUS_FD, multi_server_abort, (void *) 0);
    933     close_on_exec(MASTER_STATUS_FD, CLOSE_ON_EXEC);
    934     close_on_exec(MASTER_FLOW_READ, CLOSE_ON_EXEC);
    935     close_on_exec(MASTER_FLOW_WRITE, CLOSE_ON_EXEC);
    936     watchdog = watchdog_create(var_daemon_timeout, (WATCHDOG_FN) 0, (void *) 0);
    937 
    938     /*
    939      * The event loop, at last.
    940      */
    941     while (var_use_limit == 0 || use_count < var_use_limit || client_count > 0) {
    942 	if (multi_server_lock != 0) {
    943 	    watchdog_stop(watchdog);
    944 	    if (myflock(vstream_fileno(multi_server_lock), INTERNAL_LOCK,
    945 			MYFLOCK_OP_EXCLUSIVE) < 0)
    946 		msg_fatal("select lock: %m");
    947 	}
    948 	watchdog_start(watchdog);
    949 	delay = loop ? loop(multi_server_name, multi_server_argv) : -1;
    950 	event_loop(delay);
    951     }
    952     multi_server_exit();
    953 }
    954