1 /* $NetBSD: scache.c,v 1.4 2022/10/08 16:12:49 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* scache 8 6 /* SUMMARY 7 /* Postfix shared connection cache server 8 /* SYNOPSIS 9 /* \fBscache\fR [generic Postfix daemon options] 10 /* DESCRIPTION 11 /* The \fBscache\fR(8) server maintains a shared multi-connection 12 /* cache. This information can be used by, for example, Postfix 13 /* SMTP clients or other Postfix delivery agents. 14 /* 15 /* The connection cache is organized into logical destination 16 /* names, physical endpoint names, and connections. 17 /* 18 /* As a specific example, logical SMTP destinations specify 19 /* (transport, domain, port), and physical SMTP endpoints 20 /* specify (transport, IP address, port). An SMTP connection 21 /* may be saved after a successful mail transaction. 22 /* 23 /* In the general case, one logical destination may refer to 24 /* zero or more physical endpoints, one physical endpoint may 25 /* be referenced by zero or more logical destinations, and 26 /* one endpoint may refer to zero or more connections. 27 /* 28 /* The exact syntax of a logical destination or endpoint name 29 /* is application dependent; the \fBscache\fR(8) server does 30 /* not care. A connection is stored as a file descriptor together 31 /* with application-dependent information that is needed to 32 /* re-activate a connection object. Again, the \fBscache\fR(8) 33 /* server is completely unaware of the details of that 34 /* information. 35 /* 36 /* All information is stored with a finite time to live (ttl). 37 /* The connection cache daemon terminates when no client is 38 /* connected for \fBmax_idle\fR time units. 39 /* 40 /* This server implements the following requests: 41 /* .IP "\fBsave_endp\fI ttl endpoint endpoint_properties file_descriptor\fR" 42 /* Save the specified file descriptor and connection property data 43 /* under the specified endpoint name. The endpoint properties 44 /* are used by the client to re-activate a passivated connection 45 /* object. 46 /* .IP "\fBfind_endp\fI endpoint\fR" 47 /* Look up cached properties and a cached file descriptor for the 48 /* specified endpoint. 49 /* .IP "\fBsave_dest\fI ttl destination destination_properties endpoint\fR" 50 /* Save the binding between a logical destination and an 51 /* endpoint under the destination name, together with destination 52 /* specific connection properties. The destination properties 53 /* are used by the client to re-activate a passivated connection 54 /* object. 55 /* .IP "\fBfind_dest\fI destination\fR" 56 /* Look up cached destination properties, cached endpoint properties, 57 /* and a cached file descriptor for the specified logical destination. 58 /* SECURITY 59 /* .ad 60 /* .fi 61 /* The \fBscache\fR(8) server is not security-sensitive. It does not 62 /* talk to the network, and it does not talk to local users. 63 /* The \fBscache\fR(8) server can run chrooted at fixed low privilege. 64 /* 65 /* The \fBscache\fR(8) server is not a trusted process. It must 66 /* not be used to store information that is security sensitive. 67 /* DIAGNOSTICS 68 /* Problems and transactions are logged to \fBsyslogd\fR(8) 69 /* or \fBpostlogd\fR(8). 70 /* BUGS 71 /* The session cache cannot be shared among multiple machines. 72 /* 73 /* When a connection expires from the cache, it is closed without 74 /* the appropriate protocol specific handshake. 75 /* CONFIGURATION PARAMETERS 76 /* .ad 77 /* .fi 78 /* Changes to \fBmain.cf\fR are picked up automatically as \fBscache\fR(8) 79 /* processes run for only a limited amount of time. Use the command 80 /* "\fBpostfix reload\fR" to speed up a change. 81 /* 82 /* The text below provides only a parameter summary. See 83 /* \fBpostconf\fR(5) for more details including examples. 84 /* RESOURCE CONTROLS 85 /* .ad 86 /* .fi 87 /* .IP "\fBconnection_cache_ttl_limit (2s)\fR" 88 /* The maximal time-to-live value that the \fBscache\fR(8) connection 89 /* cache server 90 /* allows. 91 /* .IP "\fBconnection_cache_status_update_time (600s)\fR" 92 /* How frequently the \fBscache\fR(8) server logs usage statistics with 93 /* connection cache hit and miss rates for logical destinations and for 94 /* physical endpoints. 95 /* MISCELLANEOUS CONTROLS 96 /* .ad 97 /* .fi 98 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" 99 /* The default location of the Postfix main.cf and master.cf 100 /* configuration files. 101 /* .IP "\fBdaemon_timeout (18000s)\fR" 102 /* How much time a Postfix daemon process may take to handle a 103 /* request before it is terminated by a built-in watchdog timer. 104 /* .IP "\fBipc_timeout (3600s)\fR" 105 /* The time limit for sending or receiving information over an internal 106 /* communication channel. 107 /* .IP "\fBmax_idle (100s)\fR" 108 /* The maximum amount of time that an idle Postfix daemon process waits 109 /* for an incoming connection before terminating voluntarily. 110 /* .IP "\fBprocess_id (read-only)\fR" 111 /* The process ID of a Postfix command or daemon process. 112 /* .IP "\fBprocess_name (read-only)\fR" 113 /* The process name of a Postfix command or daemon process. 114 /* .IP "\fBsyslog_facility (mail)\fR" 115 /* The syslog facility of Postfix logging. 116 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" 117 /* A prefix that is prepended to the process name in syslog 118 /* records, so that, for example, "smtpd" becomes "prefix/smtpd". 119 /* .PP 120 /* Available in Postfix 3.3 and later: 121 /* .IP "\fBservice_name (read-only)\fR" 122 /* The master.cf service name of a Postfix daemon process. 123 /* SEE ALSO 124 /* smtp(8), SMTP client 125 /* postconf(5), configuration parameters 126 /* master(8), process manager 127 /* postlogd(8), Postfix logging 128 /* syslogd(8), system logging 129 /* README FILES 130 /* .ad 131 /* .fi 132 /* Use "\fBpostconf readme_directory\fR" or 133 /* "\fBpostconf html_directory\fR" to locate this information. 134 /* .na 135 /* .nf 136 /* CONNECTION_CACHE_README, Postfix connection cache 137 /* LICENSE 138 /* .ad 139 /* .fi 140 /* The Secure Mailer license must be distributed with this software. 141 /* HISTORY 142 /* This service was introduced with Postfix version 2.2. 143 /* AUTHOR(S) 144 /* Wietse Venema 145 /* IBM T.J. Watson Research 146 /* P.O. Box 704 147 /* Yorktown Heights, NY 10598, USA 148 /* 149 /* Wietse Venema 150 /* Google, Inc. 151 /* 111 8th Avenue 152 /* New York, NY 10011, USA 153 /*--*/ 154 155 /* System library. */ 156 157 #include <sys_defs.h> 158 #include <time.h> 159 160 /* Utility library. */ 161 162 #include <msg.h> 163 #include <iostuff.h> 164 #include <htable.h> 165 #include <ring.h> 166 #include <events.h> 167 168 /* Global library. */ 169 170 #include <mail_params.h> 171 #include <mail_version.h> 172 #include <mail_proto.h> 173 #include <scache.h> 174 175 /* Single server skeleton. */ 176 177 #include <mail_server.h> 178 #include <mail_conf.h> 179 180 /* Application-specific. */ 181 182 /* 183 * Tunable parameters. 184 */ 185 int var_scache_ttl_lim; 186 int var_scache_stat_time; 187 188 /* 189 * Request parameters. 190 */ 191 static VSTRING *scache_request; 192 static VSTRING *scache_dest_label; 193 static VSTRING *scache_dest_prop; 194 static VSTRING *scache_endp_label; 195 static VSTRING *scache_endp_prop; 196 197 #ifdef CANT_WRITE_BEFORE_SENDING_FD 198 static VSTRING *scache_dummy; 199 200 #endif 201 202 /* 203 * Session cache instance. 204 */ 205 static SCACHE *scache; 206 207 /* 208 * Statistics. 209 */ 210 static int scache_dest_hits; 211 static int scache_dest_miss; 212 static int scache_dest_count; 213 static int scache_endp_hits; 214 static int scache_endp_miss; 215 static int scache_endp_count; 216 static int scache_sess_count; 217 time_t scache_start_time; 218 219 /* 220 * Silly little macros. 221 */ 222 #define STR(x) vstring_str(x) 223 #define VSTREQ(x,y) (strcmp(STR(x),y) == 0) 224 225 /* scache_save_endp_service - protocol to save endpoint->stream binding */ 226 227 static void scache_save_endp_service(VSTREAM *client_stream) 228 { 229 const char *myname = "scache_save_endp_service"; 230 int ttl; 231 int fd; 232 SCACHE_SIZE size; 233 234 if (attr_scan(client_stream, 235 ATTR_FLAG_STRICT, 236 RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl), 237 RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), 238 RECV_ATTR_STR(MAIL_ATTR_PROP, scache_endp_prop), 239 ATTR_TYPE_END) != 3 240 || ttl <= 0) { 241 msg_warn("%s: bad or missing request parameter", myname); 242 attr_print(client_stream, ATTR_FLAG_NONE, 243 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), 244 ATTR_TYPE_END); 245 return; 246 } else if ( 247 #ifdef CANT_WRITE_BEFORE_SENDING_FD 248 attr_print(client_stream, ATTR_FLAG_NONE, 249 SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""), 250 ATTR_TYPE_END) != 0 251 || vstream_fflush(client_stream) != 0 252 || read_wait(vstream_fileno(client_stream), 253 client_stream->timeout) < 0 /* XXX */ 254 || 255 #endif 256 (fd = LOCAL_RECV_FD(vstream_fileno(client_stream))) < 0) { 257 msg_warn("%s: unable to receive file descriptor: %m", myname); 258 (void) attr_print(client_stream, ATTR_FLAG_NONE, 259 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), 260 ATTR_TYPE_END); 261 return; 262 } else { 263 scache_save_endp(scache, 264 ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl, 265 STR(scache_endp_label), STR(scache_endp_prop), fd); 266 (void) attr_print(client_stream, ATTR_FLAG_NONE, 267 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), 268 ATTR_TYPE_END); 269 scache_size(scache, &size); 270 if (size.endp_count > scache_endp_count) 271 scache_endp_count = size.endp_count; 272 if (size.sess_count > scache_sess_count) 273 scache_sess_count = size.sess_count; 274 return; 275 } 276 } 277 278 /* scache_find_endp_service - protocol to find connection for endpoint */ 279 280 static void scache_find_endp_service(VSTREAM *client_stream) 281 { 282 const char *myname = "scache_find_endp_service"; 283 int fd; 284 285 if (attr_scan(client_stream, 286 ATTR_FLAG_STRICT, 287 RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), 288 ATTR_TYPE_END) != 1) { 289 msg_warn("%s: bad or missing request parameter", myname); 290 attr_print(client_stream, ATTR_FLAG_NONE, 291 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), 292 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 293 ATTR_TYPE_END); 294 return; 295 } else if ((fd = scache_find_endp(scache, STR(scache_endp_label), 296 scache_endp_prop)) < 0) { 297 attr_print(client_stream, ATTR_FLAG_NONE, 298 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), 299 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 300 ATTR_TYPE_END); 301 scache_endp_miss++; 302 return; 303 } else { 304 attr_print(client_stream, ATTR_FLAG_NONE, 305 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), 306 SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)), 307 ATTR_TYPE_END); 308 if (vstream_fflush(client_stream) != 0 309 #ifdef CANT_WRITE_BEFORE_SENDING_FD 310 || attr_scan(client_stream, ATTR_FLAG_STRICT, 311 RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), 312 ATTR_TYPE_END) != 1 313 #endif 314 || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0 315 #ifdef MUST_READ_AFTER_SENDING_FD 316 || attr_scan(client_stream, ATTR_FLAG_STRICT, 317 RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), 318 ATTR_TYPE_END) != 1 319 #endif 320 ) 321 msg_warn("%s: cannot send file descriptor: %m", myname); 322 if (close(fd) < 0) 323 msg_warn("close(%d): %m", fd); 324 scache_endp_hits++; 325 return; 326 } 327 } 328 329 /* scache_save_dest_service - protocol to save destination->endpoint binding */ 330 331 static void scache_save_dest_service(VSTREAM *client_stream) 332 { 333 const char *myname = "scache_save_dest_service"; 334 int ttl; 335 SCACHE_SIZE size; 336 337 if (attr_scan(client_stream, 338 ATTR_FLAG_STRICT, 339 RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl), 340 RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label), 341 RECV_ATTR_STR(MAIL_ATTR_PROP, scache_dest_prop), 342 RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label), 343 ATTR_TYPE_END) != 4 344 || ttl <= 0) { 345 msg_warn("%s: bad or missing request parameter", myname); 346 attr_print(client_stream, ATTR_FLAG_NONE, 347 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), 348 ATTR_TYPE_END); 349 return; 350 } else { 351 scache_save_dest(scache, 352 ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl, 353 STR(scache_dest_label), STR(scache_dest_prop), 354 STR(scache_endp_label)); 355 attr_print(client_stream, ATTR_FLAG_NONE, 356 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), 357 ATTR_TYPE_END); 358 scache_size(scache, &size); 359 if (size.dest_count > scache_dest_count) 360 scache_dest_count = size.dest_count; 361 if (size.endp_count > scache_endp_count) 362 scache_endp_count = size.endp_count; 363 return; 364 } 365 } 366 367 /* scache_find_dest_service - protocol to find connection for destination */ 368 369 static void scache_find_dest_service(VSTREAM *client_stream) 370 { 371 const char *myname = "scache_find_dest_service"; 372 int fd; 373 374 if (attr_scan(client_stream, 375 ATTR_FLAG_STRICT, 376 RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label), 377 ATTR_TYPE_END) != 1) { 378 msg_warn("%s: bad or missing request parameter", myname); 379 attr_print(client_stream, ATTR_FLAG_NONE, 380 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), 381 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 382 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 383 ATTR_TYPE_END); 384 return; 385 } else if ((fd = scache_find_dest(scache, STR(scache_dest_label), 386 scache_dest_prop, 387 scache_endp_prop)) < 0) { 388 attr_print(client_stream, ATTR_FLAG_NONE, 389 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL), 390 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 391 SEND_ATTR_STR(MAIL_ATTR_PROP, ""), 392 ATTR_TYPE_END); 393 scache_dest_miss++; 394 return; 395 } else { 396 attr_print(client_stream, ATTR_FLAG_NONE, 397 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK), 398 SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_dest_prop)), 399 SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)), 400 ATTR_TYPE_END); 401 if (vstream_fflush(client_stream) != 0 402 #ifdef CANT_WRITE_BEFORE_SENDING_FD 403 || attr_scan(client_stream, ATTR_FLAG_STRICT, 404 RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), 405 ATTR_TYPE_END) != 1 406 #endif 407 || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0 408 #ifdef MUST_READ_AFTER_SENDING_FD 409 || attr_scan(client_stream, ATTR_FLAG_STRICT, 410 RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy), 411 ATTR_TYPE_END) != 1 412 #endif 413 ) 414 msg_warn("%s: cannot send file descriptor: %m", myname); 415 if (close(fd) < 0) 416 msg_warn("close(%d): %m", fd); 417 scache_dest_hits++; 418 return; 419 } 420 } 421 422 /* scache_service - perform service for client */ 423 424 static void scache_service(VSTREAM *client_stream, char *unused_service, 425 char **argv) 426 { 427 428 /* 429 * Sanity check. This service takes no command-line arguments. 430 */ 431 if (argv[0]) 432 msg_fatal("unexpected command-line argument: %s", argv[0]); 433 434 /* 435 * This routine runs whenever a client connects to the UNIX-domain socket 436 * dedicated to the scache service. All connection-management stuff is 437 * handled by the common code in multi_server.c. 438 * 439 * XXX Workaround: with some requests, the client sends a dummy message 440 * after the server replies (yes that's a botch). When the scache server 441 * is slow, this dummy message may become concatenated with the next 442 * request from the same client. The do-while loop below will repeat 443 * instead of discarding the client request. We must process it now 444 * because there will be no select() notification. 445 */ 446 do { 447 if (attr_scan(client_stream, 448 ATTR_FLAG_MORE | ATTR_FLAG_STRICT, 449 RECV_ATTR_STR(MAIL_ATTR_REQ, scache_request), 450 ATTR_TYPE_END) == 1) { 451 if (VSTREQ(scache_request, SCACHE_REQ_SAVE_DEST)) { 452 scache_save_dest_service(client_stream); 453 } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_DEST)) { 454 scache_find_dest_service(client_stream); 455 } else if (VSTREQ(scache_request, SCACHE_REQ_SAVE_ENDP)) { 456 scache_save_endp_service(client_stream); 457 } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_ENDP)) { 458 scache_find_endp_service(client_stream); 459 } else { 460 msg_warn("unrecognized request: \"%s\", ignored", 461 STR(scache_request)); 462 attr_print(client_stream, ATTR_FLAG_NONE, 463 SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD), 464 ATTR_TYPE_END); 465 } 466 } 467 } while (vstream_peek(client_stream) > 0); 468 vstream_fflush(client_stream); 469 } 470 471 /* scache_status_dump - log and reset cache statistics */ 472 473 static void scache_status_dump(char *unused_name, char **unused_argv) 474 { 475 if (scache_dest_hits || scache_dest_miss 476 || scache_endp_hits || scache_endp_miss 477 || scache_dest_count || scache_endp_count 478 || scache_sess_count) 479 msg_info("statistics: start interval %.15s", 480 ctime(&scache_start_time) + 4); 481 482 if (scache_dest_hits || scache_dest_miss) { 483 msg_info("statistics: domain lookup hits=%d miss=%d success=%d%%", 484 scache_dest_hits, scache_dest_miss, 485 scache_dest_hits * 100 486 / (scache_dest_hits + scache_dest_miss)); 487 scache_dest_hits = scache_dest_miss = 0; 488 } 489 if (scache_endp_hits || scache_endp_miss) { 490 msg_info("statistics: address lookup hits=%d miss=%d success=%d%%", 491 scache_endp_hits, scache_endp_miss, 492 scache_endp_hits * 100 493 / (scache_endp_hits + scache_endp_miss)); 494 scache_endp_hits = scache_endp_miss = 0; 495 } 496 if (scache_dest_count || scache_endp_count || scache_sess_count) { 497 msg_info("statistics: max simultaneous domains=%d addresses=%d connection=%d", 498 scache_dest_count, scache_endp_count, scache_sess_count); 499 scache_dest_count = 0; 500 scache_endp_count = 0; 501 scache_sess_count = 0; 502 } 503 scache_start_time = event_time(); 504 } 505 506 /* scache_status_update - log and reset cache statistics periodically */ 507 508 static void scache_status_update(int unused_event, void *context) 509 { 510 scache_status_dump((char *) 0, (char **) 0); 511 event_request_timer(scache_status_update, context, var_scache_stat_time); 512 } 513 514 /* post_jail_init - initialization after privilege drop */ 515 516 static void post_jail_init(char *unused_name, char **unused_argv) 517 { 518 519 /* 520 * Pre-allocate the cache instance. 521 */ 522 scache = scache_multi_create(); 523 524 /* 525 * Pre-allocate buffers. 526 */ 527 scache_request = vstring_alloc(10); 528 scache_dest_label = vstring_alloc(10); 529 scache_dest_prop = vstring_alloc(10); 530 scache_endp_label = vstring_alloc(10); 531 scache_endp_prop = vstring_alloc(10); 532 #ifdef CANT_WRITE_BEFORE_SENDING_FD 533 scache_dummy = vstring_alloc(10); 534 #endif 535 536 /* 537 * Disable the max_use limit. We still terminate when no client is 538 * connected for $idle_limit time units. 539 */ 540 var_use_limit = 0; 541 542 /* 543 * Dump and reset cache statistics every so often. 544 */ 545 event_request_timer(scache_status_update, (void *) 0, var_scache_stat_time); 546 scache_start_time = event_time(); 547 } 548 549 /* scache_post_accept - announce our protocol */ 550 551 static void scache_post_accept(VSTREAM *stream, char *unused_name, 552 char **unused_argv, HTABLE *unused_table) 553 { 554 555 /* 556 * Announce the protocol. 557 */ 558 attr_print(stream, ATTR_FLAG_NONE, 559 SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE), 560 ATTR_TYPE_END); 561 (void) vstream_fflush(stream); 562 } 563 564 MAIL_VERSION_STAMP_DECLARE; 565 566 /* main - pass control to the multi-threaded skeleton */ 567 568 int main(int argc, char **argv) 569 { 570 static const CONFIG_TIME_TABLE time_table[] = { 571 VAR_SCACHE_TTL_LIM, DEF_SCACHE_TTL_LIM, &var_scache_ttl_lim, 1, 0, 572 VAR_SCACHE_STAT_TIME, DEF_SCACHE_STAT_TIME, &var_scache_stat_time, 1, 0, 573 0, 574 }; 575 576 /* 577 * Fingerprint executables and core dumps. 578 */ 579 MAIL_VERSION_STAMP_ALLOCATE; 580 581 multi_server_main(argc, argv, scache_service, 582 CA_MAIL_SERVER_TIME_TABLE(time_table), 583 CA_MAIL_SERVER_POST_INIT(post_jail_init), 584 CA_MAIL_SERVER_POST_ACCEPT(scache_post_accept), 585 CA_MAIL_SERVER_EXIT(scache_status_dump), 586 CA_MAIL_SERVER_SOLITARY, 587 0); 588 } 589