прикручиваем 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


Источник статьи