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