Home | History | Annotate | Line # | Download | only in proxymap
      1 /*	$NetBSD: proxymap.c,v 1.6 2026/05/09 18:49:19 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	proxymap 8
      6 /* SUMMARY
      7 /*	Postfix lookup table proxy server
      8 /* SYNOPSIS
      9 /*	\fBproxymap\fR [generic Postfix daemon options]
     10 /* DESCRIPTION
     11 /*	The \fBproxymap\fR(8) server provides read-only or read-write
     12 /*	table lookup service to Postfix processes. These services are
     13 /*	implemented with distinct service names: \fBproxymap\fR and
     14 /*	\fBproxywrite\fR, respectively. The purpose of these services is:
     15 /* .IP \(bu
     16 /*	To overcome chroot restrictions. For example, a chrooted SMTP
     17 /*	server needs access to the system passwd file in order to
     18 /*	reject mail for non-existent local addresses, but it is not
     19 /*	practical to maintain a copy of the passwd file in the chroot
     20 /*	jail.  The solution:
     21 /* .sp
     22 /* .nf
     23 /*	local_recipient_maps =
     24 /*	    proxy:unix:passwd.byname $alias_maps
     25 /* .fi
     26 /* .IP \(bu
     27 /*	To consolidate the number of open lookup tables by sharing
     28 /*	one open table among multiple processes. For example, making
     29 /*	mysql connections from every Postfix daemon process results
     30 /*	in "too many connections" errors. The solution:
     31 /* .sp
     32 /* .nf
     33 /*	virtual_alias_maps =
     34 /*	    proxy:mysql:/etc/postfix/virtual_alias.cf
     35 /* .fi
     36 /* .sp
     37 /*	The total number of connections is limited by the number of
     38 /*	proxymap server processes.
     39 /* .IP \(bu
     40 /*	To provide single-updater functionality for lookup tables
     41 /*	that do not reliably support multiple writers (i.e. all
     42 /*	file-based tables that are not based on \fBlmdb\fR).
     43 /* .PP
     44 /*	The \fBproxymap\fR(8) server implements the following requests:
     45 /* .IP "\fBopen\fR \fImaptype:mapname instance-flags\fR"
     46 /*	Open the table with type \fImaptype\fR and name \fImapname\fR,
     47 /*	with initial dictionary flags \fIinstance-flags\fR. The reply
     48 /*	contains the actual dictionary flags (for example, to distinguish
     49 /*	a fixed-string table from a regular-expression table).
     50 /* .IP "\fBlookup\fR \fImaptype:mapname instance-flags request-flags key\fR"
     51 /*	Look up the data stored under the requested key using the
     52 /*	dictionary flags in \fIrequest-flags\fR.
     53 /*	The reply contains the request completion status code, the
     54 /*	resulting dictionary flags, and the lookup result value.
     55 /*	The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same
     56 /*	as with the \fBopen\fR request.
     57 /* .IP "\fBupdate\fR \fImaptype:mapname instance-flags request-flags key value\fR"
     58 /*	Update the data stored under the requested key using the
     59 /*	dictionary flags in \fIrequest-flags\fR.
     60 /*	The reply contains the request completion status code and the
     61 /*	resulting dictionary flags.
     62 /*	The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same
     63 /*	as with the \fBopen\fR request.
     64 /* .sp
     65 /*	To implement single-updater maps, specify a process limit
     66 /*	of 1 in the master.cf file entry for the \fBproxywrite\fR
     67 /*	service.
     68 /* .sp
     69 /*	This request is supported in Postfix 2.5 and later.
     70 /* .IP "\fBdelete\fR \fImaptype:mapname instance-flags request-flags key\fR"
     71 /*	Delete the data stored under the requested key, using the
     72 /*	dictionary flags in \fIrequest-flags\fR.
     73 /*	The reply contains the request completion status code and the
     74 /*	resulting dictionary flags.
     75 /*	The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same
     76 /*	as with the \fBopen\fR request.
     77 /* .sp
     78 /*	This request is supported in Postfix 2.5 and later.
     79 /* .IP "\fBsequence\fR \fImaptype:mapname instance-flags request-flags function\fR"
     80 /*	Iterate over the specified database, using the dictionary flags
     81 /*	in \fIrequest-flags\fR. The \fIfunction\fR is either
     82 /*	DICT_SEQ_FUN_FIRST or DICT_SEQ_FUN_NEXT.
     83 /*	The reply contains the request completion status code, the
     84 /*	resulting dictionary flags, and a lookup key and result value
     85 /*	if found.
     86 /*	The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same
     87 /*	as with the \fBopen\fR request.
     88 /* .sp
     89 /*	This request is supported in Postfix 2.9 and later.
     90 /* .IP "Not implemented: close"
     91 /*	There is no \fBclose\fR request, nor are tables implicitly closed
     92 /*	when a client disconnects. The purpose is to share tables among
     93 /*	multiple client processes. Due to the absence of an explicit or
     94 /*	implicit \fBclose\fR, updates are forced to be synchronous.
     95 /* .PP
     96 /*	The request completion status is one of OK, RETRY, NOKEY
     97 /*	(lookup failed because the key was not found), BAD (malformed
     98 /*	request) or DENY (the table is not approved for proxy read
     99 /*	or update access).
    100 /* SERVER PROCESS MANAGEMENT
    101 /* .ad
    102 /* .fi
    103 /*	\fBproxymap\fR(8) servers run under control by the Postfix
    104 /*	\fBmaster\fR(8)
    105 /*	server.  Each server can handle multiple simultaneous connections.
    106 /*	When all servers are busy while a client connects, the \fBmaster\fR(8)
    107 /*	creates a new \fBproxymap\fR(8) server process, provided that the
    108 /*	process limit is not exceeded.
    109 /*	Each server terminates after serving at least \fB$max_use\fR clients
    110 /*	or after \fB$max_idle\fR seconds of idle time.
    111 /* SECURITY
    112 /* .ad
    113 /* .fi
    114 /*	The \fBproxymap\fR(8) server opens only tables that are
    115 /*	approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR
    116 /*	configuration parameters, does not talk to
    117 /*	users, and can run at fixed low privilege, chrooted or not.
    118 /*	However, running the proxymap server chrooted severely limits
    119 /*	usability, because it can open only chrooted tables.
    120 /*
    121 /*	The \fBproxymap\fR(8) server is not a trusted daemon process, and must
    122 /*	not be used to look up sensitive information such as UNIX user or
    123 /*	group IDs, mailbox file/directory names or external commands.
    124 /*
    125 /*	In Postfix version 2.2 and later, the proxymap client recognizes
    126 /*	requests to access a table for security-sensitive purposes,
    127 /*	and opens the table directly. This allows the same main.cf
    128 /*	setting to be used by sensitive and non-sensitive processes.
    129 /*
    130 /*	Postfix-writable data files should be stored under a dedicated
    131 /*	directory that is writable only by the Postfix mail system,
    132 /*	such as the Postfix-owned \fBdata_directory\fR.
    133 /*
    134 /*	In particular, Postfix-writable files should never exist
    135 /*	in root-owned directories. That would open up a particular
    136 /*	type of security hole where ownership of a file or directory
    137 /*	does not match the provider of its content.
    138 /* DIAGNOSTICS
    139 /*	Problems and transactions are logged to \fBsyslogd\fR(8)
    140 /*	or \fBpostlogd\fR(8).
    141 /* BUGS
    142 /*	The \fBproxymap\fR(8) server provides service to multiple clients,
    143 /*	and must therefore not be used for tables that have high-latency
    144 /*	lookups.
    145 /*
    146 /*	The \fBproxymap\fR(8) read-write service does not explicitly
    147 /*	close lookup tables (even if it did, this could not be relied on,
    148 /*	because the process may be terminated between table updates).
    149 /*	The read-write service should therefore not be used with tables that
    150 /*	leave persistent storage in an inconsistent state between
    151 /*	updates (for example, CDB). Tables that support "sync on
    152 /*	update" should be safe (for example, Berkeley DB) as should
    153 /*	tables that are implemented by a real DBMS.
    154 /* CONFIGURATION PARAMETERS
    155 /* .ad
    156 /* .fi
    157 /*	On busy mail systems a long time may pass before
    158 /*	\fBproxymap\fR(8) relevant
    159 /*	changes to \fBmain.cf\fR are picked up. Use the command
    160 /*	"\fBpostfix reload\fR" to speed up a change.
    161 /*
    162 /*	The text below provides only a parameter summary. See
    163 /*	\fBpostconf\fR(5) for more details including examples.
    164 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
    165 /*	The default location of the Postfix main.cf and master.cf
    166 /*	configuration files.
    167 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
    168 /*	The directory with Postfix-writable data files (for example:
    169 /*	caches, pseudo-random numbers).
    170 /* .IP "\fBdaemon_timeout (18000s)\fR"
    171 /*	How much time a Postfix daemon process may take to handle a
    172 /*	request before it is terminated by a built-in watchdog timer.
    173 /* .IP "\fBipc_timeout (3600s)\fR"
    174 /*	The time limit for sending or receiving information over an internal
    175 /*	communication channel.
    176 /* .IP "\fBmax_idle (100s)\fR"
    177 /*	The maximum amount of time that an idle Postfix daemon process waits
    178 /*	for an incoming connection before terminating voluntarily.
    179 /* .IP "\fBmax_use (100)\fR"
    180 /*	The maximal number of incoming connections that a Postfix daemon
    181 /*	process will service before terminating voluntarily.
    182 /* .IP "\fBprocess_id (read-only)\fR"
    183 /*	The process ID of a Postfix command or daemon process.
    184 /* .IP "\fBprocess_name (read-only)\fR"
    185 /*	The process name of a Postfix command or daemon process.
    186 /* .IP "\fBproxy_read_maps (see 'postconf -d' output)\fR"
    187 /*	The lookup tables that the \fBproxymap\fR(8) server is allowed to
    188 /*	access for the read-only service.
    189 /* .PP
    190 /*	Available in Postfix 2.5 and later:
    191 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
    192 /*	The directory with Postfix-writable data files (for example:
    193 /*	caches, pseudo-random numbers).
    194 /* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR"
    195 /*	The lookup tables that the \fBproxymap\fR(8) server is allowed to
    196 /*	access for the read-write service.
    197 /* .PP
    198 /*	Available in Postfix 3.3 and later:
    199 /* .IP "\fBservice_name (read-only)\fR"
    200 /*	The master.cf service name of a Postfix daemon process.
    201 /* SEE ALSO
    202 /*	postconf(5), configuration parameters
    203 /*	master(5), generic daemon options
    204 /* README FILES
    205 /* .ad
    206 /* .fi
    207 /*	Use "\fBpostconf readme_directory\fR" or
    208 /*	"\fBpostconf html_directory\fR" to locate this information.
    209 /* .na
    210 /* .nf
    211 /*	DATABASE_README, Postfix lookup table overview
    212 /* LICENSE
    213 /* .ad
    214 /* .fi
    215 /*	The Secure Mailer license must be distributed with this software.
    216 /* HISTORY
    217 /* .ad
    218 /* .fi
    219 /*	The proxymap service was introduced with Postfix 2.0.
    220 /* AUTHOR(S)
    221 /*	Wietse Venema
    222 /*	IBM T.J. Watson Research
    223 /*	P.O. Box 704
    224 /*	Yorktown Heights, NY 10598, USA
    225 /*
    226 /*	Wietse Venema
    227 /*	Google, Inc.
    228 /*	111 8th Avenue
    229 /*	New York, NY 10011, USA
    230 /*
    231 /*	Wietse Venema
    232 /*	porcupine.org
    233 /*--*/
    234 
    235 /* System library. */
    236 
    237 #include <sys_defs.h>
    238 #include <string.h>
    239 #include <stdlib.h>
    240 #include <unistd.h>
    241 #include <ctype.h>
    242 
    243 /* Utility library. */
    244 
    245 #include <msg.h>
    246 #include <mymalloc.h>
    247 #include <vstring.h>
    248 #include <htable.h>
    249 #include <stringops.h>
    250 #include <dict.h>
    251 #include <dict_pipe.h>
    252 #include <dict_union.h>
    253 #include <readlline.h>
    254 
    255 /* Global library. */
    256 
    257 #include <mail_conf.h>
    258 #include <mail_params.h>
    259 #include <mail_version.h>
    260 #include <mail_proto.h>
    261 #include <dict_proxy.h>
    262 
    263 /* Server skeleton. */
    264 
    265 #include <mail_server.h>
    266 
    267 /* Application-specific. */
    268 
    269  /*
    270   * XXX All but the last are needed here so that $name expansion dependencies
    271   * aren't too broken. The fix is to gather all parameter default settings in
    272   * one place.
    273   */
    274 char   *var_alias_maps;
    275 char   *var_local_rcpt_maps;
    276 char   *var_virt_alias_maps;
    277 char   *var_virt_alias_doms;
    278 char   *var_virt_mailbox_maps;
    279 char   *var_virt_mailbox_doms;
    280 char   *var_relay_rcpt_maps;
    281 char   *var_canonical_maps;
    282 char   *var_send_canon_maps;
    283 char   *var_rcpt_canon_maps;
    284 char   *var_relocated_maps;
    285 char   *var_transport_maps;
    286 char   *var_verify_map;
    287 char   *var_smtpd_snd_auth_maps;
    288 char   *var_psc_cache_map;
    289 char   *var_smtpd_forbid_cmds;
    290 char   *var_psc_forbid_cmds;
    291 char   *var_proxy_read_maps;
    292 char   *var_proxy_write_maps;
    293 char   *var_rest_classes;
    294 
    295  /*
    296   * The pre-approved, pre-parsed list of maps.
    297   */
    298 static HTABLE *proxy_auth_maps;
    299 
    300  /*
    301   * Shared and static to reduce memory allocation overhead.
    302   */
    303 static VSTRING *request;
    304 static VSTRING *request_map;
    305 static VSTRING *request_key;
    306 static VSTRING *request_value;
    307 static VSTRING *map_type_name_flags;
    308 
    309  /*
    310   * Are we a proxy writer or not?
    311   */
    312 static int proxy_writer;
    313 
    314  /*
    315   * Do we implement the proxymap or proxywrite service, or do we export
    316   * type:table information to stdout? The exported info is without the proxy:
    317   * prefix.
    318   *
    319   * EXPORT_PROXY lists only tables that proxied.
    320   *
    321   * EXPORT_ALL lists both proxied and non-proxied tables that Postfix is
    322   * configured to use. This requires that proxy_read_maps and
    323   * proxy_write_maps list all tables, including those that will never be
    324   * proxied.
    325   */
    326 static enum {
    327     EXPORT_NONE,			/* Answer requests */
    328     EXPORT_PROXY,			/* Export proxied type:table */
    329     EXPORT_ALL,				/* Export all type:table */
    330 }       proxy_exporter = EXPORT_NONE;
    331 
    332  /*
    333   * Silly little macros.
    334   */
    335 #define STR(x)			vstring_str(x)
    336 #define VSTREQ(x,y)		(strcmp(STR(x),y) == 0)
    337 
    338 /* get_nested_dict_name - return nested dictionary name pointer, or null */
    339 
    340 static char *get_nested_dict_name(char *type_name)
    341 {
    342     const struct {
    343 	const char *type_col;
    344 	ssize_t type_col_len;
    345     }      *prefix, prefixes[] = {
    346 	DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1),
    347 	DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1),
    348     };
    349 
    350 #define COUNT_OF(x) (sizeof(x)/sizeof((x)[0]))
    351 
    352     for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) {
    353 	if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0)
    354 	    return (type_name + prefix->type_col_len);
    355     }
    356     return (0);
    357 }
    358 
    359 /* proxy_map_find - look up or open table */
    360 
    361 static DICT *proxy_map_find(const char *map_type_name, int inst_flags,
    362 			            int *statp)
    363 {
    364     static HTABLE *new_flags;
    365     HTABLE_INFO *ht;
    366     DICT   *dict;
    367 
    368 #define PROXY_COLON	DICT_TYPE_PROXY ":"
    369 #define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
    370 #define READ_OPEN_FLAGS	O_RDONLY
    371 #define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)
    372 
    373     /*
    374      * Canonicalize the map name. If the map is not on the approved list,
    375      * deny the request.
    376      */
    377 #define PROXY_MAP_FIND_ERROR_RETURN(x)  { *statp = (x); return (0); }
    378 #define PROXY_MAP_PARAM_NAME(proxy_writer)  \
    379 	((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS)
    380 
    381     while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
    382 	map_type_name += PROXY_COLON_LEN;
    383     /* XXX The following breaks with maps that have ':' in their name. */
    384     if (strchr(map_type_name, ':') == 0)
    385 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
    386     if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
    387 	msg_warn("request for unapproved table: \"%s\"", map_type_name);
    388 	msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
    389 		 proxy_writer == 0 ? "read-only" : "read-write",
    390 		 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
    391 		 PROXY_MAP_PARAM_NAME(proxy_writer));
    392 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
    393     }
    394 
    395     /*
    396      * Open one instance of a map for each combination of name+flags.
    397      */
    398     dict = dict_open(map_type_name, proxy_writer ?
    399 		     WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
    400 		     inst_flags);
    401     if (dict == 0)
    402 	msg_panic("proxy_map_find: dict_open null result");
    403 
    404     /*
    405      * Remember the mapping from dict->reg_name to the dict->flags of a
    406      * newly-initialized instance. Always return an instance with those new
    407      * dict->flags, to avoid crosstalk between different clients.
    408      */
    409     if (new_flags == 0)
    410 	new_flags = htable_create(100);
    411     if ((ht = htable_locate(new_flags, dict->reg_name)) == 0) {
    412 	(void) htable_enter(new_flags, dict->reg_name,
    413 			    CAST_INT_TO_VOID_PTR(dict->flags));
    414     } else {
    415 	dict->flags = CAST_ANY_PTR_TO_INT(ht->value);
    416     }
    417     dict->error = 0;
    418     return (dict);
    419 }
    420 
    421 /* proxymap_sequence_service - remote sequence service */
    422 
    423 static void proxymap_sequence_service(VSTREAM *client_stream)
    424 {
    425     int     inst_flags;
    426     int     request_flags;
    427     DICT   *dict = 0;
    428     int     request_func;
    429     const char *reply_key;
    430     const char *reply_value;
    431     int     dict_status;
    432     int     reply_status;
    433 
    434     /*
    435      * Process the request.
    436      */
    437     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    438 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
    439 		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
    440 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
    441 		  RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func),
    442 		  ATTR_TYPE_END) != 4
    443 	|| (request_func != DICT_SEQ_FUN_FIRST
    444 	    && request_func != DICT_SEQ_FUN_NEXT)) {
    445 	reply_status = PROXY_STAT_BAD;
    446 	reply_key = reply_value = "";
    447     } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
    448 				      &reply_status)) == 0) {
    449 	reply_key = reply_value = "";
    450     } else {
    451 	dict->flags = request_flags;
    452 	dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
    453 	if (dict_status == 0) {
    454 	    reply_status = PROXY_STAT_OK;
    455 	} else if (dict->error == 0) {
    456 	    reply_status = PROXY_STAT_NOKEY;
    457 	    reply_key = reply_value = "";
    458 	} else {
    459 	    reply_status = (dict->error == DICT_ERR_RETRY ?
    460 			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
    461 	    reply_key = reply_value = "";
    462 	}
    463     }
    464 
    465     /*
    466      * Respond to the client. 202604 Claude: don't dereference uninitialized
    467      * dict.
    468      */
    469     attr_print(client_stream, ATTR_FLAG_NONE,
    470 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
    471 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
    472 	       SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key),
    473 	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
    474 	       ATTR_TYPE_END);
    475 }
    476 
    477 /* proxymap_lookup_service - remote lookup service */
    478 
    479 static void proxymap_lookup_service(VSTREAM *client_stream)
    480 {
    481     int     inst_flags;
    482     int     request_flags;
    483     DICT   *dict = 0;
    484     const char *reply_value;
    485     int     reply_status;
    486 
    487     /*
    488      * Process the request.
    489      */
    490     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    491 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
    492 		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
    493 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
    494 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
    495 		  ATTR_TYPE_END) != 4) {
    496 	reply_status = PROXY_STAT_BAD;
    497 	reply_value = "";
    498     } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
    499 				      &reply_status)) == 0) {
    500 	reply_value = "";
    501     } else if (dict->flags = request_flags,
    502 	       (reply_value = dict_get(dict, STR(request_key))) != 0) {
    503 	reply_status = PROXY_STAT_OK;
    504     } else if (dict->error == 0) {
    505 	reply_status = PROXY_STAT_NOKEY;
    506 	reply_value = "";
    507     } else {
    508 	reply_status = (dict->error == DICT_ERR_RETRY ?
    509 			PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
    510 	reply_value = "";
    511     }
    512 
    513     /*
    514      * Respond to the client. 202604 Claude: don't dereference uninitialized
    515      * dict.
    516      */
    517     attr_print(client_stream, ATTR_FLAG_NONE,
    518 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
    519 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
    520 	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
    521 	       ATTR_TYPE_END);
    522 }
    523 
    524 /* proxymap_update_service - remote update service */
    525 
    526 static void proxymap_update_service(VSTREAM *client_stream)
    527 {
    528     int     inst_flags;
    529     int     request_flags;
    530     DICT   *dict = 0;
    531     int     dict_status;
    532     int     reply_status;
    533 
    534     /*
    535      * Process the request.
    536      *
    537      * XXX We don't close maps, so we must turn on synchronous update to ensure
    538      * that the on-disk data is in a consistent state between updates.
    539      *
    540      * XXX We ignore duplicates, because the proxymap server would abort
    541      * otherwise.
    542      */
    543     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    544 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
    545 		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
    546 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
    547 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
    548 		  RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value),
    549 		  ATTR_TYPE_END) != 5) {
    550 	reply_status = PROXY_STAT_BAD;
    551     } else if (proxy_writer == 0) {
    552 	msg_warn("refusing %s update request on non-%s service",
    553 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
    554 	reply_status = PROXY_STAT_DENY;
    555     } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
    556 				      &reply_status)) == 0) {
    557 	 /* void */ ;
    558     } else {
    559 	/* Sync the table now. Don't abort on duplicate update. */
    560 	dict->flags = (request_flags
    561 		       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
    562 	dict_status = dict_put(dict, STR(request_key), STR(request_value));
    563 	if (dict_status == 0) {
    564 	    reply_status = PROXY_STAT_OK;
    565 	} else if (dict->error == 0) {
    566 	    reply_status = PROXY_STAT_NOKEY;
    567 	} else {
    568 	    reply_status = (dict->error == DICT_ERR_RETRY ?
    569 			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
    570 	}
    571     }
    572 
    573     /*
    574      * Respond to the client. 202604 Claude: don't dereference uninitialized
    575      * dict.
    576      */
    577     attr_print(client_stream, ATTR_FLAG_NONE,
    578 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
    579 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
    580 	       ATTR_TYPE_END);
    581 }
    582 
    583 /* proxymap_delete_service - remote delete service */
    584 
    585 static void proxymap_delete_service(VSTREAM *client_stream)
    586 {
    587     int     inst_flags;
    588     int     request_flags;
    589     DICT   *dict = 0;
    590     int     dict_status;
    591     int     reply_status;
    592 
    593     /*
    594      * Process the request.
    595      *
    596      * XXX We don't close maps, so we must turn on synchronous update to ensure
    597      * that the on-disk data is in a consistent state between updates.
    598      */
    599     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    600 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
    601 		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
    602 		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
    603 		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
    604 		  ATTR_TYPE_END) != 4) {
    605 	reply_status = PROXY_STAT_BAD;
    606     } else if (proxy_writer == 0) {
    607 	msg_warn("refusing %s delete request on non-%s service",
    608 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
    609 	reply_status = PROXY_STAT_DENY;
    610     } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
    611 				      &reply_status)) == 0) {
    612 	 /* void */ ;
    613     } else {
    614 	/* Sync the table now. There is no close() request. */
    615 	dict->flags = (request_flags
    616 		       | DICT_FLAG_SYNC_UPDATE);
    617 	dict_status = dict_del(dict, STR(request_key));
    618 	if (dict_status == 0) {
    619 	    reply_status = PROXY_STAT_OK;
    620 	} else if (dict->error == 0) {
    621 	    reply_status = PROXY_STAT_NOKEY;
    622 	} else {
    623 	    reply_status = (dict->error == DICT_ERR_RETRY ?
    624 			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
    625 	}
    626     }
    627 
    628     /*
    629      * Respond to the client. 202604 Claude: don't dereference uninitialized
    630      * dict.
    631      */
    632     attr_print(client_stream, ATTR_FLAG_NONE,
    633 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
    634 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0),
    635 	       ATTR_TYPE_END);
    636 }
    637 
    638 /* proxymap_open_service - open remote lookup table */
    639 
    640 static void proxymap_open_service(VSTREAM *client_stream)
    641 {
    642     int     inst_flags;
    643     DICT   *dict;
    644     int     reply_status;
    645     int     reply_flags;
    646 
    647     /*
    648      * Process the request.
    649      */
    650     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
    651 		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
    652 		  RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags),
    653 		  ATTR_TYPE_END) != 2) {
    654 	reply_status = PROXY_STAT_BAD;
    655 	reply_flags = 0;
    656     } else if ((dict = proxy_map_find(STR(request_map), inst_flags,
    657 				      &reply_status)) == 0) {
    658 	reply_flags = 0;
    659     } else {
    660 	reply_status = PROXY_STAT_OK;
    661 	reply_flags = dict->flags;
    662     }
    663 
    664     /*
    665      * Respond to the client.
    666      */
    667     attr_print(client_stream, ATTR_FLAG_NONE,
    668 	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
    669 	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags),
    670 	       ATTR_TYPE_END);
    671 }
    672 
    673 /* proxymap_service - perform service for client */
    674 
    675 static void proxymap_service(VSTREAM *client_stream, char *unused_service,
    676 			             char **argv)
    677 {
    678 
    679     /*
    680      * Sanity check. This service takes no command-line arguments.
    681      */
    682     if (argv[0])
    683 	msg_fatal("unexpected command-line argument: %s", argv[0]);
    684 
    685     /*
    686      * Deadline enforcement.
    687      */
    688     if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0)
    689 	vstream_control(client_stream,
    690 			CA_VSTREAM_CTL_TIMEOUT(1),
    691 			CA_VSTREAM_CTL_END);
    692 
    693     /*
    694      * This routine runs whenever a client connects to the socket dedicated
    695      * to the proxymap service. All connection-management stuff is handled by
    696      * the common code in multi_server.c.
    697      */
    698     vstream_control(client_stream,
    699 		    CA_VSTREAM_CTL_START_DEADLINE,
    700 		    CA_VSTREAM_CTL_END);
    701     if (attr_scan(client_stream,
    702 		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
    703 		  RECV_ATTR_STR(MAIL_ATTR_REQ, request),
    704 		  ATTR_TYPE_END) == 1) {
    705 	if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
    706 	    proxymap_lookup_service(client_stream);
    707 	} else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
    708 	    proxymap_update_service(client_stream);
    709 	} else if (VSTREQ(request, PROXY_REQ_DELETE)) {
    710 	    proxymap_delete_service(client_stream);
    711 	} else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) {
    712 	    proxymap_sequence_service(client_stream);
    713 	} else if (VSTREQ(request, PROXY_REQ_OPEN)) {
    714 	    proxymap_open_service(client_stream);
    715 	} else {
    716 	    msg_warn("unrecognized request: \"%s\", ignored", STR(request));
    717 	    attr_print(client_stream, ATTR_FLAG_NONE,
    718 		       SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD),
    719 		       ATTR_TYPE_END);
    720 	}
    721     }
    722     vstream_control(client_stream,
    723 		    CA_VSTREAM_CTL_START_DEADLINE,
    724 		    CA_VSTREAM_CTL_END);
    725     vstream_fflush(client_stream);
    726 }
    727 
    728 /* dict_proxy_open - intercept remote map request from inside library */
    729 
    730 DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
    731 {
    732     if (msg_verbose)
    733 	msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine",
    734 		 map, open_flags, dict_flags);
    735     while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0)
    736 	map += PROXY_COLON_LEN;
    737     return (dict_open(map, open_flags, dict_flags));
    738 }
    739 
    740 static void authorize_file_content(const char *);
    741 
    742 /* authorize_proxied_maps - recursively authorize maps */
    743 
    744 static void authorize_proxied_maps(char *bp, const char *origin)
    745 {
    746     const char *sep = CHARS_COMMA_SP;
    747     const char *parens = CHARS_BRACE;
    748     char   *type_name;
    749 
    750     while ((type_name = mystrtokq(&bp, sep, parens)) != 0) {
    751 	char   *nested_info;
    752 
    753 	/* Maybe { maptype:mapname attr=value... } */
    754 	if (*type_name == parens[0]) {
    755 	    char   *err;
    756 
    757 	    /* Warn about blatant syntax error. */
    758 	    if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) {
    759 		msg_warn("bad syntax in %s content: %s", origin, err);
    760 		myfree(err);
    761 		continue;
    762 	    }
    763 	    /* Don't try to second-guess the semantics of { }. */
    764 	    if ((type_name = mystrtokq(&type_name, sep, parens)) == 0)
    765 		continue;
    766 	}
    767 	/* Recurse into /file/name. */
    768 	if (*type_name == '/') {
    769 	    authorize_file_content(type_name);
    770 	    continue;
    771 	}
    772 	/* Skip [ipv6::addr] and other non-table forms. */
    773 	if (!ISALNUM(*type_name) || strchr(type_name, ':') == 0)
    774 	    continue;
    775 	/* Recurse into nested map (pipemap, unionmap). */
    776 	if ((nested_info = get_nested_dict_name(type_name)) != 0) {
    777 	    char   *err;
    778 
    779 	    if (*nested_info != parens[0])
    780 		continue;
    781 	    /* Warn about blatant syntax error. */
    782 	    if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) {
    783 		msg_warn("bad syntax in %s content: %s", origin, err);
    784 		myfree(err);
    785 		continue;
    786 	    }
    787 	    authorize_proxied_maps(nested_info, origin);
    788 	    continue;
    789 	}
    790 	switch (proxy_exporter) {
    791 	case EXPORT_NONE:
    792 	case EXPORT_PROXY:
    793 	    if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
    794 		continue;
    795 	    do {
    796 		type_name += PROXY_COLON_LEN;
    797 	    } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
    798 	    break;
    799 	case EXPORT_ALL:
    800 	    while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
    801 		type_name += PROXY_COLON_LEN;
    802 	    break;
    803 	}
    804 	if (htable_locate(proxy_auth_maps, type_name) == 0) {
    805 	    (void) htable_enter(proxy_auth_maps, type_name, mystrdup(origin));
    806 	    if (msg_verbose)
    807 		msg_info("allowlisting %s from %s", type_name, origin);
    808 	}
    809     }
    810 }
    811 
    812 /* authorize_file_content - authorize table references in /file/name */
    813 
    814 static void authorize_file_content(const char *file_name)
    815 {
    816     VSTREAM *fp;
    817     VSTRING *buf;
    818 
    819     if ((fp = vstream_fopen(file_name, O_RDONLY, 0)) == 0) {
    820 	msg_warn("open %s: %m", file_name);
    821 	return;
    822     }
    823     buf = vstring_alloc(100);
    824     while (readlline(buf, fp, (int *) 0))
    825 	authorize_proxied_maps(STR(buf), file_name);
    826     if (vstream_ferror(fp))
    827 	msg_warn("read %s: %m", file_name);
    828     vstring_free(buf);
    829     vstream_fclose(fp);
    830 }
    831 
    832 /* authorize_rest_classes - scan restriction classes for table references */
    833 
    834 static void authorize_rest_classes(const char *rest_classes)
    835 {
    836     char   *bp, *saved_rest_classes, *class_name, *saved_class_val;
    837     const char *class_val;
    838 
    839     bp = saved_rest_classes = mystrdup(rest_classes);
    840     while ((class_name = mystrtok(&bp, CHARS_COMMA_SP)) != 0) {
    841 	if ((class_val = mail_conf_lookup_eval(class_name)) != 0) {
    842 	    saved_class_val = mystrdup(class_val);
    843 	    authorize_proxied_maps(saved_class_val, VAR_REST_CLASSES);
    844 	    myfree(saved_class_val);
    845 	}
    846     }
    847     myfree(saved_rest_classes);
    848 }
    849 
    850 /* cmp_ht_key - qsort helper for ht_info pointer array */
    851 
    852 static int cmp_ht_key(const void *a, const void *b)
    853 {
    854     HTABLE_INFO **ap = (HTABLE_INFO **) a;
    855     HTABLE_INFO **bp = (HTABLE_INFO **) b;
    856 
    857     return (strcmp(ap[0]->key, bp[0]->key));
    858 }
    859 
    860 /* export_type_name_entries - serialize table authorizations to stdout */
    861 
    862 static void export_type_name_entries(void)
    863 {
    864     HTABLE_INFO **ht_info, **ht;
    865 
    866     ht_info = htable_list(proxy_auth_maps);
    867     qsort((void *) ht_info, proxy_auth_maps->used, sizeof(*ht_info),
    868 	  cmp_ht_key);
    869     for (ht = ht_info; *ht; ht++)
    870 	vstream_printf("%s\n", ht[0]->key);
    871     vstream_fflush(VSTREAM_OUT);
    872     myfree(ht_info);
    873 }
    874 
    875 /* pre_jail_init - initialization after privilege drop */
    876 
    877 static void pre_jail_init(char *service_name, char **unused_argv)
    878 {
    879     char   *saved_filter;
    880 
    881     /*
    882      * Is this a request for service, or export?
    883      */
    884     if (strncmp(service_name, "export-all-", 11) == 0) {
    885 	proxy_exporter = EXPORT_ALL;
    886 	service_name += 11;
    887     } else if (strncmp(service_name, "export-proxy-", 13) == 0) {
    888 	proxy_exporter = EXPORT_PROXY;
    889 	service_name += 13;
    890     }
    891 
    892     /*
    893      * Are we proxy writer?
    894      */
    895     if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
    896 	proxy_writer = 1;
    897     else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
    898 	msg_fatal("invalid service name: \"%s\" - "
    899 		  "service name must be \"%s\" or \"%s\"",
    900 		  service_name, MAIL_SERVICE_PROXYWRITE,
    901 		  MAIL_SERVICE_PROXYMAP);
    902 
    903     /*
    904      * Pre-allocate buffers.
    905      */
    906     request = vstring_alloc(10);
    907     request_map = vstring_alloc(10);
    908     request_key = vstring_alloc(10);
    909     request_value = vstring_alloc(10);
    910     map_type_name_flags = vstring_alloc(10);
    911 
    912     /*
    913      * Prepare the pre-approved list of proxied tables.
    914      */
    915     saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps :
    916 			    var_proxy_read_maps);
    917     proxy_auth_maps = htable_create(13);
    918     authorize_proxied_maps(saved_filter, PROXY_MAP_PARAM_NAME(proxy_writer));
    919     myfree(saved_filter);
    920 
    921     /*
    922      * Authorize tables in restriction_classes.
    923      */
    924     if (proxy_writer == 0 && *var_rest_classes)
    925 	authorize_rest_classes(var_rest_classes);
    926 
    927     /*
    928      * If run as exporter, serialize the authorization table to stdout.
    929      */
    930     if (proxy_exporter != EXPORT_NONE) {
    931 	export_type_name_entries();
    932 	exit(0);
    933     }
    934 
    935     /*
    936      * Never, ever, get killed by a master signal, as that could corrupt a
    937      * persistent database when we're in the middle of an update.
    938      */
    939     if (proxy_writer != 0)
    940 	setsid();
    941 }
    942 
    943 /* pre_accept - see if tables have changed */
    944 
    945 static void pre_accept(char *unused_name, char **unused_argv)
    946 {
    947     const char *table;
    948 
    949     if (proxy_writer == 0 && (table = dict_changed_name()) != 0) {
    950 	msg_info("table %s has changed -- restarting", table);
    951 	exit(0);
    952     }
    953 }
    954 
    955 /* post_accept - announce our protocol name */
    956 
    957 static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv,
    958 			        HTABLE *unused_attr)
    959 {
    960 
    961     /*
    962      * Announce the protocol.
    963      */
    964     attr_print(stream, ATTR_FLAG_NONE,
    965 	       SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP),
    966 	       ATTR_TYPE_END);
    967     (void) vstream_fflush(stream);
    968 }
    969 
    970 MAIL_VERSION_STAMP_DECLARE;
    971 
    972 /* main - pass control to the multi-threaded skeleton */
    973 
    974 int     main(int argc, char **argv)
    975 {
    976 
    977     /*
    978      * Respect the proxy_read_maps and proxy_write_maps dependency graphs.
    979      * First, initialize the parameters that specify tables in their
    980      * non-empty default values, then initialize the parameters that depend
    981      * on those parameters, and so on. Only at the end initialize
    982      * proxy_read_maps and proxy_write_maps.
    983      *
    984      * TODO(wietse) remove parameters below that have empty default values. It
    985      * is sufficient to list their $name in the proxy_*_maps default values.
    986      * The list below should be checked with a tool that runs during
    987      * pre-release-checks.
    988      *
    989      * List parameters even if their defaults do not specify proxied queries (as
    990      * of Postfix 3.11, only local_recipient_maps does that). The goal is to
    991      * implement an authoritative source of truth tat covers all Postfix
    992      * table lookups.
    993      */
    994     static const CONFIG_STR_TABLE str_table[] = {
    995 	/* Dependencies of proxy_read_maps. */
    996 	VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
    997 	VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
    998 	VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
    999 	VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
   1000 	VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
   1001 	VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
   1002 	VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
   1003 	VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
   1004 	VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
   1005 	VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
   1006 	VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
   1007 	VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
   1008 	VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
   1009 	VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0,
   1010 	VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0,
   1011 	/* Dependencies of proxy_write_maps. */
   1012 	VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
   1013 	VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
   1014 	/* The following two must be last for $mapname to work as expected. */
   1015 	VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
   1016 	VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
   1017 	/* Settings that don't affect or depend on proxy_read/write_maps. */
   1018 	VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0,
   1019 	0,
   1020     };
   1021 
   1022     /*
   1023      * Fingerprint executables and core dumps.
   1024      */
   1025     MAIL_VERSION_STAMP_ALLOCATE;
   1026 
   1027     /*
   1028      * XXX When invoked with the master.cf service name "proxywrite", the
   1029      * proxymap daemon will allow update requests. To update a table that is
   1030      * not multi-writer safe (for example, some versions of Berkeley DB), the
   1031      * "proxywrite" service should run as a single updater (i.e. a process
   1032      * limit of 1, which could be enforced below by requesting
   1033      * CA_MAIL_SERVER_SOLITARY).
   1034      *
   1035      * In the default master.cf file, the "proxywrite" service has a process
   1036      * limit of 1. Assuming that updates will be rare, this process limit
   1037      * will suffice. Latency-sensitive services such as postscreen must not
   1038      * use the proxywrite service (in fact, postscreen has a latency check
   1039      * built-in).
   1040      *
   1041      * Optimizing for multi-writer operation would suffer from all kinds of
   1042      * complexity that would make it hard to use:
   1043      *
   1044      * - The master daemon specifies the "proxywrite" service name with the -n
   1045      * command-line option. This information is not known here, before the
   1046      * multi_server_main() call. The multi_server_main() function could
   1047      * reveal process limit information to its call-back functions, and leave
   1048      * single-updater enforcement to its call-back functions.
   1049      *
   1050      * - If we really want multi-writer update support, the "proxywrite" service
   1051      * would have to parse the $proxy_write_maps value, and permit
   1052      * multi-writer operation only if all tables are multi-writer safe. That
   1053      * would require a new dict(3) method, to query each lookup table
   1054      * implementation if it is multi-writer safe, without instantiating a
   1055      * lookup table client.
   1056      */
   1057     multi_server_main(argc, argv, proxymap_service,
   1058 		      CA_MAIL_SERVER_STR_TABLE(str_table),
   1059 		      CA_MAIL_SERVER_PRE_INIT(pre_jail_init),
   1060 		      CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
   1061 		      CA_MAIL_SERVER_POST_ACCEPT(post_accept),
   1062     /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */
   1063 		      0);
   1064 }
   1065