I mentioned a while back when I found a utility to convert ABNF to
regular expressions that I found some problems with the current ABNF
for the SPF records. One of the problems I found was that the CIDR
length wasn't nailed down.
I have created some updated ABNF that I would like to bounce off
everyone before I even consider putting it into the spec.
The changes are:
* The definition for the unknown-modifier has been changed slightly to
note that unknown modifiers MUST NOT match "redirect" and "exp".
The current ABNF technically allows this and since the "redirect"
and "exp" modifiers require <domain-spec>s instead of
<macro-strings>, things like "redirect=1.2.3.4" will, technically,
match the unknown-modifier case.
If anyone knows how to easily say "match <name> except if it is
'redirect' or 'exp'" in ABNF, please let me know. I know it is
technically possible, but you end up with REs that start with stuff
like:
[a-z0-9_.-]|[a-qs-z][a-z0-9_.-]*|[a-z][a-z0-9_.-]|[a-z][a-df-z0-9_.-]|[a-z][a-z0-9_.-]
... probably around 1000 chars deleted ...
* I have created a series of ANBF terms in the form of range$i-$j
which validate only integers in the range of $i to $j. These ABNF
terms are used in the CIDR lengths, the IPv4 addresses, and the macro
variable truncation number.
* The old ABNF allowed things invalid TLDs like "a:foo.b%-"
Here is the new suggested ABNF for SPF records:
record = version terms *SP
version = "v=spf1"
terms = *( 1*SP ( directive / modifier ) )
directive = [ qualifier ] mechanism
qualifier = "+" / "-" / "?" / "~"
mechanism = ( all / include
/ A / MX / PTR / IP4 / IP6 / exists )
all = "all"
include = "include" ":" domain-spec
A = "a" [ ":" domain-spec ] [ dual-cidr-length ]
MX = "mx" [ ":" domain-spec ] [ dual-cidr-length ]
PTR = "ptr" [ ":" domain-spec ]
IP4 = "ip4" ":" ip4-network [ ip4-cidr-length ]
IP6 = "ip6" ":" ip6-network [ ip6-cidr-length ]
exists = "exists" ":" domain-spec
modifier = redirect / explanation / unknown-modifier
redirect = "redirect" "=" domain-spec
explanation = "exp" "=" domain-spec
unknown-modifier = unknown-mod-name "=" macro-string
unknown-mod-name = <the same as 'name' except it MUST NOT match "redirect"
or "exp"> ; this can not be easily expressed in ABNF
ip4-cidr-length = "/" range1-32
ip6-cidr-length = "/" range1-128
; integers restricted to a given range
range1-9 = %x31-39 ; 1 .. 9
range1-32 = range1-9 ; 1 .. 9
/ "1" DIGIT ; 10 .. 19
/ "2" DIGIT ; 20 .. 29
/ "3" %x30-32 ; 30 .. 32
range1-128 = range1-9 [ DIGIT ] ; 1 .. 99
/ "10" DIGIT ; 100 .. 109
/ "11" DIGIT ; 110 .. 119
/ "12" %x30-38 ; 120 .. 128
range0-255 = DIGIT ; 0 .. 9
/ range1-9 DIGIT ; 10 .. 99
/ "1" 2DIGIT ; 100 .. 199
/ "2" %x30-34 DIGIT ; 200 .. 249
/ "25" %x30-35 ; 250 .. 255
dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ]
ip4-network = range0-255 "." range0-255 "." range0-255 "." range0-255
; conventional dotted quad notation. e.g. 192.0.2.0
ip6-network = <as per [RFC 3513], section 2.2>
; e.g. 2001:DB8::CD30
domain-spec = macro-string domain-end
domain-end = ( "." toplabel ) / macro-var
toplabel = ALPHA / ALPHA *[ alphanum / "-" ] alphanum
; LDH rule (See [RFC3696])
alphanum = ALPHA / DIGIT
explain-string = *( macro-string / SP )
macro-string = *( macro-expand / macro-literal )
macro-var = "%{" macro-letter transformers *delimiter "}"
macro-expand = macro-var / "%%" / "%_" / "%-"
macro-literal = %x21-24 / %x26-7E
; visible characters except "%"
macro-letter = "s" / "l" / "o" / "d" / "i" / "p" / "h" /
"c" / "r" / "t"
transformers = [ range1-128 ] [ "r" ]
delimiter = "." / "-" / "+" / "," / "/" / "_" / "="
name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )
Here is a diff:
--- spf_rec_abnf-02.rb 2005-07-10 01:12:26.000000000 -0500
+++ spf_rec_abnf-03.rb 2005-07-10 12:51:00.000000000 -0500
@@ -30,25 +30,41 @@
modifier = redirect / explanation / unknown-modifier
redirect = "redirect" "=" domain-spec
explanation = "exp" "=" domain-spec
- unknown-modifier = name "=" macro-string
+ unknown-modifier = unknown-mod-name "=" macro-string
+ unknown-mod-name = <the same as 'name' except it MUST NOT match "redirect"
+ or "exp"> ; this can not be easily expressed in ABNF
+
+
+ ip4-cidr-length = "/" range1-32
+ ip6-cidr-length = "/" range1-128
+
+ ; integers restricted to a given range
+ range1-9 = %x31-39 ; 1 .. 9
+ range1-32 = range1-9 ; 1 .. 9
+ / "1" DIGIT ; 10 .. 19
+ / "2" DIGIT ; 20 .. 29
+ / "3" %x30-32 ; 30 .. 32
+ range1-128 = range1-9 [ DIGIT ] ; 1 .. 99
+ / "10" DIGIT ; 100 .. 109
+ / "11" DIGIT ; 110 .. 119
+ / "12" %x30-38 ; 120 .. 128
+ range0-255 = DIGIT ; 0 .. 9
+ / range1-9 DIGIT ; 10 .. 99
+ / "1" 2DIGIT ; 100 .. 199
+ / "2" %x30-34 DIGIT ; 200 .. 249
+ / "25" %x30-35 ; 250 .. 255
+
- ip4-cidr-length = "/" 1*DIGIT
- ip6-cidr-length = "/" 1*DIGIT
dual-cidr-length = [ ip4-cidr-length ] [ "/" ip6-cidr-length ]
- ip4-network = qnum "." qnum "." qnum "." qnum
- qnum = DIGIT ; 0-9
- / %x31-39 DIGIT ; 10-99
- / "1" 2DIGIT ; 100-199
- / "2" %x30-34 DIGIT ; 200-249
- / "25" %x30-35 ; 250-255
+ ip4-network = range0-255 "." range0-255 "." range0-255 "." range0-255
; conventional dotted quad notation. e.g. 192.0.2.0
; ip6-network = <as per [RFC 3513], section 2.2>
; ; e.g. 2001:DB8::CD30
domain-spec = macro-string domain-end
- domain-end = ( "." toplabel ) / macro-expand
+ domain-end = ( "." toplabel ) / macro-var
toplabel = ALPHA / ALPHA *[ alphanum / "-" ] alphanum
; LDH rule (See [RFC3696])
alphanum = ALPHA / DIGIT
@@ -56,13 +72,13 @@
explain-string = *( macro-string / SP )
macro-string = *( macro-expand / macro-literal )
- macro-expand = ( "%{" macro-letter transformers *delimiter "}" )
- / "%%" / "%_" / "%-"
+ macro-var = "%{" macro-letter transformers *delimiter "}"
+ macro-expand = macro-var / "%%" / "%_" / "%-"
macro-literal = %x21-24 / %x26-7E
; visible characters except "%"
macro-letter = "s" / "l" / "o" / "d" / "i" / "p" / "h" /
"c" / "r" / "t"
- transformers = *DIGIT [ "r" ]
+ transformers = [ range1-128 ] [ "r" ]
delimiter = "." / "-" / "+" / "," / "/" / "_" / "="
name = ALPHA *( ALPHA / DIGIT / "-" / "_" / "." )
-wayne