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
8ae7bd42
Commit
8ae7bd42
authored
Jan 6, 2007
by
Stuart Gathman
Browse files
Options
Downloads
Patches
Plain Diff
Add config file to spfmilter
parent
139e141e
Branches
Branches containing commit
Tags
Tags containing commit
No related merge requests found
Changes
5
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
Milter/config.py
+59
-0
59 additions, 0 deletions
Milter/config.py
bms.py
+5
-60
5 additions, 60 deletions
bms.py
milter.cfg
+1
-1
1 addition, 1 deletion
milter.cfg
spfmilter.cfg
+20
-0
20 additions, 0 deletions
spfmilter.cfg
spfmilter.py
+104
-36
104 additions, 36 deletions
spfmilter.py
with
189 additions
and
97 deletions
Milter/config.py
0 → 100644
+
59
−
0
View file @
8ae7bd42
from
ConfigParser
import
ConfigParser
class
MilterConfigParser
(
ConfigParser
):
def
__init__
(
self
,
defaults
=
{}):
ConfigParser
.
__init__
(
self
)
self
.
defaults
=
defaults
# The defaults provided by ConfigParser show up in all sections,
# which screws up iterating over all options in a section.
# Worse, passing "defaults" with vars= overrides the config file!
# So we roll our own defaults.
def
get
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
)
and
opt
in
self
.
defaults
:
return
self
.
defaults
[
opt
]
return
ConfigParser
.
get
(
self
,
sect
,
opt
)
def
getlist
(
self
,
sect
,
opt
):
if
self
.
has_option
(
sect
,
opt
):
return
[
q
.
strip
()
for
q
in
self
.
get
(
sect
,
opt
).
split
(
'
,
'
)]
return
[]
def
getaddrset
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
):
return
{}
s
=
self
.
get
(
sect
,
opt
)
d
=
{}
for
q
in
s
.
split
(
'
,
'
):
q
=
q
.
strip
()
if
q
.
startswith
(
'
file:
'
):
domain
=
q
[
5
:].
lower
()
d
[
domain
]
=
d
.
setdefault
(
domain
,[])
+
open
(
domain
,
'
r
'
).
read
().
split
()
else
:
user
,
domain
=
q
.
split
(
'
@
'
)
d
.
setdefault
(
domain
.
lower
(),[]).
append
(
user
)
return
d
def
getaddrdict
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
):
return
{}
d
=
{}
for
q
in
self
.
get
(
sect
,
opt
).
split
(
'
,
'
):
q
=
q
.
strip
()
if
self
.
has_option
(
sect
,
q
):
l
=
self
.
get
(
sect
,
q
)
for
addr
in
l
.
split
(
'
,
'
):
addr
=
addr
.
strip
()
if
addr
.
startswith
(
'
file:
'
):
fname
=
addr
[
5
:]
for
a
in
open
(
fname
,
'
r
'
).
read
().
split
():
d
[
a
]
=
q
else
:
d
[
addr
]
=
q
return
d
def
getdefault
(
self
,
sect
,
opt
,
default
=
None
):
if
self
.
has_option
(
sect
,
opt
):
return
self
.
get
(
sect
,
opt
)
return
default
This diff is collapsed.
Click to expand it.
bms.py
+
5
−
60
View file @
8ae7bd42
#!/usr/bin/env python
#!/usr/bin/env python
# A simple milter that has grown quite a bit.
# A simple milter that has grown quite a bit.
# $Log$
# $Log$
# Revision 1.81 2007/01/05 23:33:55 customdesigned
# Make blacklist an AddrCache
#
# Revision 1.80 2007/01/05 23:12:12 customdesigned
# Revision 1.80 2007/01/05 23:12:12 customdesigned
# Move parse_addr, iniplist, ip4re to Milter.utils
# Move parse_addr, iniplist, ip4re to Milter.utils
#
#
...
@@ -49,7 +52,6 @@ import mime
...
@@ -49,7 +52,6 @@ import mime
import
email.Errors
import
email.Errors
import
Milter
import
Milter
import
tempfile
import
tempfile
from
ConfigParser
import
ConfigParser
import
time
import
time
import
socket
import
socket
import
struct
import
struct
...
@@ -60,6 +62,7 @@ import anydbm
...
@@ -60,6 +62,7 @@ import anydbm
import
Milter.dsn
as
dsn
import
Milter.dsn
as
dsn
from
Milter.dynip
import
is_dynip
as
dynip
from
Milter.dynip
import
is_dynip
as
dynip
from
Milter.utils
import
iniplist
,
parse_addr
,
ip4re
from
Milter.utils
import
iniplist
,
parse_addr
,
ip4re
from
Milter.config
import
MilterConfigParser
from
fnmatch
import
fnmatchcase
from
fnmatch
import
fnmatchcase
from
email.Header
import
decode_header
from
email.Header
import
decode_header
...
@@ -167,64 +170,6 @@ milter_log = logging.getLogger('milter')
...
@@ -167,64 +170,6 @@ milter_log = logging.getLogger('milter')
if
gossip
:
if
gossip
:
gossip_node
=
Gossip
(
'
gossip4.db
'
,
120
)
gossip_node
=
Gossip
(
'
gossip4.db
'
,
120
)
class
MilterConfigParser
(
ConfigParser
):
def
__init__
(
self
,
defaults
):
ConfigParser
.
__init__
(
self
)
self
.
defaults
=
defaults
# The defaults provided by ConfigParser show up in all sections,
# which screws up iterating over all options in a section.
# Worse, passing "defaults" with vars= overrides the config file!
# So we roll our own defaults.
def
get
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
)
and
opt
in
self
.
defaults
:
return
self
.
defaults
[
opt
]
return
ConfigParser
.
get
(
self
,
sect
,
opt
)
def
getlist
(
self
,
sect
,
opt
):
if
self
.
has_option
(
sect
,
opt
):
return
[
q
.
strip
()
for
q
in
self
.
get
(
sect
,
opt
).
split
(
'
,
'
)]
return
[]
def
getaddrset
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
):
return
{}
s
=
self
.
get
(
sect
,
opt
)
d
=
{}
for
q
in
s
.
split
(
'
,
'
):
q
=
q
.
strip
()
if
q
.
startswith
(
'
file:
'
):
domain
=
q
[
5
:].
lower
()
d
[
domain
]
=
d
.
setdefault
(
domain
,[])
+
open
(
domain
,
'
r
'
).
read
().
split
()
else
:
user
,
domain
=
q
.
split
(
'
@
'
)
d
.
setdefault
(
domain
.
lower
(),[]).
append
(
user
)
return
d
def
getaddrdict
(
self
,
sect
,
opt
):
if
not
self
.
has_option
(
sect
,
opt
):
return
{}
d
=
{}
for
q
in
self
.
get
(
sect
,
opt
).
split
(
'
,
'
):
q
=
q
.
strip
()
if
self
.
has_option
(
sect
,
q
):
l
=
self
.
get
(
sect
,
q
)
for
addr
in
l
.
split
(
'
,
'
):
addr
=
addr
.
strip
()
if
addr
.
startswith
(
'
file:
'
):
fname
=
addr
[
5
:]
for
a
in
open
(
fname
,
'
r
'
).
read
().
split
():
d
[
a
]
=
q
else
:
d
[
addr
]
=
q
return
d
def
getdefault
(
self
,
sect
,
opt
,
default
=
None
):
if
self
.
has_option
(
sect
,
opt
):
return
self
.
get
(
sect
,
opt
)
return
default
def
read_config
(
list
):
def
read_config
(
list
):
cp
=
MilterConfigParser
({
cp
=
MilterConfigParser
({
'
tempdir
'
:
"
/var/log/milter/save
"
,
'
tempdir
'
:
"
/var/log/milter/save
"
,
...
@@ -393,7 +338,7 @@ def parse_header(val):
...
@@ -393,7 +338,7 @@ def parse_header(val):
return
val
return
val
class
SPFPolicy
(
object
):
class
SPFPolicy
(
object
):
"
Get SPF policy by result
, defaulting to classic policy from pymilter.cfg
"
"
Get SPF policy by result
from sendmail style access file.
"
def
__init__
(
self
,
sender
):
def
__init__
(
self
,
sender
):
self
.
sender
=
sender
self
.
sender
=
sender
self
.
domain
=
sender
.
split
(
'
@
'
)[
-
1
].
lower
()
self
.
domain
=
sender
.
split
(
'
@
'
)[
-
1
].
lower
()
...
...
This diff is collapsed.
Click to expand it.
milter.cfg
+
1
−
1
View file @
8ae7bd42
...
@@ -78,7 +78,7 @@ reject_spoofed = 0
...
@@ -78,7 +78,7 @@ reject_spoofed = 0
# refuses mail from user names commonly abused in that way.
# refuses mail from user names commonly abused in that way.
;banned_users = postmaster, mailer-daemon, clamav
;banned_users = postmaster, mailer-daemon, clamav
# See http://
spf.pobox
.com for more info on SPF.
# See http://
www.openspf
.com for more info on SPF.
[spf]
[spf]
# namespace where SPF records can be supplied for domains without one
# namespace where SPF records can be supplied for domains without one
# records are searched for under _spf.domain.com
# records are searched for under _spf.domain.com
...
...
This diff is collapsed.
Click to expand it.
spfmilter.cfg
0 → 100644
+
20
−
0
View file @
8ae7bd42
[milter]
# The socket used to communicate with sendmail
socketname
=
/tmp/spfmiltersock
# Name of the milter given to sendmail
name
=
pyspffilter
# Trusted relays such as secondary MXes that should not have SPF checked.
;trusted_relay =
# Internal networks that should not have SPF checked.
internal_connect
=
127.0.0.1,192.168.0.0/16
# See http://www.openspf.com for more info on SPF.
[spf]
# Use sendmail access map or similar format for detailed spf policy.
# SPF entries in the access map will override defaults.
;access_file = /etc/mail/access.db
# Connections that get an SPF pass for a pretend MAIL FROM of
# postmaster@sometrustedforwarder.com skip SPF checks for the real MAIL FROM.
# This is for non-SRS forwarders. It is a simple implementation that
# is inefficient for more than a few entries.
;trusted_forwarder = careerbuilder.com
This diff is collapsed.
Click to expand it.
spfmilter.py
+
104
−
36
View file @
8ae7bd42
...
@@ -13,26 +13,55 @@ import spf
...
@@ -13,26 +13,55 @@ import spf
import
struct
import
struct
import
socket
import
socket
import
syslog
import
syslog
from
Milter.config
import
MilterConfigParser
from
Milter.utils
import
iniplist
,
parse_addr
from
Milter.utils
import
iniplist
,
parse_addr
syslog
.
openlog
(
'
spfmilter
'
,
0
,
syslog
.
LOG_MAIL
)
syslog
.
openlog
(
'
spfmilter
'
,
0
,
syslog
.
LOG_MAIL
)
# list of trusted forwarder domains. An SPF record for a forwarder
class
Config
(
object
):
# domain lists IP addresses from which forwarded mail is accepted.
"
Hold configuration options.
"
trusted_forwarder
=
[]
pass
# list of internal LAN ips. No SPF check is done for these.
internal_connect
=
[
'
127.0.0.1
'
,
'
192.168.0.0/16
'
]
def
read_config
(
list
):
# list of trusted relays. These are typically secondary MXes, and
"
Return new config object.
"
# no SPF check is done for these.
cp
=
MilterConfigParser
()
trusted_relay
=
[]
cp
.
read
(
list
)
conf
=
Config
()
socketname
=
"
/var/run/milter/spfmiltersock
"
conf
.
socketname
=
cp
.
getdefault
(
'
milter
'
,
'
socketname
'
,
'
/tmp/spfmiltersock
'
)
#socketname = os.getenv("HOME") + "/pythonsock"
conf
.
miltername
=
cp
.
getdefault
(
'
milter
'
,
'
name
'
,
'
pyspffilter
'
)
miltername
=
"
pyspffilter
"
conf
.
trusted_relay
=
cp
.
getlist
(
'
milter
'
,
'
trusted_relay
'
)
conf
.
internal_connect
=
cp
.
getlist
(
'
milter
'
,
'
internal_connect
'
)
conf
.
trusted_forwarder
=
cp
.
getlist
(
'
spf
'
,
'
trusted_relay
'
)
conf
.
access_file
=
cp
.
getdefault
(
'
spf
'
,
'
access_file
'
,
None
)
return
conf
class
SPFPolicy
(
object
):
"
Get SPF policy by result from sendmail style access file.
"
def
__init__
(
self
,
sender
):
self
.
sender
=
sender
self
.
domain
=
sender
.
split
(
'
@
'
)[
-
1
].
lower
()
if
access_file
:
try
:
acf
=
anydbm
.
open
(
access_file
,
'
r
'
)
except
:
acf
=
None
else
:
acf
=
None
self
.
acf
=
acf
def
getPolicy
(
self
,
pfx
):
acf
=
self
.
acf
if
not
acf
:
return
None
try
:
return
acf
[
pfx
+
self
.
sender
]
except
KeyError
:
try
:
return
acf
[
pfx
+
self
.
domain
]
except
KeyError
:
try
:
return
acf
[
pfx
]
except
KeyError
:
return
None
class
spfMilter
(
Milter
.
Milter
):
class
spfMilter
(
Milter
.
Milter
):
"
Milter to check SPF.
"
"
Milter to check SPF.
Each connection gets its own instance.
"
def
log
(
self
,
*
msg
):
def
log
(
self
,
*
msg
):
syslog
.
syslog
(
'
[%d] %s
'
%
(
self
.
id
,
'
'
.
join
([
str
(
m
)
for
m
in
msg
])))
syslog
.
syslog
(
'
[%d] %s
'
%
(
self
.
id
,
'
'
.
join
([
str
(
m
)
for
m
in
msg
])))
...
@@ -40,6 +69,8 @@ class spfMilter(Milter.Milter):
...
@@ -40,6 +69,8 @@ class spfMilter(Milter.Milter):
def
__init__
(
self
):
def
__init__
(
self
):
self
.
mailfrom
=
None
self
.
mailfrom
=
None
self
.
id
=
Milter
.
uniqueID
()
self
.
id
=
Milter
.
uniqueID
()
# we don't want config used to change during a connection
self
.
conf
=
config
# addheader can only be called from eom(). This accumulates added headers
# addheader can only be called from eom(). This accumulates added headers
# which can then be applied by alter_headers()
# which can then be applied by alter_headers()
...
@@ -55,9 +86,9 @@ class spfMilter(Milter.Milter):
...
@@ -55,9 +86,9 @@ class spfMilter(Milter.Milter):
self
.
receiver
=
self
.
getsymval
(
'
j
'
).
strip
()
self
.
receiver
=
self
.
getsymval
(
'
j
'
).
strip
()
if
hostaddr
and
len
(
hostaddr
)
>
0
:
if
hostaddr
and
len
(
hostaddr
)
>
0
:
ipaddr
=
hostaddr
[
0
]
ipaddr
=
hostaddr
[
0
]
if
iniplist
(
ipaddr
,
internal_connect
):
if
iniplist
(
ipaddr
,
self
.
conf
.
internal_connect
):
self
.
internal_connection
=
True
self
.
internal_connection
=
True
if
iniplist
(
ipaddr
,
trusted_relay
):
if
iniplist
(
ipaddr
,
self
.
conf
.
trusted_relay
):
self
.
trusted_relay
=
True
self
.
trusted_relay
=
True
else
:
ipaddr
=
''
else
:
ipaddr
=
''
self
.
connectip
=
ipaddr
self
.
connectip
=
ipaddr
...
@@ -112,7 +143,7 @@ class spfMilter(Milter.Milter):
...
@@ -112,7 +143,7 @@ class spfMilter(Milter.Milter):
def
check_spf
(
self
):
def
check_spf
(
self
):
receiver
=
self
.
receiver
receiver
=
self
.
receiver
for
tf
in
trusted_forwarder
:
for
tf
in
self
.
conf
.
trusted_forwarder
:
q
=
spf
.
query
(
self
.
connectip
,
''
,
tf
,
receiver
=
receiver
,
strict
=
False
)
q
=
spf
.
query
(
self
.
connectip
,
''
,
tf
,
receiver
=
receiver
,
strict
=
False
)
res
,
code
,
txt
=
q
.
check
()
res
,
code
,
txt
=
q
.
check
()
if
res
==
'
pass
'
:
if
res
==
'
pass
'
:
...
@@ -141,14 +172,30 @@ class spfMilter(Milter.Milter):
...
@@ -141,14 +172,30 @@ class spfMilter(Milter.Milter):
else
:
else
:
hres
,
hcode
,
htxt
=
res
,
code
,
txt
hres
,
hcode
,
htxt
=
res
,
code
,
txt
else
:
hres
=
None
else
:
hres
=
None
p
=
SPFPolicy
(
q
.
s
)
if
res
==
'
fail
'
:
if
res
==
'
fail
'
:
policy
=
p
.
getPolicy
(
'
spf-fail:
'
)
if
not
policy
or
policy
==
'
REJECT
'
:
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
setreply
(
str
(
code
),
'
5.7.1
'
,
txt
)
self
.
setreply
(
str
(
code
),
'
5.7.1
'
,
txt
)
# A proper SPF fail error message would read:
# A proper SPF fail error message would read:
# forger.biz [1.2.3.4] is not allowed to send mail with the domain
# forger.biz [1.2.3.4] is not allowed to send mail with the domain
# "forged.org" in the sender address. Contact <postmaster@forged.org>.
# "forged.org" in the sender address. Contact <postmaster@forged.org>.
return
Milter
.
REJECT
return
Milter
.
REJECT
if
res
==
'
permerror
'
:
if
res
==
'
softfail
'
:
policy
=
p
.
getPolicy
(
'
spf-softfail:
'
)
if
policy
and
policy
==
'
REJECT
'
:
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
setreply
(
str
(
code
),
'
5.7.1
'
,
txt
)
# A proper SPF fail error message would read:
# forger.biz [1.2.3.4] is not allowed to send mail with the domain
# "forged.org" in the sender address. Contact <postmaster@forged.org>.
return
Milter
.
REJECT
elif
res
==
'
permerror
'
:
policy
=
p
.
getPolicy
(
'
spf-permerror:
'
)
if
not
policy
or
policy
==
'
REJECT
'
:
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
# latest SPF draft recommends 5.5.2 instead of 5.7.1
# latest SPF draft recommends 5.5.2 instead of 5.7.1
self
.
setreply
(
str
(
code
),
'
5.5.2
'
,
txt
,
self
.
setreply
(
str
(
code
),
'
5.5.2
'
,
txt
,
...
@@ -156,10 +203,27 @@ class spfMilter(Milter.Milter):
...
@@ -156,10 +203,27 @@ class spfMilter(Milter.Milter):
'
We cannot accept mail from %s until this is corrected.
'
%
q
.
o
'
We cannot accept mail from %s until this is corrected.
'
%
q
.
o
)
)
return
Milter
.
REJECT
return
Milter
.
REJECT
if
res
==
'
temperror
'
:
elif
res
==
'
temperror
'
:
policy
=
p
.
getPolicy
(
'
spf-temperror:
'
)
if
not
policy
or
policy
==
'
REJECT
'
:
self
.
log
(
'
TEMPFAIL: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
log
(
'
TEMPFAIL: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
setreply
(
str
(
code
),
'
4.3.0
'
,
txt
)
self
.
setreply
(
str
(
code
),
'
4.3.0
'
,
txt
)
return
Milter
.
TEMPFAIL
return
Milter
.
TEMPFAIL
elif
res
==
'
neutral
'
or
res
==
'
none
'
:
policy
=
p
.
getPolicy
(
'
spf-neutral:
'
)
if
policy
and
policy
==
'
REJECT
'
:
self
.
log
(
'
REJECT NEUTRAL:
'
,
q
.
s
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
"
%s requires and SPF PASS to accept mail from %s. [http://openspf.org]
"
%
(
receiver
,
q
.
s
))
return
Milter
.
REJECT
elif
res
==
'
pass
'
:
policy
=
p
.
getPolicy
(
'
spf-pass:
'
)
if
policy
and
policy
==
'
REJECT
'
:
self
.
log
(
'
REJECT PASS:
'
,
q
.
s
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
"
%s has been blacklisted by %s.
"
%
(
q
.
s
,
receiver
))
return
Milter
.
REJECT
self
.
add_header
(
'
Received-SPF
'
,
q
.
get_header
(
res
,
receiver
),
0
)
self
.
add_header
(
'
Received-SPF
'
,
q
.
get_header
(
res
,
receiver
),
0
)
if
hres
and
q
.
h
!=
q
.
o
:
if
hres
and
q
.
h
!=
q
.
o
:
self
.
add_header
(
'
X-Hello-SPF
'
,
hres
,
0
)
self
.
add_header
(
'
X-Hello-SPF
'
,
hres
,
0
)
...
@@ -168,6 +232,10 @@ class spfMilter(Milter.Milter):
...
@@ -168,6 +232,10 @@ class spfMilter(Milter.Milter):
if
__name__
==
"
__main__
"
:
if
__name__
==
"
__main__
"
:
Milter
.
factory
=
spfMilter
Milter
.
factory
=
spfMilter
Milter
.
set_flags
(
Milter
.
CHGHDRS
+
Milter
.
ADDHDRS
)
Milter
.
set_flags
(
Milter
.
CHGHDRS
+
Milter
.
ADDHDRS
)
global
config
config
=
read_config
([
'
spfmilter.cfg
'
,
'
/etc/mail/spfmilter.cfg
'
])
miltername
=
config
.
miltername
socketname
=
config
.
socketname
print
"""
To use this with sendmail, add the following to sendmail.cf:
print
"""
To use this with sendmail, add the following to sendmail.cf:
O InputMailFilters=%s
O InputMailFilters=%s
...
...
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
sign in
to comment