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

Категория: Решение задач | Скилл: 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:

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 (ключ указан с правками):

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

Проверка SPF

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

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 запросов, и вот еще.

//полчение всех 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");
Я Виталий, ник в сети Byurrer.
Увлекаюсь программированием, веду интересные проекты, пишу здесь об интересующих меня вещах: о работе, проектах, увлечениях и проффесиональном развитии.
Мое резюме

Проекты
SkyXEngine, PHP-API, S4G
Категории
В разработке :)
Популярное
В разработке :)