xref: /netbsd-src/external/ibm-public/postfix/dist/src/anvil/anvil.c (revision 413d532bcc3f62d122e56d92e13ac64825a40baf)
1 /*	$NetBSD: anvil.c,v 1.1.1.1 2009/06/23 10:08:42 tron Exp $	*/
2 
3 /*++
4 /* NAME
5 /*	anvil 8
6 /* SUMMARY
7 /*	Postfix session count and request rate control
8 /* SYNOPSIS
9 /*	\fBanvil\fR [generic Postfix daemon options]
10 /* DESCRIPTION
11 /*	The Postfix \fBanvil\fR(8) server maintains statistics about
12 /*	client connection counts or client request rates. This
13 /*	information can be used to defend against clients that
14 /*	hammer a server with either too many simultaneous sessions,
15 /*	or with too many successive requests within a configurable
16 /*	time interval.  This server is designed to run under control
17 /*	by the Postfix \fBmaster\fR(8) server.
18 /*
19 /*	In the following text, \fBident\fR specifies a (service,
20 /*	client) combination. The exact syntax of that information
21 /*	is application-dependent; the \fBanvil\fR(8) server does
22 /*	not care.
23 /* CONNECTION COUNT/RATE CONTROL
24 /* .ad
25 /* .fi
26 /*	To register a new connection send the following request to
27 /*	the \fBanvil\fR(8) server:
28 /*
29 /* .nf
30 /*	    \fBrequest=connect\fR
31 /*	    \fBident=\fIstring\fR
32 /* .fi
33 /*
34 /*	The \fBanvil\fR(8) server answers with the number of
35 /*	simultaneous connections and the number of connections per
36 /*	unit time for the (service, client) combination specified
37 /*	with \fBident\fR:
38 /*
39 /* .nf
40 /*	    \fBstatus=0\fR
41 /*	    \fBcount=\fInumber\fR
42 /*	    \fBrate=\fInumber\fR
43 /* .fi
44 /*
45 /*	To register a disconnect event send the following request
46 /*	to the \fBanvil\fR(8) server:
47 /*
48 /* .nf
49 /*	    \fBrequest=disconnect\fR
50 /*	    \fBident=\fIstring\fR
51 /* .fi
52 /*
53 /*	The \fBanvil\fR(8) server replies with:
54 /*
55 /* .nf
56 /*	    \fBstatus=0\fR
57 /* .fi
58 /* MESSAGE RATE CONTROL
59 /* .ad
60 /* .fi
61 /*	To register a message delivery request send the following
62 /*	request to the \fBanvil\fR(8) server:
63 /*
64 /* .nf
65 /*	    \fBrequest=message\fR
66 /*	    \fBident=\fIstring\fR
67 /* .fi
68 /*
69 /*	The \fBanvil\fR(8) server answers with the number of message
70 /*	delivery requests per unit time for the (service, client)
71 /*	combination specified with \fBident\fR:
72 /*
73 /* .nf
74 /*	    \fBstatus=0\fR
75 /*	    \fBrate=\fInumber\fR
76 /* .fi
77 /* RECIPIENT RATE CONTROL
78 /* .ad
79 /* .fi
80 /*	To register a recipient request send the following request
81 /*	to the \fBanvil\fR(8) server:
82 /*
83 /* .nf
84 /*	    \fBrequest=recipient\fR
85 /*	    \fBident=\fIstring\fR
86 /* .fi
87 /*
88 /*	The \fBanvil\fR(8) server answers with the number of recipient
89 /*	addresses per unit time for the (service, client) combination
90 /*	specified with \fBident\fR:
91 /*
92 /* .nf
93 /*	    \fBstatus=0\fR
94 /*	    \fBrate=\fInumber\fR
95 /* .fi
96 /* TLS SESSION NEGOTIATION RATE CONTROL
97 /* .ad
98 /* .fi
99 /*	The features described in this section are available with
100 /*	Postfix 2.3 and later.
101 /*
102 /*	To register a request for a new (i.e. not cached) TLS session
103 /*	send the following request to the \fBanvil\fR(8) server:
104 /*
105 /* .nf
106 /*	    \fBrequest=newtls\fR
107 /*	    \fBident=\fIstring\fR
108 /* .fi
109 /*
110 /*	The \fBanvil\fR(8) server answers with the number of new
111 /*	TLS session requests per unit time for the (service, client)
112 /*	combination specified with \fBident\fR:
113 /*
114 /* .nf
115 /*	    \fBstatus=0\fR
116 /*	    \fBrate=\fInumber\fR
117 /* .fi
118 /*
119 /*	To retrieve new TLS session request rate information without
120 /*	updating the counter information, send:
121 /*
122 /* .nf
123 /*	    \fBrequest=newtls_report\fR
124 /*	    \fBident=\fIstring\fR
125 /* .fi
126 /*
127 /*	The \fBanvil\fR(8) server answers with the number of new
128 /*	TLS session requests per unit time for the (service, client)
129 /*	combination specified with \fBident\fR:
130 /*
131 /* .nf
132 /*	    \fBstatus=0\fR
133 /*	    \fBrate=\fInumber\fR
134 /* .fi
135 /* SECURITY
136 /* .ad
137 /* .fi
138 /*	The \fBanvil\fR(8) server does not talk to the network or to local
139 /*	users, and can run chrooted at fixed low privilege.
140 /*
141 /*	The \fBanvil\fR(8) server maintains an in-memory table with
142 /*	information about recent clients requests.  No persistent
143 /*	state is kept because standard system library routines are
144 /*	not sufficiently robust for update-intensive applications.
145 /*
146 /*	Although the in-memory state is kept only temporarily, this
147 /*	may require a lot of memory on systems that handle connections
148 /*	from many remote clients.  To reduce memory usage, reduce
149 /*	the time unit over which state is kept.
150 /* DIAGNOSTICS
151 /*	Problems and transactions are logged to \fBsyslogd\fR(8).
152 /*
153 /*	Upon exit, and every \fBanvil_status_update_time\fR
154 /*	seconds, the server logs the maximal count and rate values measured,
155 /*	together with (service, client) information and the time of day
156 /*	associated with those events.
157 /*	In order to avoid unnecessary overhead, no measurements
158 /*	are done for activity that isn't concurrency limited or
159 /*	rate limited.
160 /* BUGS
161 /*	Systems behind network address translating routers or proxies
162 /*	appear to have the same client address and can run into connection
163 /*	count and/or rate limits falsely.
164 /*
165 /*	In this preliminary implementation, a count (or rate) limited server
166 /*	process can have only one remote client at a time. If a
167 /*	server process reports
168 /*	multiple simultaneous clients, state is kept only for the last
169 /*	reported client.
170 /*
171 /*	The \fBanvil\fR(8) server automatically discards client
172 /*	request information after it expires.  To prevent the
173 /*	\fBanvil\fR(8) server from discarding client request rate
174 /*	information too early or too late, a rate limited service
175 /*	should always register connect/disconnect events even when
176 /*	it does not explicitly limit them.
177 /* CONFIGURATION PARAMETERS
178 /* .ad
179 /* .fi
180 /*	On low-traffic mail systems, changes to \fBmain.cf\fR are
181 /*	picked up automatically as \fBanvil\fR(8) processes run for
182 /*	only a limited amount of time. On other mail systems, use
183 /*	the command "\fBpostfix reload\fR" to speed up a change.
184 /*
185 /*	The text below provides only a parameter summary. See
186 /*	\fBpostconf\fR(5) for more details including examples.
187 /* .IP "\fBanvil_rate_time_unit (60s)\fR"
188 /*	The time unit over which client connection rates and other rates
189 /*	are calculated.
190 /* .IP "\fBanvil_status_update_time (600s)\fR"
191 /*	How frequently the \fBanvil\fR(8) connection and rate limiting server
192 /*	logs peak usage information.
193 /* .IP "\fBconfig_directory (see 'postconf -d' output)\fR"
194 /*	The default location of the Postfix main.cf and master.cf
195 /*	configuration files.
196 /* .IP "\fBdaemon_timeout (18000s)\fR"
197 /*	How much time a Postfix daemon process may take to handle a
198 /*	request before it is terminated by a built-in watchdog timer.
199 /* .IP "\fBipc_timeout (3600s)\fR"
200 /*	The time limit for sending or receiving information over an internal
201 /*	communication channel.
202 /* .IP "\fBmax_idle (100s)\fR"
203 /*	The maximum amount of time that an idle Postfix daemon process waits
204 /*	for an incoming connection before terminating voluntarily.
205 /* .IP "\fBmax_use (100)\fR"
206 /*	The maximal number of incoming connections that a Postfix daemon
207 /*	process will service before terminating voluntarily.
208 /* .IP "\fBprocess_id (read-only)\fR"
209 /*	The process ID of a Postfix command or daemon process.
210 /* .IP "\fBprocess_name (read-only)\fR"
211 /*	The process name of a Postfix command or daemon process.
212 /* .IP "\fBsyslog_facility (mail)\fR"
213 /*	The syslog facility of Postfix logging.
214 /* .IP "\fBsyslog_name (see 'postconf -d' output)\fR"
215 /*	The mail system name that is prepended to the process name in syslog
216 /*	records, so that "smtpd" becomes, for example, "postfix/smtpd".
217 /* SEE ALSO
218 /*	smtpd(8), Postfix SMTP server
219 /*	postconf(5), configuration parameters
220 /*	master(5), generic daemon options
221 /* README FILES
222 /* .ad
223 /* .fi
224 /*	Use "\fBpostconf readme_directory\fR" or
225 /*	"\fBpostconf html_directory\fR" to locate this information.
226 /* .na
227 /* .nf
228 /*	TUNING_README, performance tuning
229 /* LICENSE
230 /* .ad
231 /* .fi
232 /*	The Secure Mailer license must be distributed with this software.
233 /* HISTORY
234 /* .ad
235 /* .fi
236 /*	The anvil service is available in Postfix 2.2 and later.
237 /* AUTHOR(S)
238 /*	Wietse Venema
239 /*	IBM T.J. Watson Research
240 /*	P.O. Box 704
241 /*	Yorktown Heights, NY 10598, USA
242 /*--*/
243 
244 /* System library. */
245 
246 #include <sys_defs.h>
247 #include <sys/time.h>
248 #include <limits.h>
249 
250 /* Utility library. */
251 
252 #include <msg.h>
253 #include <mymalloc.h>
254 #include <htable.h>
255 #include <stringops.h>
256 #include <events.h>
257 
258 /* Global library. */
259 
260 #include <mail_conf.h>
261 #include <mail_params.h>
262 #include <mail_version.h>
263 #include <mail_proto.h>
264 #include <anvil_clnt.h>
265 
266 /* Server skeleton. */
267 
268 #include <mail_server.h>
269 
270 /* Application-specific. */
271 
272  /*
273   * Configuration parameters.
274   */
275 int     var_anvil_time_unit;
276 int     var_anvil_stat_time;
277 
278  /*
279   * Global dynamic state.
280   */
281 static HTABLE *anvil_remote_map;	/* indexed by service+ remote client */
282 
283  /*
284   * Remote connection state, one instance for each (service, client) pair.
285   */
286 typedef struct {
287     char   *ident;			/* lookup key */
288     int     count;			/* connection count */
289     int     rate;			/* connection rate */
290     int     mail;			/* message rate */
291     int     rcpt;			/* recipient rate */
292     int     ntls;			/* new TLS session rate */
293     time_t  start;			/* time of first rate sample */
294 } ANVIL_REMOTE;
295 
296  /*
297   * Local server state, one instance per anvil client connection. This allows
298   * us to clean up remote connection state when a local server goes away
299   * without cleaning up.
300   */
301 typedef struct {
302     ANVIL_REMOTE *anvil_remote;		/* XXX should be list */
303 } ANVIL_LOCAL;
304 
305  /*
306   * The following operations are implemented as macros with recognizable
307   * names so that we don't lose sight of what the code is trying to do.
308   *
309   * Related operations are defined side by side so that the code implementing
310   * them isn't pages apart.
311   */
312 
313 /* Create new (service, client) state. */
314 
315 #define ANVIL_REMOTE_FIRST_CONN(remote, id) \
316     do { \
317 	(remote)->ident = mystrdup(id); \
318 	(remote)->count = 1; \
319 	(remote)->rate = 1; \
320 	(remote)->mail = 0; \
321 	(remote)->rcpt = 0; \
322 	(remote)->ntls = 0; \
323 	(remote)->start = event_time(); \
324     } while(0)
325 
326 /* Destroy unused (service, client) state. */
327 
328 #define ANVIL_REMOTE_FREE(remote) \
329     do { \
330 	myfree((remote)->ident); \
331 	myfree((char *) (remote)); \
332     } while(0)
333 
334 /* Reset or update rate information for existing (service, client) state. */
335 
336 #define ANVIL_REMOTE_RSET_RATE(remote, _start) \
337     do { \
338 	(remote)->rate = 0; \
339 	(remote)->mail = 0; \
340 	(remote)->rcpt = 0; \
341 	(remote)->ntls = 0; \
342 	(remote)->start = _start; \
343     } while(0)
344 
345 #define ANVIL_REMOTE_INCR_RATE(remote, _what) \
346     do { \
347 	time_t _now = event_time(); \
348 	if ((remote)->start + var_anvil_time_unit < _now) \
349 	    ANVIL_REMOTE_RSET_RATE((remote), _now); \
350 	if ((remote)->_what < INT_MAX) \
351             (remote)->_what += 1; \
352     } while(0)
353 
354 /* Update existing (service, client) state. */
355 
356 #define ANVIL_REMOTE_NEXT_CONN(remote) \
357     do { \
358 	ANVIL_REMOTE_INCR_RATE((remote), rate); \
359 	if ((remote)->count == 0) \
360 	    event_cancel_timer(anvil_remote_expire, (char *) remote); \
361 	(remote)->count++; \
362     } while(0)
363 
364 #define ANVIL_REMOTE_INCR_MAIL(remote)	ANVIL_REMOTE_INCR_RATE((remote), mail)
365 
366 #define ANVIL_REMOTE_INCR_RCPT(remote)	ANVIL_REMOTE_INCR_RATE((remote), rcpt)
367 
368 #define ANVIL_REMOTE_INCR_NTLS(remote)	ANVIL_REMOTE_INCR_RATE((remote), ntls)
369 
370 /* Drop connection from (service, client) state. */
371 
372 #define ANVIL_REMOTE_DROP_ONE(remote) \
373     do { \
374 	if ((remote) && (remote)->count > 0) { \
375 	    if (--(remote)->count == 0) \
376 		event_request_timer(anvil_remote_expire, (char *) remote, \
377 			var_anvil_time_unit); \
378 	} \
379     } while(0)
380 
381 /* Create local server state. */
382 
383 #define ANVIL_LOCAL_INIT(local) \
384     do { \
385 	(local)->anvil_remote = 0; \
386     } while(0)
387 
388 /* Add remote connection to local server. */
389 
390 #define ANVIL_LOCAL_ADD_ONE(local, remote) \
391     do { \
392 	/* XXX allow multiple remote clients per local server. */ \
393 	if ((local)->anvil_remote) \
394 	    ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \
395 	(local)->anvil_remote = (remote); \
396     } while(0)
397 
398 /* Test if this remote connection is listed for this local server. */
399 
400 #define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \
401     ((local)->anvil_remote == (remote))
402 
403 /* Drop specific remote connection from local server. */
404 
405 #define ANVIL_LOCAL_DROP_ONE(local, remote) \
406     do { \
407 	/* XXX allow multiple remote clients per local server. */ \
408 	if ((local)->anvil_remote == (remote)) \
409 	    (local)->anvil_remote = 0; \
410     } while(0)
411 
412 /* Drop all remote connections from local server. */
413 
414 #define ANVIL_LOCAL_DROP_ALL(stream, local) \
415     do { \
416 	 /* XXX allow multiple remote clients per local server. */ \
417 	if ((local)->anvil_remote) \
418 	    anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \
419     } while (0)
420 
421  /*
422   * Lookup table to map request names to action routines.
423   */
424 typedef struct {
425     const char *name;
426     void    (*action) (VSTREAM *, const char *);
427 } ANVIL_REQ_TABLE;
428 
429  /*
430   * Run-time statistics for maximal connection counts and event rates. These
431   * store the peak resource usage, remote connection, and time. Absent a
432   * query interface, this information is logged at process exit time and at
433   * configurable intervals.
434   */
435 typedef struct {
436     int     value;			/* peak value */
437     char   *ident;			/* lookup key */
438     time_t  when;			/* time of peak value */
439 } ANVIL_MAX;
440 
441 static ANVIL_MAX max_conn_count;	/* peak connection count */
442 static ANVIL_MAX max_conn_rate;		/* peak connection rate */
443 static ANVIL_MAX max_mail_rate;		/* peak message rate */
444 static ANVIL_MAX max_rcpt_rate;		/* peak recipient rate */
445 static ANVIL_MAX max_ntls_rate;		/* peak new TLS session rate */
446 
447 static int max_cache_size;		/* peak cache size */
448 static time_t max_cache_time;		/* time of peak size */
449 
450 /* Update/report peak usage. */
451 
452 #define ANVIL_MAX_UPDATE(_max, _value, _ident) \
453     do { \
454 	_max.value = _value; \
455 	if (_max.ident == 0) { \
456 	    _max.ident = mystrdup(_ident); \
457 	} else if (!STREQ(_max.ident, _ident)) { \
458 	    myfree(_max.ident); \
459 	    _max.ident = mystrdup(_ident); \
460 	} \
461 	_max.when = event_time(); \
462     } while (0)
463 
464 #define ANVIL_MAX_RATE_REPORT(_max, _name) \
465     do { \
466 	if (_max.value > 0) { \
467 	    msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \
468 		_max.value, var_anvil_time_unit, \
469 		_max.ident, ctime(&_max.when) + 4); \
470 	    _max.value = 0; \
471 	} \
472     } while (0);
473 
474 #define ANVIL_MAX_COUNT_REPORT(_max, _name) \
475     do { \
476 	if (_max.value > 0) { \
477 	    msg_info("statistics: max " _name " count %d for (%s) at %.15s", \
478 		_max.value, _max.ident, ctime(&_max.when) + 4); \
479 	    _max.value = 0; \
480 	} \
481     } while (0);
482 
483  /*
484   * Silly little macros.
485   */
486 #define STR(x)			vstring_str(x)
487 #define STREQ(x,y)		(strcmp((x), (y)) == 0)
488 
489 /* anvil_remote_expire - purge expired connection state */
490 
491 static void anvil_remote_expire(int unused_event, char *context)
492 {
493     ANVIL_REMOTE *anvil_remote = (ANVIL_REMOTE *) context;
494     const char *myname = "anvil_remote_expire";
495 
496     if (msg_verbose)
497 	msg_info("%s %s", myname, anvil_remote->ident);
498 
499     if (anvil_remote->count != 0)
500 	msg_panic("%s: bad connection count: %d",
501 		  myname, anvil_remote->count);
502 
503     htable_delete(anvil_remote_map, anvil_remote->ident,
504 		  (void (*) (char *)) 0);
505     ANVIL_REMOTE_FREE(anvil_remote);
506 
507     if (msg_verbose)
508 	msg_info("%s: anvil_remote_map used=%d",
509 		 myname, anvil_remote_map->used);
510 }
511 
512 /* anvil_remote_lookup - dump address status */
513 
514 static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident)
515 {
516     ANVIL_REMOTE *anvil_remote;
517     const char *myname = "anvil_remote_lookup";
518 
519     if (msg_verbose)
520 	msg_info("%s fd=%d stream=0x%lx ident=%s",
521 		 myname, vstream_fileno(client_stream),
522 		 (unsigned long) client_stream, ident);
523 
524     /*
525      * Look up remote client information.
526      */
527     if ((anvil_remote =
528 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
529 	attr_print_plain(client_stream, ATTR_FLAG_NONE,
530 			 ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
531 			 ATTR_TYPE_INT, ANVIL_ATTR_COUNT, 0,
532 			 ATTR_TYPE_INT, ANVIL_ATTR_RATE, 0,
533 			 ATTR_TYPE_INT, ANVIL_ATTR_MAIL, 0,
534 			 ATTR_TYPE_INT, ANVIL_ATTR_RCPT, 0,
535 			 ATTR_TYPE_INT, ANVIL_ATTR_NTLS, 0,
536 			 ATTR_TYPE_END);
537     } else {
538 
539 	/*
540 	 * Do not report stale information.
541 	 */
542 	if (anvil_remote->start != 0
543 	    && anvil_remote->start + var_anvil_time_unit < event_time())
544 	    ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
545 	attr_print_plain(client_stream, ATTR_FLAG_NONE,
546 			 ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
547 		       ATTR_TYPE_INT, ANVIL_ATTR_COUNT, anvil_remote->count,
548 			 ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rate,
549 			 ATTR_TYPE_INT, ANVIL_ATTR_MAIL, anvil_remote->mail,
550 			 ATTR_TYPE_INT, ANVIL_ATTR_RCPT, anvil_remote->rcpt,
551 			 ATTR_TYPE_INT, ANVIL_ATTR_NTLS, anvil_remote->ntls,
552 			 ATTR_TYPE_END);
553     }
554 }
555 
556 /* anvil_remote_conn_update - instantiate or update connection info */
557 
558 static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char *ident)
559 {
560     ANVIL_REMOTE *anvil_remote;
561     ANVIL_LOCAL *anvil_local;
562     const char *myname = "anvil_remote_conn_update";
563 
564     if (msg_verbose)
565 	msg_info("%s fd=%d stream=0x%lx ident=%s",
566 		 myname, vstream_fileno(client_stream),
567 		 (unsigned long) client_stream, ident);
568 
569     /*
570      * Look up remote connection count information. Update remote connection
571      * rate information. Simply reset the counter every var_anvil_time_unit
572      * seconds. This is easier than maintaining a moving average and it gives
573      * a quicker response to tresspassers.
574      */
575     if ((anvil_remote =
576 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
577 	anvil_remote = (ANVIL_REMOTE *) mymalloc(sizeof(*anvil_remote));
578 	ANVIL_REMOTE_FIRST_CONN(anvil_remote, ident);
579 	htable_enter(anvil_remote_map, ident, (char *) anvil_remote);
580 	if (max_cache_size < anvil_remote_map->used) {
581 	    max_cache_size = anvil_remote_map->used;
582 	    max_cache_time = event_time();
583 	}
584     } else {
585 	ANVIL_REMOTE_NEXT_CONN(anvil_remote);
586     }
587 
588     /*
589      * Record this connection under the local server information, so that we
590      * can clean up all its connection state when the local server goes away.
591      */
592     if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) {
593 	anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local));
594 	ANVIL_LOCAL_INIT(anvil_local);
595 	vstream_control(client_stream,
596 			VSTREAM_CTL_CONTEXT, (void *) anvil_local,
597 			VSTREAM_CTL_END);
598     }
599     ANVIL_LOCAL_ADD_ONE(anvil_local, anvil_remote);
600     if (msg_verbose)
601 	msg_info("%s: anvil_local 0x%lx",
602 		 myname, (unsigned long) anvil_local);
603 
604     return (anvil_remote);
605 }
606 
607 /* anvil_remote_connect - report connection event, query address status */
608 
609 static void anvil_remote_connect(VSTREAM *client_stream, const char *ident)
610 {
611     ANVIL_REMOTE *anvil_remote;
612 
613     /*
614      * Update or instantiate connection info.
615      */
616     anvil_remote = anvil_remote_conn_update(client_stream, ident);
617 
618     /*
619      * Respond to the local server.
620      */
621     attr_print_plain(client_stream, ATTR_FLAG_NONE,
622 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
623 		     ATTR_TYPE_INT, ANVIL_ATTR_COUNT, anvil_remote->count,
624 		     ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rate,
625 		     ATTR_TYPE_END);
626 
627     /*
628      * Update peak statistics.
629      */
630     if (anvil_remote->rate > max_conn_rate.value)
631 	ANVIL_MAX_UPDATE(max_conn_rate, anvil_remote->rate, anvil_remote->ident);
632     if (anvil_remote->count > max_conn_count.value)
633 	ANVIL_MAX_UPDATE(max_conn_count, anvil_remote->count, anvil_remote->ident);
634 }
635 
636 /* anvil_remote_mail - register message delivery request */
637 
638 static void anvil_remote_mail(VSTREAM *client_stream, const char *ident)
639 {
640     ANVIL_REMOTE *anvil_remote;
641 
642     /*
643      * Be prepared for "postfix reload" after "connect".
644      */
645     if ((anvil_remote =
646 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
647 	anvil_remote = anvil_remote_conn_update(client_stream, ident);
648 
649     /*
650      * Update message delivery request rate and respond to local server.
651      */
652     ANVIL_REMOTE_INCR_MAIL(anvil_remote);
653     attr_print_plain(client_stream, ATTR_FLAG_NONE,
654 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
655 		     ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->mail,
656 		     ATTR_TYPE_END);
657 
658     /*
659      * Update peak statistics.
660      */
661     if (anvil_remote->mail > max_mail_rate.value)
662 	ANVIL_MAX_UPDATE(max_mail_rate, anvil_remote->mail, anvil_remote->ident);
663 }
664 
665 /* anvil_remote_rcpt - register recipient address event */
666 
667 static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident)
668 {
669     ANVIL_REMOTE *anvil_remote;
670 
671     /*
672      * Be prepared for "postfix reload" after "connect".
673      */
674     if ((anvil_remote =
675 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
676 	anvil_remote = anvil_remote_conn_update(client_stream, ident);
677 
678     /*
679      * Update recipient address rate and respond to local server.
680      */
681     ANVIL_REMOTE_INCR_RCPT(anvil_remote);
682     attr_print_plain(client_stream, ATTR_FLAG_NONE,
683 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
684 		     ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->rcpt,
685 		     ATTR_TYPE_END);
686 
687     /*
688      * Update peak statistics.
689      */
690     if (anvil_remote->rcpt > max_rcpt_rate.value)
691 	ANVIL_MAX_UPDATE(max_rcpt_rate, anvil_remote->rcpt, anvil_remote->ident);
692 }
693 
694 /* anvil_remote_newtls - register newtls event */
695 
696 static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident)
697 {
698     ANVIL_REMOTE *anvil_remote;
699 
700     /*
701      * Be prepared for "postfix reload" after "connect".
702      */
703     if ((anvil_remote =
704 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0)
705 	anvil_remote = anvil_remote_conn_update(client_stream, ident);
706 
707     /*
708      * Update newtls rate and respond to local server.
709      */
710     ANVIL_REMOTE_INCR_NTLS(anvil_remote);
711     attr_print_plain(client_stream, ATTR_FLAG_NONE,
712 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
713 		     ATTR_TYPE_INT, ANVIL_ATTR_RATE, anvil_remote->ntls,
714 		     ATTR_TYPE_END);
715 
716     /*
717      * Update peak statistics.
718      */
719     if (anvil_remote->ntls > max_ntls_rate.value)
720 	ANVIL_MAX_UPDATE(max_ntls_rate, anvil_remote->ntls, anvil_remote->ident);
721 }
722 
723 /* anvil_remote_newtls_stat - report newtls stats */
724 
725 static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident)
726 {
727     ANVIL_REMOTE *anvil_remote;
728     int     rate;
729 
730     /*
731      * Be prepared for "postfix reload" after "connect".
732      */
733     if ((anvil_remote =
734 	 (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) {
735 	rate = 0;
736     }
737 
738     /*
739      * Do not report stale information.
740      */
741     else {
742 	if (anvil_remote->start != 0
743 	    && anvil_remote->start + var_anvil_time_unit < event_time())
744 	    ANVIL_REMOTE_RSET_RATE(anvil_remote, 0);
745 	rate = anvil_remote->ntls;
746     }
747 
748     /*
749      * Respond to local server.
750      */
751     attr_print_plain(client_stream, ATTR_FLAG_NONE,
752 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
753 		     ATTR_TYPE_INT, ANVIL_ATTR_RATE, rate,
754 		     ATTR_TYPE_END);
755 }
756 
757 /* anvil_remote_disconnect - report disconnect event */
758 
759 static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident)
760 {
761     ANVIL_REMOTE *anvil_remote;
762     ANVIL_LOCAL *anvil_local;
763     const char *myname = "anvil_remote_disconnect";
764 
765     if (msg_verbose)
766 	msg_info("%s fd=%d stream=0x%lx ident=%s",
767 		 myname, vstream_fileno(client_stream),
768 		 (unsigned long) client_stream, ident);
769 
770     /*
771      * Update local and remote info if this remote connection is listed for
772      * this local server.
773      */
774     if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0
775 	&& (anvil_remote =
776 	    (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) != 0
777 	&& ANVIL_LOCAL_REMOTE_LINKED(anvil_local, anvil_remote)) {
778 	ANVIL_REMOTE_DROP_ONE(anvil_remote);
779 	ANVIL_LOCAL_DROP_ONE(anvil_local, anvil_remote);
780     }
781     if (msg_verbose)
782 	msg_info("%s: anvil_local 0x%lx",
783 		 myname, (unsigned long) anvil_local);
784 
785     /*
786      * Respond to the local server.
787      */
788     attr_print_plain(client_stream, ATTR_FLAG_NONE,
789 		     ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_OK,
790 		     ATTR_TYPE_END);
791 }
792 
793 /* anvil_service_done - clean up */
794 
795 static void anvil_service_done(VSTREAM *client_stream, char *unused_service,
796 			               char **unused_argv)
797 {
798     ANVIL_LOCAL *anvil_local;
799     const char *myname = "anvil_service_done";
800 
801     if (msg_verbose)
802 	msg_info("%s fd=%d stream=0x%lx",
803 		 myname, vstream_fileno(client_stream),
804 		 (unsigned long) client_stream);
805 
806     /*
807      * Look up the local server, and get rid of any remote connection state
808      * that we still have for this local server. Do not destroy remote client
809      * status information before it expires.
810      */
811     if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) {
812 	if (msg_verbose)
813 	    msg_info("%s: anvil_local 0x%lx",
814 		     myname, (unsigned long) anvil_local);
815 	ANVIL_LOCAL_DROP_ALL(client_stream, anvil_local);
816 	myfree((char *) anvil_local);
817     } else if (msg_verbose)
818 	msg_info("client socket not found for fd=%d",
819 		 vstream_fileno(client_stream));
820 }
821 
822 /* anvil_status_dump - log and reset extreme usage */
823 
824 static void anvil_status_dump(char *unused_name, char **unused_argv)
825 {
826     ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection");
827     ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection");
828     ANVIL_MAX_RATE_REPORT(max_mail_rate, "message");
829     ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient");
830     ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls");
831 
832     if (max_cache_size > 0) {
833 	msg_info("statistics: max cache size %d at %.15s",
834 		 max_cache_size, ctime(&max_cache_time) + 4);
835 	max_cache_size = 0;
836     }
837 }
838 
839 /* anvil_status_update - log and reset extreme usage periodically */
840 
841 static void anvil_status_update(int unused_event, char *context)
842 {
843     anvil_status_dump((char *) 0, (char **) 0);
844     event_request_timer(anvil_status_update, context, var_anvil_stat_time);
845 }
846 
847 /* anvil_service - perform service for client */
848 
849 static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv)
850 {
851     static VSTRING *request;
852     static VSTRING *ident;
853     static const ANVIL_REQ_TABLE request_table[] = {
854 	ANVIL_REQ_CONN, anvil_remote_connect,
855 	ANVIL_REQ_MAIL, anvil_remote_mail,
856 	ANVIL_REQ_RCPT, anvil_remote_rcpt,
857 	ANVIL_REQ_NTLS, anvil_remote_newtls,
858 	ANVIL_REQ_DISC, anvil_remote_disconnect,
859 	ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat,
860 	ANVIL_REQ_LOOKUP, anvil_remote_lookup,
861 	0, 0,
862     };
863     const ANVIL_REQ_TABLE *rp;
864 
865     /*
866      * Sanity check. This service takes no command-line arguments.
867      */
868     if (argv[0])
869 	msg_fatal("unexpected command-line argument: %s", argv[0]);
870 
871     /*
872      * Initialize.
873      */
874     if (request == 0) {
875 	request = vstring_alloc(10);
876 	ident = vstring_alloc(10);
877     }
878 
879     /*
880      * This routine runs whenever a client connects to the socket dedicated
881      * to the client connection rate management service. All
882      * connection-management stuff is handled by the common code in
883      * multi_server.c.
884      */
885     if (msg_verbose)
886 	msg_info("--- start request ---");
887     if (attr_scan_plain(client_stream,
888 			ATTR_FLAG_MISSING | ATTR_FLAG_STRICT,
889 			ATTR_TYPE_STR, ANVIL_ATTR_REQ, request,
890 			ATTR_TYPE_STR, ANVIL_ATTR_IDENT, ident,
891 			ATTR_TYPE_END) == 2) {
892 	for (rp = request_table; /* see below */ ; rp++) {
893 	    if (rp->name == 0) {
894 		msg_warn("unrecognized request: \"%s\", ignored", STR(request));
895 		attr_print_plain(client_stream, ATTR_FLAG_NONE,
896 			  ATTR_TYPE_INT, ANVIL_ATTR_STATUS, ANVIL_STAT_FAIL,
897 				 ATTR_TYPE_END);
898 		break;
899 	    }
900 	    if (STREQ(rp->name, STR(request))) {
901 		rp->action(client_stream, STR(ident));
902 		break;
903 	    }
904 	}
905 	vstream_fflush(client_stream);
906     } else {
907 	/* Note: invokes anvil_service_done() */
908 	multi_server_disconnect(client_stream);
909     }
910     if (msg_verbose)
911 	msg_info("--- end request ---");
912 }
913 
914 /* post_jail_init - post-jail initialization */
915 
916 static void post_jail_init(char *unused_name, char **unused_argv)
917 {
918 
919     /*
920      * Dump and reset extreme usage every so often.
921      */
922     event_request_timer(anvil_status_update, (char *) 0, var_anvil_stat_time);
923 
924     /*
925      * Initial client state tables.
926      */
927     anvil_remote_map = htable_create(1000);
928 
929     /*
930      * Do not limit the number of client requests.
931      */
932     var_use_limit = 0;
933 
934     /*
935      * Don't exit before the sampling interval ends.
936      */
937     if (var_idle_limit < var_anvil_time_unit)
938 	var_idle_limit = var_anvil_time_unit;
939 }
940 
941 MAIL_VERSION_STAMP_DECLARE;
942 
943 /* main - pass control to the multi-threaded skeleton */
944 
945 int     main(int argc, char **argv)
946 {
947     static const CONFIG_TIME_TABLE time_table[] = {
948 	VAR_ANVIL_TIME_UNIT, DEF_ANVIL_TIME_UNIT, &var_anvil_time_unit, 1, 0,
949 	VAR_ANVIL_STAT_TIME, DEF_ANVIL_STAT_TIME, &var_anvil_stat_time, 1, 0,
950 	0,
951     };
952 
953     /*
954      * Fingerprint executables and core dumps.
955      */
956     MAIL_VERSION_STAMP_ALLOCATE;
957 
958     multi_server_main(argc, argv, anvil_service,
959 		      MAIL_SERVER_TIME_TABLE, time_table,
960 		      MAIL_SERVER_POST_INIT, post_jail_init,
961 		      MAIL_SERVER_SOLITARY,
962 		      MAIL_SERVER_PRE_DISCONN, anvil_service_done,
963 		      MAIL_SERVER_EXIT, anvil_status_dump,
964 		      0);
965 }
966