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 Lenas 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