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