прикручиваем spf к postfix-у Уже достаточно давно к postfix-у прикручен у меня грейлистинг sqlgrey. Работает, вроде, исправно, спамеров давит, почту вот только порядком задерживает при доставке. А поскольку заносить ручками блоки адресов "хороших" отправителей в whitelist сильно влом, возникло желание прикрутить проверку spf перед грейлистингом. Чтобы те, кто успешно прошел проверку spf (pass) доставлялись сразу, кто не прошел (fail) шли нафиг, а остальные (те у кого нет записи spf, а также SoftFail и Neutral) мариновались грейлистингом. С SoftFail и Neutral я жесток, поскольку большинство spf записей оканчиваются на ~all или ?all и если их принимать, то эффективность проверки spf будет стремиться в ноль. Для проверки spf я буду использовать postfix-policyd-spf-perl, который можно взять отсюда. Для работы ему нужен модуль Mail::SPF, который, поскольку у меня установлен репозитарий rpmforge, устанавливается одним движением: # yum install perl-Mail-SPF После чего распаковываем скаченный файл и немножко правим postfix-policyd-spf-perl, что бы его поведение соответствовало желаемому. В подпрограмме sender_policy_framework находим строки
# Reject on HELO fail. Defer on HELO temperror if message would otherwise # be accepted. Use the HELO result and return for null sender. if ($helo_result->is_code('fail')) { syslog( info => "%s: SPF %s: HELO/EHLO: %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "550 $helo_authority_exp"; } elsif ($helo_result->is_code('temperror')) { syslog( info => "%s: SPF %s: HELO/EHLO: %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp"; } elsif ($attr->{sender} eq '') { syslog( info => "%s: SPF %s: HELO/EHLO (Null Sender): %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "PREPEND $helo_spf_header" unless $cache->{added_spf_header}++; } и приводим их к следующему виду
# Reject on HELO fail. Defer on HELO temperror if message would otherwise # be accepted. Use the HELO result and return for null sender. if ($helo_result->is_code('fail')) { syslog( info => "%s: SPF %s: HELO/EHLO: %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "550 $helo_authority_exp"; } elsif ($helo_result->is_code('temperror')) { syslog( info => "%s: SPF %s: HELO/EHLO: %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "DEFER_IF_PERMIT SPF-Result=$helo_local_exp"; } elsif ($attr->{sender} eq '' && $helo_result->is_code('pass')) { syslog( info => "%s: SPF %s: HELO/EHLO (Null Sender): %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return 'OK'; } elsif ($attr->{sender} eq '') { syslog( info => "%s: SPF %s: HELO/EHLO (Null Sender): %s", $attr->{queue_id}, $helo_result, $attr->{helo_name} ); return "DUNNO" } Эти строки связаны с spf-проверкой заголовка helo. В оригинальной версии, если не произошло spf-отказа (fail) и все нормально работает, то при пустом отправителе, вне зависимочти от статуса (нет spf-записи, pass, softfail или neutral) в заголовки письма добавляется запись типа Received-SPF: pass (mail.domain.ru: xxx.xxx.xxx.xxx is authorized to use бла-бла-бла бла-бла-бла... и письмо проходит дальше. А дальше стоит грейлистинг... В поправленом варианте, при пустом отправителе, если helo прошло spf-проверку -- статус OK, письмо на доставку, если не прошло -- мариноваться в грейлистинг. Далее ищем строки
# Same approach as HELO.... syslog( info => "%s: SPF %s: Envelope-from: %s", $attr->{queue_id}, $mfrom_result, $attr->{sender} ); if ($mfrom_result->is_code('fail')) { return "550 $mfrom_authority_exp"; } elsif ($mfrom_result->is_code('temperror')) { return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp"; } else { return "PREPEND $mfrom_spf_header" unless $cache->{added_spf_header}++; } return; и меняем их на
# Same approach as HELO.... syslog( info => "%s: SPF %s: Envelope-from: %s", $attr->{queue_id}, $mfrom_result, $attr->{sender} ); if ($mfrom_result->is_code('fail')) { return "550 $mfrom_authority_exp"; } elsif ($mfrom_result->is_code('temperror')) { return "DEFER_IF_PERMIT SPF-Result=$mfrom_local_exp"; } elsif ($mfrom_result->is_code('pass')) { return 'OK'; } return; Тут тоже все просто. Проверка по отправителю. Вместо добавления аналогичного заголовка и отправки в грейлистинг всех, кто не fail, измененная версия прошедшим проверку -- статус OK, письмо на доставку, остальных мариноваться в грейлистинг. Fail изменения не затронули, они все также идут нафиг. Проверить работу модифицированного postfix-policyd-spf-perl можно запустив его (можно с ключиком -v), скармливая ему кусочками содержимое идущего в комплекте файлика test_cases и наблюдая за реакцией подопытного. Если все устраивает, копируем файлик postfix-policyd-spf-perl в /usr/local/lib/ - добавляем в файл /etc/postfix/master.cf
policy unix - n n - 0 spawn user=nobody argv=/usr/local/lib/postfix-policyd-spf-perlпользователь nobody тут используется для примера, лучше создать отдельного пользователя - конфигурируем проверку в /etc/postfix/main.cf
smtpd_recipient_restrictions = ... reject_unauth_destination check_policy_service unix:private/policy check_policy_service inet:127.0.0.1:2501 ... - и добавляем в него же policy_time_limit = 3600 - перегружаем postfix |