xref: /netbsd-src/external/ibm-public/postfix/dist/src/proxymap/proxymap.c (revision c2f76ff004a2cb67efe5b12d97bd3ef7fe89e18d)
1 /*	$NetBSD: proxymap.c,v 1.1.1.2 2010/06/17 18:06:59 tron 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).
43 /* .PP
44 /*	The \fBproxymap\fR(8) server implements the following requests:
45 /* .IP "\fBopen\fR \fImaptype:mapname flags\fR"
46 /*	Open the table with type \fImaptype\fR and name \fImapname\fR,
47 /*	as controlled by \fIflags\fR. The reply includes the \fImaptype\fR
48 /*	dependent flags (to distinguish a fixed string table from a regular
49 /*	expression table).
50 /* .IP "\fBlookup\fR \fImaptype:mapname flags key\fR"
51 /*	Look up the data stored under the requested key.
52 /*	The reply is the request completion status code and
53 /*	the lookup result value.
54 /*	The \fImaptype:mapname\fR and \fIflags\fR are the same
55 /*	as with the \fBopen\fR request.
56 /* .IP "\fBupdate\fR \fImaptype:mapname flags key value\fR"
57 /*	Update the data stored under the requested key.
58 /*	The reply is the request completion status code.
59 /*	The \fImaptype:mapname\fR and \fIflags\fR are the same
60 /*	as with the \fBopen\fR request.
61 /* .sp
62 /*	To implement single-updater maps, specify a process limit
63 /*	of 1 in the master.cf file entry for the \fBproxywrite\fR
64 /*	service.
65 /* .sp
66 /*	This request is supported in Postfix 2.5 and later.
67 /* .IP "\fBdelete\fR \fImaptype:mapname flags key\fR"
68 /*	Delete the data stored under the requested key.
69 /*	The reply is the request completion status code.
70 /*	The \fImaptype:mapname\fR and \fIflags\fR are the same
71 /*	as with the \fBopen\fR request.
72 /* .sp
73 /*	This request is supported in Postfix 2.5 and later.
74 /* .PP
75 /*	The request completion status is one of OK, RETRY, NOKEY
76 /*	(lookup failed because the key was not found), BAD (malformed
77 /*	request) or DENY (the table is not approved for proxy read
78 /*	or update access).
79 /*
80 /*	There is no \fBclose\fR command, nor are tables implicitly closed
81 /*	when a client disconnects. The purpose is to share tables among
82 /*	multiple client processes.
83 /* SERVER PROCESS MANAGEMENT
84 /* .ad
85 /* .fi
86 /*	\fBproxymap\fR(8) servers run under control by the Postfix
87 /*	\fBmaster\fR(8)
88 /*	server.  Each server can handle multiple simultaneous connections.
89 /*	When all servers are busy while a client connects, the \fBmaster\fR(8)
90 /*	creates a new \fBproxymap\fR(8) server process, provided that the
91 /*	process limit is not exceeded.
92 /*	Each server terminates after serving at least \fB$max_use\fR clients
93 /*	or after \fB$max_idle\fR seconds of idle time.
94 /* SECURITY
95 /* .ad
96 /* .fi
97 /*	The \fBproxymap\fR(8) server opens only tables that are
98 /*	approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR
99 /*	configuration parameters, does not talk to
100 /*	users, and can run at fixed low privilege, chrooted or not.
101 /*	However, running the proxymap server chrooted severely limits
102 /*	usability, because it can open only chrooted tables.
103 /*
104 /*	The \fBproxymap\fR(8) server is not a trusted daemon process, and must
105 /*	not be used to look up sensitive information such as user or
106 /*	group IDs, mailbox file/directory names or external commands.
107 /*
108 /*	In Postfix version 2.2 and later, the proxymap client recognizes
109 /*	requests to access a table for security-sensitive purposes,
110 /*	and opens the table directly. This allows the same main.cf
111 /*	setting to be used by sensitive and non-sensitive processes.
112 /*
113 /*	Postfix-writable data files should be stored under a dedicated
114 /*	directory that is writable only by the Postfix mail system,
115 /*	such as the Postfix-owned \fBdata_directory\fR.
116 /*
117 /*	In particular, Postfix-writable files should never exist
118 /*	in root-owned directories. That would open up a particular
119 /*	type of security hole where ownership of a file or directory
120 /*	does not match the provider of its content.
121 /* DIAGNOSTICS
122 /*	Problems and transactions are logged to \fBsyslogd\fR(8).
123 /* BUGS
124 /*	The \fBproxymap\fR(8) server provides service to multiple clients,
125 /*	and must therefore not be used for tables that have high-latency
126 /*	lookups.
127 /*
128 /*	The \fBproxymap\fR(8) read-write service does not explicitly
129 /*	close lookup tables (even if it did, this could not be relied on,
130 /*	because the process may be terminated between table updates).
131 /*	The read-write service should therefore not be used with tables that
132 /*	leave persistent storage in an inconsistent state between
133 /*	updates (for example, CDB). Tables that support "sync on
134 /*	update" should be safe (for example, Berkeley DB) as should
135 /*	tables that are implemented by a real DBMS.
136 /* CONFIGURATION PARAMETERS
137 /* .ad
138 /* .fi
139 /*	On busy mail systems a long time may pass before
140 /*	\fBproxymap\fR(8) relevant
141 /*	changes to \fBmain.cf\fR are picked up. Use the command
142 /*	"\fBpostfix reload\fR" to speed up a change.
143 /*
144 /*	The text below provides only a parameter summary. See
145 /*	\fBpostconf\fR(5) for more details including examples.
146 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
147 /*	The default location of the Postfix main.cf and master.cf
148 /*	configuration files.
149 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
150 /*	The directory with Postfix-writable data files (for example:
151 /*	caches, pseudo-random numbers).
152 /* .IP "\fBdaemon_timeout (18000s)\fR"
153 /*	How much time a Postfix daemon process may take to handle a
154 /*	request before it is terminated by a built-in watchdog timer.
155 /* .IP "\fBipc_timeout (3600s)\fR"
156 /*	The time limit for sending or receiving information over an internal
157 /*	communication channel.
158 /* .IP "\fBmax_idle (100s)\fR"
159 /*	The maximum amount of time that an idle Postfix daemon process waits
160 /*	for an incoming connection before terminating voluntarily.
161 /* .IP "\fBmax_use (100)\fR"
162 /*	The maximal number of incoming connections that a Postfix daemon
163 /*	process will service before terminating voluntarily.
164 /* .IP "\fBprocess_id (read-only)\fR"
165 /*	The process ID of a Postfix command or daemon process.
166 /* .IP "\fBprocess_name (read-only)\fR"
167 /*	The process name of a Postfix command or daemon process.
168 /* .IP "\fBproxy_read_maps (see 'postconf -d' output)\fR"
169 /*	The lookup tables that the \fBproxymap\fR(8) server is allowed to
170 /*	access for the read-only service.
171 /* .PP
172 /*	Available in Postfix 2.5 and later:
173 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR"
174 /*	The directory with Postfix-writable data files (for example:
175 /*	caches, pseudo-random numbers).
176 /* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR"
177 /*	The lookup tables that the \fBproxymap\fR(8) server is allowed to
178 /*	access for the read-write service.
179 /* SEE ALSO
180 /*	postconf(5), configuration parameters
181 /*	master(5), generic daemon options
182 /* README FILES
183 /* .ad
184 /* .fi
185 /*	Use "\fBpostconf readme_directory\fR" or
186 /*	"\fBpostconf html_directory\fR" to locate this information.
187 /* .na
188 /* .nf
189 /*	DATABASE_README, Postfix lookup table overview
190 /* LICENSE
191 /* .ad
192 /* .fi
193 /*	The Secure Mailer license must be distributed with this software.
194 /* HISTORY
195 /* .ad
196 /* .fi
197 /*	The proxymap service was introduced with Postfix 2.0.
198 /* AUTHOR(S)
199 /*	Wietse Venema
200 /*	IBM T.J. Watson Research
201 /*	P.O. Box 704
202 /*	Yorktown Heights, NY 10598, USA
203 /*--*/
204 
205 /* System library. */
206 
207 #include <sys_defs.h>
208 #include <string.h>
209 #include <stdlib.h>
210 #include <unistd.h>
211 
212 /* Utility library. */
213 
214 #include <msg.h>
215 #include <mymalloc.h>
216 #include <vstring.h>
217 #include <htable.h>
218 #include <stringops.h>
219 #include <dict.h>
220 
221 /* Global library. */
222 
223 #include <mail_conf.h>
224 #include <mail_params.h>
225 #include <mail_version.h>
226 #include <mail_proto.h>
227 #include <dict_proxy.h>
228 
229 /* Server skeleton. */
230 
231 #include <mail_server.h>
232 
233 /* Application-specific. */
234 
235  /*
236   * XXX All but the last are needed here so that $name expansion dependencies
237   * aren't too broken. The fix is to gather all parameter default settings in
238   * one place.
239   */
240 char   *var_local_rcpt_maps;
241 char   *var_virt_alias_maps;
242 char   *var_virt_alias_doms;
243 char   *var_virt_mailbox_maps;
244 char   *var_virt_mailbox_doms;
245 char   *var_relay_rcpt_maps;
246 char   *var_relay_domains;
247 char   *var_canonical_maps;
248 char   *var_send_canon_maps;
249 char   *var_rcpt_canon_maps;
250 char   *var_relocated_maps;
251 char   *var_transport_maps;
252 char   *var_proxy_read_maps;
253 char   *var_proxy_write_maps;
254 
255  /*
256   * The pre-approved, pre-parsed list of maps.
257   */
258 static HTABLE *proxy_auth_maps;
259 
260  /*
261   * Shared and static to reduce memory allocation overhead.
262   */
263 static VSTRING *request;
264 static VSTRING *request_map;
265 static VSTRING *request_key;
266 static VSTRING *request_value;
267 static VSTRING *map_type_name_flags;
268 
269  /*
270   * Are we a proxy writer or not?
271   */
272 static int proxy_writer;
273 
274  /*
275   * Silly little macros.
276   */
277 #define STR(x)			vstring_str(x)
278 #define VSTREQ(x,y)		(strcmp(STR(x),y) == 0)
279 
280 /* proxy_map_find - look up or open table */
281 
282 static DICT *proxy_map_find(const char *map_type_name, int request_flags,
283 			            int *statp)
284 {
285     DICT   *dict;
286 
287 #define PROXY_COLON	DICT_TYPE_PROXY ":"
288 #define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
289 #define READ_OPEN_FLAGS	O_RDONLY
290 #define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)
291 
292     /*
293      * Canonicalize the map name. If the map is not on the approved list,
294      * deny the request.
295      */
296 #define PROXY_MAP_FIND_ERROR_RETURN(x)  { *statp = (x); return (0); }
297 
298     while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
299 	map_type_name += PROXY_COLON_LEN;
300     if (strchr(map_type_name, ':') == 0)
301 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
302     if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
303 	msg_warn("request for unapproved table: \"%s\"", map_type_name);
304 	msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
305 		 proxy_writer == 0 ? "read-only" : "read-write",
306 		 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
307 		 proxy_writer == 0 ? VAR_PROXY_READ_MAPS :
308 		 VAR_PROXY_WRITE_MAPS);
309 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
310     }
311 
312     /*
313      * Open one instance of a map for each combination of name+flags.
314      *
315      * Assume that a map instance can be shared among clients with different
316      * paranoia flag settings and with different map lookup flag settings.
317      *
318      * XXX The open() flags are passed implicitly, via the selection of the
319      * service name. For a more sophisticated interface, appropriate subsets
320      * of open() flags should be received directly from the client.
321      */
322     vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
323 		    dict_flags_str(request_flags & DICT_FLAG_NP_INST_MASK));
324     if ((dict = dict_handle(STR(map_type_name_flags))) == 0)
325 	dict = dict_open(map_type_name, proxy_writer ?
326 			 WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
327 			 request_flags);
328     if (dict == 0)
329 	msg_panic("proxy_map_find: dict_open null result");
330     dict_register(STR(map_type_name_flags), dict);
331     return (dict);
332 }
333 
334 /* proxymap_lookup_service - remote lookup service */
335 
336 static void proxymap_lookup_service(VSTREAM *client_stream)
337 {
338     int     request_flags;
339     DICT   *dict;
340     const char *reply_value;
341     int     reply_status;
342 
343     /*
344      * Process the request.
345      */
346     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
347 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
348 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
349 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
350 		  ATTR_TYPE_END) != 3) {
351 	reply_status = PROXY_STAT_BAD;
352 	reply_value = "";
353     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
354 				      &reply_status)) == 0) {
355 	reply_value = "";
356     } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
357 			      | (request_flags & DICT_FLAG_RQST_MASK)),
358 	       (reply_value = dict_get(dict, STR(request_key))) != 0) {
359 	reply_status = PROXY_STAT_OK;
360     } else if (dict_errno == 0) {
361 	reply_status = PROXY_STAT_NOKEY;
362 	reply_value = "";
363     } else {
364 	reply_status = PROXY_STAT_RETRY;
365 	reply_value = "";
366     }
367 
368     /*
369      * Respond to the client.
370      */
371     attr_print(client_stream, ATTR_FLAG_NONE,
372 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
373 	       ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value,
374 	       ATTR_TYPE_END);
375 }
376 
377 /* proxymap_update_service - remote update service */
378 
379 static void proxymap_update_service(VSTREAM *client_stream)
380 {
381     int     request_flags;
382     DICT   *dict;
383     int     reply_status;
384 
385     /*
386      * Process the request.
387      *
388      * XXX We don't close maps, so we must turn on synchronous update to ensure
389      * that the on-disk data is in a consistent state between updates.
390      *
391      * XXX We ignore duplicates, because the proxymap server would abort
392      * otherwise.
393      */
394     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
395 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
396 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
397 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
398 		  ATTR_TYPE_STR, MAIL_ATTR_VALUE, request_value,
399 		  ATTR_TYPE_END) != 4) {
400 	reply_status = PROXY_STAT_BAD;
401     } else if (proxy_writer == 0) {
402 	msg_warn("refusing %s update request on non-%s service",
403 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
404 	reply_status = PROXY_STAT_DENY;
405     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
406 				      &reply_status)) == 0) {
407 	 /* void */ ;
408     } else {
409 	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
410 		       | (request_flags & DICT_FLAG_RQST_MASK)
411 		       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
412 	dict_put(dict, STR(request_key), STR(request_value));
413 	reply_status = PROXY_STAT_OK;
414     }
415 
416     /*
417      * Respond to the client.
418      */
419     attr_print(client_stream, ATTR_FLAG_NONE,
420 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
421 	       ATTR_TYPE_END);
422 }
423 
424 /* proxymap_delete_service - remote delete service */
425 
426 static void proxymap_delete_service(VSTREAM *client_stream)
427 {
428     int     request_flags;
429     DICT   *dict;
430     int     reply_status;
431 
432     /*
433      * Process the request.
434      *
435      * XXX We don't close maps, so we must turn on synchronous update to ensure
436      * that the on-disk data is in a consistent state between updates.
437      */
438     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
439 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
440 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
441 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
442 		  ATTR_TYPE_END) != 3) {
443 	reply_status = PROXY_STAT_BAD;
444     } else if (proxy_writer == 0) {
445 	msg_warn("refusing %s delete request on non-%s service",
446 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
447 	reply_status = PROXY_STAT_DENY;
448     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
449 				      &reply_status)) == 0) {
450 	 /* void */ ;
451     } else {
452 	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
453 		       | (request_flags & DICT_FLAG_RQST_MASK)
454 		       | DICT_FLAG_SYNC_UPDATE);
455 	reply_status =
456 	    dict_del(dict, STR(request_key)) ? PROXY_STAT_OK : PROXY_STAT_NOKEY;
457     }
458 
459     /*
460      * Respond to the client.
461      */
462     attr_print(client_stream, ATTR_FLAG_NONE,
463 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
464 	       ATTR_TYPE_END);
465 }
466 
467 /* proxymap_open_service - open remote lookup table */
468 
469 static void proxymap_open_service(VSTREAM *client_stream)
470 {
471     int     request_flags;
472     DICT   *dict;
473     int     reply_status;
474     int     reply_flags;
475 
476     /*
477      * Process the request.
478      */
479     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
480 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
481 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
482 		  ATTR_TYPE_END) != 2) {
483 	reply_status = PROXY_STAT_BAD;
484 	reply_flags = 0;
485     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
486 				      &reply_status)) == 0) {
487 	reply_flags = 0;
488     } else {
489 	reply_status = PROXY_STAT_OK;
490 	reply_flags = dict->flags;
491     }
492 
493     /*
494      * Respond to the client.
495      */
496     attr_print(client_stream, ATTR_FLAG_NONE,
497 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
498 	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, reply_flags,
499 	       ATTR_TYPE_END);
500 }
501 
502 /* proxymap_service - perform service for client */
503 
504 static void proxymap_service(VSTREAM *client_stream, char *unused_service,
505 			             char **argv)
506 {
507 
508     /*
509      * Sanity check. This service takes no command-line arguments.
510      */
511     if (argv[0])
512 	msg_fatal("unexpected command-line argument: %s", argv[0]);
513 
514     /*
515      * This routine runs whenever a client connects to the socket dedicated
516      * to the proxymap service. All connection-management stuff is handled by
517      * the common code in multi_server.c.
518      */
519     if (attr_scan(client_stream,
520 		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
521 		  ATTR_TYPE_STR, MAIL_ATTR_REQ, request,
522 		  ATTR_TYPE_END) == 1) {
523 	if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
524 	    proxymap_lookup_service(client_stream);
525 	} else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
526 	    proxymap_update_service(client_stream);
527 	} else if (VSTREQ(request, PROXY_REQ_DELETE)) {
528 	    proxymap_delete_service(client_stream);
529 	} else if (VSTREQ(request, PROXY_REQ_OPEN)) {
530 	    proxymap_open_service(client_stream);
531 	} else {
532 	    msg_warn("unrecognized request: \"%s\", ignored", STR(request));
533 	    attr_print(client_stream, ATTR_FLAG_NONE,
534 		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, PROXY_STAT_BAD,
535 		       ATTR_TYPE_END);
536 	}
537     }
538     vstream_fflush(client_stream);
539 }
540 
541 /* dict_proxy_open - intercept remote map request from inside library */
542 
543 DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
544 {
545     if (msg_verbose)
546 	msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine",
547 		 map, open_flags, dict_flags);
548     while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0)
549 	map += PROXY_COLON_LEN;
550     return (dict_open(map, open_flags, dict_flags));
551 }
552 
553 /* post_jail_init - initialization after privilege drop */
554 
555 static void post_jail_init(char *service_name, char **unused_argv)
556 {
557     const char *sep = ", \t\r\n";
558     char   *saved_filter;
559     char   *bp;
560     char   *type_name;
561 
562     /*
563      * Are we proxy writer?
564      */
565     if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
566 	proxy_writer = 1;
567     else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
568 	msg_fatal("service name must be one of %s or %s",
569 		  MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP);
570 
571     /*
572      * Pre-allocate buffers.
573      */
574     request = vstring_alloc(10);
575     request_map = vstring_alloc(10);
576     request_key = vstring_alloc(10);
577     request_value = vstring_alloc(10);
578     map_type_name_flags = vstring_alloc(10);
579 
580     /*
581      * Prepare the pre-approved list of proxied tables.
582      */
583     saved_filter = bp = mystrdup(proxy_writer ? var_proxy_write_maps :
584 				 var_proxy_read_maps);
585     proxy_auth_maps = htable_create(13);
586     while ((type_name = mystrtok(&bp, sep)) != 0) {
587 	if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
588 	    continue;
589 	do {
590 	    type_name += PROXY_COLON_LEN;
591 	} while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
592 	if (strchr(type_name, ':') != 0
593 	    && htable_locate(proxy_auth_maps, type_name) == 0)
594 	    (void) htable_enter(proxy_auth_maps, type_name, (char *) 0);
595     }
596     myfree(saved_filter);
597 
598     /*
599      * Never, ever, get killed by a master signal, as that could corrupt a
600      * persistent database when we're in the middle of an update.
601      */
602     if (proxy_writer != 0)
603 	setsid();
604 }
605 
606 /* pre_accept - see if tables have changed */
607 
608 static void pre_accept(char *unused_name, char **unused_argv)
609 {
610     const char *table;
611 
612     if (proxy_writer == 0 && (table = dict_changed_name()) != 0) {
613 	msg_info("table %s has changed -- restarting", table);
614 	exit(0);
615     }
616 }
617 
618 MAIL_VERSION_STAMP_DECLARE;
619 
620 /* main - pass control to the multi-threaded skeleton */
621 
622 int     main(int argc, char **argv)
623 {
624     static const CONFIG_STR_TABLE str_table[] = {
625 	VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
626 	VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
627 	VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
628 	VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
629 	VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
630 	VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
631 	VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0,
632 	VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
633 	VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
634 	VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
635 	VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
636 	VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
637 	VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
638 	VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
639 	0,
640     };
641 
642     /*
643      * Fingerprint executables and core dumps.
644      */
645     MAIL_VERSION_STAMP_ALLOCATE;
646 
647     multi_server_main(argc, argv, proxymap_service,
648 		      MAIL_SERVER_STR_TABLE, str_table,
649 		      MAIL_SERVER_POST_INIT, post_jail_init,
650 		      MAIL_SERVER_PRE_ACCEPT, pre_accept,
651     /* XXX MAIL_SERVER_SOLITARY if proxywrite */
652 		      0);
653 }
654