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