1 /* $NetBSD: proxymap.c,v 1.6 2026/05/09 18:49: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 that are not based on \fBlmdb\fR). 43 /* .PP 44 /* The \fBproxymap\fR(8) server implements the following requests: 45 /* .IP "\fBopen\fR \fImaptype:mapname instance-flags\fR" 46 /* Open the table with type \fImaptype\fR and name \fImapname\fR, 47 /* with initial dictionary flags \fIinstance-flags\fR. The reply 48 /* contains the actual dictionary flags (for example, to distinguish 49 /* a fixed-string table from a regular-expression table). 50 /* .IP "\fBlookup\fR \fImaptype:mapname instance-flags request-flags key\fR" 51 /* Look up the data stored under the requested key using the 52 /* dictionary flags in \fIrequest-flags\fR. 53 /* The reply contains the request completion status code, the 54 /* resulting dictionary flags, and the lookup result value. 55 /* The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same 56 /* as with the \fBopen\fR request. 57 /* .IP "\fBupdate\fR \fImaptype:mapname instance-flags request-flags key value\fR" 58 /* Update the data stored under the requested key using the 59 /* dictionary flags in \fIrequest-flags\fR. 60 /* The reply contains the request completion status code and the 61 /* resulting dictionary flags. 62 /* The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same 63 /* as with the \fBopen\fR request. 64 /* .sp 65 /* To implement single-updater maps, specify a process limit 66 /* of 1 in the master.cf file entry for the \fBproxywrite\fR 67 /* service. 68 /* .sp 69 /* This request is supported in Postfix 2.5 and later. 70 /* .IP "\fBdelete\fR \fImaptype:mapname instance-flags request-flags key\fR" 71 /* Delete the data stored under the requested key, using the 72 /* dictionary flags in \fIrequest-flags\fR. 73 /* The reply contains the request completion status code and the 74 /* resulting dictionary flags. 75 /* The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same 76 /* as with the \fBopen\fR request. 77 /* .sp 78 /* This request is supported in Postfix 2.5 and later. 79 /* .IP "\fBsequence\fR \fImaptype:mapname instance-flags request-flags function\fR" 80 /* Iterate over the specified database, using the dictionary flags 81 /* in \fIrequest-flags\fR. The \fIfunction\fR is either 82 /* DICT_SEQ_FUN_FIRST or DICT_SEQ_FUN_NEXT. 83 /* The reply contains the request completion status code, the 84 /* resulting dictionary flags, and a lookup key and result value 85 /* if found. 86 /* The \fImaptype:mapname\fR and \fIinstance-flags\fR are the same 87 /* as with the \fBopen\fR request. 88 /* .sp 89 /* This request is supported in Postfix 2.9 and later. 90 /* .IP "Not implemented: close" 91 /* There is no \fBclose\fR request, nor are tables implicitly closed 92 /* when a client disconnects. The purpose is to share tables among 93 /* multiple client processes. Due to the absence of an explicit or 94 /* implicit \fBclose\fR, updates are forced to be synchronous. 95 /* .PP 96 /* The request completion status is one of OK, RETRY, NOKEY 97 /* (lookup failed because the key was not found), BAD (malformed 98 /* request) or DENY (the table is not approved for proxy read 99 /* or update access). 100 /* SERVER PROCESS MANAGEMENT 101 /* .ad 102 /* .fi 103 /* \fBproxymap\fR(8) servers run under control by the Postfix 104 /* \fBmaster\fR(8) 105 /* server. Each server can handle multiple simultaneous connections. 106 /* When all servers are busy while a client connects, the \fBmaster\fR(8) 107 /* creates a new \fBproxymap\fR(8) server process, provided that the 108 /* process limit is not exceeded. 109 /* Each server terminates after serving at least \fB$max_use\fR clients 110 /* or after \fB$max_idle\fR seconds of idle time. 111 /* SECURITY 112 /* .ad 113 /* .fi 114 /* The \fBproxymap\fR(8) server opens only tables that are 115 /* approved via the \fBproxy_read_maps\fR or \fBproxy_write_maps\fR 116 /* configuration parameters, does not talk to 117 /* users, and can run at fixed low privilege, chrooted or not. 118 /* However, running the proxymap server chrooted severely limits 119 /* usability, because it can open only chrooted tables. 120 /* 121 /* The \fBproxymap\fR(8) server is not a trusted daemon process, and must 122 /* not be used to look up sensitive information such as UNIX user or 123 /* group IDs, mailbox file/directory names or external commands. 124 /* 125 /* In Postfix version 2.2 and later, the proxymap client recognizes 126 /* requests to access a table for security-sensitive purposes, 127 /* and opens the table directly. This allows the same main.cf 128 /* setting to be used by sensitive and non-sensitive processes. 129 /* 130 /* Postfix-writable data files should be stored under a dedicated 131 /* directory that is writable only by the Postfix mail system, 132 /* such as the Postfix-owned \fBdata_directory\fR. 133 /* 134 /* In particular, Postfix-writable files should never exist 135 /* in root-owned directories. That would open up a particular 136 /* type of security hole where ownership of a file or directory 137 /* does not match the provider of its content. 138 /* DIAGNOSTICS 139 /* Problems and transactions are logged to \fBsyslogd\fR(8) 140 /* or \fBpostlogd\fR(8). 141 /* BUGS 142 /* The \fBproxymap\fR(8) server provides service to multiple clients, 143 /* and must therefore not be used for tables that have high-latency 144 /* lookups. 145 /* 146 /* The \fBproxymap\fR(8) read-write service does not explicitly 147 /* close lookup tables (even if it did, this could not be relied on, 148 /* because the process may be terminated between table updates). 149 /* The read-write service should therefore not be used with tables that 150 /* leave persistent storage in an inconsistent state between 151 /* updates (for example, CDB). Tables that support "sync on 152 /* update" should be safe (for example, Berkeley DB) as should 153 /* tables that are implemented by a real DBMS. 154 /* CONFIGURATION PARAMETERS 155 /* .ad 156 /* .fi 157 /* On busy mail systems a long time may pass before 158 /* \fBproxymap\fR(8) relevant 159 /* changes to \fBmain.cf\fR are picked up. Use the command 160 /* "\fBpostfix reload\fR" to speed up a change. 161 /* 162 /* The text below provides only a parameter summary. See 163 /* \fBpostconf\fR(5) for more details including examples. 164 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 165 /* The default location of the Postfix main.cf and master.cf 166 /* configuration files. 167 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR" 168 /* The directory with Postfix-writable data files (for example: 169 /* caches, pseudo-random numbers). 170 /* .IP "\fBdaemon_timeout (18000s)\fR" 171 /* How much time a Postfix daemon process may take to handle a 172 /* request before it is terminated by a built-in watchdog timer. 173 /* .IP "\fBipc_timeout (3600s)\fR" 174 /* The time limit for sending or receiving information over an internal 175 /* communication channel. 176 /* .IP "\fBmax_idle (100s)\fR" 177 /* The maximum amount of time that an idle Postfix daemon process waits 178 /* for an incoming connection before terminating voluntarily. 179 /* .IP "\fBmax_use (100)\fR" 180 /* The maximal number of incoming connections that a Postfix daemon 181 /* process will service before terminating voluntarily. 182 /* .IP "\fBprocess_id (read-only)\fR" 183 /* The process ID of a Postfix command or daemon process. 184 /* .IP "\fBprocess_name (read-only)\fR" 185 /* The process name of a Postfix command or daemon process. 186 /* .IP "\fBproxy_read_maps (see 'postconf -d' output)\fR" 187 /* The lookup tables that the \fBproxymap\fR(8) server is allowed to 188 /* access for the read-only service. 189 /* .PP 190 /* Available in Postfix 2.5 and later: 191 /* .IP "\fBdata_directory (see 'postconf -d' output)\fR" 192 /* The directory with Postfix-writable data files (for example: 193 /* caches, pseudo-random numbers). 194 /* .IP "\fBproxy_write_maps (see 'postconf -d' output)\fR" 195 /* The lookup tables that the \fBproxymap\fR(8) server is allowed to 196 /* access for the read-write service. 197 /* .PP 198 /* Available in Postfix 3.3 and later: 199 /* .IP "\fBservice_name (read-only)\fR" 200 /* The master.cf service name of a Postfix daemon process. 201 /* SEE ALSO 202 /* postconf(5), configuration parameters 203 /* master(5), generic daemon options 204 /* README FILES 205 /* .ad 206 /* .fi 207 /* Use "\fBpostconf readme_directory\fR" or 208 /* "\fBpostconf html_directory\fR" to locate this information. 209 /* .na 210 /* .nf 211 /* DATABASE_README, Postfix lookup table overview 212 /* LICENSE 213 /* .ad 214 /* .fi 215 /* The Secure Mailer license must be distributed with this software. 216 /* HISTORY 217 /* .ad 218 /* .fi 219 /* The proxymap service was introduced with Postfix 2.0. 220 /* AUTHOR(S) 221 /* Wietse Venema 222 /* IBM T.J. Watson Research 223 /* P.O. Box 704 224 /* Yorktown Heights, NY 10598, USA 225 /* 226 /* Wietse Venema 227 /* Google, Inc. 228 /* 111 8th Avenue 229 /* New York, NY 10011, USA 230 /* 231 /* Wietse Venema 232 /* porcupine.org 233 /*--*/ 234 235 /* System library. */ 236 237 #include <sys_defs.h> 238 #include <string.h> 239 #include <stdlib.h> 240 #include <unistd.h> 241 #include <ctype.h> 242 243 /* Utility library. */ 244 245 #include <msg.h> 246 #include <mymalloc.h> 247 #include <vstring.h> 248 #include <htable.h> 249 #include <stringops.h> 250 #include <dict.h> 251 #include <dict_pipe.h> 252 #include <dict_union.h> 253 #include <readlline.h> 254 255 /* Global library. */ 256 257 #include <mail_conf.h> 258 #include <mail_params.h> 259 #include <mail_version.h> 260 #include <mail_proto.h> 261 #include <dict_proxy.h> 262 263 /* Server skeleton. */ 264 265 #include <mail_server.h> 266 267 /* Application-specific. */ 268 269 /* 270 * XXX All but the last are needed here so that $name expansion dependencies 271 * aren't too broken. The fix is to gather all parameter default settings in 272 * one place. 273 */ 274 char *var_alias_maps; 275 char *var_local_rcpt_maps; 276 char *var_virt_alias_maps; 277 char *var_virt_alias_doms; 278 char *var_virt_mailbox_maps; 279 char *var_virt_mailbox_doms; 280 char *var_relay_rcpt_maps; 281 char *var_canonical_maps; 282 char *var_send_canon_maps; 283 char *var_rcpt_canon_maps; 284 char *var_relocated_maps; 285 char *var_transport_maps; 286 char *var_verify_map; 287 char *var_smtpd_snd_auth_maps; 288 char *var_psc_cache_map; 289 char *var_smtpd_forbid_cmds; 290 char *var_psc_forbid_cmds; 291 char *var_proxy_read_maps; 292 char *var_proxy_write_maps; 293 char *var_rest_classes; 294 295 /* 296 * The pre-approved, pre-parsed list of maps. 297 */ 298 static HTABLE *proxy_auth_maps; 299 300 /* 301 * Shared and static to reduce memory allocation overhead. 302 */ 303 static VSTRING *request; 304 static VSTRING *request_map; 305 static VSTRING *request_key; 306 static VSTRING *request_value; 307 static VSTRING *map_type_name_flags; 308 309 /* 310 * Are we a proxy writer or not? 311 */ 312 static int proxy_writer; 313 314 /* 315 * Do we implement the proxymap or proxywrite service, or do we export 316 * type:table information to stdout? The exported info is without the proxy: 317 * prefix. 318 * 319 * EXPORT_PROXY lists only tables that proxied. 320 * 321 * EXPORT_ALL lists both proxied and non-proxied tables that Postfix is 322 * configured to use. This requires that proxy_read_maps and 323 * proxy_write_maps list all tables, including those that will never be 324 * proxied. 325 */ 326 static enum { 327 EXPORT_NONE, /* Answer requests */ 328 EXPORT_PROXY, /* Export proxied type:table */ 329 EXPORT_ALL, /* Export all type:table */ 330 } proxy_exporter = EXPORT_NONE; 331 332 /* 333 * Silly little macros. 334 */ 335 #define STR(x) vstring_str(x) 336 #define VSTREQ(x,y) (strcmp(STR(x),y) == 0) 337 338 /* get_nested_dict_name - return nested dictionary name pointer, or null */ 339 340 static char *get_nested_dict_name(char *type_name) 341 { 342 const struct { 343 const char *type_col; 344 ssize_t type_col_len; 345 } *prefix, prefixes[] = { 346 DICT_TYPE_UNION ":", (sizeof(DICT_TYPE_UNION ":") - 1), 347 DICT_TYPE_PIPE ":", (sizeof(DICT_TYPE_PIPE ":") - 1), 348 }; 349 350 #define COUNT_OF(x) (sizeof(x)/sizeof((x)[0])) 351 352 for (prefix = prefixes; prefix < prefixes + COUNT_OF(prefixes); prefix++) { 353 if (strncmp(type_name, prefix->type_col, prefix->type_col_len) == 0) 354 return (type_name + prefix->type_col_len); 355 } 356 return (0); 357 } 358 359 /* proxy_map_find - look up or open table */ 360 361 static DICT *proxy_map_find(const char *map_type_name, int inst_flags, 362 int *statp) 363 { 364 static HTABLE *new_flags; 365 HTABLE_INFO *ht; 366 DICT *dict; 367 368 #define PROXY_COLON DICT_TYPE_PROXY ":" 369 #define PROXY_COLON_LEN (sizeof(PROXY_COLON) - 1) 370 #define READ_OPEN_FLAGS O_RDONLY 371 #define WRITE_OPEN_FLAGS (O_RDWR | O_CREAT) 372 373 /* 374 * Canonicalize the map name. If the map is not on the approved list, 375 * deny the request. 376 */ 377 #define PROXY_MAP_FIND_ERROR_RETURN(x) { *statp = (x); return (0); } 378 #define PROXY_MAP_PARAM_NAME(proxy_writer) \ 379 ((proxy_writer) == 0 ? VAR_PROXY_READ_MAPS : VAR_PROXY_WRITE_MAPS) 380 381 while (strncmp(map_type_name, PROXY_COLON, PROXY_COLON_LEN) == 0) 382 map_type_name += PROXY_COLON_LEN; 383 /* XXX The following breaks with maps that have ':' in their name. */ 384 if (strchr(map_type_name, ':') == 0) 385 PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_BAD); 386 if (htable_locate(proxy_auth_maps, map_type_name) == 0) { 387 msg_warn("request for unapproved table: \"%s\"", map_type_name); 388 msg_warn("to approve this table for %s access, list %s:%s in %s:%s", 389 proxy_writer == 0 ? "read-only" : "read-write", 390 DICT_TYPE_PROXY, map_type_name, MAIN_CONF_FILE, 391 PROXY_MAP_PARAM_NAME(proxy_writer)); 392 PROXY_MAP_FIND_ERROR_RETURN(PROXY_STAT_DENY); 393 } 394 395 /* 396 * Open one instance of a map for each combination of name+flags. 397 */ 398 dict = dict_open(map_type_name, proxy_writer ? 399 WRITE_OPEN_FLAGS : READ_OPEN_FLAGS, 400 inst_flags); 401 if (dict == 0) 402 msg_panic("proxy_map_find: dict_open null result"); 403 404 /* 405 * Remember the mapping from dict->reg_name to the dict->flags of a 406 * newly-initialized instance. Always return an instance with those new 407 * dict->flags, to avoid crosstalk between different clients. 408 */ 409 if (new_flags == 0) 410 new_flags = htable_create(100); 411 if ((ht = htable_locate(new_flags, dict->reg_name)) == 0) { 412 (void) htable_enter(new_flags, dict->reg_name, 413 CAST_INT_TO_VOID_PTR(dict->flags)); 414 } else { 415 dict->flags = CAST_ANY_PTR_TO_INT(ht->value); 416 } 417 dict->error = 0; 418 return (dict); 419 } 420 421 /* proxymap_sequence_service - remote sequence service */ 422 423 static void proxymap_sequence_service(VSTREAM *client_stream) 424 { 425 int inst_flags; 426 int request_flags; 427 DICT *dict = 0; 428 int request_func; 429 const char *reply_key; 430 const char *reply_value; 431 int dict_status; 432 int reply_status; 433 434 /* 435 * Process the request. 436 */ 437 if (attr_scan(client_stream, ATTR_FLAG_STRICT, 438 RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), 439 RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags), 440 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), 441 RECV_ATTR_INT(MAIL_ATTR_FUNC, &request_func), 442 ATTR_TYPE_END) != 4 443 || (request_func != DICT_SEQ_FUN_FIRST 444 && request_func != DICT_SEQ_FUN_NEXT)) { 445 reply_status = PROXY_STAT_BAD; 446 reply_key = reply_value = ""; 447 } else if ((dict = proxy_map_find(STR(request_map), inst_flags, 448 &reply_status)) == 0) { 449 reply_key = reply_value = ""; 450 } else { 451 dict->flags = request_flags; 452 dict_status = dict_seq(dict, request_func, &reply_key, &reply_value); 453 if (dict_status == 0) { 454 reply_status = PROXY_STAT_OK; 455 } else if (dict->error == 0) { 456 reply_status = PROXY_STAT_NOKEY; 457 reply_key = reply_value = ""; 458 } else { 459 reply_status = (dict->error == DICT_ERR_RETRY ? 460 PROXY_STAT_RETRY : PROXY_STAT_CONFIG); 461 reply_key = reply_value = ""; 462 } 463 } 464 465 /* 466 * Respond to the client. 202604 Claude: don't dereference uninitialized 467 * dict. 468 */ 469 attr_print(client_stream, ATTR_FLAG_NONE, 470 SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), 471 SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0), 472 SEND_ATTR_STR(MAIL_ATTR_KEY, reply_key), 473 SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), 474 ATTR_TYPE_END); 475 } 476 477 /* proxymap_lookup_service - remote lookup service */ 478 479 static void proxymap_lookup_service(VSTREAM *client_stream) 480 { 481 int inst_flags; 482 int request_flags; 483 DICT *dict = 0; 484 const char *reply_value; 485 int reply_status; 486 487 /* 488 * Process the request. 489 */ 490 if (attr_scan(client_stream, ATTR_FLAG_STRICT, 491 RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), 492 RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags), 493 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), 494 RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), 495 ATTR_TYPE_END) != 4) { 496 reply_status = PROXY_STAT_BAD; 497 reply_value = ""; 498 } else if ((dict = proxy_map_find(STR(request_map), inst_flags, 499 &reply_status)) == 0) { 500 reply_value = ""; 501 } else if (dict->flags = request_flags, 502 (reply_value = dict_get(dict, STR(request_key))) != 0) { 503 reply_status = PROXY_STAT_OK; 504 } else if (dict->error == 0) { 505 reply_status = PROXY_STAT_NOKEY; 506 reply_value = ""; 507 } else { 508 reply_status = (dict->error == DICT_ERR_RETRY ? 509 PROXY_STAT_RETRY : PROXY_STAT_CONFIG); 510 reply_value = ""; 511 } 512 513 /* 514 * Respond to the client. 202604 Claude: don't dereference uninitialized 515 * dict. 516 */ 517 attr_print(client_stream, ATTR_FLAG_NONE, 518 SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), 519 SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0), 520 SEND_ATTR_STR(MAIL_ATTR_VALUE, reply_value), 521 ATTR_TYPE_END); 522 } 523 524 /* proxymap_update_service - remote update service */ 525 526 static void proxymap_update_service(VSTREAM *client_stream) 527 { 528 int inst_flags; 529 int request_flags; 530 DICT *dict = 0; 531 int dict_status; 532 int reply_status; 533 534 /* 535 * Process the request. 536 * 537 * XXX We don't close maps, so we must turn on synchronous update to ensure 538 * that the on-disk data is in a consistent state between updates. 539 * 540 * XXX We ignore duplicates, because the proxymap server would abort 541 * otherwise. 542 */ 543 if (attr_scan(client_stream, ATTR_FLAG_STRICT, 544 RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), 545 RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags), 546 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), 547 RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), 548 RECV_ATTR_STR(MAIL_ATTR_VALUE, request_value), 549 ATTR_TYPE_END) != 5) { 550 reply_status = PROXY_STAT_BAD; 551 } else if (proxy_writer == 0) { 552 msg_warn("refusing %s update request on non-%s service", 553 STR(request_map), MAIL_SERVICE_PROXYWRITE); 554 reply_status = PROXY_STAT_DENY; 555 } else if ((dict = proxy_map_find(STR(request_map), inst_flags, 556 &reply_status)) == 0) { 557 /* void */ ; 558 } else { 559 /* Sync the table now. Don't abort on duplicate update. */ 560 dict->flags = (request_flags 561 | DICT_FLAG_SYNC_UPDATE | DICT_FLAG_DUP_REPLACE); 562 dict_status = dict_put(dict, STR(request_key), STR(request_value)); 563 if (dict_status == 0) { 564 reply_status = PROXY_STAT_OK; 565 } else if (dict->error == 0) { 566 reply_status = PROXY_STAT_NOKEY; 567 } else { 568 reply_status = (dict->error == DICT_ERR_RETRY ? 569 PROXY_STAT_RETRY : PROXY_STAT_CONFIG); 570 } 571 } 572 573 /* 574 * Respond to the client. 202604 Claude: don't dereference uninitialized 575 * dict. 576 */ 577 attr_print(client_stream, ATTR_FLAG_NONE, 578 SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), 579 SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0), 580 ATTR_TYPE_END); 581 } 582 583 /* proxymap_delete_service - remote delete service */ 584 585 static void proxymap_delete_service(VSTREAM *client_stream) 586 { 587 int inst_flags; 588 int request_flags; 589 DICT *dict = 0; 590 int dict_status; 591 int reply_status; 592 593 /* 594 * Process the request. 595 * 596 * XXX We don't close maps, so we must turn on synchronous update to ensure 597 * that the on-disk data is in a consistent state between updates. 598 */ 599 if (attr_scan(client_stream, ATTR_FLAG_STRICT, 600 RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), 601 RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags), 602 RECV_ATTR_INT(MAIL_ATTR_FLAGS, &request_flags), 603 RECV_ATTR_STR(MAIL_ATTR_KEY, request_key), 604 ATTR_TYPE_END) != 4) { 605 reply_status = PROXY_STAT_BAD; 606 } else if (proxy_writer == 0) { 607 msg_warn("refusing %s delete request on non-%s service", 608 STR(request_map), MAIL_SERVICE_PROXYWRITE); 609 reply_status = PROXY_STAT_DENY; 610 } else if ((dict = proxy_map_find(STR(request_map), inst_flags, 611 &reply_status)) == 0) { 612 /* void */ ; 613 } else { 614 /* Sync the table now. There is no close() request. */ 615 dict->flags = (request_flags 616 | DICT_FLAG_SYNC_UPDATE); 617 dict_status = dict_del(dict, STR(request_key)); 618 if (dict_status == 0) { 619 reply_status = PROXY_STAT_OK; 620 } else if (dict->error == 0) { 621 reply_status = PROXY_STAT_NOKEY; 622 } else { 623 reply_status = (dict->error == DICT_ERR_RETRY ? 624 PROXY_STAT_RETRY : PROXY_STAT_CONFIG); 625 } 626 } 627 628 /* 629 * Respond to the client. 202604 Claude: don't dereference uninitialized 630 * dict. 631 */ 632 attr_print(client_stream, ATTR_FLAG_NONE, 633 SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), 634 SEND_ATTR_INT(MAIL_ATTR_FLAGS, dict ? dict->flags : 0), 635 ATTR_TYPE_END); 636 } 637 638 /* proxymap_open_service - open remote lookup table */ 639 640 static void proxymap_open_service(VSTREAM *client_stream) 641 { 642 int inst_flags; 643 DICT *dict; 644 int reply_status; 645 int reply_flags; 646 647 /* 648 * Process the request. 649 */ 650 if (attr_scan(client_stream, ATTR_FLAG_STRICT, 651 RECV_ATTR_STR(MAIL_ATTR_TABLE, request_map), 652 RECV_ATTR_INT(MAIL_ATTR_INST_FLAGS, &inst_flags), 653 ATTR_TYPE_END) != 2) { 654 reply_status = PROXY_STAT_BAD; 655 reply_flags = 0; 656 } else if ((dict = proxy_map_find(STR(request_map), inst_flags, 657 &reply_status)) == 0) { 658 reply_flags = 0; 659 } else { 660 reply_status = PROXY_STAT_OK; 661 reply_flags = dict->flags; 662 } 663 664 /* 665 * Respond to the client. 666 */ 667 attr_print(client_stream, ATTR_FLAG_NONE, 668 SEND_ATTR_INT(MAIL_ATTR_STATUS, reply_status), 669 SEND_ATTR_INT(MAIL_ATTR_FLAGS, reply_flags), 670 ATTR_TYPE_END); 671 } 672 673 /* proxymap_service - perform service for client */ 674 675 static void proxymap_service(VSTREAM *client_stream, char *unused_service, 676 char **argv) 677 { 678 679 /* 680 * Sanity check. This service takes no command-line arguments. 681 */ 682 if (argv[0]) 683 msg_fatal("unexpected command-line argument: %s", argv[0]); 684 685 /* 686 * Deadline enforcement. 687 */ 688 if (vstream_fstat(client_stream, VSTREAM_FLAG_DEADLINE) == 0) 689 vstream_control(client_stream, 690 CA_VSTREAM_CTL_TIMEOUT(1), 691 CA_VSTREAM_CTL_END); 692 693 /* 694 * This routine runs whenever a client connects to the socket dedicated 695 * to the proxymap service. All connection-management stuff is handled by 696 * the common code in multi_server.c. 697 */ 698 vstream_control(client_stream, 699 CA_VSTREAM_CTL_START_DEADLINE, 700 CA_VSTREAM_CTL_END); 701 if (attr_scan(client_stream, 702 ATTR_FLAG_MORE | ATTR_FLAG_STRICT, 703 RECV_ATTR_STR(MAIL_ATTR_REQ, request), 704 ATTR_TYPE_END) == 1) { 705 if (VSTREQ(request, PROXY_REQ_LOOKUP)) { 706 proxymap_lookup_service(client_stream); 707 } else if (VSTREQ(request, PROXY_REQ_UPDATE)) { 708 proxymap_update_service(client_stream); 709 } else if (VSTREQ(request, PROXY_REQ_DELETE)) { 710 proxymap_delete_service(client_stream); 711 } else if (VSTREQ(request, PROXY_REQ_SEQUENCE)) { 712 proxymap_sequence_service(client_stream); 713 } else if (VSTREQ(request, PROXY_REQ_OPEN)) { 714 proxymap_open_service(client_stream); 715 } else { 716 msg_warn("unrecognized request: \"%s\", ignored", STR(request)); 717 attr_print(client_stream, ATTR_FLAG_NONE, 718 SEND_ATTR_INT(MAIL_ATTR_STATUS, PROXY_STAT_BAD), 719 ATTR_TYPE_END); 720 } 721 } 722 vstream_control(client_stream, 723 CA_VSTREAM_CTL_START_DEADLINE, 724 CA_VSTREAM_CTL_END); 725 vstream_fflush(client_stream); 726 } 727 728 /* dict_proxy_open - intercept remote map request from inside library */ 729 730 DICT *dict_proxy_open(const char *map, int open_flags, int dict_flags) 731 { 732 if (msg_verbose) 733 msg_info("dict_proxy_open(%s, 0%o, 0%o) called from internal routine", 734 map, open_flags, dict_flags); 735 while (strncmp(map, PROXY_COLON, PROXY_COLON_LEN) == 0) 736 map += PROXY_COLON_LEN; 737 return (dict_open(map, open_flags, dict_flags)); 738 } 739 740 static void authorize_file_content(const char *); 741 742 /* authorize_proxied_maps - recursively authorize maps */ 743 744 static void authorize_proxied_maps(char *bp, const char *origin) 745 { 746 const char *sep = CHARS_COMMA_SP; 747 const char *parens = CHARS_BRACE; 748 char *type_name; 749 750 while ((type_name = mystrtokq(&bp, sep, parens)) != 0) { 751 char *nested_info; 752 753 /* Maybe { maptype:mapname attr=value... } */ 754 if (*type_name == parens[0]) { 755 char *err; 756 757 /* Warn about blatant syntax error. */ 758 if ((err = extpar(&type_name, parens, EXTPAR_FLAG_NONE)) != 0) { 759 msg_warn("bad syntax in %s content: %s", origin, err); 760 myfree(err); 761 continue; 762 } 763 /* Don't try to second-guess the semantics of { }. */ 764 if ((type_name = mystrtokq(&type_name, sep, parens)) == 0) 765 continue; 766 } 767 /* Recurse into /file/name. */ 768 if (*type_name == '/') { 769 authorize_file_content(type_name); 770 continue; 771 } 772 /* Skip [ipv6::addr] and other non-table forms. */ 773 if (!ISALNUM(*type_name) || strchr(type_name, ':') == 0) 774 continue; 775 /* Recurse into nested map (pipemap, unionmap). */ 776 if ((nested_info = get_nested_dict_name(type_name)) != 0) { 777 char *err; 778 779 if (*nested_info != parens[0]) 780 continue; 781 /* Warn about blatant syntax error. */ 782 if ((err = extpar(&nested_info, parens, EXTPAR_FLAG_NONE)) != 0) { 783 msg_warn("bad syntax in %s content: %s", origin, err); 784 myfree(err); 785 continue; 786 } 787 authorize_proxied_maps(nested_info, origin); 788 continue; 789 } 790 switch (proxy_exporter) { 791 case EXPORT_NONE: 792 case EXPORT_PROXY: 793 if (strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)) 794 continue; 795 do { 796 type_name += PROXY_COLON_LEN; 797 } while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)); 798 break; 799 case EXPORT_ALL: 800 while (!strncmp(type_name, PROXY_COLON, PROXY_COLON_LEN)) 801 type_name += PROXY_COLON_LEN; 802 break; 803 } 804 if (htable_locate(proxy_auth_maps, type_name) == 0) { 805 (void) htable_enter(proxy_auth_maps, type_name, mystrdup(origin)); 806 if (msg_verbose) 807 msg_info("allowlisting %s from %s", type_name, origin); 808 } 809 } 810 } 811 812 /* authorize_file_content - authorize table references in /file/name */ 813 814 static void authorize_file_content(const char *file_name) 815 { 816 VSTREAM *fp; 817 VSTRING *buf; 818 819 if ((fp = vstream_fopen(file_name, O_RDONLY, 0)) == 0) { 820 msg_warn("open %s: %m", file_name); 821 return; 822 } 823 buf = vstring_alloc(100); 824 while (readlline(buf, fp, (int *) 0)) 825 authorize_proxied_maps(STR(buf), file_name); 826 if (vstream_ferror(fp)) 827 msg_warn("read %s: %m", file_name); 828 vstring_free(buf); 829 vstream_fclose(fp); 830 } 831 832 /* authorize_rest_classes - scan restriction classes for table references */ 833 834 static void authorize_rest_classes(const char *rest_classes) 835 { 836 char *bp, *saved_rest_classes, *class_name, *saved_class_val; 837 const char *class_val; 838 839 bp = saved_rest_classes = mystrdup(rest_classes); 840 while ((class_name = mystrtok(&bp, CHARS_COMMA_SP)) != 0) { 841 if ((class_val = mail_conf_lookup_eval(class_name)) != 0) { 842 saved_class_val = mystrdup(class_val); 843 authorize_proxied_maps(saved_class_val, VAR_REST_CLASSES); 844 myfree(saved_class_val); 845 } 846 } 847 myfree(saved_rest_classes); 848 } 849 850 /* cmp_ht_key - qsort helper for ht_info pointer array */ 851 852 static int cmp_ht_key(const void *a, const void *b) 853 { 854 HTABLE_INFO **ap = (HTABLE_INFO **) a; 855 HTABLE_INFO **bp = (HTABLE_INFO **) b; 856 857 return (strcmp(ap[0]->key, bp[0]->key)); 858 } 859 860 /* export_type_name_entries - serialize table authorizations to stdout */ 861 862 static void export_type_name_entries(void) 863 { 864 HTABLE_INFO **ht_info, **ht; 865 866 ht_info = htable_list(proxy_auth_maps); 867 qsort((void *) ht_info, proxy_auth_maps->used, sizeof(*ht_info), 868 cmp_ht_key); 869 for (ht = ht_info; *ht; ht++) 870 vstream_printf("%s\n", ht[0]->key); 871 vstream_fflush(VSTREAM_OUT); 872 myfree(ht_info); 873 } 874 875 /* pre_jail_init - initialization after privilege drop */ 876 877 static void pre_jail_init(char *service_name, char **unused_argv) 878 { 879 char *saved_filter; 880 881 /* 882 * Is this a request for service, or export? 883 */ 884 if (strncmp(service_name, "export-all-", 11) == 0) { 885 proxy_exporter = EXPORT_ALL; 886 service_name += 11; 887 } else if (strncmp(service_name, "export-proxy-", 13) == 0) { 888 proxy_exporter = EXPORT_PROXY; 889 service_name += 13; 890 } 891 892 /* 893 * Are we proxy writer? 894 */ 895 if (strcmp(service_name, MAIL_SERVICE_PROXYWRITE) == 0) 896 proxy_writer = 1; 897 else if (strcmp(service_name, MAIL_SERVICE_PROXYMAP) != 0) 898 msg_fatal("invalid service name: \"%s\" - " 899 "service name must be \"%s\" or \"%s\"", 900 service_name, MAIL_SERVICE_PROXYWRITE, 901 MAIL_SERVICE_PROXYMAP); 902 903 /* 904 * Pre-allocate buffers. 905 */ 906 request = vstring_alloc(10); 907 request_map = vstring_alloc(10); 908 request_key = vstring_alloc(10); 909 request_value = vstring_alloc(10); 910 map_type_name_flags = vstring_alloc(10); 911 912 /* 913 * Prepare the pre-approved list of proxied tables. 914 */ 915 saved_filter = mystrdup(proxy_writer ? var_proxy_write_maps : 916 var_proxy_read_maps); 917 proxy_auth_maps = htable_create(13); 918 authorize_proxied_maps(saved_filter, PROXY_MAP_PARAM_NAME(proxy_writer)); 919 myfree(saved_filter); 920 921 /* 922 * Authorize tables in restriction_classes. 923 */ 924 if (proxy_writer == 0 && *var_rest_classes) 925 authorize_rest_classes(var_rest_classes); 926 927 /* 928 * If run as exporter, serialize the authorization table to stdout. 929 */ 930 if (proxy_exporter != EXPORT_NONE) { 931 export_type_name_entries(); 932 exit(0); 933 } 934 935 /* 936 * Never, ever, get killed by a master signal, as that could corrupt a 937 * persistent database when we're in the middle of an update. 938 */ 939 if (proxy_writer != 0) 940 setsid(); 941 } 942 943 /* pre_accept - see if tables have changed */ 944 945 static void pre_accept(char *unused_name, char **unused_argv) 946 { 947 const char *table; 948 949 if (proxy_writer == 0 && (table = dict_changed_name()) != 0) { 950 msg_info("table %s has changed -- restarting", table); 951 exit(0); 952 } 953 } 954 955 /* post_accept - announce our protocol name */ 956 957 static void post_accept(VSTREAM *stream, char *unused_name, char **unused_argv, 958 HTABLE *unused_attr) 959 { 960 961 /* 962 * Announce the protocol. 963 */ 964 attr_print(stream, ATTR_FLAG_NONE, 965 SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_PROXYMAP), 966 ATTR_TYPE_END); 967 (void) vstream_fflush(stream); 968 } 969 970 MAIL_VERSION_STAMP_DECLARE; 971 972 /* main - pass control to the multi-threaded skeleton */ 973 974 int main(int argc, char **argv) 975 { 976 977 /* 978 * Respect the proxy_read_maps and proxy_write_maps dependency graphs. 979 * First, initialize the parameters that specify tables in their 980 * non-empty default values, then initialize the parameters that depend 981 * on those parameters, and so on. Only at the end initialize 982 * proxy_read_maps and proxy_write_maps. 983 * 984 * TODO(wietse) remove parameters below that have empty default values. It 985 * is sufficient to list their $name in the proxy_*_maps default values. 986 * The list below should be checked with a tool that runs during 987 * pre-release-checks. 988 * 989 * List parameters even if their defaults do not specify proxied queries (as 990 * of Postfix 3.11, only local_recipient_maps does that). The goal is to 991 * implement an authoritative source of truth tat covers all Postfix 992 * table lookups. 993 */ 994 static const CONFIG_STR_TABLE str_table[] = { 995 /* Dependencies of proxy_read_maps. */ 996 VAR_ALIAS_MAPS, DEF_ALIAS_MAPS, &var_alias_maps, 0, 0, 997 VAR_LOCAL_RCPT_MAPS, DEF_LOCAL_RCPT_MAPS, &var_local_rcpt_maps, 0, 0, 998 VAR_VIRT_ALIAS_MAPS, DEF_VIRT_ALIAS_MAPS, &var_virt_alias_maps, 0, 0, 999 VAR_VIRT_ALIAS_DOMS, DEF_VIRT_ALIAS_DOMS, &var_virt_alias_doms, 0, 0, 1000 VAR_VIRT_MAILBOX_MAPS, DEF_VIRT_MAILBOX_MAPS, &var_virt_mailbox_maps, 0, 0, 1001 VAR_VIRT_MAILBOX_DOMS, DEF_VIRT_MAILBOX_DOMS, &var_virt_mailbox_doms, 0, 0, 1002 VAR_RELAY_RCPT_MAPS, DEF_RELAY_RCPT_MAPS, &var_relay_rcpt_maps, 0, 0, 1003 VAR_CANONICAL_MAPS, DEF_CANONICAL_MAPS, &var_canonical_maps, 0, 0, 1004 VAR_SEND_CANON_MAPS, DEF_SEND_CANON_MAPS, &var_send_canon_maps, 0, 0, 1005 VAR_RCPT_CANON_MAPS, DEF_RCPT_CANON_MAPS, &var_rcpt_canon_maps, 0, 0, 1006 VAR_RELOCATED_MAPS, DEF_RELOCATED_MAPS, &var_relocated_maps, 0, 0, 1007 VAR_TRANSPORT_MAPS, DEF_TRANSPORT_MAPS, &var_transport_maps, 0, 0, 1008 VAR_SMTPD_SND_AUTH_MAPS, DEF_SMTPD_SND_AUTH_MAPS, &var_smtpd_snd_auth_maps, 0, 0, 1009 VAR_SMTPD_FORBID_CMDS, DEF_SMTPD_FORBID_CMDS, &var_smtpd_forbid_cmds, 0, 0, 1010 VAR_PSC_FORBID_CMDS, DEF_PSC_FORBID_CMDS, &var_psc_forbid_cmds, 0, 0, 1011 /* Dependencies of proxy_write_maps. */ 1012 VAR_VERIFY_MAP, DEF_VERIFY_MAP, &var_verify_map, 0, 0, 1013 VAR_PSC_CACHE_MAP, DEF_PSC_CACHE_MAP, &var_psc_cache_map, 0, 0, 1014 /* The following two must be last for $mapname to work as expected. */ 1015 VAR_PROXY_READ_MAPS, DEF_PROXY_READ_MAPS, &var_proxy_read_maps, 0, 0, 1016 VAR_PROXY_WRITE_MAPS, DEF_PROXY_WRITE_MAPS, &var_proxy_write_maps, 0, 0, 1017 /* Settings that don't affect or depend on proxy_read/write_maps. */ 1018 VAR_REST_CLASSES, DEF_REST_CLASSES, &var_rest_classes, 0, 0, 1019 0, 1020 }; 1021 1022 /* 1023 * Fingerprint executables and core dumps. 1024 */ 1025 MAIL_VERSION_STAMP_ALLOCATE; 1026 1027 /* 1028 * XXX When invoked with the master.cf service name "proxywrite", the 1029 * proxymap daemon will allow update requests. To update a table that is 1030 * not multi-writer safe (for example, some versions of Berkeley DB), the 1031 * "proxywrite" service should run as a single updater (i.e. a process 1032 * limit of 1, which could be enforced below by requesting 1033 * CA_MAIL_SERVER_SOLITARY). 1034 * 1035 * In the default master.cf file, the "proxywrite" service has a process 1036 * limit of 1. Assuming that updates will be rare, this process limit 1037 * will suffice. Latency-sensitive services such as postscreen must not 1038 * use the proxywrite service (in fact, postscreen has a latency check 1039 * built-in). 1040 * 1041 * Optimizing for multi-writer operation would suffer from all kinds of 1042 * complexity that would make it hard to use: 1043 * 1044 * - The master daemon specifies the "proxywrite" service name with the -n 1045 * command-line option. This information is not known here, before the 1046 * multi_server_main() call. The multi_server_main() function could 1047 * reveal process limit information to its call-back functions, and leave 1048 * single-updater enforcement to its call-back functions. 1049 * 1050 * - If we really want multi-writer update support, the "proxywrite" service 1051 * would have to parse the $proxy_write_maps value, and permit 1052 * multi-writer operation only if all tables are multi-writer safe. That 1053 * would require a new dict(3) method, to query each lookup table 1054 * implementation if it is multi-writer safe, without instantiating a 1055 * lookup table client. 1056 */ 1057 multi_server_main(argc, argv, proxymap_service, 1058 CA_MAIL_SERVER_STR_TABLE(str_table), 1059 CA_MAIL_SERVER_PRE_INIT(pre_jail_init), 1060 CA_MAIL_SERVER_PRE_ACCEPT(pre_accept), 1061 CA_MAIL_SERVER_POST_ACCEPT(post_accept), 1062 /* XXX CA_MAIL_SERVER_SOLITARY if proxywrite */ 1063 0); 1064 } 1065