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