Helpful Exim ACLs -- Add Your Own

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
With so much spam discussion on the board lately, I thought maybe we can put our heads together in one thread and come up with the most helpful Exim ACLs for everyone to share.

I'll start off with a custom HELO ACL we use on our servers:

HELO BASED EXIM ACL (CPANEL ADDITIONAL)

This is a tutorial on how to add a HELO scanning ACL to your exim configuration in cPanel to help cut down on load and spam at the same time. Use this at your own risk! The potential to block legitimate traffic is of course a factor. Educate your users, clients and friends. Oh, and yourself! :)

Background Information
The "HELO" (or "EHLO") is part of the simple procedure machines use to send mail - SMTP, or Simple Mail Transfer Protocol. We won't delve into the details here, suffice it to say that mail servers should follow the SMTP procedure. Most mail servers (including cPanel's standard Exim configuration) are set up to be forgiving. Spammers sometimes don't follow the rules in order to take advantage of our attempt to accept every piece of mail that comes down the pipe. But we can set up our cPanel server to block what we know is not following the rules.

The Procedure
1. Log in to WHM as root and scroll down the left hand side until you find "Service Configuration". Click the "Exim Configuration Editor" link.

2. Click the "Advanced Editor" button at the right side bottom.

3. In the first input box you might see some options already there. Scroll below them and add the following two lines, replacing the IPs in the local_ips hostlist with your own IPs for your server:
Code:
hostlist local_ips = 127.0.0.1: :192.168.1.1:192.168.1.2:192.168.1.3
acl_smtp_helo = check_helo
Be sure you add the extra "empty space" after 127.0.0.1 - it's not a typo!

4. Now scroll all the way down until you find "begin acl" and three input boxes in a row. In the second box you will most likely see the "check_recipient" ACL. I don't know why everyone always uses the second box, but I usually use the first one to keep my addtions separated from cPanels. In the first box, add the following:
Code:
check_helo:

# HELO is empty or not sent
deny condition = ${if eq{$sender_helo_name}{}}
  message = You have sent no HELO! Please see RFC 2821 section 4.1.1.1
  log_message = Bad HELO: Empty HELO

# HELO is not a fully qualified domain name
deny condition = ${if match {$sender_helo_name} {\.} {no}{yes}}
  message = Your mail server announcement ($sender_helo_name) \
                     is a single word rather than a FQDN. This is \
                     in breach of RFC2821
  log_message = Bad HELO: Not FQDN

# IP Only is sent as the HELO
deny condition = ${if match {$sender_helo_name}\
                            {^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\$}\
                            {yes}{no}}
  message = Your server announces itself ($sender_helo_name) with a plain \
                     IP address which is in breach of RFC2821.
  log_message   = Bad HELO: IP Only Announce

# Someone is trying to spoof your own IPs!
deny condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt

# Someone is trying to spoof a domain on the server
deny condition = ${if match_domain{$sender_helo_name}\
                          {+local_domains}}
  message = Forged HELO: you are not $sender_helo_name
  log_message   = Forged HELO: $sender_helo_name Spoof Attempt

accept
5. I've added a comment before each distinct rule so you can have some idea of what these do if you've never used them before. In a nut shell, these are going to drop any connection to the mail server if:
(a) There is no HELO set or it's empty. A HELO statement must by definition be followed by a fully qualified domain name (FQDN). Some spammers try to get around this by sending an empty HELO.
(b) The HELO is not a FQDN. Same as above - a FQDN should be a domain name that can be used to find an IP. (IE: timmysbox is not a FQDN, but mail.timmysbox.net is!)
(c) An IP is sent instead of a FQDN. Spammers will sometimes even try to put your IP as the sending server, hoping it will trick the MTA into thinking the connection is local I suppose. We zap it here.
(d) A domain that resides on your server is sent as the HELO. How could your server be sending itself an email? Zap that connection!

6. Now scroll to the page bottom and SAVE. This will save your new configuration within cPanel so that it will not be overwritten when upgrading to a new version. It restarts Exim as well for you so you can test the results right away. And if your spam problem is like most people, you'll see the results pretty quickly. Do
Code:
tail -f /var/log/exim_mainlog
and watch the scrolling log for any forged or bad HELO messages from our ACL. After a little while, try this one:
Code:
grep "HELO" /var/log/exim_mainlog | more
and scroll through all the spammer connections your machine has dropped BEFORE processing. This procedure is especially helpful to those running high memory eaters like SpamAssassin and/or MailScanner, since the message is dropped rather than run through the queue and scanned.

Hope this helps people out there. Comments, suggestions, additions welcomed!
 
Last edited:

mickalo

