Проверка SPF и DKIM домена на PHP

19.06.2020
Для разработки модуля массовых email рассылок сервиса uppleseen.com потребовалось проверять SPF и DKIM записи домена, так как пользователь осуществляя рассылку, мог бы указать любой домен, без этих записей все письма пойдут прямиком в спам (вообще-то не только по этому), а если речь идет о массовой рассылке значит это касается репутации сервера в глазах принимающих почтовых служб. Рисковать нельзя, надо проверять. Для проверки на php будет использована функция dns_get_record. Но в конце статьи есть вариант для получения DNS при помощи dig.

Теория

SPF (Sender Policy Framework) - метод проверки подлинности сервера отправителя. Кратко: владелец домена, от имени которого преполагается рассылка почты, указывает список серверов, которые могут осуществлять рассылку, и что делать если прислал чужой сервер. Указывается в TXT записи DNS домена отправителя. Ссылки на более подробное изучение вот и вот.

Пример SPF записи где допускается ip адрес из 'a' записи в DNS домена и хоста _spf.yandex.net, и отвергает все письма, отправители которых не проходят проверку SPF:

•••
plaintext
v=spf1 a include:_spf.yandex.net -all

DKIM (DomainKeys Identified Mail) - метод проверки подлинности отправителя с использованием асиметричной криптографии. Очень кратко: отправитель формирует заголовок (с подписью) DKIM-Signature и отправляет письмо вместе с ним, получатель на основании публичного ключа находящегося в TXT записи DNS домена (указанного в DKIM-Signature в теге 's' с постфиксом ._domainkey) магическим образом определяет подлинность подписи. Ссылки на почитать dkim.org, wiki, обобщенное описание, rfc6376. Указывается в TXT записи с именем которое требует отправляемый сервер (тег 's' в заголовке DKIM-Signature) на поддомене _domainkey (пример: ds-servers._domainkey).

Пример публичного ключа DKIM (ключ указан с правками):

•••
plaintext
v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDM05YTilNfwbfXpt4qxsk5ssayloExeD8VRE8bl33RGrekyn2a0kLFUZ5nlEdb6I8vO/GWe3b46R0enLl8PNi5XFsaI+lpBBuknwZdVs7PfnFsodh6dr15SJ0eVlns2b3C9i0qFYaJA2N1yueb2l4MwwEvz4wrU5ejmAbz5CgSqQIDAQAB

Проверка SPF

•••
php
function DomainCheckSPF($sDomain, $sSPFip) { //получение A и TXT записей домена $aRecords = dns_get_record($sDomain, DNS_A | DNS_TXT); print_r($aRecords); $aA = []; //массив A записей $sSPF = ""; //SPF запись //поиск всех A записей и SPF записи foreach($aRecords as $aRecord) { if($aRecord["type"] == "TXT" && stripos($aRecord["txt"], "v=spf1") !== FALSE) $sSPF = $aRecord["txt"]; if($aRecord["type"] == "A") $aA[] = $aRecord["ip"]; } //поиск всех ip4 адресов (если есть) if(preg_match_all("/ip4\:((?:[0-9]{1,3}[\.]){3}[0-9]{1,3})/", $sSPF, $aMatches, PREG_SET_ORDER) > 0) { //сравнение всех найденных ip адресов с нужным (который должен быть указан) foreach($aMatches as $aMatch) { if(strcasecmp($aMatch[1], $sSPFip) == 0) return true; } } //если в SPF записи есть 'a' значит допускается отправка со всех ip4 адресов в A записях if(preg_match("/\sa\s/", $sSPF)) { //сравнение со всеми ip4 адресами из A записей foreach($aA as $sIP) { if($sIP == $sSPFip) return true; } } //ни одна проверка не прошла, отправка писем с sSPFip адреса не разрешена return false; } //использование, вернет true/false DomainCheckSPF('byurrer.ru', '95.163.78.61');

Все просто, отправитель либо может отправлять с указанного сервера либо нет.

Проверка DKIM

•••
php
function DomainGetDKIM($sDomain) { //получение всех TXT записей $aRecords = dns_get_record("ds-servers._domainkey.$sDomain", DNS_TXT); $sDKIM = ""; //перебор всех записей и поиск DKIM foreach($aRecords as $aRecord) { if($aRecord["type"] == "TXT" && stripos($aRecord["txt"], "v=DKIM1") !== FALSE) { //поиск ключа if(preg_match("/p\=(.[^\s]*)/iu", $aRecord["txt"], $aMatches)) { $sDKIM = $aMatches[1]; break; } } } return $sDKIM; } //использование, вернет строку, если DKIM запись найдена тогда вернет ключ, если не найдена - пустую строку DomainGetDKIM('byurrer.ru');

В данном случае речь идет о получении записи из определенного поддомена, который каждый сервер-отправитель указывает свой (в данном случае ds-servers._domainkey), (получить DNS домена и всех его поддоменов штатными средствами php и прямым путем мне не удалось).

dig

Вместо php функции @%dns_get_record можно заюзать утилиту командной строки dig. Статья по использованию dig для DNS запросов, и вот еще.

•••
php
//полчение всех TXT записей (для DKIM) $sTXT = shell_exec("dig +short ds-servers._domainkey.$sDomain TXT"); //получение всех TXT записей (для SPF) $sTXT = shell_exec("dig +short $sDomain TXT"); //получение A записей $sTXT = shell_exec("dig +short $sDomain A");