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 */
259 char *var_alias_maps;
260 char *var_local_rcpt_maps;
261 char *var_virt_alias_maps;
262 char *var_virt_alias_doms;
263 char *var_virt_mailbox_maps;
264 char *var_virt_mailbox_doms;
265 char *var_relay_rcpt_maps;
266 char *var_canonical_maps;
267 char *var_send_canon_maps;
268 char *var_rcpt_canon_maps;
269 char *var_relocated_maps;
270 char *var_transport_maps;
271 char *var_verify_map;
272 char *var_smtpd_snd_auth_maps;
273 char *var_psc_cache_map;
274 char *var_proxy_read_maps;
275 char *var_proxy_write_maps;
276
277 /*
278 * The pre-approved, pre-parsed list of maps.
279 */
280 static HTABLE *proxy_auth_maps;
281
282 /*
283 * Shared and static to reduce memory allocation overhead.
284 */
285 static VSTRING *request;
286 static VSTRING *request_map;
287 static VSTRING *request_key;
288 static VSTRING *request_value;
289 static VSTRING *map_type_name_flags;
290
291 /*
292 * Are we a proxy writer or not?
293 */
294 static 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
get_nested_dict_name(char * type_name)304 static 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
proxy_map_find(const char * map_type_name,int request_flags,int * statp)325 static 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
proxymap_sequence_service(VSTREAM * client_stream)385 static 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
proxymap_lookup_service(VSTREAM * client_stream)438 static 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
proxymap_update_service(VSTREAM * client_stream)482 static 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
proxymap_delete_service(VSTREAM * client_stream)537 static 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
proxymap_open_service(VSTREAM * client_stream)588 static 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
proxymap_service(VSTREAM * client_stream,char * unused_service,char ** argv)623 static 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
dict_proxy_open(const char * map,int open_flags,int dict_flags)678 DICT *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
authorize_proxied_maps(char * bp)690 static 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
post_jail_init(char * service_name,char ** unused_argv)747 static 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
pre_accept(char * unused_name,char ** unused_argv)788 static 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
post_accept(VSTREAM * stream,char * unused_name,char ** unused_argv,HTABLE * unused_attr)800 static 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
813 MAIL_VERSION_STAMP_DECLARE;
814
815 /* main - pass control to the multi-threaded skeleton */
816
main(int argc,char ** argv)817 int 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