📜 ⬆️ ⬇️

Fighting spam with standard mailer tools (using Exim for example)

I regularly come across articles about screwing anti-spam systems (for example, spamassassin and similar) to mailboxes. Every time, looking at these bundles and a bunch of problems that they bring, I “shrug” and sincerely do not understand why all this? Spam can be very effectively hacked directly by the postal forces, without the involvement of third-party programs, some of which still require lengthy training (as far as I know, but I can be wrong - I do not use them).

The method of catching spam, which I will describe in this article, is about 97% effective. It has been tested on 10 ke servers, and has been running for at least seven years.

I will give all configuration examples for the Exim mail server compiled with mysql support. But transfer them to the same postfix is ​​not difficult.

Users are stored in the database. In order not to clutter the article the structure of the database in the attached file ( link ). The structure is taken from earlier versions of postfixadmin. In order to be convenient to adminit users.
')
To begin with, we initialize several variables that will be used in our mail checks. Variable names are quite “talking”.

domainlist local_domains = ${lookup mysql{SELECT `domain` FROM `domain` WHERE `domain`='${domain}' AND `active`='1'}} domainlist relay_to_domains = ${lookup mysql{SELECT `domain` FROM `domain` WHERE `domain`='${domain}' AND `active`='1'}} 


Specify to the mailer the rules by which the mail will be checked.
 acl_smtp_rcpt = acl_check_rcpt acl_smtp_data = acl_check_data 


Headers and text of the letter directly.

Well, now let's start checking our mail. We start with the headlines.

 acl_check_rcpt: #      ,   TCP/IP accept hosts = : #        @; %; !; /; |. deny message = "Incorrect symbol in address" domains = +local_domains local_parts = ^[.] : ^.*[@%!/|] #      : deny message = "Incorrect symbol in address" domains = !+local_domains local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./ #          accept local_parts = postmaster domains = +local_domains # ,     (    ) require verify = sender #  ,      (HELO/EHLO) deny message = "HELO/EHLO require by SMTP RFC" condition = ${if eq{$sender_helo_name}{}{yes}{no}} #    ,   accept authenticated = * #  ,    IP  HELO deny message = "Your IP in HELO - access denied!" #    ,   relay_from_hosts hosts = * : !+relay_from_hosts condition = ${if eq{$sender_helo_name}{$sender_host_address} \ {true}{false}} #    *adsl*; *dialup*; *pool*;.... deny message = "your hostname is bad (adsl, poll, ppp & etc)." condition = ${if match{$sender_host_name} \ {adsl|dialup|pool|peer|dhcp} \ {yes}{no}} #  ,   -. deny message = "you in blacklist - $dnslist_domain" hosts = !+relay_from_hosts dnslists = dul.dnsbl.sorbs.net : \ sbl-xbl.spamhaus.org #      .     . warn set acl_m0 = 0 logwrite = "ACL m0 set default as $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name (domain in e-mail = $sender_address_domain)" #     Vigra,    ,      # 200  warn condition = ${if match{$h_subject} \ {viagra} \ {yes}{no}} set acl_m0 = ${eval:$acl_m0+200} logwrite = "STAGE0: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - VIAGRA!!!!" #   HELO    DNS -       # 30  warn condition = ${if !eq{$sender_helo_name}{$sender_host_name}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+30} logwrite = "STAGE1: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - reverse zone not match with HELO" #       # 30  warn condition = ${if eq{$host_lookup_failed}{1}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+30} logwrite = "STAGE2: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - no reverse zone for host" #        .   4-      # 40  warn condition = ${if match{$sender_host_name} \ {\N((?>\w+[\.|\-]){4,})\N}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+40} logwrite = "STAGE3: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - more dots or defice in name" #     .  30    # 10  warn condition = ${if >{${strlen:$sender_address}}{30}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+10} logwrite = STAGE4: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with HELO=$sender_helo_name \ - many big sender address [$sender_address] #      .        . ,   ,      #        ,        # 60  warn condition = ${lookup{$sender_host_name} \ wildlsearch{/usr/local/etc/exim/dialup_hosts} \ {yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+60} logwrite = "STAGE5: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ host=$sender_helo_name - dialup, ppp & etc..." #     HELO # 60  warn condition = ${lookup{$sender_helo_name} \ wildlsearch{/usr/local/etc/exim/dialup_hosts} \ {yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+60} logwrite = "STAGE6: ACL m0 set = $acl_m0 for \ host=$sender_host_name [$sender_host_address] with \ HELO=$sender_helo_name - dialup, ppp & etc..." #        HELO # 150  warn condition = ${if !eq{${lookup mysql{SELECT 1 FROM \ `list_top_level_domains` WHERE `zone` = \ LCASE(CONCAT('.', SUBSTRING_INDEX( \ '${quote_mysql:$sender_helo_name}', \ '.', -1)))}}}{1}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m0 = ${eval:$acl_m0+150} logwrite = non-existent domain in HELO - \ '$sender_helo_name' setting acl_m0 = $acl_m0 warn set acl_m2 = 0 # ,          . #    whitelist       warn condition = ${if eq{${lookup mysql{SELECT 1 FROM `sended_list` \ WHERE `user_to` = \ LCASE('${quote_mysql:$sender_address}') \ AND `user_from` \ = LCASE('${quote_mysql:$local_part@$domain}') \ AND `last_mail_timestamp` < `last_mail_timestamp` \ + (60*24*60*60) LIMIT 1}}}{1}{yes}{no}} condition = ${lookup mysql{INSERT IGNORE INTO `domain_whitelist` \ (`domainname`, `domain_ip`, `added_timestamp`, \ `last_mail_timestamp`, `mail_count`) VALUES \ (LCASE('${quote_mysql:$sender_address_domain}'), \ '${quote_mysql:$sender_host_address}', \ UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), '1') \ ON DUPLICATE KEY UPDATE \ `last_mail_timestamp` = UNIX_TIMESTAMP(), \ `mail_count` = `mail_count` + 1}} hosts = !+relay_from_hosts : * set acl_m2 = 1 logwrite = STAGE7: $sender_address ==> $local_part@$domain; \ setting acl_m2 = $acl_m2; WHITELIST for this addresses #     whitelist warn condition = ${if eq{${lookup mysql{SELECT 1 \ FROM `domain_whitelist` \ WHERE `domain_ip` = \ '${quote_mysql:$sender_host_address}' \ LIMIT 1}}}{1}{yes}{no}} hosts = !+relay_from_hosts : * set acl_m2 = 1 logwrite = STAGE8: $sender_address ==> $local_part@$domain; \ setting acl_m2 = $acl_m2; WHITELIST for ALL domains #         whitelist        warn condition = ${if eq{$acl_m2}{1}{yes}{no}} logwrite = Resetting acl_m0 $acl_m0 --> 0, host in whitelist \ ($sender_address ==> $local_part@$domain) set acl_m0 = 0 #     .   ,    ACL accept domains = +local_domains endpass message = "In my mailserver not stored this user" verify = recipient #      accept domains = +relay_to_domains endpass message = "main server not know relay to this address" verify = recipient #       relay_from_hosts accept hosts = +relay_from_hosts #      -     deny message = "This is not open relay" 


