summaryrefslogtreecommitdiffstats
path: root/src/scache/scache.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/scache/scache.c')
-rw-r--r--src/scache/scache.c586
1 files changed, 586 insertions, 0 deletions
diff --git a/src/scache/scache.c b/src/scache/scache.c
new file mode 100644
index 0000000..85f32ef
--- /dev/null
+++ b/src/scache/scache.c
@@ -0,0 +1,586 @@
+/*++
+/* NAME
+/* scache 8
+/* SUMMARY
+/* Postfix shared connection cache server
+/* SYNOPSIS
+/* \fBscache\fR [generic Postfix daemon options]
+/* DESCRIPTION
+/* The \fBscache\fR(8) server maintains a shared multi-connection
+/* cache. This information can be used by, for example, Postfix
+/* SMTP clients or other Postfix delivery agents.
+/*
+/* The connection cache is organized into logical destination
+/* names, physical endpoint names, and connections.
+/*
+/* As a specific example, logical SMTP destinations specify
+/* (transport, domain, port), and physical SMTP endpoints
+/* specify (transport, IP address, port). An SMTP connection
+/* may be saved after a successful mail transaction.
+/*
+/* In the general case, one logical destination may refer to
+/* zero or more physical endpoints, one physical endpoint may
+/* be referenced by zero or more logical destinations, and
+/* one endpoint may refer to zero or more connections.
+/*
+/* The exact syntax of a logical destination or endpoint name
+/* is application dependent; the \fBscache\fR(8) server does
+/* not care. A connection is stored as a file descriptor together
+/* with application-dependent information that is needed to
+/* re-activate a connection object. Again, the \fBscache\fR(8)
+/* server is completely unaware of the details of that
+/* information.
+/*
+/* All information is stored with a finite time to live (ttl).
+/* The connection cache daemon terminates when no client is
+/* connected for \fBmax_idle\fR time units.
+/*
+/* This server implements the following requests:
+/* .IP "\fBsave_endp\fI ttl endpoint endpoint_properties file_descriptor\fR"
+/* Save the specified file descriptor and connection property data
+/* under the specified endpoint name. The endpoint properties
+/* are used by the client to re-activate a passivated connection
+/* object.
+/* .IP "\fBfind_endp\fI endpoint\fR"
+/* Look up cached properties and a cached file descriptor for the
+/* specified endpoint.
+/* .IP "\fBsave_dest\fI ttl destination destination_properties endpoint\fR"
+/* Save the binding between a logical destination and an
+/* endpoint under the destination name, together with destination
+/* specific connection properties. The destination properties
+/* are used by the client to re-activate a passivated connection
+/* object.
+/* .IP "\fBfind_dest\fI destination\fR"
+/* Look up cached destination properties, cached endpoint properties,
+/* and a cached file descriptor for the specified logical destination.
+/* SECURITY
+/* .ad
+/* .fi
+/* The \fBscache\fR(8) server is not security-sensitive. It does not
+/* talk to the network, and it does not talk to local users.
+/* The \fBscache\fR(8) server can run chrooted at fixed low privilege.
+/*
+/* The \fBscache\fR(8) server is not a trusted process. It must
+/* not be used to store information that is security sensitive.
+/* DIAGNOSTICS
+/* Problems and transactions are logged to \fBsyslogd\fR(8)
+/* or \fBpostlogd\fR(8).
+/* BUGS
+/* The session cache cannot be shared among multiple machines.
+/*
+/* When a connection expires from the cache, it is closed without
+/* the appropriate protocol specific handshake.
+/* CONFIGURATION PARAMETERS
+/* .ad
+/* .fi
+/* Changes to \fBmain.cf\fR are picked up automatically as \fBscache\fR(8)
+/* processes run for only a limited amount of time. Use the command
+/* "\fBpostfix reload\fR" to speed up a change.
+/*
+/* The text below provides only a parameter summary. See
+/* \fBpostconf\fR(5) for more details including examples.
+/* RESOURCE CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconnection_cache_ttl_limit (2s)\fR"
+/* The maximal time-to-live value that the \fBscache\fR(8) connection
+/* cache server
+/* allows.
+/* .IP "\fBconnection_cache_status_update_time (600s)\fR"
+/* How frequently the \fBscache\fR(8) server logs usage statistics with
+/* connection cache hit and miss rates for logical destinations and for
+/* physical endpoints.
+/* MISCELLANEOUS CONTROLS
+/* .ad
+/* .fi
+/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
+/* The default location of the Postfix main.cf and master.cf
+/* configuration files.
+/* .IP "\fBdaemon_timeout (18000s)\fR"
+/* How much time a Postfix daemon process may take to handle a
+/* request before it is terminated by a built-in watchdog timer.
+/* .IP "\fBipc_timeout (3600s)\fR"
+/* The time limit for sending or receiving information over an internal
+/* communication channel.
+/* .IP "\fBmax_idle (100s)\fR"
+/* The maximum amount of time that an idle Postfix daemon process waits
+/* for an incoming connection before terminating voluntarily.
+/* .IP "\fBprocess_id (read-only)\fR"
+/* The process ID of a Postfix command or daemon process.
+/* .IP "\fBprocess_name (read-only)\fR"
+/* The process name of a Postfix command or daemon process.
+/* .IP "\fBsyslog_facility (mail)\fR"
+/* The syslog facility of Postfix logging.
+/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
+/* A prefix that is prepended to the process name in syslog
+/* records, so that, for example, "smtpd" becomes "prefix/smtpd".
+/* .PP
+/* Available in Postfix 3.3 and later:
+/* .IP "\fBservice_name (read-only)\fR"
+/* The master.cf service name of a Postfix daemon process.
+/* SEE ALSO
+/* smtp(8), SMTP client
+/* postconf(5), configuration parameters
+/* master(8), process manager
+/* postlogd(8), Postfix logging
+/* syslogd(8), system logging
+/* README FILES
+/* .ad
+/* .fi
+/* Use "\fBpostconf readme_directory\fR" or
+/* "\fBpostconf html_directory\fR" to locate this information.
+/* .na
+/* .nf
+/* CONNECTION_CACHE_README, Postfix connection cache
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* HISTORY
+/* This service was introduced with Postfix version 2.2.
+/* AUTHOR(S)
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <time.h>
+
+/* Utility library. */
+
+#include <msg.h>
+#include <iostuff.h>
+#include <htable.h>
+#include <ring.h>
+#include <events.h>
+
+/* Global library. */
+
+#include <mail_params.h>
+#include <mail_version.h>
+#include <mail_proto.h>
+#include <scache.h>
+
+/* Single server skeleton. */
+
+#include <mail_server.h>
+#include <mail_conf.h>
+
+/* Application-specific. */
+
+ /*
+ * Tunable parameters.
+ */
+int var_scache_ttl_lim;
+int var_scache_stat_time;
+
+ /*
+ * Request parameters.
+ */
+static VSTRING *scache_request;
+static VSTRING *scache_dest_label;
+static VSTRING *scache_dest_prop;
+static VSTRING *scache_endp_label;
+static VSTRING *scache_endp_prop;
+
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+static VSTRING *scache_dummy;
+
+#endif
+
+ /*
+ * Session cache instance.
+ */
+static SCACHE *scache;
+
+ /*
+ * Statistics.
+ */
+static int scache_dest_hits;
+static int scache_dest_miss;
+static int scache_dest_count;
+static int scache_endp_hits;
+static int scache_endp_miss;
+static int scache_endp_count;
+static int scache_sess_count;
+time_t scache_start_time;
+
+ /*
+ * Silly little macros.
+ */
+#define STR(x) vstring_str(x)
+#define VSTREQ(x,y) (strcmp(STR(x),y) == 0)
+
+/* scache_save_endp_service - protocol to save endpoint->stream binding */
+
+static void scache_save_endp_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_save_endp_service";
+ int ttl;
+ int fd;
+ SCACHE_SIZE size;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, scache_endp_prop),
+ ATTR_TYPE_END) != 3
+ || ttl <= 0) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ return;
+ } else if (
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_DUMMY, ""),
+ ATTR_TYPE_END) != 0
+ || vstream_fflush(client_stream) != 0
+ || read_wait(vstream_fileno(client_stream),
+ client_stream->timeout) < 0 /* XXX */
+ ||
+#endif
+ (fd = LOCAL_RECV_FD(vstream_fileno(client_stream))) < 0) {
+ msg_warn("%s: unable to receive file descriptor: %m", myname);
+ (void) attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ ATTR_TYPE_END);
+ return;
+ } else {
+ scache_save_endp(scache,
+ ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl,
+ STR(scache_endp_label), STR(scache_endp_prop), fd);
+ (void) attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ ATTR_TYPE_END);
+ scache_size(scache, &size);
+ if (size.endp_count > scache_endp_count)
+ scache_endp_count = size.endp_count;
+ if (size.sess_count > scache_sess_count)
+ scache_sess_count = size.sess_count;
+ return;
+ }
+}
+
+/* scache_find_endp_service - protocol to find connection for endpoint */
+
+static void scache_find_endp_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_find_endp_service";
+ int fd;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ return;
+ } else if ((fd = scache_find_endp(scache, STR(scache_endp_label),
+ scache_endp_prop)) < 0) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ scache_endp_miss++;
+ return;
+ } else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)),
+ ATTR_TYPE_END);
+ if (vstream_fflush(client_stream) != 0
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0
+#ifdef MUST_READ_AFTER_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ )
+ msg_warn("%s: cannot send file descriptor: %m", myname);
+ if (close(fd) < 0)
+ msg_warn("close(%d): %m", fd);
+ scache_endp_hits++;
+ return;
+ }
+}
+
+/* scache_save_dest_service - protocol to save destination->endpoint binding */
+
+static void scache_save_dest_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_save_dest_service";
+ int ttl;
+ SCACHE_SIZE size;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_INT(MAIL_ATTR_TTL, &ttl),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label),
+ RECV_ATTR_STR(MAIL_ATTR_PROP, scache_dest_prop),
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_endp_label),
+ ATTR_TYPE_END) != 4
+ || ttl <= 0) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ return;
+ } else {
+ scache_save_dest(scache,
+ ttl > var_scache_ttl_lim ? var_scache_ttl_lim : ttl,
+ STR(scache_dest_label), STR(scache_dest_prop),
+ STR(scache_endp_label));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ ATTR_TYPE_END);
+ scache_size(scache, &size);
+ if (size.dest_count > scache_dest_count)
+ scache_dest_count = size.dest_count;
+ if (size.endp_count > scache_endp_count)
+ scache_endp_count = size.endp_count;
+ return;
+ }
+}
+
+/* scache_find_dest_service - protocol to find connection for destination */
+
+static void scache_find_dest_service(VSTREAM *client_stream)
+{
+ const char *myname = "scache_find_dest_service";
+ int fd;
+
+ if (attr_scan(client_stream,
+ ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_LABEL, scache_dest_label),
+ ATTR_TYPE_END) != 1) {
+ msg_warn("%s: bad or missing request parameter", myname);
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ return;
+ } else if ((fd = scache_find_dest(scache, STR(scache_dest_label),
+ scache_dest_prop,
+ scache_endp_prop)) < 0) {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_FAIL),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, ""),
+ ATTR_TYPE_END);
+ scache_dest_miss++;
+ return;
+ } else {
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_OK),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_dest_prop)),
+ SEND_ATTR_STR(MAIL_ATTR_PROP, STR(scache_endp_prop)),
+ ATTR_TYPE_END);
+ if (vstream_fflush(client_stream) != 0
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ || LOCAL_SEND_FD(vstream_fileno(client_stream), fd) < 0
+#ifdef MUST_READ_AFTER_SENDING_FD
+ || attr_scan(client_stream, ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_DUMMY, scache_dummy),
+ ATTR_TYPE_END) != 1
+#endif
+ )
+ msg_warn("%s: cannot send file descriptor: %m", myname);
+ if (close(fd) < 0)
+ msg_warn("close(%d): %m", fd);
+ scache_dest_hits++;
+ return;
+ }
+}
+
+/* scache_service - perform service for client */
+
+static void scache_service(VSTREAM *client_stream, char *unused_service,
+ char **argv)
+{
+
+ /*
+ * Sanity check. This service takes no command-line arguments.
+ */
+ if (argv[0])
+ msg_fatal("unexpected command-line argument: %s", argv[0]);
+
+ /*
+ * This routine runs whenever a client connects to the UNIX-domain socket
+ * dedicated to the scache service. All connection-management stuff is
+ * handled by the common code in multi_server.c.
+ *
+ * XXX Workaround: with some requests, the client sends a dummy message
+ * after the server replies (yes that's a botch). When the scache server
+ * is slow, this dummy message may become concatenated with the next
+ * request from the same client. The do-while loop below will repeat
+ * instead of discarding the client request. We must process it now
+ * because there will be no select() notification.
+ */
+ do {
+ if (attr_scan(client_stream,
+ ATTR_FLAG_MORE | ATTR_FLAG_STRICT,
+ RECV_ATTR_STR(MAIL_ATTR_REQ, scache_request),
+ ATTR_TYPE_END) == 1) {
+ if (VSTREQ(scache_request, SCACHE_REQ_SAVE_DEST)) {
+ scache_save_dest_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_DEST)) {
+ scache_find_dest_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_SAVE_ENDP)) {
+ scache_save_endp_service(client_stream);
+ } else if (VSTREQ(scache_request, SCACHE_REQ_FIND_ENDP)) {
+ scache_find_endp_service(client_stream);
+ } else {
+ msg_warn("unrecognized request: \"%s\", ignored",
+ STR(scache_request));
+ attr_print(client_stream, ATTR_FLAG_NONE,
+ SEND_ATTR_INT(MAIL_ATTR_STATUS, SCACHE_STAT_BAD),
+ ATTR_TYPE_END);
+ }
+ }
+ } while (vstream_peek(client_stream) > 0);
+ vstream_fflush(client_stream);
+}
+
+/* scache_status_dump - log and reset cache statistics */
+
+static void scache_status_dump(char *unused_name, char **unused_argv)
+{
+ if (scache_dest_hits || scache_dest_miss
+ || scache_endp_hits || scache_endp_miss
+ || scache_dest_count || scache_endp_count
+ || scache_sess_count)
+ msg_info("statistics: start interval %.15s",
+ ctime(&scache_start_time) + 4);
+
+ if (scache_dest_hits || scache_dest_miss) {
+ msg_info("statistics: domain lookup hits=%d miss=%d success=%d%%",
+ scache_dest_hits, scache_dest_miss,
+ scache_dest_hits * 100
+ / (scache_dest_hits + scache_dest_miss));
+ scache_dest_hits = scache_dest_miss = 0;
+ }
+ if (scache_endp_hits || scache_endp_miss) {
+ msg_info("statistics: address lookup hits=%d miss=%d success=%d%%",
+ scache_endp_hits, scache_endp_miss,
+ scache_endp_hits * 100
+ / (scache_endp_hits + scache_endp_miss));
+ scache_endp_hits = scache_endp_miss = 0;
+ }
+ if (scache_dest_count || scache_endp_count || scache_sess_count) {
+ msg_info("statistics: max simultaneous domains=%d addresses=%d connection=%d",
+ scache_dest_count, scache_endp_count, scache_sess_count);
+ scache_dest_count = 0;
+ scache_endp_count = 0;
+ scache_sess_count = 0;
+ }
+ scache_start_time = event_time();
+}
+
+/* scache_status_update - log and reset cache statistics periodically */
+
+static void scache_status_update(int unused_event, void *context)
+{
+ scache_status_dump((char *) 0, (char **) 0);
+ event_request_timer(scache_status_update, context, var_scache_stat_time);
+}
+
+/* post_jail_init - initialization after privilege drop */
+
+static void post_jail_init(char *unused_name, char **unused_argv)
+{
+
+ /*
+ * Pre-allocate the cache instance.
+ */
+ scache = scache_multi_create();
+
+ /*
+ * Pre-allocate buffers.
+ */
+ scache_request = vstring_alloc(10);
+ scache_dest_label = vstring_alloc(10);
+ scache_dest_prop = vstring_alloc(10);
+ scache_endp_label = vstring_alloc(10);
+ scache_endp_prop = vstring_alloc(10);
+#ifdef CANT_WRITE_BEFORE_SENDING_FD
+ scache_dummy = vstring_alloc(10);
+#endif
+
+ /*
+ * Disable the max_use limit. We still terminate when no client is
+ * connected for $idle_limit time units.
+ */
+ var_use_limit = 0;
+
+ /*
+ * Dump and reset cache statistics every so often.
+ */
+ event_request_timer(scache_status_update, (void *) 0, var_scache_stat_time);
+ scache_start_time = event_time();
+}
+
+/* scache_post_accept - announce our protocol */
+
+static void scache_post_accept(VSTREAM *stream, char *unused_name,
+ char **unused_argv, HTABLE *unused_table)
+{
+
+ /*
+ * Announce the protocol.
+ */
+ attr_print(stream, ATTR_FLAG_NONE,
+ SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_SCACHE),
+ ATTR_TYPE_END);
+ (void) vstream_fflush(stream);
+}
+
+MAIL_VERSION_STAMP_DECLARE;
+
+/* main - pass control to the multi-threaded skeleton */
+
+int main(int argc, char **argv)
+{
+ static const CONFIG_TIME_TABLE time_table[] = {
+ VAR_SCACHE_TTL_LIM, DEF_SCACHE_TTL_LIM, &var_scache_ttl_lim, 1, 0,
+ VAR_SCACHE_STAT_TIME, DEF_SCACHE_STAT_TIME, &var_scache_stat_time, 1, 0,
+ 0,
+ };
+
+ /*
+ * Fingerprint executables and core dumps.
+ */
+ MAIL_VERSION_STAMP_ALLOCATE;
+
+ multi_server_main(argc, argv, scache_service,
+ CA_MAIL_SERVER_TIME_TABLE(time_table),
+ CA_MAIL_SERVER_POST_INIT(post_jail_init),
+ CA_MAIL_SERVER_POST_ACCEPT(scache_post_accept),
+ CA_MAIL_SERVER_EXIT(scache_status_dump),
+ CA_MAIL_SERVER_SOLITARY,
+ 0);
+}