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 
scache_save_endp_service(VSTREAM * client_stream)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 
scache_find_endp_service(VSTREAM * client_stream)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 
scache_save_dest_service(VSTREAM * client_stream)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 
scache_find_dest_service(VSTREAM * client_stream)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 
scache_service(VSTREAM * client_stream,char * unused_service,char ** argv)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 
scache_status_dump(char * unused_name,char ** unused_argv)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 
scache_status_update(int unused_event,void * context)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 
post_jail_init(char * unused_name,char ** unused_argv)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 
scache_post_accept(VSTREAM * stream,char * unused_name,char ** unused_argv,HTABLE * unused_table)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 
main(int argc,char ** argv)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