Well-Known Member
Apr 16, 2002
782
5
318
N.W. Iowa
Got a couple question on this setup, as it looks very helpful, but what to make sure I've got the setup correct.

Code:
3. In the first input box you might see some options already there. 
Scroll below them and add the following two lines, replacing the IPs in the 
local_ips hostlist with your own IPs for your server:
now the IP's for the server, your referring to the main IP assigned to the server(DNS) IP for ns.hostname and ns2.hostname IP's ??

Code:
4. Now scroll all the way down until you find "begin acl" and three input boxes in a row. 
In the second box you will most likely see the "check_recipient" ACL. I don't know why 
everyone always uses the second box, but I usually use the first one to keep 
my addtions separated from cPanels. In the first box, add the following:
now this is added right AFTER the "begin acl" and BEFORE the "check_recipient:"

TIA,
Mickalo
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
Code:
3. In the first input box you might see some options already there. 
Scroll below them and add the following two lines, replacing the IPs in the 
local_ips hostlist with your own IPs for your server:
now the IP's for the server, your referring to the main IP assigned to the server(DNS) IP for ns.hostname and ns2.hostname IP's ??
Actually, any IPs associated with the server. Not just those used for Bind (NS). You, you're resellers and hosting clients. Any IP assigned to that box. Sometimes you can find them in a file like /etc/ips but I was trying to make it easy to see what was happening. :)

Code:
4. Now scroll all the way down until you find "begin acl" and three input boxes in a row.
now this is added right AFTER the "begin acl" and BEFORE the "check_recipient:"
You got it, although I don't think it matters what order they're in. My understanding is you could still have our new ACL after the other two that were there and it wouldn't matter. :)
 

mickalo

Well-Known Member
Apr 16, 2002
782
5
318
N.W. Iowa
Actually, any IPs associated with the server. Not just those used for Bind (NS). You, you're resellers and hosting clients. Any IP assigned to that box. Sometimes you can find them in a file like /etc/ips but I was trying to make it easy to see what was happening. :)



You got it, although I don't think it matters what order they're in. My understanding is you could still have our new ACL after the other two that were there and it wouldn't matter. :)
Ok, got it now .... ;)

we'll give a try and see how well it works.

UPDATE:
Seems to work very well .... but it sure fills up the exim log files FAST!!! :)


Thx's much!
Mickalo
 
Last edited:

mickalo

Well-Known Member
Apr 16, 2002
782
5
318
N.W. Iowa
hostlist local_ips = 127.0.0.1: :192.168.1.1:192.168.1.2:192.168.1.3
is it possible for this to be setup to read/search from a file instead of manually listing IP's here, as IP's are added/removed during the course of time. It would make it easier to maintain. Other then that, this seems to work quiet well. Nice little tweak.

Thx's
Mickalo
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
is it possible for this to be setup to read/search from a file instead of manually listing IP's here
Testing this now, not sure if it will work as expected or not - I know Exim grabs the ip literals for the server's domains before delivering emails (part of the RFC that most people ignore):

Code:
# Someone is trying to spoof your own IPs!
deny condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt
This change does work well, so I have edited my original post. Spammers spoofing local IP addresses were caught regularly in our test of this addition.
 
Last edited:

isputra

Well-Known Member
May 3, 2003
574
0
166
Mbelitar
Hi,

Is there any rules that can block script from my server sending mass email out using fake email address ?

I have client that installed script like mailbomb that can send email and using fake email as From, not his own email address :

----------
1GxvF5-0002La-Ln-H
gembong 32244 500
<[email protected]>
1166835603 0
-ident myclient
-received_protocol local
-body_linecount 29
-auth_id myclient
-auth_sender [email protected]
-allow_unqualified_recipient
-allow_unqualified_sender
-deliver_firsttime
-local
XX
1
[email protected]

157P Received: from myclient by myserver.net with local (Exim 4.52)
id 1GxvF5-0002La-Ln
for [email protected]; Sat, 23 Dec 2006 08:00:04 +0700
030T To: [email protected]
031 Subject: NOT A DREAM ANYMORE
034F From: [email protected]
---------------------

This account already suspended so he can't send this mailbomb again

Thanks
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
I have edited my original posts above, testing of the change to the HELO IP Spoof portion were sucessful.

@ Chirpy: Nice! Close to what I have (which I got from here - should have given credit!). The one they had which I did not I just tried:
Code:
deny message = Invalid HELO. You must be spam or a virus, or your system \
   administrator is an idiot.
   condition = ${if match{$sender_helo_name}{\\.}{no}{yes}}
