SPFCheck : Allow to test an IP against the SPF DNS entries sets
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5940 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
208
Tests/spfcheckTest.php
Normal file
208
Tests/spfcheckTest.php
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<?php
|
||||||
|
/** DomFramework
|
||||||
|
* @package domframework
|
||||||
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** Test the spfcheck tools
|
||||||
|
*/
|
||||||
|
class spfcheckTest extends PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function test_getRecords_NoSPF ()
|
||||||
|
{
|
||||||
|
$this->expectException ("Exception",
|
||||||
|
"Can not find a valid SPF TXT entry in DNS for domain ".
|
||||||
|
"'notfound.tester.fournier38.fr'", 403);
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("notfound.tester.fournier38.fr");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_SPFReject ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("reject.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("reject.spf.tester.fournier38.fr" =>
|
||||||
|
array ("-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_Loop ()
|
||||||
|
{
|
||||||
|
$this->expectException ("Exception",
|
||||||
|
"SPFCheck : Too much DNS requests (30 >= 30)", 500);
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("loop.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res, array ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_Include_emptyInclude ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("includeempty.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("includeempty.spf.tester.fournier38.fr" =>
|
||||||
|
array ("include:" => array (), "-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_Redirect_emptyRedirect ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("redirectempty.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("redirectempty.spf.tester.fournier38.fr" =>
|
||||||
|
array ("redirect=" => array (), "-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_MX_emptyMX ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("mx.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("mx.spf.tester.fournier38.fr" =>
|
||||||
|
array ("mx" => array (), "-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_MX_validMX ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("mxvalid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("mxvalid.spf.tester.fournier38.fr" => array (
|
||||||
|
"mx:tester.fournier38.fr" => array (
|
||||||
|
"2a01:e0a:234:3930::103",
|
||||||
|
"2a01:e0a:289:3090::206",
|
||||||
|
"82.64.55.197",
|
||||||
|
"82.64.75.195"),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_A_emptyA ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("a.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("a.spf.tester.fournier38.fr" =>
|
||||||
|
array ("a" => array (), "-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_A_validA ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("avalid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("avalid.spf.tester.fournier38.fr" => array (
|
||||||
|
"a:tester.fournier38.fr" => array (
|
||||||
|
"2a01:e0a:234:3930::100",
|
||||||
|
"82.64.55.197",),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP4_emptyIP4 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip4empty.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip4empty.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip4:" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP4_invalidIP4 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip4invalid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip4invalid.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip4:0::1" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP4_validIP4 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip4valid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip4valid.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip4:192.168.1.1" => array ("192.168.1.1"),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP6_emptyIP6 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip6empty.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip6empty.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip6:" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP6_invalidIP6 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip6invalid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip6invalid.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip6:192.168.1.1" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_IP6_validIP6 ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ip6valid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ip6valid.spf.tester.fournier38.fr" => array (
|
||||||
|
"ip6:0::1" => array ("0::1"),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_PTR ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("ptrvalid.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("ptrvalid.spf.tester.fournier38.fr" => array (
|
||||||
|
"ptr" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_All_Multiple ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("allmultiple.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("allmultiple.spf.tester.fournier38.fr" => array (
|
||||||
|
"+all" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_All_NotEnd ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("allnotend.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("allnotend.spf.tester.fournier38.fr" => array (
|
||||||
|
"+all" => array (),
|
||||||
|
"mx" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_All_NotSet ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("allnotset.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("allnotset.spf.tester.fournier38.fr" => array (
|
||||||
|
"mx" => array ())));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_getRecords_Unknown ()
|
||||||
|
{
|
||||||
|
$spfcheck = new spfcheck ();
|
||||||
|
$res = $spfcheck->getRecords ("unknown.spf.tester.fournier38.fr");
|
||||||
|
$this->assertSame ($res,
|
||||||
|
array ("unknown.spf.tester.fournier38.fr" => array (
|
||||||
|
"unknown" => array (),
|
||||||
|
"-all" => array ())));
|
||||||
|
}
|
||||||
|
}
|
||||||
472
spfcheck.php
Normal file
472
spfcheck.php
Normal file
@@ -0,0 +1,472 @@
|
|||||||
|
<?php
|
||||||
|
/** DomFramework
|
||||||
|
* @package domframework
|
||||||
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
||||||
|
*/
|
||||||
|
|
||||||
|
require ("domframework/ipaddresses.php");
|
||||||
|
|
||||||
|
/** This class allow to get a SPF record for a domain and check an IP against
|
||||||
|
* the rules set in SPF record.
|
||||||
|
* It also says in which rule the IP match
|
||||||
|
* RFC 7208
|
||||||
|
* SPF Format :
|
||||||
|
* v=spf1 (ip4:[0-9.]+(/\d+)|ip6:[0-9a-f:]+(/\d+)|mx(:\S+)|
|
||||||
|
* a(:\S+)|
|
||||||
|
* redirect=\S+|include:\S+)
|
||||||
|
*/
|
||||||
|
class spfcheck
|
||||||
|
{
|
||||||
|
////////////////////
|
||||||
|
// PROPERTIES //
|
||||||
|
////////////////////
|
||||||
|
/** The stack of errors detected
|
||||||
|
*/
|
||||||
|
private $errors = array ();
|
||||||
|
/** The [+-~]all parameter first get
|
||||||
|
*/
|
||||||
|
private $catchAll = "";
|
||||||
|
/** The domain catchAll
|
||||||
|
*/
|
||||||
|
private $catchAllDomain = "";
|
||||||
|
/** The rule matching the search
|
||||||
|
*/
|
||||||
|
private $matchRule = "";
|
||||||
|
/** The IP records get from SPF record
|
||||||
|
*/
|
||||||
|
private $ipRecords = array ();
|
||||||
|
/** Store all the DNS requests done
|
||||||
|
*/
|
||||||
|
private $dnsRequests = array ();
|
||||||
|
|
||||||
|
/** Set the DNS maximum number of requests
|
||||||
|
*/
|
||||||
|
const dnsRequestsMax = 30;
|
||||||
|
|
||||||
|
////////////////////////
|
||||||
|
// PUBLIC METHODS //
|
||||||
|
////////////////////////
|
||||||
|
/** Get all the IP entries set in all the blocks of the SPF for provided
|
||||||
|
* domain. Manage all the redirections, and get extract all the MX, IP4, IP6
|
||||||
|
* content.
|
||||||
|
* Set also the $this->errors and $this->catchAll properties available with
|
||||||
|
* the associated getters
|
||||||
|
* @param string $domain The domain to check
|
||||||
|
* @return array (The netmasks to match, the last all)
|
||||||
|
*/
|
||||||
|
public function getRecords ($domain)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->errors = array ();
|
||||||
|
$this->dnsRequests = array ();
|
||||||
|
$this->catchAll = "";
|
||||||
|
$this->ipRecords = $this->getRecordsRecurse ($domain);
|
||||||
|
if ($this->catchAll === "")
|
||||||
|
$this->errors[$domain] = dgettext ("domframework",
|
||||||
|
"No catch all defined for the domain");
|
||||||
|
return $this->ipRecords;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Try to match the provided IP address against the $domain SPF record to
|
||||||
|
* be get
|
||||||
|
* @param string $domain The domain to check
|
||||||
|
* @param string $ip The IPv4 or IPv6 address to check against the SPF record
|
||||||
|
* @return string PASS/FAIL/SOFTFAIL
|
||||||
|
*/
|
||||||
|
public function ipCheckToSPF ($domain, $ip)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ips = $this->getRecords ($domain);
|
||||||
|
if (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false)
|
||||||
|
$ipType = "ipv4";
|
||||||
|
elseif (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false)
|
||||||
|
$ipType = "ipv6";
|
||||||
|
else
|
||||||
|
throw new \Exception (dgettext ("domframework",
|
||||||
|
"SFPCheck : Invalid IP address provided : Not Ipv4 neither IPv6"), 403);
|
||||||
|
$ipaddresses = new ipaddresses ();
|
||||||
|
foreach ($ips as $key => $sub)
|
||||||
|
{
|
||||||
|
foreach ($sub as $part => $spfips)
|
||||||
|
{
|
||||||
|
foreach ($spfips as $ipToTest)
|
||||||
|
{
|
||||||
|
@list ($ipToTest, $mask) = explode ("/", $ipToTest, 2);
|
||||||
|
if ($ipType === "ipv4")
|
||||||
|
{
|
||||||
|
if (! filter_var ($ipToTest, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4))
|
||||||
|
continue;
|
||||||
|
if ($mask === null)
|
||||||
|
$mask = "32";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (! filter_var ($ipToTest, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6))
|
||||||
|
continue;
|
||||||
|
if ($mask === null)
|
||||||
|
$mask = "128";
|
||||||
|
}
|
||||||
|
if ($ipaddresses->ipInNetwork ($ip, $ipToTest, $mask))
|
||||||
|
{
|
||||||
|
$this->matchRule = "$key/$part";
|
||||||
|
return "PASS";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($this->catchAll === "-all")
|
||||||
|
{
|
||||||
|
$this->matchRule = "$this->catchAllDomain/$this->catchAll";
|
||||||
|
return "FAIL";
|
||||||
|
}
|
||||||
|
if ($this->catchAll === "~all")
|
||||||
|
{
|
||||||
|
$this->matchRule = "$this->catchAllDomain/$this->catchAll";
|
||||||
|
return "SOFTFAIL";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/////////////////
|
||||||
|
// GETTERS //
|
||||||
|
/////////////////
|
||||||
|
/** Get the errors detected when reading the SPF record
|
||||||
|
*/
|
||||||
|
public function getErrors ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return $this->errors;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get the DNS requests done to get the SPF records
|
||||||
|
*/
|
||||||
|
public function getDNSRequests ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return $this->dnsRequests;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get the number of DNS queries
|
||||||
|
*/
|
||||||
|
public function getDNSRequestNumber ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return count ($this->dnsRequests);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get default case (if set)
|
||||||
|
*/
|
||||||
|
public function getDefaultCase ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return $this->catchAll;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get the matching rule when testing an IP against a SPF domain record
|
||||||
|
*/
|
||||||
|
public function getMatchRule ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return $this->matchRule;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get the IPs from the SPF records
|
||||||
|
*/
|
||||||
|
public function getIpRecords ()
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
return $this->ipRecords;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// PRIVATE METHODS //
|
||||||
|
/////////////////////////
|
||||||
|
/** Get all the IP entries set in all the blocks of the SPF for provided
|
||||||
|
* domain. Manage all the redirections, and get extract all the MX, IP4, IP6
|
||||||
|
* content.
|
||||||
|
* Set also the $this->errors and $this->catchAll properties available with
|
||||||
|
* the associated getters
|
||||||
|
* Recursive, can be called by itself
|
||||||
|
* @param string $domain The domain to check
|
||||||
|
* @return array (The netmasks to match, the last all)
|
||||||
|
*/
|
||||||
|
private function getRecordsRecurse ($domain)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$records = array ();
|
||||||
|
$localAll = "";
|
||||||
|
// TODO : Catch timeouts !
|
||||||
|
foreach ($this->dns_get_record ($domain, DNS_TXT, $domain) as $record)
|
||||||
|
{
|
||||||
|
if (substr ($record, 0, 7) !== "v=spf1 ")
|
||||||
|
continue;
|
||||||
|
// Do not allow more than 1 SPF record by domain
|
||||||
|
if (key_exists ($domain, $records))
|
||||||
|
$this->errors[$domain][] = sprintf (dgettext ("domframework",
|
||||||
|
"More than one SPF record for domain '%s'"), $domain);
|
||||||
|
$records[$domain] = $record;
|
||||||
|
}
|
||||||
|
if (empty ($records))
|
||||||
|
throw new \Exception (sprintf (dgettext ("domframework",
|
||||||
|
"Can not find a valid SPF TXT entry in DNS for domain '%s'"), $domain),
|
||||||
|
403);
|
||||||
|
$ips = array ();
|
||||||
|
foreach ($records as $domain => $record)
|
||||||
|
{
|
||||||
|
$split = preg_split ("#\s+#", $record);
|
||||||
|
foreach ($split as $nb => $part)
|
||||||
|
{
|
||||||
|
if ($part === "v=spf1")
|
||||||
|
continue;
|
||||||
|
// "redirect=" part
|
||||||
|
$ips[$domain][$part] = array ();
|
||||||
|
if (stripos ($part, "redirect=") === 0)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ext = substr ($part, 9);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid redirect set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ips = $ips + $this->getRecordsRecurse ($ext);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "include:" part
|
||||||
|
elseif (stripos ($part, "include:") === 0)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ext = substr ($part, 8);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid include set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ips = $ips + $this->getRecordsRecurse ($ext);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "mx:" / "mx" part
|
||||||
|
elseif (stripos ($part, "mx:") === 0 || strtolower ($part) === "mx")
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$partWithDomain = $part;
|
||||||
|
if ($partWithDomain === "mx")
|
||||||
|
$partWithDomain = "mx:$domain";
|
||||||
|
$ext = substr ($partWithDomain, 3);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid mx set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($this->dns_get_record ($ext, DNS_MX, $domain) as $record)
|
||||||
|
{
|
||||||
|
foreach ($this->dns_get_record ($record, DNS_A | DNS_AAAA, $domain)
|
||||||
|
as $ip)
|
||||||
|
{
|
||||||
|
$ips[$domain][$part][] = $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort ($ips[$domain][$part]);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "ip4:" part
|
||||||
|
elseif (stripos ($part, "ip4:") === 0)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ext = substr ($part, 4);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid ip4 set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@list ($ip, $mask) = explode ("/", $ext);
|
||||||
|
$mask = ($mask === null) ? $mask = "" : $mask = "/$mask";
|
||||||
|
if (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) === false)
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid ip4 set for domain '%s' : Not a valid IPv4 '%s'"),
|
||||||
|
$domain, $ext);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ips[$domain][$part][] = $ip.$mask;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "ip6:" part
|
||||||
|
elseif (stripos ($part, "ip6:") === 0)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ext = substr ($part, 4);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid ip6 set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
@list ($ip, $mask) = explode ("/", $ext);
|
||||||
|
$mask = ($mask === null) ? $mask = "" : $mask = "/$mask";
|
||||||
|
if (filter_var ($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false)
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid ip6 set for domain '%s' : Not a valid IPv6 '%s'"),
|
||||||
|
$domain, $ext);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ips[$domain][$part][] = $ip.$mask;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "ptr:" MUST NOT BE USED
|
||||||
|
elseif (stripos ($part, "ptr:") === 0 || strtolower ($part) === "ptr")
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid ptr set for domain '%s' : PTR must not be used anymore ".
|
||||||
|
"(see RFC7208)"), $domain, $part);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "a:" part
|
||||||
|
elseif (stripos ($part, "a:") === 0 || strtolower ($part) === "a")
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$partWithDomain = $part;
|
||||||
|
if ($partWithDomain === "a")
|
||||||
|
$partWithDomain = "a:$domain";
|
||||||
|
$ext = substr ($partWithDomain, 2);
|
||||||
|
if (! is_string ($ext) || trim ($ext) === "")
|
||||||
|
{
|
||||||
|
$this->errors[$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Invalid A set form domain '%s' : empty"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($this->dns_get_record ($ext, DNS_A | DNS_AAAA, $domain) as
|
||||||
|
$record)
|
||||||
|
{
|
||||||
|
$ips[$domain][$part][] = $record;
|
||||||
|
}
|
||||||
|
sort ($ips[$domain][$part]);
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
// "-all" / "~all" / "+all" part
|
||||||
|
elseif (strtolower ($part) === "-all" ||
|
||||||
|
strtolower ($part) === "~all" ||
|
||||||
|
strtolower ($part) === "+all")
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$ips[$domain][$part] = array ();
|
||||||
|
if ($localAll !== "")
|
||||||
|
{
|
||||||
|
$this->errors [$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Multiple 'all' definitions for domain '%s'"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($nb < count ($split) -1)
|
||||||
|
{
|
||||||
|
$this->errors [$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"'all' must be the last part of the record for domain '%s'"),
|
||||||
|
$domain);
|
||||||
|
}
|
||||||
|
$localAll = $part;
|
||||||
|
$this->catchAll = $part;
|
||||||
|
$this->catchAllDomain = $domain;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->errors [$domain][$part] = sprintf (dgettext ("domframework",
|
||||||
|
"Unknown record part for domain '%s' : '%s'"), $domain, $part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $ips;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
|
||||||
|
/** Get the requested hostname and get the type
|
||||||
|
* @param string $hostname The hostname to get
|
||||||
|
* @param integer $type The type to get
|
||||||
|
* @param string $domain The domain to impact the errors
|
||||||
|
* @return array (array (ip (for A), target (for TXT));
|
||||||
|
*/
|
||||||
|
private function dns_get_record ($hostname, $type, $domain)
|
||||||
|
// {{{
|
||||||
|
{
|
||||||
|
$typeStr = "";
|
||||||
|
switch ($type)
|
||||||
|
{
|
||||||
|
case DNS_TXT: $typeStr = "TXT"; break;
|
||||||
|
case DNS_MX: $typeStr = "MX"; break;
|
||||||
|
case DNS_A | DNS_AAAA: $typeStr = "A | AAAA"; break;
|
||||||
|
default: throw new \Exception (dgettext ("domframework",
|
||||||
|
"SPFCheck : Invalid type for DNS get record"), 500);
|
||||||
|
}
|
||||||
|
if (count ($this->dnsRequests) >= self::dnsRequestsMax)
|
||||||
|
throw new \Exception (sprintf (dgettext ("domframework",
|
||||||
|
"SPFCheck : Too much DNS requests (%d >= %d)"),
|
||||||
|
count ($this->dnsRequests), self::dnsRequestsMax), 500);
|
||||||
|
$this->dnsRequests[] = "$hostname, $typeStr";
|
||||||
|
$res = array ();
|
||||||
|
if ($type === DNS_TXT)
|
||||||
|
{
|
||||||
|
foreach (dns_get_record ($hostname, DNS_TXT) as $record)
|
||||||
|
{
|
||||||
|
if (! isset ($record["txt"]))
|
||||||
|
{
|
||||||
|
$this->errors[$somain][] = sprintf (dgettext ("domframework",
|
||||||
|
"No TXT record for domain '%s'"), $domain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$res[] = $record["txt"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($type === DNS_MX)
|
||||||
|
{
|
||||||
|
foreach (dns_get_record ($hostname, DNS_MX) as $record)
|
||||||
|
{
|
||||||
|
if (! isset ($record["target"]))
|
||||||
|
{
|
||||||
|
$this->errors[$domain][] = sprintf (dgettext ("domframework",
|
||||||
|
"No MX record for domain '%s' : '%s' not found"), $domain,
|
||||||
|
$hostname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$res[] = $record["target"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($type === DNS_A | DNS_AAAA)
|
||||||
|
{
|
||||||
|
$records = dns_get_record ($hostname, DNS_A | DNS_AAAA);
|
||||||
|
if (empty ($records))
|
||||||
|
$this->errors[$domain][] = sprintf (dgettext ("domframework",
|
||||||
|
"No IP record for domain '%s' : '%s' not found"), $domain,
|
||||||
|
$hostname);
|
||||||
|
foreach ($records as $record)
|
||||||
|
{
|
||||||
|
if (! isset ($record["ip"]) && ! isset ($record["ipv6"]))
|
||||||
|
{
|
||||||
|
$this->errors[$domain][] = sprintf (dgettext ("domframework",
|
||||||
|
"No IP record for domain '%s' : '%s' not IPv4 nor IPv6"), $domain,
|
||||||
|
$hostname);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$ip = isset ($record["ip"]) ? $record["ip"] : $record["ipv6"];
|
||||||
|
$res[] = $ip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new \Exception ("Can not get unknown type : $type");
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
// }}}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user