We now turn to checking the body of the letter.

 acl_check_data: deny message = contains $found_extension file (blacklisted). demime = com:vbs:bat:pif:scr:exe deny message = This message contains a MIME error ($demime_reason) demime = * condition = ${if >{$demime_errorlevel}{2}{1}{0}} deny message = This message contains NUL characters log_message = NUL characters! condition = ${if >{$body_zerocount}{0}{1}{0}} deny message = Incorrect headers syntax hosts = !+relay_from_hosts:* !verify = header_syntax #         .     99, ,   . # ,   ,    ,      #  -       ,       :)       #     #deny message = Possible SPAM message # log_message = Possible SPAM message # condition = ${if >{$acl_m0}{99}{yes}{no}} #   accept 


Exim has a system filter mechanism. That's where we add

 if $acl_m0 matches ^\\d+ then logwrite "FILTER: debug - digit in variable acl_m0 = $acl_m0 (after first if)" if $acl_m0 is above 99 then headers add "X-Spam-Description: if spam count >= 100 - this is spam" headers add "X-Spam-Count: $acl_m0" headers add "Old-Subject: $h_subject:" headers remove "Subject" headers add "Subject: (*** SPAM ***) $h_old-subject:" headers add "X-Spam: YES" logwrite "EXIM FILTER: Spam count = $acl_m0 ; Added SPAM header" endif endif 


Those. we insert a "(*** SPAM ***)" at the beginning of the subject line, according to which users' users will already sort spam.
As you can see, the set of rules is not great, but it allows you to effectively filter spam without setting up any additional systems. Of course, there is a possibility of false positives, but after the first letter from our user to the one who was accidentally included in spam, he gets into whitelist.

I repeat, according to my observations, such fairly simple rules do not miss about 97% of spam.

Source: https://habr.com/ru/post/179833/


All Articles