However, I found that one client actually hit this message when sending from their office PCs, which were called things like JACKSPC, OFFICE1 and such. Their sys admin was not amused by the message. :)
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
what did you change in your original coding ??
Local IP Spoof scan was changed to match the code in post #7:
Code:
# Someone is trying to spoof your own IPs!
deny condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt
Where we used to use the hostlist called "local_ips", we now use "$interface_address" in the code above. Works just fine! :) No need to manually add local IPs to that IP list now. :)
 
Last edited:

mickalo

Well-Known Member
Apr 16, 2002
782
5
318
N.W. Iowa
Local IP Spoof scan was changed to match the code in post #7:
Code:
# Someone is trying to spoof your own IPs!
condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt
Where we used to use the hostlist called "local_ips", we now use "$interface_address" in the code above. Works just fine! :) No need to manually add local IPs to that IP list now. :)
Hmm... I think you lost me here. where is this "$interface_address" coming from ?? Would it be possible to post the new code completely ??

is this the only part of the code that was changed ??

shouldn't that be deny condition instead of just condition ??


Mickalo
 
Last edited:

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
shouldn't that be deny condition instead of just condition ??
Yes. That's what I get for trying to post quick before the Christmas festivities. :)

That "deny" should definitely be in there. And the $interface_address is an Exim assigned variable the contains a list of all the local IPs, which it collects prior to receipt from your server. I wasn't sure how it would match in that reference, but it seems to be working well on the box I am testing it on. It blocked attempts to spoof the IP of a couple of the IPs assigned to that machine. Success! And without the need for that hostlist we created called "local_ips", so you can remove that too. Here's the revised configurations:

Very first input box when you click Advanced Editor in WHM's Exim Config Editor:
Code:
acl_smtp_helo = check_helo
Then scroll all the way down until you find "begin acl" and three input boxes in a row and in the first box, add the following:
Code:
check_helo:

# HELO is empty or not sent
deny condition = ${if eq{$sender_helo_name}{}}
  message = You have sent no HELO! Please see RFC 2821 section 4.1.1.1
  log_message = Bad HELO: Empty HELO

# HELO is not a fully qualified domain name
deny condition = ${if match {$sender_helo_name} {\.} {no}{yes}}
  message = Your mail server announcement ($sender_helo_name) \
                     is a single word rather than a FQDN. This is \
                     in breach of RFC2821
  log_message = Bad HELO: Not FQDN

# IP Only is sent as the HELO
deny condition = ${if match {$sender_helo_name}\
                            {^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\$}\
                            {yes}{no}}
  message = Your server announces itself ($sender_helo_name) with a plain \
                     IP address which is in breach of RFC2821.
  log_message   = Bad HELO: IP Only Announce

# Someone is trying to spoof your own IPs!
deny condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt

# Someone is trying to spoof a domain on the server
deny condition = ${if match_domain{$sender_helo_name}\
                          {+local_domains}}
  message = Forged HELO: you are not $sender_helo_name
  log_message   = Forged HELO: $sender_helo_name Spoof Attempt

accept
Scroll to the page bottom and SAVE. Test the results after some time by doing:
Code:
grep "Local IP Spoof" /var/log/exim_mainlog
 

mickalo

Well-Known Member
Apr 16, 2002
782
5
318
N.W. Iowa
Yes. That's what I get for trying to post quick before the Christmas festivities. :)

That "deny" should definitely be in there. And the $interface_address is an Exim assigned variable the contains a list of all the local IPs, which it collects prior to receipt from your server. I wasn't sure how it would match in that reference, but it seems to be working well on the box I am testing it on. It blocked attempts to spoof the IP of a couple of the IPs assigned to that machine. Success! And without the need for that hostlist we created called "local_ips", so you can remove that too. Here's the revised configurations:

Very first input box when you click Advanced Editor in WHM's Exim Config Editor:
Code:
acl_smtp_helo = check_helo
Then scroll all the way down until you find "begin acl" and three input boxes in a row and in the first box, add the following:
Code:
check_helo:

# HELO is empty or not sent
deny condition = ${if eq{$sender_helo_name}{}}
  message = You have sent no HELO! Please see RFC 2821 section 4.1.1.1
  log_message = Bad HELO: Empty HELO

# HELO is not a fully qualified domain name
deny condition = ${if match {$sender_helo_name} {\.} {no}{yes}}
  message = Your mail server announcement ($sender_helo_name) \
                     is a single word rather than a FQDN. This is \
                     in breach of RFC2821
  log_message = Bad HELO: Not FQDN

# IP Only is sent as the HELO
deny condition = ${if match {$sender_helo_name}\
                            {^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\$}\
                            {yes}{no}}
  message = Your server announces itself ($sender_helo_name) with a plain \
                     IP address which is in breach of RFC2821.
  log_message   = Bad HELO: IP Only Announce

