diff options
Diffstat (limited to 'src/anvil')
l--------- | src/anvil/.indent.pro | 1 | ||||
-rw-r--r-- | src/anvil/.printfck | 25 | ||||
-rw-r--r-- | src/anvil/Makefile.in | 82 | ||||
-rw-r--r-- | src/anvil/anvil.c | 1047 |
4 files changed, 1155 insertions, 0 deletions
diff --git a/src/anvil/.indent.pro b/src/anvil/.indent.pro new file mode 120000 index 0000000..5c837ec --- /dev/null +++ b/src/anvil/.indent.pro @@ -0,0 +1 @@ +../../.indent.pro
\ No newline at end of file diff --git a/src/anvil/.printfck b/src/anvil/.printfck new file mode 100644 index 0000000..66016ed --- /dev/null +++ b/src/anvil/.printfck @@ -0,0 +1,25 @@ +been_here_xt 2 0 +bounce_append 5 0 +cleanup_out_format 1 0 +defer_append 5 0 +mail_command 1 0 +mail_print 1 0 +msg_error 0 0 +msg_fatal 0 0 +msg_info 0 0 +msg_panic 0 0 +msg_warn 0 0 +opened 4 0 +post_mail_fprintf 1 0 +qmgr_message_bounce 2 0 +rec_fprintf 2 0 +sent 4 0 +smtp_cmd 1 0 +smtp_mesg_fail 2 0 +smtp_printf 1 0 +smtp_rcpt_fail 3 0 +smtp_site_fail 2 0 +udp_syslog 1 0 +vstream_fprintf 1 0 +vstream_printf 0 0 +vstring_sprintf 1 0 diff --git a/src/anvil/Makefile.in b/src/anvil/Makefile.in new file mode 100644 index 0000000..2caa0a9 --- /dev/null +++ b/src/anvil/Makefile.in @@ -0,0 +1,82 @@ +SHELL = /bin/sh +SRCS = anvil.c +OBJS = anvil.o +HDRS = +TESTSRC = +DEFS = -I. -I$(INC_DIR) -D$(SYSTYPE) +CFLAGS = $(DEBUG) $(OPT) $(DEFS) +TESTPROG= +PROG = anvil +INC_DIR = ../../include +LIBS = ../../lib/lib$(LIB_PREFIX)master$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)global$(LIB_SUFFIX) \ + ../../lib/lib$(LIB_PREFIX)util$(LIB_SUFFIX) + +.c.o:; $(CC) $(CFLAGS) -c $*.c + +$(PROG): $(OBJS) $(LIBS) + $(CC) $(CFLAGS) $(SHLIB_RPATH) -o $@ $(OBJS) $(LIBS) $(SYSLIBS) + +$(OBJS): ../../conf/makedefs.out + +Makefile: Makefile.in + cat ../../conf/makedefs.out $? >$@ + +test: $(TESTPROG) + +tests: + +root_tests: + +update: ../../libexec/$(PROG) + +../../libexec/$(PROG): $(PROG) + cp $(PROG) ../../libexec + +printfck: $(OBJS) $(PROG) + rm -rf printfck + mkdir printfck + sed '1,/^# do not edit/!d' Makefile >printfck/Makefile + set -e; for i in *.c; do printfck -f .printfck $$i >printfck/$$i; done + cd printfck; make "INC_DIR=../../../include" `cd ..; ls *.o` + +lint: + lint $(DEFS) $(SRCS) $(LINTFIX) + +clean: + rm -f *.o *core $(PROG) $(TESTPROG) junk + rm -rf printfck + +tidy: clean + +depend: $(MAKES) + (sed '1,/^# do not edit/!d' Makefile.in; \ + set -e; for i in [a-z][a-z0-9]*.c; do \ + $(CC) -E $(DEFS) $(INCL) $$i | grep -v '[<>]' | sed -n -e '/^# *1 *"\([^"]*\)".*/{' \ + -e 's//'`echo $$i|sed 's/c$$/o/'`': \1/' \ + -e 's/o: \.\//o: /' -e p -e '}' ; \ + done | LANG=C sort -u) | grep -v '[.][o][:][ ][/]' >$$$$ && mv $$$$ Makefile.in + @$(EXPORT) make -f Makefile.in Makefile 1>&2 + +# do not edit below this line - it is generated by 'make depend' +anvil.o: ../../include/anvil_clnt.h +anvil.o: ../../include/attr.h +anvil.o: ../../include/attr_clnt.h +anvil.o: ../../include/check_arg.h +anvil.o: ../../include/events.h +anvil.o: ../../include/htable.h +anvil.o: ../../include/iostuff.h +anvil.o: ../../include/mail_conf.h +anvil.o: ../../include/mail_params.h +anvil.o: ../../include/mail_proto.h +anvil.o: ../../include/mail_server.h +anvil.o: ../../include/mail_version.h +anvil.o: ../../include/msg.h +anvil.o: ../../include/mymalloc.h +anvil.o: ../../include/nvtable.h +anvil.o: ../../include/stringops.h +anvil.o: ../../include/sys_defs.h +anvil.o: ../../include/vbuf.h +anvil.o: ../../include/vstream.h +anvil.o: ../../include/vstring.h +anvil.o: anvil.c diff --git a/src/anvil/anvil.c b/src/anvil/anvil.c new file mode 100644 index 0000000..884be28 --- /dev/null +++ b/src/anvil/anvil.c @@ -0,0 +1,1047 @@ +/*++ +/* NAME +/* anvil 8 +/* SUMMARY +/* Postfix session count and request rate control +/* SYNOPSIS +/* \fBanvil\fR [generic Postfix daemon options] +/* DESCRIPTION +/* The Postfix \fBanvil\fR(8) server maintains statistics about +/* client connection counts or client request rates. This +/* information can be used to defend against clients that +/* hammer a server with either too many simultaneous sessions, +/* or with too many successive requests within a configurable +/* time interval. This server is designed to run under control +/* by the Postfix \fBmaster\fR(8) server. +/* +/* In the following text, \fBident\fR specifies a (service, +/* client) combination. The exact syntax of that information +/* is application-dependent; the \fBanvil\fR(8) server does +/* not care. +/* CONNECTION COUNT/RATE CONTROL +/* .ad +/* .fi +/* To register a new connection send the following request to +/* the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=connect\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of +/* simultaneous connections and the number of connections per +/* unit time for the (service, client) combination specified +/* with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBcount=\fInumber\fR +/* \fBrate=\fInumber\fR +/* .fi +/* +/* To register a disconnect event send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=disconnect\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server replies with: +/* +/* .nf +/* \fBstatus=0\fR +/* .fi +/* MESSAGE RATE CONTROL +/* .ad +/* .fi +/* To register a message delivery request send the following +/* request to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=message\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of message +/* delivery requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* RECIPIENT RATE CONTROL +/* .ad +/* .fi +/* To register a recipient request send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=recipient\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of recipient +/* addresses per unit time for the (service, client) combination +/* specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* TLS SESSION NEGOTIATION RATE CONTROL +/* .ad +/* .fi +/* The features described in this section are available with +/* Postfix 2.3 and later. +/* +/* To register a request for a new (i.e. not cached) TLS session +/* send the following request to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=newtls\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of new +/* TLS session requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* +/* To retrieve new TLS session request rate information without +/* updating the counter information, send: +/* +/* .nf +/* \fBrequest=newtls_report\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of new +/* TLS session requests per unit time for the (service, client) +/* combination specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* AUTH RATE CONTROL +/* .ad +/* .fi +/* To register an AUTH request send the following request +/* to the \fBanvil\fR(8) server: +/* +/* .nf +/* \fBrequest=auth\fR +/* \fBident=\fIstring\fR +/* .fi +/* +/* The \fBanvil\fR(8) server answers with the number of auth +/* requests per unit time for the (service, client) combination +/* specified with \fBident\fR: +/* +/* .nf +/* \fBstatus=0\fR +/* \fBrate=\fInumber\fR +/* .fi +/* SECURITY +/* .ad +/* .fi +/* The \fBanvil\fR(8) server does not talk to the network or to local +/* users, and can run chrooted at fixed low privilege. +/* +/* The \fBanvil\fR(8) server maintains an in-memory table with +/* information about recent clients requests. No persistent +/* state is kept because standard system library routines are +/* not sufficiently robust for update-intensive applications. +/* +/* Although the in-memory state is kept only temporarily, this +/* may require a lot of memory on systems that handle connections +/* from many remote clients. To reduce memory usage, reduce +/* the time unit over which state is kept. +/* DIAGNOSTICS +/* Problems and transactions are logged to \fBsyslogd\fR(8) +/* or \fBpostlogd\fR(8). +/* +/* Upon exit, and every \fBanvil_status_update_time\fR +/* seconds, the server logs the maximal count and rate values measured, +/* together with (service, client) information and the time of day +/* associated with those events. +/* In order to avoid unnecessary overhead, no measurements +/* are done for activity that isn't concurrency limited or +/* rate limited. +/* BUGS +/* Systems behind network address translating routers or proxies +/* appear to have the same client address and can run into connection +/* count and/or rate limits falsely. +/* +/* In this preliminary implementation, a count (or rate) limited server +/* process can have only one remote client at a time. If a +/* server process reports +/* multiple simultaneous clients, state is kept only for the last +/* reported client. +/* +/* The \fBanvil\fR(8) server automatically discards client +/* request information after it expires. To prevent the +/* \fBanvil\fR(8) server from discarding client request rate +/* information too early or too late, a rate limited service +/* should always register connect/disconnect events even when +/* it does not explicitly limit them. +/* CONFIGURATION PARAMETERS +/* .ad +/* .fi +/* On low-traffic mail systems, changes to \fBmain.cf\fR are +/* picked up automatically as \fBanvil\fR(8) processes run for +/* only a limited amount of time. On other mail systems, use +/* the command "\fBpostfix reload\fR" to speed up a change. +/* +/* The text below provides only a parameter summary. See +/* \fBpostconf\fR(5) for more details including examples. +/* .IP "\fBanvil_rate_time_unit (60s)\fR" +/* The time unit over which client connection rates and other rates +/* are calculated. +/* .IP "\fBanvil_status_update_time (600s)\fR" +/* How frequently the \fBanvil\fR(8) connection and rate limiting server +/* logs peak usage information. +/* .IP "\fBconfig_directory (see 'postconf -d' output)\fR" +/* The default location of the Postfix main.cf and master.cf +/* configuration files. +/* .IP "\fBdaemon_timeout (18000s)\fR" +/* How much time a Postfix daemon process may take to handle a +/* request before it is terminated by a built-in watchdog timer. +/* .IP "\fBipc_timeout (3600s)\fR" +/* The time limit for sending or receiving information over an internal +/* communication channel. +/* .IP "\fBmax_idle (100s)\fR" +/* The maximum amount of time that an idle Postfix daemon process waits +/* for an incoming connection before terminating voluntarily. +/* .IP "\fBmax_use (100)\fR" +/* The maximal number of incoming connections that a Postfix daemon +/* process will service before terminating voluntarily. +/* .IP "\fBprocess_id (read-only)\fR" +/* The process ID of a Postfix command or daemon process. +/* .IP "\fBprocess_name (read-only)\fR" +/* The process name of a Postfix command or daemon process. +/* .IP "\fBsyslog_facility (mail)\fR" +/* The syslog facility of Postfix logging. +/* .IP "\fBsyslog_name (see 'postconf -d' output)\fR" +/* A prefix that is prepended to the process name in syslog +/* records, so that, for example, "smtpd" becomes "prefix/smtpd". +/* .PP +/* Available in Postfix 3.3 and later: +/* .IP "\fBservice_name (read-only)\fR" +/* The master.cf service name of a Postfix daemon process. +/* SEE ALSO +/* smtpd(8), Postfix SMTP server +/* postconf(5), configuration parameters +/* master(5), generic daemon options +/* README FILES +/* .ad +/* .fi +/* Use "\fBpostconf readme_directory\fR" or +/* "\fBpostconf html_directory\fR" to locate this information. +/* .na +/* .nf +/* TUNING_README, performance tuning +/* LICENSE +/* .ad +/* .fi +/* The Secure Mailer license must be distributed with this software. +/* HISTORY +/* .ad +/* .fi +/* The anvil service is available in Postfix 2.2 and later. +/* AUTHOR(S) +/* Wietse Venema +/* IBM T.J. Watson Research +/* P.O. Box 704 +/* Yorktown Heights, NY 10598, USA +/* +/* Wietse Venema +/* Google, Inc. +/* 111 8th Avenue +/* New York, NY 10011, USA +/*--*/ + +/* System library. */ + +#include <sys_defs.h> +#include <sys/time.h> +#include <limits.h> + +/* Utility library. */ + +#include <msg.h> +#include <mymalloc.h> +#include <htable.h> +#include <stringops.h> +#include <events.h> + +/* Global library. */ + +#include <mail_conf.h> +#include <mail_params.h> +#include <mail_version.h> +#include <mail_proto.h> +#include <anvil_clnt.h> + +/* Server skeleton. */ + +#include <mail_server.h> + +/* Application-specific. */ + + /* + * Configuration parameters. + */ +int var_anvil_time_unit; +int var_anvil_stat_time; + + /* + * Global dynamic state. + */ +static HTABLE *anvil_remote_map; /* indexed by service+ remote client */ + + /* + * Remote connection state, one instance for each (service, client) pair. + */ +typedef struct { + char *ident; /* lookup key */ + int count; /* connection count */ + int rate; /* connection rate */ + int mail; /* message rate */ + int rcpt; /* recipient rate */ + int ntls; /* new TLS session rate */ + int auth; /* AUTH request rate */ + time_t start; /* time of first rate sample */ +} ANVIL_REMOTE; + + /* + * Local server state, one instance per anvil client connection. This allows + * us to clean up remote connection state when a local server goes away + * without cleaning up. + */ +typedef struct { + ANVIL_REMOTE *anvil_remote; /* XXX should be list */ +} ANVIL_LOCAL; + + /* + * The following operations are implemented as macros with recognizable + * names so that we don't lose sight of what the code is trying to do. + * + * Related operations are defined side by side so that the code implementing + * them isn't pages apart. + */ + +/* Create new (service, client) state. */ + +#define ANVIL_REMOTE_FIRST_CONN(remote, id) \ + do { \ + (remote)->ident = mystrdup(id); \ + (remote)->count = 1; \ + (remote)->rate = 1; \ + (remote)->mail = 0; \ + (remote)->rcpt = 0; \ + (remote)->ntls = 0; \ + (remote)->auth = 0; \ + (remote)->start = event_time(); \ + } while(0) + +/* Destroy unused (service, client) state. */ + +#define ANVIL_REMOTE_FREE(remote) \ + do { \ + myfree((remote)->ident); \ + myfree((void *) (remote)); \ + } while(0) + +/* Reset or update rate information for existing (service, client) state. */ + +#define ANVIL_REMOTE_RSET_RATE(remote, _start) \ + do { \ + (remote)->rate = 0; \ + (remote)->mail = 0; \ + (remote)->rcpt = 0; \ + (remote)->ntls = 0; \ + (remote)->auth = 0; \ + (remote)->start = _start; \ + } while(0) + +#define ANVIL_REMOTE_INCR_RATE(remote, _what) \ + do { \ + time_t _now = event_time(); \ + if ((remote)->start + var_anvil_time_unit < _now) \ + ANVIL_REMOTE_RSET_RATE((remote), _now); \ + if ((remote)->_what < INT_MAX) \ + (remote)->_what += 1; \ + } while(0) + +/* Update existing (service, client) state. */ + +#define ANVIL_REMOTE_NEXT_CONN(remote) \ + do { \ + ANVIL_REMOTE_INCR_RATE((remote), rate); \ + if ((remote)->count == 0) \ + event_cancel_timer(anvil_remote_expire, (void *) remote); \ + (remote)->count++; \ + } while(0) + +#define ANVIL_REMOTE_INCR_MAIL(remote) ANVIL_REMOTE_INCR_RATE((remote), mail) + +#define ANVIL_REMOTE_INCR_RCPT(remote) ANVIL_REMOTE_INCR_RATE((remote), rcpt) + +#define ANVIL_REMOTE_INCR_NTLS(remote) ANVIL_REMOTE_INCR_RATE((remote), ntls) + +#define ANVIL_REMOTE_INCR_AUTH(remote) ANVIL_REMOTE_INCR_RATE((remote), auth) + +/* Drop connection from (service, client) state. */ + +#define ANVIL_REMOTE_DROP_ONE(remote) \ + do { \ + if ((remote) && (remote)->count > 0) { \ + if (--(remote)->count == 0) \ + event_request_timer(anvil_remote_expire, (void *) remote, \ + var_anvil_time_unit); \ + } \ + } while(0) + +/* Create local server state. */ + +#define ANVIL_LOCAL_INIT(local) \ + do { \ + (local)->anvil_remote = 0; \ + } while(0) + +/* Add remote connection to local server. */ + +#define ANVIL_LOCAL_ADD_ONE(local, remote) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote) \ + ANVIL_REMOTE_DROP_ONE((local)->anvil_remote); \ + (local)->anvil_remote = (remote); \ + } while(0) + +/* Test if this remote connection is listed for this local server. */ + +#define ANVIL_LOCAL_REMOTE_LINKED(local, remote) \ + ((local)->anvil_remote == (remote)) + +/* Drop specific remote connection from local server. */ + +#define ANVIL_LOCAL_DROP_ONE(local, remote) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote == (remote)) \ + (local)->anvil_remote = 0; \ + } while(0) + +/* Drop all remote connections from local server. */ + +#define ANVIL_LOCAL_DROP_ALL(stream, local) \ + do { \ + /* XXX allow multiple remote clients per local server. */ \ + if ((local)->anvil_remote) \ + anvil_remote_disconnect((stream), (local)->anvil_remote->ident); \ + } while (0) + + /* + * Lookup table to map request names to action routines. + */ +typedef struct { + const char *name; + void (*action) (VSTREAM *, const char *); +} ANVIL_REQ_TABLE; + + /* + * Run-time statistics for maximal connection counts and event rates. These + * store the peak resource usage, remote connection, and time. Absent a + * query interface, this information is logged at process exit time and at + * configurable intervals. + */ +typedef struct { + int value; /* peak value */ + char *ident; /* lookup key */ + time_t when; /* time of peak value */ +} ANVIL_MAX; + +static ANVIL_MAX max_conn_count; /* peak connection count */ +static ANVIL_MAX max_conn_rate; /* peak connection rate */ +static ANVIL_MAX max_mail_rate; /* peak message rate */ +static ANVIL_MAX max_rcpt_rate; /* peak recipient rate */ +static ANVIL_MAX max_ntls_rate; /* peak new TLS session rate */ +static ANVIL_MAX max_auth_rate; /* peak AUTH request rate */ + +static int max_cache_size; /* peak cache size */ +static time_t max_cache_time; /* time of peak size */ + +/* Update/report peak usage. */ + +#define ANVIL_MAX_UPDATE(_max, _value, _ident) \ + do { \ + _max.value = _value; \ + if (_max.ident == 0) { \ + _max.ident = mystrdup(_ident); \ + } else if (!STREQ(_max.ident, _ident)) { \ + myfree(_max.ident); \ + _max.ident = mystrdup(_ident); \ + } \ + _max.when = event_time(); \ + } while (0) + +#define ANVIL_MAX_RATE_REPORT(_max, _name) \ + do { \ + if (_max.value > 0) { \ + msg_info("statistics: max " _name " rate %d/%ds for (%s) at %.15s", \ + _max.value, var_anvil_time_unit, \ + _max.ident, ctime(&_max.when) + 4); \ + _max.value = 0; \ + } \ + } while (0); + +#define ANVIL_MAX_COUNT_REPORT(_max, _name) \ + do { \ + if (_max.value > 0) { \ + msg_info("statistics: max " _name " count %d for (%s) at %.15s", \ + _max.value, _max.ident, ctime(&_max.when) + 4); \ + _max.value = 0; \ + } \ + } while (0); + + /* + * Silly little macros. + */ +#define STR(x) vstring_str(x) +#define STREQ(x,y) (strcmp((x), (y)) == 0) + +/* anvil_remote_expire - purge expired connection state */ + +static void anvil_remote_expire(int unused_event, void *context) +{ + ANVIL_REMOTE *anvil_remote = (ANVIL_REMOTE *) context; + const char *myname = "anvil_remote_expire"; + + if (msg_verbose) + msg_info("%s %s", myname, anvil_remote->ident); + + if (anvil_remote->count != 0) + msg_panic("%s: bad connection count: %d", + myname, anvil_remote->count); + + htable_delete(anvil_remote_map, anvil_remote->ident, + (void (*) (void *)) 0); + ANVIL_REMOTE_FREE(anvil_remote); + + if (msg_verbose) + msg_info("%s: anvil_remote_map used=%ld", + myname, (long) anvil_remote_map->used); +} + +/* anvil_remote_lookup - dump address status */ + +static void anvil_remote_lookup(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + const char *myname = "anvil_remote_lookup"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Look up remote client information. + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, 0), + SEND_ATTR_INT(ANVIL_ATTR_RATE, 0), + SEND_ATTR_INT(ANVIL_ATTR_MAIL, 0), + SEND_ATTR_INT(ANVIL_ATTR_RCPT, 0), + SEND_ATTR_INT(ANVIL_ATTR_NTLS, 0), + SEND_ATTR_INT(ANVIL_ATTR_AUTH, 0), + ATTR_TYPE_END); + } else { + + /* + * Do not report stale information. + */ + if (anvil_remote->start != 0 + && anvil_remote->start + var_anvil_time_unit < event_time()) + ANVIL_REMOTE_RSET_RATE(anvil_remote, 0); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate), + SEND_ATTR_INT(ANVIL_ATTR_MAIL, anvil_remote->mail), + SEND_ATTR_INT(ANVIL_ATTR_RCPT, anvil_remote->rcpt), + SEND_ATTR_INT(ANVIL_ATTR_NTLS, anvil_remote->ntls), + SEND_ATTR_INT(ANVIL_ATTR_AUTH, anvil_remote->auth), + ATTR_TYPE_END); + } +} + +/* anvil_remote_conn_update - instantiate or update connection info */ + +static ANVIL_REMOTE *anvil_remote_conn_update(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_remote_conn_update"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Look up remote connection count information. Update remote connection + * rate information. Simply reset the counter every var_anvil_time_unit + * seconds. This is easier than maintaining a moving average and it gives + * a quicker response to tresspassers. + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + anvil_remote = (ANVIL_REMOTE *) mymalloc(sizeof(*anvil_remote)); + ANVIL_REMOTE_FIRST_CONN(anvil_remote, ident); + htable_enter(anvil_remote_map, ident, (void *) anvil_remote); + if (max_cache_size < anvil_remote_map->used) { + max_cache_size = anvil_remote_map->used; + max_cache_time = event_time(); + } + } else { + ANVIL_REMOTE_NEXT_CONN(anvil_remote); + } + + /* + * Record this connection under the local server information, so that we + * can clean up all its connection state when the local server goes away. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) == 0) { + anvil_local = (ANVIL_LOCAL *) mymalloc(sizeof(*anvil_local)); + ANVIL_LOCAL_INIT(anvil_local); + vstream_control(client_stream, + CA_VSTREAM_CTL_CONTEXT((void *) anvil_local), + CA_VSTREAM_CTL_END); + } + ANVIL_LOCAL_ADD_ONE(anvil_local, anvil_remote); + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + + return (anvil_remote); +} + +/* anvil_remote_connect - report connection event, query address status */ + +static void anvil_remote_connect(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Update or instantiate connection info. + */ + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Respond to the local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_COUNT, anvil_remote->count), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rate), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->rate > max_conn_rate.value) + ANVIL_MAX_UPDATE(max_conn_rate, anvil_remote->rate, anvil_remote->ident); + if (anvil_remote->count > max_conn_count.value) + ANVIL_MAX_UPDATE(max_conn_count, anvil_remote->count, anvil_remote->ident); +} + +/* anvil_remote_mail - register message delivery request */ + +static void anvil_remote_mail(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update message delivery request rate and respond to local server. + */ + ANVIL_REMOTE_INCR_MAIL(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->mail), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->mail > max_mail_rate.value) + ANVIL_MAX_UPDATE(max_mail_rate, anvil_remote->mail, anvil_remote->ident); +} + +/* anvil_remote_rcpt - register recipient address event */ + +static void anvil_remote_rcpt(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update recipient address rate and respond to local server. + */ + ANVIL_REMOTE_INCR_RCPT(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->rcpt), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->rcpt > max_rcpt_rate.value) + ANVIL_MAX_UPDATE(max_rcpt_rate, anvil_remote->rcpt, anvil_remote->ident); +} + +/* anvil_remote_auth - register auth request event */ + +static void anvil_remote_auth(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update recipient address rate and respond to local server. + */ + ANVIL_REMOTE_INCR_AUTH(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->auth), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->auth > max_auth_rate.value) + ANVIL_MAX_UPDATE(max_auth_rate, anvil_remote->auth, anvil_remote->ident); +} + +/* anvil_remote_newtls - register newtls event */ + +static void anvil_remote_newtls(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) + anvil_remote = anvil_remote_conn_update(client_stream, ident); + + /* + * Update newtls rate and respond to local server. + */ + ANVIL_REMOTE_INCR_NTLS(anvil_remote); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, anvil_remote->ntls), + ATTR_TYPE_END); + + /* + * Update peak statistics. + */ + if (anvil_remote->ntls > max_ntls_rate.value) + ANVIL_MAX_UPDATE(max_ntls_rate, anvil_remote->ntls, anvil_remote->ident); +} + +/* anvil_remote_newtls_stat - report newtls stats */ + +static void anvil_remote_newtls_stat(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + int rate; + + /* + * Be prepared for "postfix reload" after "connect". + */ + if ((anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) == 0) { + rate = 0; + } + + /* + * Do not report stale information. + */ + else { + if (anvil_remote->start != 0 + && anvil_remote->start + var_anvil_time_unit < event_time()) + ANVIL_REMOTE_RSET_RATE(anvil_remote, 0); + rate = anvil_remote->ntls; + } + + /* + * Respond to local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + SEND_ATTR_INT(ANVIL_ATTR_RATE, rate), + ATTR_TYPE_END); +} + +/* anvil_remote_disconnect - report disconnect event */ + +static void anvil_remote_disconnect(VSTREAM *client_stream, const char *ident) +{ + ANVIL_REMOTE *anvil_remote; + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_remote_disconnect"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx ident=%s", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream, ident); + + /* + * Update local and remote info if this remote connection is listed for + * this local server. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0 + && (anvil_remote = + (ANVIL_REMOTE *) htable_find(anvil_remote_map, ident)) != 0 + && ANVIL_LOCAL_REMOTE_LINKED(anvil_local, anvil_remote)) { + ANVIL_REMOTE_DROP_ONE(anvil_remote); + ANVIL_LOCAL_DROP_ONE(anvil_local, anvil_remote); + } + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + + /* + * Respond to the local server. + */ + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_OK), + ATTR_TYPE_END); +} + +/* anvil_service_done - clean up */ + +static void anvil_service_done(VSTREAM *client_stream, char *unused_service, + char **unused_argv) +{ + ANVIL_LOCAL *anvil_local; + const char *myname = "anvil_service_done"; + + if (msg_verbose) + msg_info("%s fd=%d stream=0x%lx", + myname, vstream_fileno(client_stream), + (unsigned long) client_stream); + + /* + * Look up the local server, and get rid of any remote connection state + * that we still have for this local server. Do not destroy remote client + * status information before it expires. + */ + if ((anvil_local = (ANVIL_LOCAL *) vstream_context(client_stream)) != 0) { + if (msg_verbose) + msg_info("%s: anvil_local 0x%lx", + myname, (unsigned long) anvil_local); + ANVIL_LOCAL_DROP_ALL(client_stream, anvil_local); + myfree((void *) anvil_local); + } else if (msg_verbose) + msg_info("client socket not found for fd=%d", + vstream_fileno(client_stream)); +} + +/* anvil_status_dump - log and reset extreme usage */ + +static void anvil_status_dump(char *unused_name, char **unused_argv) +{ + ANVIL_MAX_RATE_REPORT(max_conn_rate, "connection"); + ANVIL_MAX_COUNT_REPORT(max_conn_count, "connection"); + ANVIL_MAX_RATE_REPORT(max_mail_rate, "message"); + ANVIL_MAX_RATE_REPORT(max_rcpt_rate, "recipient"); + ANVIL_MAX_RATE_REPORT(max_ntls_rate, "newtls"); + ANVIL_MAX_RATE_REPORT(max_auth_rate, "auth"); + + if (max_cache_size > 0) { + msg_info("statistics: max cache size %d at %.15s", + max_cache_size, ctime(&max_cache_time) + 4); + max_cache_size = 0; + } +} + +/* anvil_status_update - log and reset extreme usage periodically */ + +static void anvil_status_update(int unused_event, void *context) +{ + anvil_status_dump((char *) 0, (char **) 0); + event_request_timer(anvil_status_update, context, var_anvil_stat_time); +} + +/* anvil_service - perform service for client */ + +static void anvil_service(VSTREAM *client_stream, char *unused_service, char **argv) +{ + static VSTRING *request; + static VSTRING *ident; + static const ANVIL_REQ_TABLE request_table[] = { + ANVIL_REQ_CONN, anvil_remote_connect, + ANVIL_REQ_MAIL, anvil_remote_mail, + ANVIL_REQ_RCPT, anvil_remote_rcpt, + ANVIL_REQ_NTLS, anvil_remote_newtls, + ANVIL_REQ_DISC, anvil_remote_disconnect, + ANVIL_REQ_NTLS_STAT, anvil_remote_newtls_stat, + ANVIL_REQ_AUTH, anvil_remote_auth, + ANVIL_REQ_LOOKUP, anvil_remote_lookup, + 0, 0, + }; + const ANVIL_REQ_TABLE *rp; + + /* + * Sanity check. This service takes no command-line arguments. + */ + if (argv[0]) + msg_fatal("unexpected command-line argument: %s", argv[0]); + + /* + * Initialize. + */ + if (request == 0) { + request = vstring_alloc(10); + ident = vstring_alloc(10); + } + + /* + * This routine runs whenever a client connects to the socket dedicated + * to the client connection rate management service. All + * connection-management stuff is handled by the common code in + * multi_server.c. + */ + if (msg_verbose) + msg_info("--- start request ---"); + if (attr_scan_plain(client_stream, + ATTR_FLAG_MISSING | ATTR_FLAG_STRICT, + RECV_ATTR_STR(ANVIL_ATTR_REQ, request), + RECV_ATTR_STR(ANVIL_ATTR_IDENT, ident), + ATTR_TYPE_END) == 2) { + for (rp = request_table; /* see below */ ; rp++) { + if (rp->name == 0) { + msg_warn("unrecognized request: \"%s\", ignored", STR(request)); + attr_print_plain(client_stream, ATTR_FLAG_NONE, + SEND_ATTR_INT(ANVIL_ATTR_STATUS, ANVIL_STAT_FAIL), + ATTR_TYPE_END); + break; + } + if (STREQ(rp->name, STR(request))) { + rp->action(client_stream, STR(ident)); + break; + } + } + vstream_fflush(client_stream); + } else { + /* Note: invokes anvil_service_done() */ + multi_server_disconnect(client_stream); + } + if (msg_verbose) + msg_info("--- end request ---"); +} + +/* post_jail_init - post-jail initialization */ + +static void post_jail_init(char *unused_name, char **unused_argv) +{ + + /* + * Dump and reset extreme usage every so often. + */ + event_request_timer(anvil_status_update, (void *) 0, var_anvil_stat_time); + + /* + * Initial client state tables. + */ + anvil_remote_map = htable_create(1000); + + /* + * Do not limit the number of client requests. + */ + var_use_limit = 0; + + /* + * Don't exit before the sampling interval ends. + */ + if (var_idle_limit < var_anvil_time_unit) + var_idle_limit = var_anvil_time_unit; +} + +MAIL_VERSION_STAMP_DECLARE; + +/* post_accept - announce our protocol */ + +static void post_accept(VSTREAM *stream, char *unused_name, + char **unused_argv, HTABLE *unused_table) +{ + + /* + * Announce the protocol. + */ + attr_print_plain(stream, ATTR_FLAG_NONE, + SEND_ATTR_STR(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_ANVIL), + ATTR_TYPE_END); + (void) vstream_fflush(stream); +} + +/* main - pass control to the multi-threaded skeleton */ + +int main(int argc, char **argv) +{ + static const CONFIG_TIME_TABLE time_table[] = { + VAR_ANVIL_TIME_UNIT, DEF_ANVIL_TIME_UNIT, &var_anvil_time_unit, 1, 0, + VAR_ANVIL_STAT_TIME, DEF_ANVIL_STAT_TIME, &var_anvil_stat_time, 1, 0, + 0, + }; + + /* + * Fingerprint executables and core dumps. + */ + MAIL_VERSION_STAMP_ALLOCATE; + + multi_server_main(argc, argv, anvil_service, + CA_MAIL_SERVER_TIME_TABLE(time_table), + CA_MAIL_SERVER_POST_INIT(post_jail_init), + CA_MAIL_SERVER_POST_ACCEPT(post_accept), + CA_MAIL_SERVER_SOLITARY, + CA_MAIL_SERVER_PRE_DISCONN(anvil_service_done), + CA_MAIL_SERVER_EXIT(anvil_status_dump), + 0); +} |