Hi Grant,
Something that isn't clear from your request is - is this due to brute force attacks or number of valid users logging in.
If you are being brute forced, logging the failed attempts then using something like fail2ban is a better solution.
From here, brute force against Dovecot/Exim is has increased expotentially within the last month or so.
Ta
Chris
-----Original Message-----
From: exim-users-bounces+chris.russell=knowledgeit.co.uk@??? [
mailto:exim-users-bounces+chris.russell=knowledgeit.co.uk@exim.org] On Behalf Of Grant Peel
Sent: 21 October 2013 00:30
To: exim-users@???
Subject: [exim] Authenticated Ratelimiting.
Hi all,
Recent events have left us wanting to add rate limiting to our exim configuration.
I have seen several examples in various places on the web and everything I can find in the exim specification, and I must say I am a little more than confused.
All I really want to do is ratelimit everyone (locally authenticated) to 250/hour - as a start anyways.
A while back, we implemented authentication checking with includes some ratelimiting, via 'Lena's" post:
https://lists.exim.org/lurker/message/20121117.144211.c96f81fc.pl.html
Any primers or suggestion(s) would be appreciated.
-G
Here is our configuration file as it stands now.
######################################################################
# MAIN CONFIGURATION SETTINGS #
######################################################################
primary_hostname = server.ourdomain.ext
domainlist relay_to_domains =
domainlist local_domains = /etc/virtual/domains
domainlist filtered_domains = /etc/virtual/filtered_domains
hostlist filtering_hosts = /etc/virtual/filtering_hosts
hostlist relay_from_hosts = /etc/virtual/domains
hostlist blacklisted_domains = /etc/virtual/blacklist
hostlist spf_bypass = /etc/virtual/spf_bypass
hostlist whitelist = /etc/virtual/whitelist
trusted_users = mailnull:root:webmail:www
exim_user = mailnull
exim_group = mail
never_users =
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 5s
ignore_bounce_errors_after = 0s
timeout_frozen_after = 0s
auto_thaw = 6h
return_path_remove
untrusted_set_sender = *
helo_allow_chars = _
daemon_smtp_ports = 25 : 109 : 587
bounce_message_file = /usr/local/etc/exim/bounce_message_file
warn_message_file = /usr/local/etc/exim/warn_message_file
return_size_limit = 10000
bounce_return_message = true
bounce_return_size_limit = 1000
delay_warning = 72h
smtp_accept_max = 100
smtp_accept_max_per_host = 10
smtp_return_error_details = yes
log_selector = +incoming_interface +deliver_time +delivery_size
+received_sender \
+received_recipients +sender_on_delivery +subject +address_rewrite
+all_parents
# log_selector = +all
message_logs = true
#######################################################################
# Added for Authentication Limiting. #
#######################################################################
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_connect = acl_check_connect
acl_smtp_auth = acl_check_auth
acl_smtp_mail = acl_check_mail
acl_smtp_quit = acl_check_quit
acl_smtp_notquit = acl_check_notquit
LIM = 100
PERIOD = 1h
WARNTO = me@???
EXIMBINARY = /usr/local/sbin/exim -f root
SHELL = /bin/sh
#######################################################################
# End of additions. #
#######################################################################
#
# An Attempt at greylisting
#
hide mysql_servers = localhost/exim_db/exim/<passwd>:
GREYLIST_TEST = SELECT IF(NOW() > block_expires, 2, 1) \
FROM exim_greylist \
WHERE relay_ip = '${quote_mysql:$sender_host_address}' \
AND from_domain = '${quote_mysql:$sender_address_domain}' \
AND record_expires > NOW()
GREYLIST_ADD = INSERT INTO exim_greylist \
SET relay_ip = '${quote_mysql:$sender_host_address}', \
from_domain = '${quote_mysql:$sender_address_domain}', \
block_expires = DATE_ADD(NOW(), INTERVAL 1 MINUTE), \
record_expires = DATE_ADD(NOW(), INTERVAL 14 DAY), \
origin_type = 'AUTO', \
create_time = NOW()
GREYLIST_UPDATE = UPDATE exim_greylist \
SET record_expires = DATE_ADD(now(), INTERVAL 14 DAY) \
WHERE relay_ip = '${quote_mysql:$sender_host_address}' \
AND from_domain = '${quote_mysql:$sender_address_domain}' \
AND record_expires > NOW()
######################################################################
# ACL CONFIGURATION #
# Specifies access control lists for incoming SMTP mail #
######################################################################
begin acl
######################################################################
# Added for the Authentication Limiting #
######################################################################
acl_check_auth:
drop message = authentication is allowed only once per message in order \
to slow down bruteforce cracking
set acl_m_auth = ${eval10:0$acl_m_auth+1}
condition = ${if >{$acl_m_auth}{2}}
delay = 22s
drop message = blacklisted for bruteforce cracking attempt
set acl_c_authnomail = ${eval10:0$acl_c_authnomail+1}
condition = ${if >{$acl_c_authnomail}{4}}
continue = ${run{SHELL -c "echo $sender_host_address \
>>$spool_directory/blocked_IPs; \
\N{\N echo Subject: $sender_host_address blocked; echo; echo \
for bruteforce auth cracking attempt.; \
\N}\N | EXIMBINARY WARNTO"}}
accept
acl_check_quit:
warn condition = ${if def:authentication_failed}
condition = $authentication_failed
logwrite = :reject: quit after authentication failed: \
${sg{$sender_rcvhost}{\N[\n\t]+\N}{\040}}
ratelimit = 7 / 5m / strict / per_conn
continue = ${run{SHELL -c "echo $sender_host_address \
>>$spool_directory/blocked_IPs; \
\N{\N echo Subject: $sender_host_address blocked; echo; echo \
for bruteforce auth cracking attempt.; \
\N}\N | EXIMBINARY WARNTO"}}
acl_check_notquit:
warn condition = ${if def:authentication_failed}
condition = $authentication_failed
logwrite = :reject: $smtp_notquit_reason after authentication
failed: \
${sg{$sender_rcvhost}{\N[\n\t]+\N}{\040}}
condition = ${if eq{$smtp_notquit_reason}{connection-lost}}
ratelimit = 7 / 5m / strict / per_conn
continue = ${run{SHELL -c "echo $sender_host_address \
>>$spool_directory/blocked_IPs; \
\N{\N echo Subject: $sender_host_address blocked; echo; echo \
for bruteforce auth cracking attempt.; \
\N}\N | EXIMBINARY WARNTO"}}
acl_check_mail:
accept set acl_c_authnomail = 0
acl_check_connect:
drop message = $sender_host_address locally blacklisted for a bruteforce \
auth (username+password) cracking attempt
condition = ${if exists{$spool_directory/blocked_IPs}}
condition = ${lookup{$sender_host_address}lsearch\
{$spool_directory/blocked_IPs}{1}{0}}
accept
############################################################################
##
# End of the Authentication Limit additions.
#
############################################################################
##
acl_check_rcpt:
accept hosts = :
logwrite = ACL - ACCEPTED (EMPTY LIST)
deny local_parts = ^.*[@%!/|] : ^\\.
log_message = ACL - DENIED (LOCAL PART SYNTAX)
############################################################################
#
# This block replaced with the three paragraphs below
#
# for authentication limiting.
#
# accept authenticated = *
#
# logwrite = ACL - ACCEPTED
#
# endpass
#
############################################################################
#
accept authenticated = *
set acl_m_user = $authenticated_id
condition = ${if
exists{$spool_directory/blocked_authenticated_users}}
condition = ${lookup{$acl_m_user}lsearch\
{$spool_directory/blocked_authenticated_users}{1}{0}}
control = freeze/no_tell
control = submission/domain=
add_header = X-Authenticated-As: $acl_m_user
accept authenticated = *
!verify = recipient/defer_ok/callout=10s,defer_ok,use_sender
ratelimit = LIM / PERIOD / per_rcpt / user-$acl_m_user
continue = ${run{SHELL -c "echo $acl_m_user \
>>$spool_directory/blocked_authenticated_users; \
\N{\N echo Subject: user $acl_m_user blocked; echo; echo because \
has sent mail to LIM invalid recipients during PERIOD.; \
\N}\N | EXIMBINARY WARNTO"}}
control = freeze/no_tell
control = submission/domain=
add_header = X-Authenticated-As: $acl_m_user
accept authenticated = *
control = submission/domain=
logwrite = ACL - ACCEPTED (authenticated block)
########################################################################
# END of authentication replacement. #
########################################################################
########################################################################
# This block Replaced with the following #
# for Authentication Limitiing: #
# accept hosts = +relay_from_hosts #
# logwrite = ACL - ACCEPTED #
# endpass #
########################################################################
accept hosts = !@[] : +relay_from_hosts
set acl_m_user = $sender_host_address
# or username from RADIUS
condition = ${if exists{$spool_directory/blocked_relay_users}}
condition = ${lookup{$acl_m_user}lsearch\
{$spool_directory/blocked_relay_users}{1}{0}}
control = freeze/no_tell
control = submission/domain=
add_header = X-Relayed-From: $acl_m_user
accept hosts = !@[] : +relay_from_hosts
!verify = recipient/defer_ok/callout=10s,defer_ok,use_sender
ratelimit = LIM / PERIOD / per_rcpt / relayuser-$acl_m_user
continue = ${run{SHELL -c "echo $acl_m_user \
>>$spool_directory/blocked_relay_users; \
\N{\N echo Subject: relay user $acl_m_user blocked; echo; echo \
because has sent mail to LIM invalid recipients during PERIOD.; \
\N}\N | EXIMBINARY WARNTO"}}
control = freeze/no_tell
control = submission/domain=
add_header = X-Relayed-From: $acl_m_user
accept hosts = +relay_from_hosts
control = submission/domain=
logwrite = ACL - ACCEPTED (hosts block)
######################################################################
# END of authentication Replacement. #
######################################################################
######################################################################
# Hello checks added November 1 2009.
######################################################################
# If the remote host greets with an IP address, then reject the mail.
deny message = RATWARE - IP address.
log_message = ACL - DENIED - RATWARE remote host used IP
address in HELO/EHLO greeting
condition = ${if isip
{$sender_helo_name}{true}{false}}
# Likewise if the peer greets with one of our own names
deny message = RATWARE - Fake HELO Domain
log_message = ACL - DENIED - RATWARE remote host used
our name in HELO/EHLO greeting.
condition = ${if match_domain{$sender_helo_name}\
{$primary_hostname:+local_domains:+relay_to_domains}\
{true}{false}}
deny message = RATWARE - No HELO
log_message = ACL - DENIED - RATWARE remote host did not
present HELO/EHLO greeting.
condition = ${if def:sender_helo_name {false}{true}}
# If HELO verification fails, we add a X-HELO-Warning: header in the message.
warn message = X-HELO-Warning: Remote host
$sender_host_address \
${if def:sender_host_name
{($sender_host_name) }}\
incorrectly presented itself as $sender_helo_name
log_message = ACL - WARN - UNVERIFIABLE HELO/EHLO
greeting.
!verify = helo
# An attempt to block using list of subjects ###########################################
# deny message = X-Blackhole: Yes
# log_message = REJECTED - Subject in blocksubject list -
$h_Subject
# condition = ${if
exists{/etc/virtual/blocksubject.txt}\
#
{${lookup{$h_Subject:}wildlsearch{/etc/virtual/blocksubject.txt}{yes}{no}}}\
# {no}}
############################################################################
#############
#######################################################################
# Mail is being rejected on some hosts because the mail MX is only set to SMARTHOST
# and our server is rejecting it because some mail servers see the lesser priorty
# MX and try to oour server directly instead of going though SMARTHOST.
accept domains = +filtered_domains
hosts = +filtering_hosts
verify = recipient
log_message = ACL - ACCEPTED - MXTEXT
deny message = Please use the public MX server for the
domain $domain
domains = +filtered_domains
! hosts = +filtering_hosts
log_message = ACL - DENIED - MXTEST
######################################################################
######################################################################
# DNS checks
######################################################################
# The results of these checks are cached, so multiple recipients
# does not translate into multiple DNS lookups.
#
# If the connecting host is in one of a select few DNSbls, then
# reject the message. Be careful when selecting these lists; many
# would cause a large number of false postives, and/or have no
# clear removal policy.
#
# deny dnslists = dnsbl.sorbs.net : \
# dnsbl.njabl.org : \
# cbl.abuseat.org : \
# bl.spamcop.net
# message = RBL - $sender_host_address is listed in
$dnslist_domain\
# ${if def:dnslist_text { ($dnslist_text)}}
#####################################################################
deny senders = :
condition = ${if > {$recipients_count}{2}{1}}
message = Bounces must have only a single recipient
log_message = ACL - DENIED - BACKSCATTER - RECIPIENTS
$recipients_count
deny message = rejected because $sender_host_address was
\
found in our blacklist
hosts = +blacklisted_domains
log_message = ACL - DENIED - BLACKLISTED DOMAIN FOUND IN
$blacklisted_domains
deny
! condition = ${lookup
dnsdb{defer_never,ptr=$sender_host_address}{yes}}
log_message = ACL - DENIED - NO PTR [rDNS] FOUND FOR
$sender_host_address
message = We do not accept mail from hosts with
missing \
or incorrect rDNS.
deny senders = :
! hosts = +whitelist
! domains = +local_domains
dnslists = ips.backscatterer.org
message = This message looks like a bounce, and your
server is listed at \
ips.backscatterer.org, so I assume that this is "backscatter". \
Please configure your mail server to not send "backscatter spam". \
For advice, try http://www.dontbouncespam.org/
log_message = ACL - DENIED - BACKSCATTER - INCOMING
warn set acl_m2 = ${lookup mysql{GREYLIST_TEST}{$value}{0}}
defer ! hosts = +whitelist
! hosts = +relay_from_hosts
! authenticated = *
condition = ${if eq{$acl_m2}{0}{yes}}
condition = ${lookup mysql{GREYLIST_ADD}{yes}{no}}
message = Now greylisted - please try again in 1
minute.
log_message = ACL - DEFERED - ADDING TO GREYLIST
defer ! hosts = +whitelist
! hosts = +relay_from_hosts
! authenticated = *
condition = ${if eq{$acl_m2}{1}{yes}}
message = Still greylisted - please try again in 1
minute.
log_message = ACL - DEFERED - STILL GREYLISTED
defer
! hosts = +whitelist
! hosts = +relay_from_hosts
! authenticated = *
condition = ${lookup mysql{GREYLIST_UPDATE}{no}{no}}
message = Greylist update failed
log_message = ACL - DEFERED - GREYLIST UPDATE FAILED
require verify = sender
accept hosts = +spf_bypass
verify = recipient
spf = fail
logwrite = SPF - REFLEXION $sender_host_address is OK
for \
$sender_address_domain
deny message = SPF - INCOMING $sender_host_address \
is not allowed to send mail from $sender_address_domain
spf = fail
log_message = ACL - DENIED - SPF Mismatch
accept domains = +local_domains
endpass
message = unknown user
verify = recipient
accept domains = +relay_to_domains
endpass
message = unrouteable address
verify = recipient
######################################################################
# ROUTERS CONFIGURATION #
# Specifies how addresses are handled #
######################################################################
# THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! #
# An address is passed to each router in turn until it is accepted. #
######################################################################
begin routers
dnslookup_owm_www:
driver = dnslookup
domains = ! +local_domains
condition = ${if eq {$sender_host_address}{127.0.0.1} {yes}{no}}
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
dnslookup_local:
driver = dnslookup
domains = ! +local_domains
condition = ${lookup {$sender_address_domain} lsearch {/etc/virtual/domains} {yes}{no}}
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
dnslookup_bounce:
driver = dnslookup
domains = ! +local_domains
condition = ${if eq {$sender_address_local_part}{} {yes}{no}}
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
dnslookup_alias:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
spamcheck_router:
driver = accept
no_verify
condition = "${if and { {!def:h_X-Spam-Flag:} {!eq {$received_protocol}\
{spam-scanned}}} {1}{0}}"
transport = spamcheck
virtual_alias:
driver = redirect
allow_defer
allow_fail
data = ${lookup {$local_part} lsearch {/home/$domain/mail/aliases}}
domains = /etc/virtual/domains
require_files = /home/$domain/mail/aliases
condition = ${lookup {$local_part} lsearch {/home/$domain/mail/aliases} {yes}{no}}
qualify_preserve_domain
retry_use_local_part
check_ancestor
one_time
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply
autoreply_router:
driver = accept
require_files = /home/$domain/mail/auto-replies/$local_part
transport = autoreply_transport
no_verify
unseen
virtual_localuser:
driver = accept
require_files = /etc/virtual/$domain/passwd
domains = /etc/virtual/domains
condition = ${lookup {$local_part} lsearch {/etc/virtual/$domain/passwd}{$value}}
transport = virtual_localdelivery
virtual_catchall:
driver = redirect
allow_defer
allow_fail
data = ${lookup {catchall} lsearch {/home/$domain/mail/aliases}}
domains = /etc/virtual/domains
require_files = /home/$domain/mail/aliases
condition = ${lookup {catchall}lsearch{/home/$domain/mail/aliases}
{yes}{no}}
qualify_preserve_domain
retry_use_local_part
check_ancestor
one_time
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply
localuser:
driver = accept
check_local_user
condition = ${lookup {$sender_helo_name} lsearch {/etc/virtual/domains}{YES}{NO}}
transport = local_delivery
######################################################################
# TRANSPORTS CONFIGURATION #
######################################################################
# ORDER DOES NOT MATTER #
# Only one appropriate transport is called for each delivery. #
######################################################################
begin transports
remote_smtp:
driver = smtp
return_path_add = true
autoreply_transport:
driver = pipe
command = /usr/local/bin/autoreply.pl
/home/$domain/mail/auto-replies/$local_part
spamcheck:
driver = pipe
command = /usr/local/sbin/exim -oMr spam-scanned -bS
use_bsmtp = true
transport_filter = /usr/local/bin/spamc -u ${lookup{$domain}lsearch{/etc/virtual/domains_users}}
home_directory = "/tmp"
current_directory = "/tmp"
# must use a privileged user to set $received_protocol on the way back in!
user = mailnull
group = mailnull
log_output = true
return_fail_output = false
return_path_add
message_prefix =
message_suffix =
virtual_localdelivery:
driver = appendfile
create_directory = true
directory_mode = 700
file = /var/spool/virtual/${domain}/${local_part}
headers_remove = "Bcc"
return_path_add
user = ${lookup{$domain}lsearch{/etc/virtual/domains_users}}
group = mail
mode = 660
local_delivery:
driver = appendfile
file = /$home/mail/$local_part
delivery_date_add
envelope_to_add
return_path_add
user = mailnull
group = mail
mode = 0660
address_pipe:
driver = pipe
return_output
user = <anotheruser>
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
address_reply:
driver = autoreply
######################################################################
# RETRY CONFIGURATION #
######################################################################
begin retry
# Domain Error Retries
# ------ ----- -------
* quota_7d
* quota F,72h,1h;
* * F,60m,10m; F,11h,30m; F,12h,1h;
F,144h,2h;
######################################################################
# REWRITE CONFIGURATION #
######################################################################
# There are no rewriting specifications in this default configuration file.
begin rewrite
######################################################################
# AUTHENTICATION CONFIGURATION #
######################################################################
# There are no authenticator specifications in this default configuration file.
begin authenticators
# For Netscape/Mozilla
plain:
driver = plaintext
public_name = PLAIN
server_condition = "${if and{ {!eq{$2}{}}{!eq{$3}{}} \
{crypteq {$3} {${lookup {${local_part:$2}} lsearch \
{/etc/virtual/${domain:$2}/passwd}\
{$value} {*:*}}}} } {1}{0}}"
server_set_id = $2
# For Outlook/Outlook Express
login:
driver = plaintext
public_name = LOGIN
server_prompts = "Username:: : Password::"
server_condition = "${if and{ {!eq{$1}{}}{!eq{$2}{}} \
{crypteq {$2} {${lookup {${local_part:$1}} lsearch \
{/etc/virtual/${domain:$1}/passwd}\
{$value} {*:*}}}} } {1}{0}}"
server_set_id = $1
# End of Exim configuration file
--
## List details at
https://lists.exim.org/mailman/listinfo/exim-users
## Exim details at
http://www.exim.org/
## Please use the Wiki with this list -
http://wiki.exim.org/
website:
www.knowledgeit.co.uk | blog:
www.knowledgeit.co.uk/blog | twitter: @KnowledgeITUK
Knowledge Limited, Company Registration: 1554385
Registered Office: New Century House, Crowther Road, Washington, Tyne & Wear. NE38 0AQ
Leeds Office: Viscount Court, Leeds Road, Rothwell, Leeds. LS26 0GR
Tel: 0845 142 0020. Fax: 0845 142 0021
E-Mail Disclaimer: This e-mail message is intended to be received only by persons entitled to receive the confidential information it may contain. E-mail messages to clients of Knowledge IT may contain information that is confidential and legally privileged. Please do not read, copy, forward, or store this message unless you are an intended recipient of it. If you have received this message in error, please forward it to the sender and delete it completely from your computer system.
Please consider the environment before printing this email.