# Someone is trying to spoof your own IPs!
deny condition = ${if eq {$sender_helo_name} {$interface_address} {yes}{no}}
  message = HELO/EHLO IP is local. You are not this server.
  log_message = Bad HELO: Local IP Spoof Attempt

# Someone is trying to spoof a domain on the server
deny condition = ${if match_domain{$sender_helo_name}\
                          {+local_domains}}
  message = Forged HELO: you are not $sender_helo_name
  log_message   = Forged HELO: $sender_helo_name Spoof Attempt

accept
Scroll to the page bottom and SAVE. Test the results after some time by doing:
Code:
grep "Local IP Spoof" /var/log/exim_mainlog
yes, I finally figured it out earlier after looking at it again. made the new changes and it seems to work quiet nicely now.

thx's
Mickalo
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
Just a quick note on the ACL I posted above: investigating an incident on one of our servers turns up that the domain match HELO check breaks Mailman. Mailman sends out as localhost (127.0.0.1) and uses the server hostname as the HELO, which our ACL catches and denies.

So we have to set up the ACL to accept messages coming from localhost. So before the other checks in the ACL we need to accept locally hosted mail for things like Mailman. We could just do:
Code:
accept hosts = 127.0.0.1
But I think that would accept mail from anyone using 127.0.0.1 in their HELO - which we want to avoid since this is one way spammers push their mail through.

So I am (now - after changing it) testing this, which is similar to the exim standard check for mailman traffic:
Code:
# Accept mailman deliveries
  accept   condition    = \
           ${if and {{match{$sender_helo_name}{(.*)-bounces\+.*}} \
                     {exists {/usr/local/cpanel/3rdparty/mailman/lists/${lc:$1}/config.pck}}} \
                {yes}{no}}
           endpass
           log_message = $sender_helo_name resides in mailman-passed
The code is pasted above all else in the "check_helo:" ACL block. It goes right after "check_helo:" and right before "# HELO is empty or not sent". So the top of your ACL looks like this:
Code:
check_helo:

# Accept mailman deliveries
  accept   condition    = \
           ${if and {{match{$sender_helo_name}{(.*)-bounces\+.*}} \
                     {exists {/usr/local/cpanel/3rdparty/mailman/lists/${lc:$1}/config.pck}}} \
                {yes}{no}}
           endpass
           log_message = $sender_helo_name resides in mailman-passed

# HELO is empty or not sent
drop condition = ${if eq{$sender_helo_name}{}}
Open to suggestions from anyone who knows better but I think this might be the fix I need... But if I'm wrong, or you know a better way, feel free to chime in! :)
 
Last edited:

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
Hmm, no that's not working either. Create a mailing list and the creation announce email to the admin gets blocked... Happy New Year to me - this stinks! :)

Time to spend some quality ticks with the family and will think on this one...
 

isputra

Well-Known Member
May 3, 2003
574
0
166
Mbelitar
I use this rule :

# Someone is trying to spoof a domain on the server
deny condition = ${if match_domain{$sender_helo_name}\
{+local_domains}}
message = Forged HELO: you are not $sender_helo_name
log_message = Forged HELO: $sender_helo_name Spoof Attempt

And i have my client sending email from office pc with "clientoffice-sb2003.ClientOffice.local" as hostname rejected by this rule.

how to solve this problem ?
 

mctDarren

Well-Known Member
Jan 6, 2004
665
8
168
New Jersey
cPanel Access Level
Root Administrator
I use this rule :

# Someone is trying to spoof a domain on the server
deny condition = ${if match_domain{$sender_helo_name}\
{+local_domains}}
message = Forged HELO: you are not $sender_helo_name
log_message = Forged HELO: $sender_helo_name Spoof Attempt

And i have my client sending email from office pc with "clientoffice-sb2003.ClientOffice.local" as hostname rejected by this rule.

how to solve this problem ?
Does "clientoffice-sb2003.ClientOffice.local" match one of your local domains on the server? Is it a FQDN? If they have their domain name in their HELO, but it's not a FQDN then technically they are breaking RFC. But I know it can be tough to tell a customer they might be the cause. :)

Perhaps if we place an accept for authenticated users above all the rules it would help to solve your problem?
Code:
check_helo:

accept authenticated = *
 

isputra

Well-Known Member
May 3, 2003
574
0
166
Mbelitar
Yes, it's hard to tell my client that they are breaking RFC.

His domain for example using clientoffice.com and his office pc using clientoffice.local

Now i add accept authenticated = * and waiting for the result.
 

isputra

Well-Known Member
May 3, 2003
574
0
166
Mbelitar
using accept authenticated = * all my clients email can't received any email