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