20151017 Feature: smtpd_client_auth_rate_limit enforces a rate limit on the number of AUTH commands per client IP address. mantools/postlink, proto/postconf.proto, anvil/anvil.c, global/anvil_clnt.c, global/anvil_clnt.h, global/mail_params.h, smtpd/smtpd.c. diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/mantools/postlink ./mantools/postlink *** /var/tmp/postfix-3.1-20151011/mantools/postlink 2015-09-13 12:36:51.000000000 -0400 --- ./mantools/postlink 2015-10-17 11:20:14.000000000 -0400 *************** *** 521,526 **** --- 521,527 ---- s;\bsmtpd_autho[-]*\n*[ ]*rized_xclient_hosts\b;$&;g; s;\bsmtpd_autho[-]*\n*[ ]*rized_xforward_hosts\b;$&;g; s;\bsmtpd_ban[-]*\n*[ ]*ner\b;$&;g; + s;\bsmtpd_client_auth_rate_limit\b;$&;g; s;\bsmtpd_client_connec[-]*\n*[ ]*tion_count_limit\b;$&;g; s;\bsmtpd_client_event_limit_exceptions\b;$&;g; s;\bsmtpd_client_connec[-]*\n*[ ]*tion_rate_limit\b;$&;g; diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/proto/postconf.proto ./proto/postconf.proto *** /var/tmp/postfix-3.1-20151011/proto/postconf.proto 2015-10-10 09:36:31.000000000 -0400 --- ./proto/postconf.proto 2015-10-17 11:19:08.000000000 -0400 *************** *** 5022,5027 **** --- 5022,5054 ---- smtpd_client_new_tls_session_rate_limit = 100 + %PARAM smtpd_client_auth_rate_limit 0 + +

+ The maximal number of AUTH commands that any client is allowed to + send to this service per time unit, regardless of whether or not + Postfix actually accepts those commands. The time unit is specified + with the anvil_rate_time_unit configuration parameter. +

+ +

+ By default, there is no limit on the number AUTH commands that a + client may send. +

+ +

+ To disable this feature, specify a limit of 0. +

+ +

+ WARNING: The purpose of this feature is to limit abuse. It must + not be used to regulate legitimate mail traffic. +

+ +

+ This feature is available in Postfix 3.1 and later. +

+ %PARAM smtpd_client_restrictions

diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/src/anvil/anvil.c ./src/anvil/anvil.c *** /var/tmp/postfix-3.1-20151011/src/anvil/anvil.c 2014-12-25 11:47:18.000000000 -0500 --- ./src/anvil/anvil.c 2015-10-17 11:30:52.000000000 -0400 *************** *** 130,135 **** --- 130,154 ---- /* \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 *************** *** 288,293 **** --- 307,313 ---- 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; *************** *** 318,323 **** --- 338,344 ---- (remote)->mail = 0; \ (remote)->rcpt = 0; \ (remote)->ntls = 0; \ + (remote)->auth = 0; \ (remote)->start = event_time(); \ } while(0) *************** *** 337,342 **** --- 358,364 ---- (remote)->mail = 0; \ (remote)->rcpt = 0; \ (remote)->ntls = 0; \ + (remote)->auth = 0; \ (remote)->start = _start; \ } while(0) *************** *** 365,370 **** --- 387,394 ---- #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) \ *************** *** 441,446 **** --- 465,471 ---- 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 */ *************** *** 531,536 **** --- 556,562 ---- 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 { *************** *** 547,552 **** --- 573,579 ---- 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); } } *************** *** 689,694 **** --- 716,750 ---- 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) *************** *** 826,831 **** --- 882,888 ---- 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", *************** *** 855,860 **** --- 912,918 ---- 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, }; diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/src/global/anvil_clnt.c ./src/global/anvil_clnt.c *** /var/tmp/postfix-3.1-20151011/src/global/anvil_clnt.c 2014-12-16 20:10:36.000000000 -0500 --- ./src/global/anvil_clnt.c 2015-10-17 12:07:42.000000000 -0400 *************** *** 43,55 **** /* const char *addr; /* int *newtls; /* /* int anvil_clnt_disconnect(anvil_clnt, service, addr) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; /* ! /* int anvil_clnt_lookup(anvil_clnt, service, addr, ! /* count, rate, msgs, rcpts) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; --- 43,61 ---- /* const char *addr; /* int *newtls; /* + /* int anvil_clnt_auth(anvil_clnt, service, addr, auths) + /* ANVIL_CLNT *anvil_clnt; + /* const char *service; + /* const char *addr; + /* int *auths; + /* /* int anvil_clnt_disconnect(anvil_clnt, service, addr) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; /* ! /* int anvil_clnt_lookup(anvil_clnt, service, addr, count, ! /* rate, msgs, rcpts, ntls, auths) /* ANVIL_CLNT *anvil_clnt; /* const char *service; /* const char *addr; *************** *** 57,62 **** --- 63,70 ---- /* int *rate; /* int *msgs; /* int *rcpts; + /* int *ntls; + /* int *auths; /* DESCRIPTION /* anvil_clnt_create() instantiates a local anvil service /* client endpoint. *************** *** 80,85 **** --- 88,96 ---- /* anvil_clnt_newtls_stat() returns the current newtls request /* rate for the specified remote client. /* + /* anvil_clnt_auth() registers an AUTH event and returns the + /* current AUTH event rate for the specified remote client. + /* /* anvil_clnt_disconnect() informs the anvil server that a remote /* client has disconnected. /* *************** *** 111,116 **** --- 122,130 ---- /* .IP newtls /* Pointer to storage for the current "new TLS session" rate /* for this remote client. + /* .IP auths + /* Pointer to storage for the current AUTH event rate for this + /* remote client. /* DIAGNOSTICS /* The update and status query routines return /* ANVIL_STAT_OK in case of success, ANVIL_STAT_FAIL otherwise *************** *** 181,187 **** int anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service, const char *addr, int *count, int *rate, ! int *msgs, int *rcpts, int *newtls) { char *ident = ANVIL_IDENT(service, addr); int status; --- 195,201 ---- int anvil_clnt_lookup(ANVIL_CLNT *anvil_clnt, const char *service, const char *addr, int *count, int *rate, ! int *msgs, int *rcpts, int *newtls, int *auths) { char *ident = ANVIL_IDENT(service, addr); int status; *************** *** 198,204 **** RECV_ATTR_INT(ANVIL_ATTR_MAIL, msgs), RECV_ATTR_INT(ANVIL_ATTR_RCPT, rcpts), RECV_ATTR_INT(ANVIL_ATTR_NTLS, newtls), ! ATTR_TYPE_END) != 6) status = ANVIL_STAT_FAIL; else if (status != ANVIL_STAT_OK) status = ANVIL_STAT_FAIL; --- 212,219 ---- RECV_ATTR_INT(ANVIL_ATTR_MAIL, msgs), RECV_ATTR_INT(ANVIL_ATTR_RCPT, rcpts), RECV_ATTR_INT(ANVIL_ATTR_NTLS, newtls), ! RECV_ATTR_INT(ANVIL_ATTR_AUTH, auths), ! ATTR_TYPE_END) != 7) status = ANVIL_STAT_FAIL; else if (status != ANVIL_STAT_OK) status = ANVIL_STAT_FAIL; *************** *** 327,332 **** --- 342,371 ---- return (status); } + /* anvil_clnt_auth - heads-up and status query */ + + int anvil_clnt_auth(ANVIL_CLNT *anvil_clnt, const char *service, + const char *addr, int *auths) + { + char *ident = ANVIL_IDENT(service, addr); + int status; + + if (attr_clnt_request((ATTR_CLNT *) anvil_clnt, + ATTR_FLAG_NONE, /* Query attributes. */ + SEND_ATTR_STR(ANVIL_ATTR_REQ, ANVIL_REQ_AUTH), + SEND_ATTR_STR(ANVIL_ATTR_IDENT, ident), + ATTR_TYPE_END, + ATTR_FLAG_MISSING, /* Reply attributes. */ + RECV_ATTR_INT(ANVIL_ATTR_STATUS, &status), + RECV_ATTR_INT(ANVIL_ATTR_RATE, auths), + ATTR_TYPE_END) != 2) + status = ANVIL_STAT_FAIL; + else if (status != ANVIL_STAT_OK) + status = ANVIL_STAT_FAIL; + myfree(ident); + return (status); + } + /* anvil_clnt_disconnect - heads-up only */ int anvil_clnt_disconnect(ANVIL_CLNT *anvil_clnt, const char *service, *************** *** 371,376 **** --- 410,416 ---- ANVIL_REQ_RCPT " service addr | " ANVIL_REQ_NTLS " service addr | " ANVIL_REQ_NTLS_STAT " service addr | " + ANVIL_REQ_AUTH " service addr | " ANVIL_REQ_LOOKUP " service addr\n"); } *************** *** 387,392 **** --- 427,433 ---- int msgs; int rcpts; int newtls; + int auths; ANVIL_CLNT *anvil; msg_vstream_init(argv[0], VSTREAM_ERR); *************** *** 432,437 **** --- 473,483 ---- msg_warn("error!"); else vstream_printf("rate=%d\n", newtls); + } else if (strncmp(cmd, ANVIL_REQ_AUTH, cmd_len) == 0) { + if (anvil_clnt_auth(anvil, service, addr, &auths) != ANVIL_STAT_OK) + msg_warn("error!"); + else + vstream_printf("rate=%d\n", auths); } else if (strncmp(cmd, ANVIL_REQ_NTLS_STAT, cmd_len) == 0) { if (anvil_clnt_newtls_stat(anvil, service, addr, &newtls) != ANVIL_STAT_OK) msg_warn("error!"); *************** *** 443,454 **** else vstream_printf("OK\n"); } else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) { ! if (anvil_clnt_lookup(anvil, service, addr, &count, &rate, ! &msgs, &rcpts, &newtls) != ANVIL_STAT_OK) msg_warn("error!"); else ! vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d\n", ! count, rate, msgs, rcpts, newtls); } else { vstream_printf("bad command: \"%s\"\n", cmd); usage(); --- 489,501 ---- else vstream_printf("OK\n"); } else if (strncmp(cmd, ANVIL_REQ_LOOKUP, cmd_len) == 0) { ! if (anvil_clnt_lookup(anvil, service, addr, &count, &rate, &msgs, ! &rcpts, &newtls, &auths) != ANVIL_STAT_OK) msg_warn("error!"); else ! vstream_printf("count=%d, rate=%d msgs=%d rcpts=%d newtls=%d " ! "auths=%d\n", count, rate, msgs, rcpts, newtls, ! auths); } else { vstream_printf("bad command: \"%s\"\n", cmd); usage(); diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/src/global/anvil_clnt.h ./src/global/anvil_clnt.h *** /var/tmp/postfix-3.1-20151011/src/global/anvil_clnt.h 2005-10-10 13:34:42.000000000 -0400 --- ./src/global/anvil_clnt.h 2015-10-17 10:36:46.000000000 -0400 *************** *** 34,39 **** --- 34,40 ---- #define ANVIL_REQ_RCPT "recipient" #define ANVIL_REQ_NTLS "newtls" #define ANVIL_REQ_NTLS_STAT "newtls_status" + #define ANVIL_REQ_AUTH "auth" #define ANVIL_REQ_LOOKUP "lookup" #define ANVIL_ATTR_IDENT "ident" #define ANVIL_ATTR_COUNT "count" *************** *** 41,46 **** --- 42,48 ---- #define ANVIL_ATTR_MAIL "mail" #define ANVIL_ATTR_RCPT "rcpt" #define ANVIL_ATTR_NTLS "newtls" + #define ANVIL_ATTR_AUTH "auth" #define ANVIL_ATTR_STATUS "status" #define ANVIL_STAT_OK 0 *************** *** 57,63 **** extern int anvil_clnt_rcpt(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls_stat(ANVIL_CLNT *, const char *, const char *, int *); ! extern int anvil_clnt_lookup(ANVIL_CLNT *, const char *, const char *, int *, int *, int *, int *, int *); extern int anvil_clnt_disconnect(ANVIL_CLNT *, const char *, const char *); extern void anvil_clnt_free(ANVIL_CLNT *); --- 59,66 ---- extern int anvil_clnt_rcpt(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls(ANVIL_CLNT *, const char *, const char *, int *); extern int anvil_clnt_newtls_stat(ANVIL_CLNT *, const char *, const char *, int *); ! extern int anvil_clnt_auth(ANVIL_CLNT *, const char *, const char *, int *); ! extern int anvil_clnt_lookup(ANVIL_CLNT *, const char *, const char *, int *, int *, int *, int *, int *, int *); extern int anvil_clnt_disconnect(ANVIL_CLNT *, const char *, const char *); extern void anvil_clnt_free(ANVIL_CLNT *); diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/src/global/mail_params.h ./src/global/mail_params.h *** /var/tmp/postfix-3.1-20151011/src/global/mail_params.h 2015-09-13 12:36:51.000000000 -0400 --- ./src/global/mail_params.h 2015-10-17 11:15:40.000000000 -0400 *************** *** 2996,3001 **** --- 2996,3005 ---- #define DEF_SMTPD_CNTLS_LIMIT 0 extern int var_smtpd_cntls_limit; + #define VAR_SMTPD_CAUTH_LIMIT "smtpd_client_auth_rate_limit" + #define DEF_SMTPD_CAUTH_LIMIT 0 + extern int var_smtpd_cauth_limit; + #define VAR_SMTPD_HOGGERS "smtpd_client_event_limit_exceptions" #define DEF_SMTPD_HOGGERS "${smtpd_client_connection_limit_exceptions:$" VAR_MYNETWORKS "}" extern char *var_smtpd_hoggers; diff --exclude=man --exclude=html --exclude=README_FILES --exclude=INSTALL --exclude=.indent.pro --exclude=Makefile.in -r -cr /var/tmp/postfix-3.1-20151011/src/smtpd/smtpd.c ./src/smtpd/smtpd.c *** /var/tmp/postfix-3.1-20151011/src/smtpd/smtpd.c 2015-09-13 12:36:51.000000000 -0400 --- ./src/smtpd/smtpd.c 2015-10-17 17:36:43.000000000 -0400 *************** *** 699,704 **** --- 699,710 ---- /* time limit per read or write system call, to a time limit to send /* or receive a complete record (an SMTP command line, SMTP response /* line, SMTP message content line, or TLS protocol message). + /* .PP + /* Available in Postfix version 3.1 and later: + /* .IP "\fBsmtpd_client_auth_rate_limit (0)\fR" + /* The maximal number of AUTH commands that any client is allowed to + /* send to this service per time unit, regardless of whether or not + /* Postfix actually accepts those commands. /* TARPIT CONTROLS /* .ad /* .fi *************** *** 1292,1297 **** --- 1298,1304 ---- int var_smtpd_cmail_limit; int var_smtpd_crcpt_limit; int var_smtpd_cntls_limit; + int var_smtpd_cauth_limit; char *var_smtpd_hoggers; char *var_local_rwr_clients; char *var_smtpd_ehlo_dis_words; *************** *** 1897,1902 **** --- 1904,1940 ---- } } + /* auth_cmd_wrapper - auth_cmd front-end */ + + static int smtpd_sasl_auth_cmd_wrapper(SMTPD_STATE *state, int argc, + SMTPD_TOKEN *argv) + { + int rate; + + if (SMTPD_STAND_ALONE(state) == 0 + && !xclient_allowed + && anvil_clnt + && var_smtpd_cauth_limit > 0 + && !namadr_list_match(hogger_list, state->name, state->addr) + && anvil_clnt_auth(anvil_clnt, state->service, state->addr, + &rate) == ANVIL_STAT_OK + && rate > var_smtpd_cauth_limit) { + state->error_mask |= MAIL_ERROR_POLICY; + msg_warn("AUTH command rate limit exceeded: %d from %s for service %s", + rate, state->namaddr, state->service); + smtpd_chat_reply(state, + "450 4.7.1 Error: too many AUTH commands from %s", + state->addr); + return (-1); + } + return (smtpd_sasl_auth_cmd(state, argc, argv)); + + /* + * Redirect the smtpd_sasl_auth_cmd() call below. + */ + #define smtpd_sasl_auth_cmd smtpd_sasl_auth_cmd_wrapper + } + /* mail_open_stream - open mail queue file or IPC stream */ static int mail_open_stream(SMTPD_STATE *state) *************** *** 5577,5583 **** */ if (var_smtpd_crate_limit || var_smtpd_cconn_limit || var_smtpd_cmail_limit || var_smtpd_crcpt_limit ! || var_smtpd_cntls_limit) anvil_clnt = anvil_clnt_create(); } --- 5615,5621 ---- */ if (var_smtpd_crate_limit || var_smtpd_cconn_limit || var_smtpd_cmail_limit || var_smtpd_crcpt_limit ! || var_smtpd_cntls_limit || var_smtpd_cauth_limit) anvil_clnt = anvil_clnt_create(); } *************** *** 5625,5630 **** --- 5663,5669 ---- VAR_SMTPD_CMAIL_LIMIT, DEF_SMTPD_CMAIL_LIMIT, &var_smtpd_cmail_limit, 0, 0, VAR_SMTPD_CRCPT_LIMIT, DEF_SMTPD_CRCPT_LIMIT, &var_smtpd_crcpt_limit, 0, 0, VAR_SMTPD_CNTLS_LIMIT, DEF_SMTPD_CNTLS_LIMIT, &var_smtpd_cntls_limit, 0, 0, + VAR_SMTPD_CAUTH_LIMIT, DEF_SMTPD_CAUTH_LIMIT, &var_smtpd_cauth_limit, 0, 0, #ifdef USE_TLS VAR_SMTPD_TLS_CCERT_VD, DEF_SMTPD_TLS_CCERT_VD, &var_smtpd_tls_ccert_vd, 0, 0, #endif