Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
P
pymilter
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Deploy
Releases
Container registry
Model registry
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
misc
pymilter
Commits
357cd1b7
Commit
357cd1b7
authored
19 years ago
by
Stuart Gathman
Browse files
Options
Downloads
Patches
Plain Diff
More fixes from pyspf
parent
3a90a35c
Branches
Branches containing commit
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
spf.py
+62
-19
62 additions, 19 deletions
spf.py
with
62 additions
and
19 deletions
spf.py
+
62
−
19
View file @
357cd1b7
...
@@ -2,7 +2,8 @@
...
@@ -2,7 +2,8 @@
"""
SPF (Sender Policy Framework) implementation.
"""
SPF (Sender Policy Framework) implementation.
Copyright (c) 2003, Terence Way
Copyright (c) 2003, Terence Way
Portions Copyright (c) 2004,2005 Stuart Gathman <stuart@bmsi.com>
Portions Copyright (c) 2004,2005,2006 Stuart Gathman <stuart@bmsi.com>
Portions Copyright (c) 2005,2006 Scott Kitterman <scott@kitterman.com>
This module is free software, and you may redistribute it and/or modify
This module is free software, and you may redistribute it and/or modify
it under the same terms as Python itself, so long as this copyright message
it under the same terms as Python itself, so long as this copyright message
and disclaimer are retained in their original form.
and disclaimer are retained in their original form.
...
@@ -19,7 +20,7 @@ AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
...
@@ -19,7 +20,7 @@ AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
For more information about SPF, a tool against email forgery, see
For more information about SPF, a tool against email forgery, see
http://openspf.org/
http://
www.
openspf.org/
For news, bugfixes, etc. visit the home page for this implementation at
For news, bugfixes, etc. visit the home page for this implementation at
http://www.wayforward.net/spf/
http://www.wayforward.net/spf/
...
@@ -47,6 +48,9 @@ For news, bugfixes, etc. visit the home page for this implementation at
...
@@ -47,6 +48,9 @@ For news, bugfixes, etc. visit the home page for this implementation at
# Terrence is not responding to email.
# Terrence is not responding to email.
#
#
# $Log$
# $Log$
# Revision 1.22 2006/06/21 21:13:07 customdesigned
# initialize perm_error
#
# Revision 1.21 2006/05/12 16:15:20 customdesigned
# Revision 1.21 2006/05/12 16:15:20 customdesigned
# a:1.2.3.4 -> ip4:1.2.3.4 'lax' heuristic.
# a:1.2.3.4 -> ip4:1.2.3.4 'lax' heuristic.
#
#
...
@@ -273,7 +277,7 @@ For news, bugfixes, etc. visit the home page for this implementation at
...
@@ -273,7 +277,7 @@ For news, bugfixes, etc. visit the home page for this implementation at
__author__
=
"
Terence Way
"
__author__
=
"
Terence Way
"
__email__
=
"
terry@wayforward.net
"
__email__
=
"
terry@wayforward.net
"
__version__
=
"
1.
6
:
December 18
, 200
3
"
__version__
=
"
1.
7
:
July 26
, 200
6
"
MODULE
=
'
spf
'
MODULE
=
'
spf
'
USAGE
=
"""
To check an incoming mail request:
USAGE
=
"""
To check an incoming mail request:
...
@@ -311,6 +315,8 @@ def DNSLookup(name,qtype):
...
@@ -311,6 +315,8 @@ def DNSLookup(name,qtype):
#resp.show()
#resp.show()
# key k: ('wayforward.net', 'A'), value v
# key k: ('wayforward.net', 'A'), value v
return
[((
a
[
'
name
'
],
a
[
'
typename
'
]),
a
[
'
data
'
])
for
a
in
resp
.
answers
]
return
[((
a
[
'
name
'
],
a
[
'
typename
'
]),
a
[
'
data
'
])
for
a
in
resp
.
answers
]
except
IOError
,
x
:
raise
TempError
,
'
DNS
'
+
str
(
x
)
except
DNS
.
DNSError
,
x
:
except
DNS
.
DNSError
,
x
:
raise
TempError
,
'
DNS
'
+
str
(
x
)
raise
TempError
,
'
DNS
'
+
str
(
x
)
...
@@ -322,7 +328,7 @@ def isSPF(txt):
...
@@ -322,7 +328,7 @@ def isSPF(txt):
MASK
=
0xFFFFFFFF
L
MASK
=
0xFFFFFFFF
L
# Regular expression to look for modifiers
# Regular expression to look for modifiers
RE_MODIFIER
=
re
.
compile
(
r
'
^([a-zA-Z]+)=
'
)
RE_MODIFIER
=
re
.
compile
(
r
'
^([a-zA-Z
0-9_\-\.
]+)=
'
)
# Regular expression to find macro expansions
# Regular expression to find macro expansions
RE_CHAR
=
re
.
compile
(
r
'
%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))
'
)
RE_CHAR
=
re
.
compile
(
r
'
%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))
'
)
...
@@ -330,7 +336,7 @@ RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))')
...
@@ -330,7 +336,7 @@ RE_CHAR = re.compile(r'%(%|_|-|(\{[a-zA-Z][0-9]*r?[^\}]*\}))')
# Regular expression to break up a macro expansion
# Regular expression to break up a macro expansion
RE_ARGS
=
re
.
compile
(
r
'
([0-9]*)(r?)([^0-9a-zA-Z]*)
'
)
RE_ARGS
=
re
.
compile
(
r
'
([0-9]*)(r?)([^0-9a-zA-Z]*)
'
)
RE_CIDR
=
re
.
compile
(
r
'
/([1-9]|1[0-9]
*
|2[0-9]
*
|3[0-2]
*
)$
'
)
RE_CIDR
=
re
.
compile
(
r
'
/([1-9]|1[0-9]|2[0-9]|3[0-2])$
'
)
RE_IP4
=
re
.
compile
(
r
'
\.
'
.
join
(
RE_IP4
=
re
.
compile
(
r
'
\.
'
.
join
(
[
r
'
(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
'
]
*
4
)
+
'
$
'
)
[
r
'
(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])
'
]
*
4
)
+
'
$
'
)
...
@@ -370,9 +376,9 @@ except NameError:
...
@@ -370,9 +376,9 @@ except NameError:
DEFAULT_SPF
=
'
v=spf1 a/24 mx/24 ptr
'
DEFAULT_SPF
=
'
v=spf1 a/24 mx/24 ptr
'
# maximum DNS lookups allowed
# maximum DNS lookups allowed
MAX_LOOKUP
=
10
#
draft-schlitt-spf-classic-02
Para 10.1
MAX_LOOKUP
=
10
#
RFC 4408
Para 10.1
MAX_MX
=
10
#
draft-schlitt-spf-classic-02
Para 10.1
MAX_MX
=
10
#
RFC 4408
Para 10.1
MAX_PTR
=
10
#
draft-schlitt-spf-classic-02
Para 10.1
MAX_PTR
=
10
#
RFC 4408
Para 10.1
MAX_CNAME
=
10
# analogous interpretation to MAX_PTR
MAX_CNAME
=
10
# analogous interpretation to MAX_PTR
MAX_RECURSION
=
20
MAX_RECURSION
=
20
ALL_MECHANISMS
=
(
'
a
'
,
'
mx
'
,
'
ptr
'
,
'
exists
'
,
'
include
'
,
'
ip4
'
,
'
ip6
'
,
'
all
'
)
ALL_MECHANISMS
=
(
'
a
'
,
'
mx
'
,
'
ptr
'
,
'
exists
'
,
'
include
'
,
'
ip4
'
,
'
ip6
'
,
'
all
'
)
...
@@ -480,7 +486,7 @@ class query(object):
...
@@ -480,7 +486,7 @@ class query(object):
(
'
unknown
'
,
550
,
'
SPF Permanent Error: Unknown mechanism found: moo
'
)
(
'
unknown
'
,
550
,
'
SPF Permanent Error: Unknown mechanism found: moo
'
)
>>>
q
.
check
(
spf
=
'
v=spf1 =a ?all moo
'
)
>>>
q
.
check
(
spf
=
'
v=spf1 =a ?all moo
'
)
(
'
unknown
'
,
550
,
'
SPF Permanent Error: Unknown qualifier,
IETF draft
para 4.6.1, found in: =a
'
)
(
'
unknown
'
,
550
,
'
SPF Permanent Error: Unknown qualifier,
RFC 4408
para 4.6.1, found in: =a
'
)
>>>
q
.
check
(
spf
=
'
v=spf1 ip4:192.0.0.0/8 ~all
'
)
>>>
q
.
check
(
spf
=
'
v=spf1 ip4:192.0.0.0/8 ~all
'
)
(
'
pass
'
,
250
,
'
sender SPF verified
'
)
(
'
pass
'
,
250
,
'
sender SPF verified
'
)
...
@@ -575,6 +581,11 @@ class query(object):
...
@@ -575,6 +581,11 @@ class query(object):
Returns mech,m,arg,cidrlength,result
Returns mech,m,arg,cidrlength,result
Examples:
Examples:
>>>
q
=
query
(
s
=
'
strong-bad@email.example.com.
'
,
...
h
=
'
mx.example.org
'
,
i
=
'
192.0.2.3
'
)
>>>
q
.
validate_mechanism
(
'
A
'
)
(
'
A
'
,
'
a
'
,
'
email.example.com
'
,
32
,
'
pass
'
)
>>>
q
=
query
(
s
=
'
strong-bad@email.example.com
'
,
>>>
q
=
query
(
s
=
'
strong-bad@email.example.com
'
,
...
h
=
'
mx.example.org
'
,
i
=
'
192.0.2.3
'
)
...
h
=
'
mx.example.org
'
,
i
=
'
192.0.2.3
'
)
>>>
q
.
validate_mechanism
(
'
A
'
)
>>>
q
.
validate_mechanism
(
'
A
'
)
...
@@ -583,8 +594,24 @@ class query(object):
...
@@ -583,8 +594,24 @@ class query(object):
>>>
q
.
validate_mechanism
(
'
?mx:%{d}/27
'
)
>>>
q
.
validate_mechanism
(
'
?mx:%{d}/27
'
)
(
'
?mx:%{d}/27
'
,
'
mx
'
,
'
email.example.com
'
,
27
,
'
neutral
'
)
(
'
?mx:%{d}/27
'
,
'
mx
'
,
'
email.example.com
'
,
27
,
'
neutral
'
)
>>>
q
.
validate_mechanism
(
'
-mx::%%%_/.Clara.de/27
'
)
>>>
try
:
q
.
validate_mechanism
(
'
ip4:1.2.3.4/247
'
)
(
'
-mx::%%%_/.Clara.de/27
'
,
'
mx
'
,
'
:% /.Clara.de
'
,
27
,
'
fail
'
)
...
except
PermError
,
x
:
print
x
Invalid
IP4
address
:
ip4
:
1.2
.
3.4
/
247
>>>
try
:
q
.
validate_mechanism
(
'
a:example.com:8080
'
)
...
except
PermError
,
x
:
print
x
Too
many
:.
Not
allowed
in
domain
name
.:
a
:
example
.
com
:
8080
>>>
try
:
q
.
validate_mechanism
(
'
ip4:1.2.3.444/24
'
)
...
except
PermError
,
x
:
print
x
Invalid
IP4
address
:
ip4
:
1.2
.
3.444
/
24
>>>
try
:
q
.
validate_mechanism
(
'
-all:3030
'
)
...
except
PermError
,
x
:
print
x
Invalid
all
mechanism
format
-
only
qualifier
allowed
with
all
:
-
all
:
3030
>>>
q
.
validate_mechanism
(
'
-mx:%%%_/.Clara.de/27
'
)
(
'
-mx:%%%_/.Clara.de/27
'
,
'
mx
'
,
'
% /.Clara.de
'
,
27
,
'
fail
'
)
>>>
q
.
validate_mechanism
(
'
~exists:%{i}.%{s1}.100/86400.rate.%{d}
'
)
>>>
q
.
validate_mechanism
(
'
~exists:%{i}.%{s1}.100/86400.rate.%{d}
'
)
(
'
~exists:%{i}.%{s1}.100/86400.rate.%{d}
'
,
'
exists
'
,
'
192.0.2.3.com.100/86400.rate.email.example.com
'
,
32
,
'
softfail
'
)
(
'
~exists:%{i}.%{s1}.100/86400.rate.%{d}
'
,
'
exists
'
,
'
192.0.2.3.com.100/86400.rate.email.example.com
'
,
32
,
'
softfail
'
)
...
@@ -608,7 +635,9 @@ class query(object):
...
@@ -608,7 +635,9 @@ class query(object):
x
=
self
.
note_error
(
x
=
self
.
note_error
(
'
Use the ip4 mechanism for ip4 addresses
'
,
mech
)
'
Use the ip4 mechanism for ip4 addresses
'
,
mech
)
m
=
'
ip4
'
m
=
'
ip4
'
# Check for : within the arguement
if
arg
.
count
(
'
:
'
)
>
0
:
raise
PermError
(
'
Too many :. Not allowed in domain name.
'
,
mech
)
if
m
in
(
'
a
'
,
'
mx
'
,
'
ptr
'
,
'
exists
'
,
'
include
'
):
if
m
in
(
'
a
'
,
'
mx
'
,
'
ptr
'
,
'
exists
'
,
'
include
'
):
arg
=
self
.
expand
(
arg
)
arg
=
self
.
expand
(
arg
)
# FQDN must contain at least one '.'
# FQDN must contain at least one '.'
...
@@ -631,11 +660,18 @@ class query(object):
...
@@ -631,11 +660,18 @@ class query(object):
return
mech
,
m
,
arg
,
cidrlength
,
result
return
mech
,
m
,
arg
,
cidrlength
,
result
if
m
==
'
ip4
'
and
not
RE_IP4
.
match
(
arg
):
if
m
==
'
ip4
'
and
not
RE_IP4
.
match
(
arg
):
raise
PermError
(
'
Invalid IP4 address
'
,
mech
)
raise
PermError
(
'
Invalid IP4 address
'
,
mech
)
#validate 'all' mechanism per RFC 4408 ABNF
if
m
==
'
all
'
and
\
(
arg
!=
self
.
d
or
mech
.
count
(
'
:
'
)
or
mech
.
count
(
'
/
'
)):
print
'
|
'
+
arg
+
'
|
'
,
mech
,
self
.
d
,
self
.
note_error
(
'
Invalid all mechanism format - only qualifier allowed with all
'
,
mech
)
if
m
in
ALL_MECHANISMS
:
if
m
in
ALL_MECHANISMS
:
return
mech
,
m
,
arg
,
cidrlength
,
result
return
mech
,
m
,
arg
,
cidrlength
,
result
if
m
[
1
:]
in
ALL_MECHANISMS
:
if
m
[
1
:]
in
ALL_MECHANISMS
:
x
=
self
.
note_error
(
x
=
self
.
note_error
(
'
Unknown qualifier,
IETF draft
para 4.6.1, found in
'
,
mech
)
'
Unknown qualifier,
RFC 4408
para 4.6.1, found in
'
,
mech
)
else
:
else
:
x
=
self
.
note_error
(
'
Unknown mechanism found
'
,
mech
)
x
=
self
.
note_error
(
'
Unknown mechanism found
'
,
mech
)
return
mech
,
m
,
arg
,
cidrlength
,
x
return
mech
,
m
,
arg
,
cidrlength
,
x
...
@@ -770,11 +806,12 @@ class query(object):
...
@@ -770,11 +806,12 @@ class query(object):
def
get_explanation
(
self
,
spec
):
def
get_explanation
(
self
,
spec
):
"""
Expand an explanation.
"""
"""
Expand an explanation.
"""
if
spec
:
if
spec
:
return
self
.
expand
(
''
.
join
(
self
.
dns_txt
(
self
.
expand
(
spec
))))
txt
=
''
.
join
(
self
.
dns_txt
(
self
.
expand
(
spec
)))
return
self
.
expand
(
txt
,
stripdot
=
False
)
else
:
else
:
return
'
explanation : Required option is missing
'
return
'
explanation : Required option is missing
'
def
expand
(
self
,
str
):
def
expand
(
self
,
str
,
stripdot
=
True
):
# macros='slodipvh'
"""
Do SPF RFC macro expansion.
"""
Do SPF RFC macro expansion.
Examples:
Examples:
...
@@ -842,6 +879,9 @@ class query(object):
...
@@ -842,6 +879,9 @@ class query(object):
>>>
q
.
expand
(
'
%{p2}.trusted-domains.example.net
'
)
>>>
q
.
expand
(
'
%{p2}.trusted-domains.example.net
'
)
'
example.org.trusted-domains.example.net
'
'
example.org.trusted-domains.example.net
'
>>>
q
.
expand
(
'
%{p2}.trusted-domains.example.net.
'
)
'
example.org.trusted-domains.example.net
'
"""
"""
end
=
0
end
=
0
result
=
''
result
=
''
...
@@ -867,7 +907,10 @@ class query(object):
...
@@ -867,7 +907,10 @@ class query(object):
JOINERS
.
get
(
letter
))
JOINERS
.
get
(
letter
))
end
=
i
.
end
()
end
=
i
.
end
()
return
result
+
str
[
end
:]
result
+=
str
[
end
:]
if
stripdot
and
result
.
endswith
(
'
.
'
):
return
result
[:
-
1
]
return
result
def
dns_spf
(
self
,
domain
):
def
dns_spf
(
self
,
domain
):
"""
Get the SPF record recorded in DNS for a specific domain
"""
Get the SPF record recorded in DNS for a specific domain
...
@@ -919,7 +962,7 @@ class query(object):
...
@@ -919,7 +962,7 @@ class query(object):
"""
Get a list of IP addresses for all MX exchanges for a
"""
Get a list of IP addresses for all MX exchanges for a
domain name.
domain name.
"""
"""
#
draft-schlitt-spf-classic-02
section 5.4 "mx"
#
RFC 4408
section 5.4 "mx"
# To prevent DoS attacks, more than 10 MX names MUST NOT be looked up
# To prevent DoS attacks, more than 10 MX names MUST NOT be looked up
if
self
.
strict
:
if
self
.
strict
:
max
=
MAX_MX
max
=
MAX_MX
...
@@ -1072,8 +1115,8 @@ def parse_mechanism(str, d):
...
@@ -1072,8 +1115,8 @@ def parse_mechanism(str, d):
>>>
parse_mechanism
(
'
-exists:%{i}.%{s1}.100/86400.rate.%{d}
'
,
'
foo.com
'
)
>>>
parse_mechanism
(
'
-exists:%{i}.%{s1}.100/86400.rate.%{d}
'
,
'
foo.com
'
)
(
'
-exists
'
,
'
%{i}.%{s1}.100/86400.rate.%{d}
'
,
32
)
(
'
-exists
'
,
'
%{i}.%{s1}.100/86400.rate.%{d}
'
,
32
)
>>>
parse_mechanism
(
'
mx:
:
%%%_/.Claranet.de/27
'
,
'
foo.com
'
)
>>>
parse_mechanism
(
'
mx:%%%_/.Claranet.de/27
'
,
'
foo.com
'
)
(
'
mx
'
,
'
:
%%%_/.Claranet.de
'
,
27
)
(
'
mx
'
,
'
%%%_/.Claranet.de
'
,
27
)
>>>
parse_mechanism
(
'
mx:%{d}/27
'
,
'
foo.com
'
)
>>>
parse_mechanism
(
'
mx:%{d}/27
'
,
'
foo.com
'
)
(
'
mx
'
,
'
%{d}
'
,
27
)
(
'
mx
'
,
'
%{d}
'
,
27
)
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment