using nested acl's with exim

By thomas, 5 October, 2012

I wanted to configure rate limiting on our exim server and needed to setup an ACL that I could include in multiple spots in the configuration. The keyword acl = acl_name is supported (referred to as nested ACL's) but the logic took a little bit of thought...

I want to ratelimit users, the acl for that is here:

warn authenticated = *
ratelimit = 50 / 1h / strict / $authenticated_id
message = Your account has sent over 50 messages per hour, the hourly limit is 100 - please contact support help@example.com to change this limit
log_message = WARN USER RATE EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max 500)

deny authenticated = *
ratelimit = 100 / 1h / strict / $authenticated_id
message = Your account has sent over 100 messages per hour - please contact support help@example.com to change this limit
log_message = RATE USER EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max $sender_

Now I wanted to turn this into a named acl and include it elsewhere. The logic of this tripped me up.


deny acl = acl_ratelimit_user

acl_ratelimit_user:
warn authenticated = *
ratelimit = 50 / 1h / strict / $authenticated_id
message = Your account has sent over 50 messages per hour, the hourly limit is 100 - please contact support help@example.com to change this limit
log_message = WARN USER RATE EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max 500)

deny authenticated = *
ratelimit = 100 / 1h / strict / $authenticated_id
message = Your account has sent over 100 messages per day - please contact support help@exmaple.com to change this limit
log_message = RATE USER EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max $sender_rate_limit)

The problem here is that I have

deny acl = acl_ratelimit_user

But the logic of this reads like this:

deny if the condition is true (accept). So I need to change the acl so that anything it accepts, I will deny. Anything that is denied in the acl results in acl = acl_ratelimit_user being false which means the deny won't apply and therefore the mail will be allowed. Once I understood that, it was easy enough to change the logic, the final solution is:

acl_check_data:
deny acl = acl_ratelimit_user
accept

# since we are including this acl elsewhere as a condition, we need it to return accept (true) when we want the top acl to deny.
acl_ratelimit_user:
warn authenticated = *
ratelimit = 50 / 1h / strict / $authenticated_id
message = Your account has sent over 50 messages per hour, the daily limit is 100 - please contact support help@example.com to change this limit
log_message = WARN USER RATE EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max 500)

accept authenticated = *
ratelimit = 100 / 1h / strict / $authenticated_id
message = Your account has sent over 100 messages per hour - please contact support help@example.com to change this limit
log_message = RATE USER EXCEEDED: $authenticated_id -> $sender_rate/$sender_rate_period (max $sender_rate_limit)

I also made a rule to limit hosts, the important thing to remember there is that your smart hosts or incoming hosts should be excluded. Again, the logic for that must be reversed. You need to deny a list of hosts in order for the acl to fail, which results in a pass higher up.

acl_ratelimit_host:
warn ratelimit = 75 / 1h / strict
message = This machine has sent over 75 messages per hour, the hourly limit is 150 - please contact support help@example.com to change this limit
log_message = WARN HOST RATE EXCEEDED: $sender_host_address -> $sender_rate/$sender_rate_period (max 500)

deny hosts = 172.16.12.31 : 172.16.12.32 : 172.16.13.151
accept ratelimit = 150 / 1h / strict
message = This machine has sent over 150 messages per hour - please contact support help@example.com to change this limit
log_message = RATE HOST EXCEEDED: $sender_host_address -> $sender_rate/$sender_rate_period (max $sender_rate_limit)