1
|
|
|
<?php /** @noinspection PhpUndefinedFieldInspection */
|
2
|
|
|
|
3
|
|
|
/**
|
4
|
|
|
* RFC 4408 - Sender Policy Framework (SPF) for Authorizing Use of Domains in E-Mail, Version 1
|
5
|
|
|
*
|
6
|
|
|
* Updated by RFC 6652
|
7
|
|
|
* Obsoleted by RFC 7208
|
8
|
|
|
*
|
9
|
|
|
* @see https://tools.ietf.org/html/rfc4408
|
10
|
|
|
* @package Vanderlee\Comprehend\Library
|
11
|
|
|
*/
|
12
|
|
|
|
13
|
|
|
namespace Vanderlee\Comprehend\Library;
|
14
|
|
|
|
15
|
|
|
use Vanderlee\Comprehend\Builder\AbstractRuleset;
|
16
|
|
|
use Vanderlee\Comprehend\Parser\Parser;
|
17
|
|
|
use Vanderlee\Comprehend\Parser\Terminal\Integer;
|
18
|
|
|
|
19
|
|
|
require_once 'functions.php';
|
20
|
|
|
|
21
|
|
|
/**
|
22
|
|
|
* Class Rfc4408
|
23
|
|
|
*
|
24
|
|
|
* ABNF in official RFC specs does not take into account left hand recursion issues.
|
25
|
|
|
* Instances are fixed manually where appropriate.
|
26
|
|
|
*
|
27
|
|
|
* @property-read Parser record Complete SPF record
|
28
|
|
|
* @property-read Parser version SPF version tag
|
29
|
|
|
* @property-read Parser qnum Integer value between 0 and 255
|
30
|
|
|
* @property-read Parser IP4 IPv4 address with optional CIDR range
|
31
|
|
|
* @property-read Parser IP6 IPv6 address with optional CIDR range
|
32
|
|
|
*
|
33
|
|
|
* @package Vanderlee\Comprehend\Library
|
34
|
|
|
*/
|
35
|
|
|
class Rfc4408 extends AbstractRuleset
|
36
|
|
|
{
|
37
|
|
|
protected static $name = 'Rfc4408';
|
38
|
|
|
|
39
|
1 |
|
public function __construct($overwrites = [])
|
40
|
|
|
{
|
41
|
1 |
|
$abnf = new Rfc4234;
|
42
|
|
|
|
43
|
1 |
|
$ipv6 = new Rfc3513;
|
44
|
|
|
|
45
|
|
|
/*
|
46
|
|
|
* Normal rules
|
47
|
|
|
*/
|
48
|
|
|
$rules = [
|
49
|
|
|
// 4.5. Selecting Records
|
50
|
1 |
|
'record' => [$this->version, $this->terms, $this->SP],
|
|
|
|
|
51
|
1 |
|
'version' => 'v=spf1',
|
52
|
|
|
|
53
|
|
|
// 4.6.1. Term Evaluation
|
54
|
1 |
|
'terms' => star([plus($abnf->SP), c($this->directive, $this->modifier)]),
|
|
|
|
|
55
|
1 |
|
'directive' => [opt($this->qualifier), $this->mechanism],
|
|
|
|
|
56
|
1 |
|
'qualifier' => set('+-?~'),
|
57
|
1 |
|
'mechanism' => c($this->all, $this->include, $this->A, $this->MX, $this->PTR, $this->IP4, $this->IP6,
|
|
|
|
|
58
|
1 |
|
$this->exists), // @todo order?
|
|
|
|
|
59
|
1 |
|
'modifier' => c($this->redirect, $this->explanation, $this->unknown_modifier),
|
|
|
|
|
60
|
1 |
|
'unknown_modifier' => [$this->name, '=', $this->macro_string],
|
|
|
|
|
61
|
1 |
|
'name' => [$abnf->ALPHA, star(c($abnf->ALPHA, $abnf->DIGIT, '-', '_', '.'))],
|
62
|
|
|
|
63
|
|
|
// 5.1. "all"
|
64
|
1 |
|
'all' => 'all',
|
65
|
|
|
|
66
|
|
|
// 5.2. "include"
|
67
|
1 |
|
'include' => ['include', ':', $this->domain_spec],
|
|
|
|
|
68
|
|
|
|
69
|
|
|
// 5.3. "a"
|
70
|
1 |
|
'A' => ['a', opt(s(':', $this->domain_spec)), opt($this->dual_cidr_length)],
|
|
|
|
|
71
|
|
|
|
72
|
|
|
// 5.4. "mx"
|
73
|
1 |
|
'MX' => ['mx', opt(s(':', $this->domain_spec)), opt($this->dual_cidr_length)],
|
74
|
|
|
|
75
|
|
|
// 5.5. "ptr"
|
76
|
1 |
|
'PTR' => ['ptr', ':', $this->domain_spec],
|
77
|
|
|
|
78
|
|
|
// 5.6. "ip4" and "ip6"
|
79
|
1 |
|
'IP4' => ['ip4', ':', $this->ip4_network, opt($this->ip4_cidr_length)],
|
|
|
|
|
80
|
1 |
|
'IP6' => ['ip6', ':', $this->ip6_network, opt($this->ip6_cidr_length)],
|
|
|
|
|
81
|
1 |
|
'ip4_cidr_length' => ['/', plus($abnf->DIGIT)],
|
82
|
1 |
|
'ip6_cidr_length' => ['/', plus($abnf->DIGIT)],
|
83
|
1 |
|
'dual_cidr_length' => [opt($this->ip4_cidr_length), opt(['/', $this->ip6_cidr_length])],
|
84
|
1 |
|
'ip4_network' => s($this->qnum, '.', $this->qnum, '.', $this->qnum, '.', $this->qnum),
|
85
|
1 |
|
'qnum' => new Integer(0, 255),
|
86
|
1 |
|
'ip6_network' => $ipv6->ipv6_address,
|
87
|
|
|
|
88
|
|
|
// 5.7. "exists"
|
89
|
1 |
|
'exists' => ['exists', ':', $this->domain_spec],
|
90
|
|
|
|
91
|
|
|
// 6.1. redirect: Redirected Query
|
92
|
1 |
|
'redirect' => ['redirect', '=', $this->domain_spec],
|
93
|
|
|
|
94
|
|
|
// 6.2. exp: Explanation
|
95
|
1 |
|
'explanation' => ['exp', '=', $this->domain_spec],
|
96
|
|
|
|
97
|
|
|
// 8. Macros
|
98
|
1 |
|
'domain_spec' => [$this->macro_string, $this->domain_end],
|
|
|
|
|
99
|
1 |
|
'domain_end' => c(['.', $this->toplabel, opt('.')], $this->macro_expand),
|
|
|
|
|
100
|
1 |
|
'toplabel' => c(
|
101
|
1 |
|
[star($this->alphanum), $abnf->ALPHA, star($this->alphanum)],
|
|
|
|
|
102
|
1 |
|
[plus($this->alphanum), '=', star(c($this->alphanum, '-')), $this->alphanum]
|
103
|
|
|
), // LDH rule plus additional TLD restrictions (see [RFC3696], Section 2) @todo Read & implement
|
104
|
1 |
|
'alphanum' => c($abnf->ALPHA, $abnf->DIGIT),
|
105
|
1 |
|
'explain_string' => star(c($this->macro_string, $abnf->SP)),
|
106
|
1 |
|
'macro_string' => star(c($this->macro_expand, $this->macro_literal)),
|
|
|
|
|
107
|
1 |
|
'macro_expand' => c(
|
108
|
1 |
|
['%{', $this->macro_letter, $this->transformers, star($this->delimiter), '}'],
|
|
|
|
|
109
|
1 |
|
'%%', '%_', '%-'
|
110
|
|
|
),
|
111
|
1 |
|
'macro_literal' => c(range(0x21, 0x24), range(0x26, 0x7E)),
|
112
|
1 |
|
'macro_letter' => set('slodiphcrt'),
|
113
|
1 |
|
'transformers' => [star($abnf->DIGIT), opt('r')],
|
114
|
1 |
|
'delimiter' => set('.-+,/_='),
|
115
|
|
|
|
116
|
|
|
// 7. The Received-SPF Header Field
|
117
|
|
|
// Implement as separate ruleset?
|
118
|
|
|
// Does it conflict with the record definition?
|
119
|
|
|
|
120
|
1 |
|
self::ROOT => $this->record,
|
121
|
|
|
];
|
122
|
|
|
|
123
|
1 |
|
parent::__construct(array_merge($rules, $overwrites));
|
124
|
|
|
}
|
125
|
|
|
} |