1/*	$NetBSD: proxymap.c,v 1.4 2022/10/08 16:12:48 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#include <dict_pipe.h>
238#include <dict_union.h>
239
240/* Global library. */
241
242#include <mail_conf.h>
243#include <mail_params.h>
244#include <mail_version.h>
245#include <mail_proto.h>
246#include <dict_proxy.h>
247
248/* Server skeleton. */
249
250#include <mail_server.h>
251
252/* Application-specific. */
253
254 /*
255  * XXX All but the last are needed here so that $name expansion dependencies
256  * aren't too broken. The fix is to gather all parameter default settings in
257  * one place.
258  */
259char   *var_alias_maps;
260char   *var_local_rcpt_maps;
261char   *var_virt_alias_maps;
262char   *var_virt_alias_doms;
263char   *var_virt_mailbox_maps;
264char   *var_virt_mailbox_doms;
265char   *var_relay_rcpt_maps;
266char   *var_canonical_maps;
267char   *var_send_canon_maps;
268char   *var_rcpt_canon_maps;
269char   *var_relocated_maps;
270char   *var_transport_maps;
271char   *var_verify_map;
272char   *var_smtpd_snd_auth_maps;
273char   *var_psc_cache_map;
274char   *var_proxy_read_maps;
275char   *var_proxy_write_maps;
276
277 /*
278  * The pre-approved, pre-parsed list of maps.
279  */
280static HTABLE *proxy_auth_maps;
281
282 /*
283  * Shared and static to reduce memory allocation overhead.
284  */
285static VSTRING *request;
286static VSTRING *request_map;
287static VSTRING *request_key;
288static VSTRING *request_value;
289static VSTRING *map_type_name_flags;
290
291 /*
292  * Are we a proxy writer or not?
293  */
294static int proxy_writer;
295
296 /*
297  * Silly little macros.
298  */
299#define STR(x)			vstring_str(x)
300#define VSTREQ(x,y)		(strcmp(STR(x),y) == 0)
301
302/* get_nested_dict_name - return nested dictionary name pointer, or null */
303
304static char *get_nested_dict_name(char *type_name)
305{
306    const struct {
307	const char *type_col;
308	ssize_t type_col_len;
309    }      *prefix, prefixes[] = {
310	DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1),
311	DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1),
312    };
313
314#define COUNT_OF(x) (sizeof(x)/sizeof((x)[0]))
315
316    for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) {
317	if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0)
318	    return (type_name + prefix->type_col_len);
319    }
320    return (0);
321}
322
323/* proxy_map_find - look up or open table */
324
325static DICT *proxy_map_find(const char *map_type_name, int request_flags,
326			            int *statp)
327{
328    DICT   *dict;
329
330#define PROXY_COLON	DICT_TYPE_PROXY ":"
331#define PROXY_COLON_LEN	(sizeof(PROXY_COLON) - 1)
332#define READ_OPEN_FLAGS	O_RDONLY
333#define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT)
334
335    /*
336     * Canonicalize the map name. If the map is not on the approved list,
337     * deny the request.
338     */
339#define PROXY_MAP_FIND_ERROR_RETURN(x)  { *statp = (x); return (0); }
340#define PROXY_MAP_PARAM_NAME(proxy_writer)  \
341	((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS)
342
343    while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0)
344	map_type_name += PROXY_COLON_LEN;
345    /* XXX The following breaks with maps that have ':' in their name. */
346    if (strchr(map_type_name, ':') == 0)
347	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD);
348    if (htable_locate(proxy_auth_maps, map_type_name) == 0) {
349	msg_warn("request for unapproved table: \"%s\"", map_type_name);
350	msg_warn("to approve this table for %s access, list %s:%s in %s:%s",
351		 proxy_writer == 0 ? "read-only" : "read-write",
352		 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE,
353		 PROXY_MAP_PARAM_NAME(proxy_writer));
354	PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY);
355    }
356
357    /*
358     * Open one instance of a map for each combination of name+flags.
359     *
360     * Assume that a map instance can be shared among clients with different
361     * paranoia flag settings and with different map lookup flag settings.
362     *
363     * XXX The open() flags are passed implicitly, via the selection of the
364     * service name. For a more sophisticated interface, appropriate subsets
365     * of open() flags should be received directly from the client.
366     */
367    vstring_sprintf(map_type_name_flags, "%s:%s", map_type_name,
368		    dict_flags_str(request_flags & DICT_FLAG_INST_MASK));
369    if (msg_verbose)
370	msg_info("proxy_map_find: %s", STR(map_type_name_flags));
371    if ((dict = dict_handle(STR(map_type_name_flags))) == 0) {
372	dict = dict_open(map_type_name, proxy_writer ?
373			 WRITE_OPEN_FLAGS : READ_OPEN_FLAGS,
374			 request_flags);
375	if (dict == 0)
376	    msg_panic("proxy_map_find: dict_open null result");
377	dict_register(STR(map_type_name_flags), dict);
378    }
379    dict->error = 0;
380    return (dict);
381}
382
383/* proxymap_sequence_service - remote sequence service */
384
385static void proxymap_sequence_service(VSTREAM *client_stream)
386{
387    int     request_flags;
388    DICT   *dict;
389    int     request_func;
390    const char *reply_key;
391    const char *reply_value;
392    int     dict_status;
393    int     reply_status;
394
395    /*
396     * Process the request.
397     */
398    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
399		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
400		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
401		  RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func),
402		  ATTR_TYPE_END) != 3
403	|| (request_func != DICT_SEQ_FUN_FIRST
404	    && request_func != DICT_SEQ_FUN_NEXT)) {
405	reply_status = PROXY_STAT_BAD;
406	reply_key = reply_value = "";
407    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
408				      &reply_status)) == 0) {
409	reply_key = reply_value = "";
410    } else {
411	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
412		       | (request_flags & DICT_FLAG_RQST_MASK));
413	dict_status = dict_seq(dict, request_func, &reply_key, &reply_value);
414	if (dict_status == 0) {
415	    reply_status = PROXY_STAT_OK;
416	} else if (dict->error == 0) {
417	    reply_status = PROXY_STAT_NOKEY;
418	    reply_key = reply_value = "";
419	} else {
420	    reply_status = (dict->error == DICT_ERR_RETRY ?
421			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
422	    reply_key = reply_value = "";
423	}
424    }
425
426    /*
427     * Respond to the client.
428     */
429    attr_print(client_stream, ATTR_FLAG_NONE,
430	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
431	       SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key),
432	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
433	       ATTR_TYPE_END);
434}
435
436/* proxymap_lookup_service - remote lookup service */
437
438static void proxymap_lookup_service(VSTREAM *client_stream)
439{
440    int     request_flags;
441    DICT   *dict;
442    const char *reply_value;
443    int     reply_status;
444
445    /*
446     * Process the request.
447     */
448    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
449		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
450		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
451		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
452		  ATTR_TYPE_END) != 3) {
453	reply_status = PROXY_STAT_BAD;
454	reply_value = "";
455    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
456				      &reply_status)) == 0) {
457	reply_value = "";
458    } else if (dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
459			      | (request_flags & DICT_FLAG_RQST_MASK)),
460	       (reply_value = dict_get(dict, STR(request_key))) != 0) {
461	reply_status = PROXY_STAT_OK;
462    } else if (dict->error == 0) {
463	reply_status = PROXY_STAT_NOKEY;
464	reply_value = "";
465    } else {
466	reply_status = (dict->error == DICT_ERR_RETRY ?
467			PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
468	reply_value = "";
469    }
470
471    /*
472     * Respond to the client.
473     */
474    attr_print(client_stream, ATTR_FLAG_NONE,
475	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
476	       SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value),
477	       ATTR_TYPE_END);
478}
479
480/* proxymap_update_service - remote update service */
481
482static void proxymap_update_service(VSTREAM *client_stream)
483{
484    int     request_flags;
485    DICT   *dict;
486    int     dict_status;
487    int     reply_status;
488
489    /*
490     * Process the request.
491     *
492     * XXX We don't close maps, so we must turn on synchronous update to ensure
493     * that the on-disk data is in a consistent state between updates.
494     *
495     * XXX We ignore duplicates, because the proxymap server would abort
496     * otherwise.
497     */
498    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
499		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
500		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
501		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
502		  RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value),
503		  ATTR_TYPE_END) != 4) {
504	reply_status = PROXY_STAT_BAD;
505    } else if (proxy_writer == 0) {
506	msg_warn("refusing %s update request on non-%s service",
507		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
508	reply_status = PROXY_STAT_DENY;
509    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
510				      &reply_status)) == 0) {
511	 /* void */ ;
512    } else {
513	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
514		       | (request_flags & DICT_FLAG_RQST_MASK)
515		       | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE);
516	dict_status = dict_put(dict, STR(request_key), STR(request_value));
517	if (dict_status == 0) {
518	    reply_status = PROXY_STAT_OK;
519	} else if (dict->error == 0) {
520	    reply_status = PROXY_STAT_NOKEY;
521	} else {
522	    reply_status = (dict->error == DICT_ERR_RETRY ?
523			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
524	}
525    }
526
527    /*
528     * Respond to the client.
529     */
530    attr_print(client_stream, ATTR_FLAG_NONE,
531	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
532	       ATTR_TYPE_END);
533}
534
535/* proxymap_delete_service - remote delete service */
536
537static void proxymap_delete_service(VSTREAM *client_stream)
538{
539    int     request_flags;
540    DICT   *dict;
541    int     dict_status;
542    int     reply_status;
543
544    /*
545     * Process the request.
546     *
547     * XXX We don't close maps, so we must turn on synchronous update to ensure
548     * that the on-disk data is in a consistent state between updates.
549     */
550    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
551		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
552		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
553		  RECV_ATTR_STR(MAIL_ATTR_KEY, request_key),
554		  ATTR_TYPE_END) != 3) {
555	reply_status = PROXY_STAT_BAD;
556    } else if (proxy_writer == 0) {
557	msg_warn("refusing %s delete request on non-%s service",
558		 STR(request_map), MAIL_SERVICE_PROXYWRITE);
559	reply_status = PROXY_STAT_DENY;
560    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
561				      &reply_status)) == 0) {
562	 /* void */ ;
563    } else {
564	dict->flags = ((dict->flags & ~DICT_FLAG_RQST_MASK)
565		       | (request_flags & DICT_FLAG_RQST_MASK)
566		       | DICT_FLAG_SYNC_UPDATE);
567	dict_status = dict_del(dict, STR(request_key));
568	if (dict_status == 0) {
569	    reply_status = PROXY_STAT_OK;
570	} else if (dict->error == 0) {
571	    reply_status = PROXY_STAT_NOKEY;
572	} else {
573	    reply_status = (dict->error == DICT_ERR_RETRY ?
574			    PROXY_STAT_RETRY : PROXY_STAT_CONFIG);
575	}
576    }
577
578    /*
579     * Respond to the client.
580     */
581    attr_print(client_stream, ATTR_FLAG_NONE,
582	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
583	       ATTR_TYPE_END);
584}
585
586/* proxymap_open_service - open remote lookup table */
587
588static void proxymap_open_service(VSTREAM *client_stream)
589{
590    int     request_flags;
591    DICT   *dict;
592    int     reply_status;
593    int     reply_flags;
594
595    /*
596     * Process the request.
597     */
598    if (attr_scan(client_stream, ATTR_FLAG_STRICT,
599		  RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map),
600		  RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags),
601		  ATTR_TYPE_END) != 2) {
602	reply_status = PROXY_STAT_BAD;
603	reply_flags = 0;
604    } else if ((dict = proxy_map_find(STR(request_map), request_flags,
605				      &reply_status)) == 0) {
606	reply_flags = 0;
607    } else {
608	reply_status = PROXY_STAT_OK;
609	reply_flags = dict->flags;
610    }
611
612    /*
613     * Respond to the client.
614     */
615    attr_print(client_stream, ATTR_FLAG_NONE,
616	       SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status),
617	       SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags),
618	       ATTR_TYPE_END);
619}
620
621/* proxymap_service - perform service for client */
622
623static void proxymap_service(VSTREAM *client_stream, char *unused_service,
624			             char **argv)
625{
626
627    /*
628     * Sanity check. This service takes no command-line arguments.
629     */
630    if (argv[0])
631	msg_fatal("unexpected command-line argument: %s", argv[0]);
632
633    /*
634     * Deadline enforcement.
635     */
636    if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0)
637	vstream_control(client_stream,
638			CA_VSTREAM_CTL_TIMEOUT(1),
639			CA_VSTREAM_CTL_END);
640
641    /*
642     * This routine runs whenever a client connects to the socket dedicated
643     * to the proxymap service. All connection-management stuff is handled by
644     * the common code in multi_server.c.
645     */
646    vstream_control(client_stream,
647		    CA_VSTREAM_CTL_START_DEADLINE,
648		    CA_VSTREAM_CTL_END);
649    if (attr_scan(client_stream,
650		  ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
651		  RECV_ATTR_STR(MAIL_ATTR_REQ, request),
652		  ATTR_TYPE_END) == 1) {
653	if (VSTREQ(request, PROXY_REQ_LOOKUP)) {
654	    proxymap_lookup_service(client_stream);
655	} else if (VSTREQ(request, PROXY_REQ_UPDATE)) {
656	    proxymap_update_service(client_stream);
657	} else if (VSTREQ(request, PROXY_REQ_DELETE)) {
658	    proxymap_delete_service(client_stream);
659	} else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) {
660	    proxymap_sequence_service(client_stream);
661	} else if (VSTREQ(request, PROXY_REQ_OPEN)) {
662	    proxymap_open_service(client_stream);
663	} else {
664	    msg_warn("unrecognized request: \"%s\", ignored", STR(request));
665	    attr_print(client_stream, ATTR_FLAG_NONE,
666		       SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD),
667		       ATTR_TYPE_END);
668	}
669    }
670    vstream_control(client_stream,
671		    CA_VSTREAM_CTL_START_DEADLINE,
672		    CA_VSTREAM_CTL_END);
673    vstream_fflush(client_stream);
674}
675
676/* dict_proxy_open - intercept remote map request from inside library */
677
678DICT   *dict_proxy_open(const char *map, int open_flags, int dict_flags)
679{
680    if (msg_verbose)
681	msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine",
682		 map, open_flags, dict_flags);
683    while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0)
684	map += PROXY_COLON_LEN;
685    return (dict_open(map, open_flags, dict_flags));
686}
687
688/* authorize_proxied_maps - recursively authorize maps */
689
690static void authorize_proxied_maps(char *bp)
691{
692    const char *sep = CHARS_COMMA_SP;
693    const char *parens = CHARS_BRACE;
694    char   *type_name;
695
696    while ((type_name = mystrtokq(&bp, sep, parens)) != 0) {
697	char   *nested_info;
698
699	/* Maybe { maptype:mapname attr=value... } */
700	if (*type_name == parens[0]) {
701	    char   *err;
702
703	    /* Warn about blatant syntax error. */
704	    if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) {
705		msg_warn("bad %s parameter value: %s",
706			 PROXY_MAP_PARAM_NAME(proxy_writer), err);
707		myfree(err);
708		continue;
709	    }
710	    /* Don't try to second-guess the semantics of { }. */
711	    if ((type_name = mystrtokq(&type_name, sep, parens)) == 0)
712		continue;
713	}
714	/* Recurse into nested map (pipemap, unionmap). */
715	if ((nested_info = get_nested_dict_name(type_name)) != 0) {
716	    char   *err;
717
718	    if (*nested_info != parens[0])
719		continue;
720	    /* Warn about blatant syntax error. */
721	    if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) {
722		msg_warn("bad %s parameter value: %s",
723			 PROXY_MAP_PARAM_NAME(proxy_writer), err);
724		myfree(err);
725		continue;
726	    }
727	    authorize_proxied_maps(nested_info);
728	    continue;
729	}
730	if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN))
731	    continue;
732	do {
733	    type_name += PROXY_COLON_LEN;
734	} while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN));
735	if (strchr(type_name, ':') != 0
736	    && htable_locate(proxy_auth_maps, type_name) == 0) {
737	    (void) htable_enter(proxy_auth_maps, type_name, (void *) 0);
738	    if (msg_verbose)
739		msg_info("allowlisting %s from %s", type_name,
740			 PROXY_MAP_PARAM_NAME(proxy_writer));
741	}
742    }
743}
744
745/* post_jail_init - initialization after privilege drop */
746
747static void post_jail_init(char *service_name, char **unused_argv)
748{
749    char   *saved_filter;
750
751    /*
752     * Are we proxy writer?
753     */
754    if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0)
755	proxy_writer = 1;
756    else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0)
757	msg_fatal("service name must be one of %s or %s",
758		  MAIL_SERVICE_PROXYMAP, MAIL_SERVICE_PROXYMAP);
759
760    /*
761     * Pre-allocate buffers.
762     */
763    request = vstring_alloc(10);
764    request_map = vstring_alloc(10);
765    request_key = vstring_alloc(10);
766    request_value = vstring_alloc(10);
767    map_type_name_flags = vstring_alloc(10);
768
769    /*
770     * Prepare the pre-approved list of proxied tables.
771     */
772    saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps :
773			    var_proxy_read_maps);
774    proxy_auth_maps = htable_create(13);
775    authorize_proxied_maps(saved_filter);
776    myfree(saved_filter);
777
778    /*
779     * Never, ever, get killed by a master signal, as that could corrupt a
780     * persistent database when we're in the middle of an update.
781     */
782    if (proxy_writer != 0)
783	setsid();
784}
785
786/* pre_accept - see if tables have changed */
787
788static void pre_accept(char *unused_name, char **unused_argv)
789{
790    const char *table;
791
792    if (proxy_writer == 0 && (table = dict_changed_name()) != 0) {
793	msg_info("table %s has changed -- restarting", table);
794	exit(0);
795    }
796}
797
798/* post_accept - announce our protocol name */
799
800static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv,
801			        HTABLE *unused_attr)
802{
803
804    /*
805     * Announce the protocol.
806     */
807    attr_print(stream, ATTR_FLAG_NONE,
808	       SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP),
809	       ATTR_TYPE_END);
810    (void) vstream_fflush(stream);
811}
812
813MAIL_VERSION_STAMP_DECLARE;
814
815/* main - pass control to the multi-threaded skeleton */
816
817int     main(int argc, char **argv)
818{
819    static const CONFIG_STR_TABLE str_table[] = {
820	VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0,
821	VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0,
822	VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0,
823	VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0,
824	VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0,
825	VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0,
826	VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0,
827	VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0,
828	VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0,
829	VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0,
830	VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0,
831	VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0,
832	VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0,
833	VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0,
834	VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0,
835	/* The following two must be last for $mapname to work as expected. */
836	VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0,
837	VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0,
838	0,
839    };
840
841    /*
842     * Fingerprint executables and core dumps.
843     */
844    MAIL_VERSION_STAMP_ALLOCATE;
845
846    multi_server_main(argc, argv, proxymap_service,
847		      CA_MAIL_SERVER_STR_TABLE(str_table),
848		      CA_MAIL_SERVER_POST_INIT(post_jail_init),
849		      CA_MAIL_SERVER_PRE_ACCEPT(pre_accept),
850		      CA_MAIL_SERVER_POST_ACCEPT(post_accept),
851    /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */
852		      0);
853}
854