Используя iRedMail, столкнулся с необходимостью внести некоторые домены в белый список, чтобы по ним не проходила проверка greylist.
В случае с iRedMail за это отвечает сервис policyd, который хранит записи в mysql.
В двух словах про механизм greylist
В общих чертах механизм работает следующим образом. Почтовая система получает сообщение и проверяет отправителя, получателя и хост отправителя. В случае если в системе есть запись, что письма с такими параметрами уже приходили, письмо доставляется, если нет, то удаленному серверу отвечает примерно следующее: «Извини, сейчас твое письмо не могу принять, попробуй еще раз позже.», при этом отмечаем, что такой-то пытался доставить сообщение. Если попытаетсся это сделать, например, через 40 мин, значит очень хочет его доставить, и мы его пропустим. После этого терпеливо выжидаем данное время, и если удаленный сервер повторяет попытку, то запоминаем, что у данного отправителя все хорошо, и в след раз его сообщение примем. Именно так и поступит в большинстве случаев удаленный почтовый сервер. Спам рассылка же, как правило, ждать и повторять не будет, однако на практике все больше и больше спама прорывается, но речь не об этом. Далее речь пойдет про policyd, как реализацию данного механизма.
Если интересны подробности
Как это мешает пользователям
Очевидно, что это задержки в доставке писем. Кроме того, ситуацию усугубляет следующее.
Допустим пришло письмо
Nov 26 09:08:13 mail cbpolicyd[3451]: module=Greylisting, action=defer, host=94.100.179.58, helo=smtp3.mail.ru, from=, to=, reason=greylisted
Nov 26 09:08:13 mail postfix/smtpd[21794]: NOQUEUE: reject: RCPT from smtp3.mail.ru[94.100.179.58]: 451 4.7.1 <>: Recipient address rejected: Greylisting in effect, please come back later; from=<> to=<> proto=ESMTP helo=<smtp3.mail.ru>
После этого это же сообщение приходит совсем с другого сервера:
Nov 26 09:11:09 mail cbpolicyd[3451]: module=Greylisting, action=defer, host=94.100.181.184, helo=fallback1.mail.ru, from=, to=, reason=greylisted
Nov 26 09:11:09 mail postfix/smtpd[22176]: NOQUEUE: reject: RCPT from fallback1.mail.ru[94.100.181.184]: 451 4.7.1 <>: Recipient address rejected: Greylisting in effect, please come back later; from=<> to=<> proto=ESMTP helo=<fallback1.mail.ru>
И проверка greylist начинается с начала. Не очень приятно.
Как обновить whitelist
Для начала мне надо было понять какие домены нужно добавить.
я посмотрел так:
$ cat /var/log/maillog* | grep "rejected: Greylisting in effect" | awk '{print $24}' | awk -F@ '{print $2}' | sed 's/>//g' | sort | uniq -c | sort -n -k1
105 ya.ru
121 inbox.ru
125 bounce.linkedin.com
170 bk.ru
945 yandex.ru
1262 mail.ru
Затем определиться как их обрабатывать.
Для получения списка актуальных сетей для yandex.ru, mail.ru и т.д. логично было получать их SPF записи и добавлять их в таблицу policyd. Я придерживался этого плана.
В результате у меня появился следующий скрипт:
#!/usr/bin/python
# coding: utf-8
import dns.resolver
import MySQLdb
# mysql settings
DBHost = 'localhost'
DBUser = 'cluebringer'
DBPassword = 'password'
DBName = 'cluebringer'
domains = ['yandex.ru','mail.ru','gmail.com']
def get_networks(domain,ips=[],orig_domain=''):
def name2ip(name):
ans = dns.resolver.query(name, 'A')
out = []
for a in ans:
out.append("ip4:"+str(a))
return out
out = ips
answers = dns.resolver.query(domain, 'TXT')
answer = answers.response.to_text()
if answer.find('v=spf1') != -1:
tmp = answer[answer.find('v=spf1'):]
tmp = tmp[:tmp.find('"\n')]
tmp = tmp.replace('" "', "")
records = tmp.split(" ")
if orig_domain == '':
orig_domain = domain
for rec in records:
if rec.find('ip4') != -1:
out.append(rec)
else:
if rec.find('include') != -1:
domain = rec[rec.find(':')+1:]
get_networks(domain,out,orig_domain)
if rec.find('redirect') != -1:
domain = rec[rec.find('=')+1:]
get_networks(domain,out,orig_domain)
if rec.find('mx') != -1:
q = dns.resolver.query(orig_domain, 'MX')
for i in q:
i = i.to_text()[i.to_text().find(" ")+1:]
for j in name2ip(i):
out.append(j)
return out
else:
return False
def wl_insert(dbhost,dbuser,dbpwd,dbname,ip_array,comment):
table = 'greylisting_whitelist'
db = MySQLdb.connect(dbhost,dbuser,dbpwd,dbname)
cur = db.cursor()
values = ''
for i in ip_array:
i = i.replace('ip4:','')
values = values + '("SenderIP:'+i+'","'+comment+'",0),'
values = values[:-1] + ';'
cur.execute("insert ignore into `"+table+"` (Source,Comment,Disabled) VALUES "+ values)
db.commit()
db.close()
# MAIN
for i in domains:
networks = get_networks(i)
wl_insert(DBHost,DBUser,DBPassword,DBName,networks,i)
Хочу отметить, что скрипт не проверяет исключительные ситуации и в процессе работы ругается на дублирующие записи, однако работает:
whitelist-spf-update-v2.py:66: Warning: Duplicate entry 'SenderIP:216.239.32.0/19' for key 'Source'
cur.execute("insert ignore into `"+table+"` (Source,Comment,Disabled) VALUES "+ values)
Не забывайте про бекапы, т.к. скрипт точно обрабытывает не все записи, которые могут встретиться в TXT записи зоны домена.
Буду признателен, если у кого-нибудь хватит времени и сил допилить его до адекватного состояния.