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