xref: /netbsd-src/external/ibm-public/postfix/dist/src/proxymap/proxymap.c (revision 7788a0781fe6ff2cce37368b4578a7ade0850cb1)
1 /*	$NetBSD: proxymap.c,v 1.1.1.3 2013/01/02 18:59:06 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 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_psc_cache_map;
262 char   *var_proxy_read_maps;
263 char   *var_proxy_write_maps;
264 
265  /*
266   * The pre-approved, pre-parsed list of maps.
267   */
268 static HTABLE *proxy_auth_maps;
269 
270  /*
271   * Shared and static to reduce memory allocation overhead.
272   */
273 static VSTRING *request;
274 static VSTRING *request_map;
275 static VSTRING *request_key;
276 static VSTRING *request_value;
277 static VSTRING *map_type_name_flags;
278 
279  /*
280   * Are we a proxy writer or not?
281   */
282 static int proxy_writer;
283 
284  /*
285   * Silly little macros.
286   */
287 #define STR(x)			vstring_str(x)
288 #define VSTREQ(x,y)		(strcmp(STR(x),y) == 0)
289 
290 /* proxy_map_find - look up or open table */
291 
292 static DICT *proxy_map_find(const char *map_type_name, int request_flags,
293 			            int *statp)
294 {
295     DICT   *dict;
296 
297 #define PROXY_COLON	DICT_TYPE_PROXY ":"
298 #define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
299 #define READ_OPEN_FLAGS	O_RDONLY
300 #define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)
301 
302     /*
303      * Canonicalize the map name. If the map is not on the approved list,
304      * deny the request.
305      */
306 #define PROXY_MAP_FIND_ERROR_RETURN(x)  { *statp = (x); return (0); }
307 
308     while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
309 	map_type_name += PROXY_COLON_LEN;
310     if (strchr(map_type_name, ':') == 0)
311 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
312     if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
313 	msg_warn("request for unapproved table: \"%s\"", map_type_name);
314 	msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
315 		 proxy_writer == 0 ? "read-only" : "read-write",
316 		 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
317 		 proxy_writer == 0 ? VAR_PROXY_READ_MAPS :
318 		 VAR_PROXY_WRITE_MAPS);
319 	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
320     }
321 
322     /*
323      * Open one instance of a map for each combination of name+flags.
324      *
325      * Assume that a map instance can be shared among clients with different
326      * paranoia flag settings and with different map lookup flag settings.
327      *
328      * XXX The open() flags are passed implicitly, via the selection of the
329      * service name. For a more sophisticated interface, appropriate subsets
330      * of open() flags should be received directly from the client.
331      */
332     vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
333 		    dict_flags_str(request_flags & DICT_FLAG_INST_MASK));
334     if (msg_verbose)
335 	msg_info("proxy_map_find: %s", STR(map_type_name_flags));
336     if ((dict = dict_handle(STR(map_type_name_flags))) == 0) {
337 	dict = dict_open(map_type_name, proxy_writer ?
338 			 WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
339 			 request_flags);
340 	if (dict == 0)
341 	    msg_panic("proxy_map_find: dict_open null result");
342 	dict_register(STR(map_type_name_flags), dict);
343     }
344     dict->error = 0;
345     return (dict);
346 }
347 
348 /* proxymap_sequence_service - remote sequence service */
349 
350 static void proxymap_sequence_service(VSTREAM *client_stream)
351 {
352     int     request_flags;
353     DICT   *dict;
354     int     request_func;
355     const char *reply_key;
356     const char *reply_value;
357     int     dict_status;
358     int     reply_status;
359 
360     /*
361      * Process the request.
362      */
363     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
364 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
365 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
366 		  ATTR_TYPE_INT, MAIL_ATTR_FUNC, &request_func,
367 		  ATTR_TYPE_END) != 3
368 	|| (request_func != DICT_SEQ_FUN_FIRST
369 	    && request_func != DICT_SEQ_FUN_NEXT)) {
370 	reply_status = PROXY_STAT_BAD;
371 	reply_key = reply_value = "";
372     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
373 				      &reply_status)) == 0) {
374 	reply_key = reply_value = "";
375     } else {
376 	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
377 		       | (request_flags & DICT_FLAG_RQST_MASK));
378 	dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
379 	if (dict_status == 0) {
380 	    reply_status = PROXY_STAT_OK;
381 	} else if (dict->error == 0) {
382 	    reply_status = PROXY_STAT_NOKEY;
383 	    reply_key = reply_value = "";
384 	} else {
385 	    reply_status = PROXY_STAT_RETRY;
386 	    reply_key = reply_value = "";
387 	}
388     }
389 
390     /*
391      * Respond to the client.
392      */
393     attr_print(client_stream, ATTR_FLAG_NONE,
394 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
395 	       ATTR_TYPE_STR, MAIL_ATTR_KEY, reply_key,
396 	       ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value,
397 	       ATTR_TYPE_END);
398 }
399 
400 /* proxymap_lookup_service - remote lookup service */
401 
402 static void proxymap_lookup_service(VSTREAM *client_stream)
403 {
404     int     request_flags;
405     DICT   *dict;
406     const char *reply_value;
407     int     reply_status;
408 
409     /*
410      * Process the request.
411      */
412     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
413 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
414 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
415 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
416 		  ATTR_TYPE_END) != 3) {
417 	reply_status = PROXY_STAT_BAD;
418 	reply_value = "";
419     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
420 				      &reply_status)) == 0) {
421 	reply_value = "";
422     } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
423 			      | (request_flags & DICT_FLAG_RQST_MASK)),
424 	       (reply_value = dict_get(dict, STR(request_key))) != 0) {
425 	reply_status = PROXY_STAT_OK;
426     } else if (dict->error == 0) {
427 	reply_status = PROXY_STAT_NOKEY;
428 	reply_value = "";
429     } else {
430 	reply_status = PROXY_STAT_RETRY;
431 	reply_value = "";
432     }
433 
434     /*
435      * Respond to the client.
436      */
437     attr_print(client_stream, ATTR_FLAG_NONE,
438 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
439 	       ATTR_TYPE_STR, MAIL_ATTR_VALUE, reply_value,
440 	       ATTR_TYPE_END);
441 }
442 
443 /* proxymap_update_service - remote update service */
444 
445 static void proxymap_update_service(VSTREAM *client_stream)
446 {
447     int     request_flags;
448     DICT   *dict;
449     int     dict_status;
450     int     reply_status;
451 
452     /*
453      * Process the request.
454      *
455      * XXX We don't close maps, so we must turn on synchronous update to ensure
456      * that the on-disk data is in a consistent state between updates.
457      *
458      * XXX We ignore duplicates, because the proxymap server would abort
459      * otherwise.
460      */
461     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
462 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
463 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
464 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
465 		  ATTR_TYPE_STR, MAIL_ATTR_VALUE, request_value,
466 		  ATTR_TYPE_END) != 4) {
467 	reply_status = PROXY_STAT_BAD;
468     } else if (proxy_writer == 0) {
469 	msg_warn("refusing %s update request on non-%s service",
470 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
471 	reply_status = PROXY_STAT_DENY;
472     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
473 				      &reply_status)) == 0) {
474 	 /* void */ ;
475     } else {
476 	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
477 		       | (request_flags & DICT_FLAG_RQST_MASK)
478 		       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
479 	dict_status = dict_put(dict, STR(request_key), STR(request_value));
480 	if (dict_status == 0) {
481 	    reply_status = PROXY_STAT_OK;
482 	} else if (dict->error == 0) {
483 	    reply_status = PROXY_STAT_NOKEY;
484 	} else {
485 	    reply_status = PROXY_STAT_RETRY;
486 	}
487     }
488 
489     /*
490      * Respond to the client.
491      */
492     attr_print(client_stream, ATTR_FLAG_NONE,
493 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
494 	       ATTR_TYPE_END);
495 }
496 
497 /* proxymap_delete_service - remote delete service */
498 
499 static void proxymap_delete_service(VSTREAM *client_stream)
500 {
501     int     request_flags;
502     DICT   *dict;
503     int     dict_status;
504     int     reply_status;
505 
506     /*
507      * Process the request.
508      *
509      * XXX We don't close maps, so we must turn on synchronous update to ensure
510      * that the on-disk data is in a consistent state between updates.
511      */
512     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
513 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
514 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
515 		  ATTR_TYPE_STR, MAIL_ATTR_KEY, request_key,
516 		  ATTR_TYPE_END) != 3) {
517 	reply_status = PROXY_STAT_BAD;
518     } else if (proxy_writer == 0) {
519 	msg_warn("refusing %s delete request on non-%s service",
520 		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
521 	reply_status = PROXY_STAT_DENY;
522     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
523 				      &reply_status)) == 0) {
524 	 /* void */ ;
525     } else {
526 	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
527 		       | (request_flags & DICT_FLAG_RQST_MASK)
528 		       | DICT_FLAG_SYNC_UPDATE);
529 	dict_status = dict_del(dict, STR(request_key));
530 	if (dict_status == 0) {
531 	    reply_status = PROXY_STAT_OK;
532 	} else if (dict->error == 0) {
533 	    reply_status = PROXY_STAT_NOKEY;
534 	} else {
535 	    reply_status = PROXY_STAT_RETRY;
536 	}
537     }
538 
539     /*
540      * Respond to the client.
541      */
542     attr_print(client_stream, ATTR_FLAG_NONE,
543 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
544 	       ATTR_TYPE_END);
545 }
546 
547 /* proxymap_open_service - open remote lookup table */
548 
549 static void proxymap_open_service(VSTREAM *client_stream)
550 {
551     int     request_flags;
552     DICT   *dict;
553     int     reply_status;
554     int     reply_flags;
555 
556     /*
557      * Process the request.
558      */
559     if (attr_scan(client_stream, ATTR_FLAG_STRICT,
560 		  ATTR_TYPE_STR, MAIL_ATTR_TABLE, request_map,
561 		  ATTR_TYPE_INT, MAIL_ATTR_FLAGS, &request_flags,
562 		  ATTR_TYPE_END) != 2) {
563 	reply_status = PROXY_STAT_BAD;
564 	reply_flags = 0;
565     } else if ((dict = proxy_map_find(STR(request_map), request_flags,
566 				      &reply_status)) == 0) {
567 	reply_flags = 0;
568     } else {
569 	reply_status = PROXY_STAT_OK;
570 	reply_flags = dict->flags;
571     }
572 
573     /*
574      * Respond to the client.
575      */
576     attr_print(client_stream, ATTR_FLAG_NONE,
577 	       ATTR_TYPE_INT, MAIL_ATTR_STATUS, reply_status,
578 	       ATTR_TYPE_INT, MAIL_ATTR_FLAGS, reply_flags,
579 	       ATTR_TYPE_END);
580 }
581 
582 /* proxymap_service - perform service for client */
583 
584 static void proxymap_service(VSTREAM *client_stream, char *unused_service,
585 			             char **argv)
586 {
587 
588     /*
589      * Sanity check. This service takes no command-line arguments.
590      */
591     if (argv[0])
592 	msg_fatal("unexpected command-line argument: %s", argv[0]);
593 
594     /*
595      * Deadline enforcement.
596      */
597     if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0)
598 	vstream_control(client_stream,
599 			VSTREAM_CTL_TIMEOUT, 1,
600 			VSTREAM_CTL_END);
601 
602     /*
603      * This routine runs whenever a client connects to the socket dedicated
604      * to the proxymap service. All connection-management stuff is handled by
605      * the common code in multi_server.c.
606      */
607     vstream_control(client_stream,
608 		    VSTREAM_CTL_START_DEADLINE,
609 		    VSTREAM_CTL_END);
610     if (attr_scan(client_stream,
611 		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
612 		  ATTR_TYPE_STR, MAIL_ATTR_REQ, request,
613 		  ATTR_TYPE_END) == 1) {
614 	if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
615 	    proxymap_lookup_service(client_stream);
616 	} else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
617 	    proxymap_update_service(client_stream);
618 	} else if (VSTREQ(request, PROXY_REQ_DELETE)) {
619 	    proxymap_delete_service(client_stream);
620 	} else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) {
621 	    proxymap_sequence_service(client_stream);
622 	} else if (VSTREQ(request, PROXY_REQ_OPEN)) {
623 	    proxymap_open_service(client_stream);
624 	} else {
625 	    msg_warn("unrecognized request: \"%s\", ignored", STR(request));
626 	    attr_print(client_stream, ATTR_FLAG_NONE,
627 		       ATTR_TYPE_INT, MAIL_ATTR_STATUS, PROXY_STAT_BAD,
628 		       ATTR_TYPE_END);
629 	}
630     }
631     vstream_control(client_stream,
632 		    VSTREAM_CTL_START_DEADLINE,
633 		    VSTREAM_CTL_END);
634     vstream_fflush(client_stream);
635 }
636 
637 /* dict_proxy_open - intercept remote map request from inside library */
638 
639 DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
640 {
641     if (msg_verbose)
642 	msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine",
643 		 map, open_flags, dict_flags);
644     while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0)
645 	map += PROXY_COLON_LEN;
646     return (dict_open(map, open_flags, dict_flags));
647 }
648 
649 /* post_jail_init - initialization after privilege drop */
650 
651 static void post_jail_init(char *service_name, char **unused_argv)
652 {
653     const char *sep = ", \t\r\n";
654     char   *saved_filter;
655     char   *bp;
656     char   *type_name;
657 
658     /*
659      * Are we proxy writer?
660      */
661     if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
662 	proxy_writer = 1;
663     else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
664 	msg_fatal("service name must be one of %s or %s",
665 		  MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP);
666 
667     /*
668      * Pre-allocate buffers.
669      */
670     request = vstring_alloc(10);
671     request_map = vstring_alloc(10);
672     request_key = vstring_alloc(10);
673     request_value = vstring_alloc(10);
674     map_type_name_flags = vstring_alloc(10);
675 
676     /*
677      * Prepare the pre-approved list of proxied tables.
678      */
679     saved_filter = bp = mystrdup(proxy_writer ? var_proxy_write_maps :
680 				 var_proxy_read_maps);
681     proxy_auth_maps = htable_create(13);
682     while ((type_name = mystrtok(&bp, sep)) != 0) {
683 	if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
684 	    continue;
685 	do {
686 	    type_name += PROXY_COLON_LEN;
687 	} while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
688 	if (strchr(type_name, ':') != 0
689 	    && htable_locate(proxy_auth_maps, type_name) == 0)
690 	    (void) htable_enter(proxy_auth_maps, type_name, (char *) 0);
691     }
692     myfree(saved_filter);
693 
694     /*
695      * Never, ever, get killed by a master signal, as that could corrupt a
696      * persistent database when we're in the middle of an update.
697      */
698     if (proxy_writer != 0)
699 	setsid();
700 }
701 
702 /* pre_accept - see if tables have changed */
703 
704 static void pre_accept(char *unused_name, char **unused_argv)
705 {
706     const char *table;
707 
708     if (proxy_writer == 0 && (table = dict_changed_name()) != 0) {
709 	msg_info("table %s has changed -- restarting", table);
710 	exit(0);
711     }
712 }
713 
714 MAIL_VERSION_STAMP_DECLARE;
715 
716 /* main - pass control to the multi-threaded skeleton */
717 
718 int     main(int argc, char **argv)
719 {
720     static const CONFIG_STR_TABLE str_table[] = {
721 	VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
722 	VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
723 	VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
724 	VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
725 	VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
726 	VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
727 	VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
728 	VAR_RELAY_DOMAINS, DEF_RELAY_DOMAINS, &var_relay_domains, 0, 0,
729 	VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
730 	VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
731 	VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
732 	VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
733 	VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
734 	VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
735 	VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
736 	/* The following two must be last for $mapname to work as expected. */
737 	VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
738 	VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
739 	0,
740     };
741 
742     /*
743      * Fingerprint executables and core dumps.
744      */
745     MAIL_VERSION_STAMP_ALLOCATE;
746 
747     multi_server_main(argc, argv, proxymap_service,
748 		      MAIL_SERVER_STR_TABLE, str_table,
749 		      MAIL_SERVER_POST_INIT, post_jail_init,
750 		      MAIL_SERVER_PRE_ACCEPT, pre_accept,
751     /* XXX MAIL_SERVER_SOLITARY if proxywrite */
752 		      0);
753 }
754