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