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
2a4ab4e8
Commit
2a4ab4e8
authored
19 years ago
by
Stuart Gathman
Browse files
Options
Downloads
Patches
Plain Diff
Send DSN before adding message to quarantine.
parent
241717b0
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
Milter/dsn.py
+5
-2
5 additions, 2 deletions
Milter/dsn.py
bms.py
+70
-49
70 additions, 49 deletions
bms.py
milter.spec
+6
-3
6 additions, 3 deletions
milter.spec
with
81 additions
and
54 deletions
Milter/dsn.py
+
5
−
2
View file @
2a4ab4e8
...
@@ -112,8 +112,11 @@ def send_dsn(mailfrom,receiver,msg=None):
...
@@ -112,8 +112,11 @@ def send_dsn(mailfrom,receiver,msg=None):
smtp
.
connect
(
host
)
smtp
.
connect
(
host
)
code
,
resp
=
smtp
.
helo
(
receiver
)
code
,
resp
=
smtp
.
helo
(
receiver
)
# some wiley spammers have MX records that resolve to 127.0.0.1
# some wiley spammers have MX records that resolve to 127.0.0.1
if
resp
.
split
()[
0
]
==
receiver
:
a
=
resp
.
split
()
return
(
553
,
'
Fraudulent MX for %s
'
%
domain
)
if
not
a
:
return
(
553
,
'
MX for %s has no hostname in banner: %s
'
%
(
domain
,
host
))
if
a
[
0
]
==
receiver
:
return
(
553
,
'
Fraudulent MX for %s: %s
'
%
(
domain
,
host
))
if
not
(
200
<=
code
<=
299
):
if
not
(
200
<=
code
<=
299
):
raise
smtplib
.
SMTPHeloError
(
code
,
resp
)
raise
smtplib
.
SMTPHeloError
(
code
,
resp
)
if
msg
:
if
msg
:
...
...
This diff is collapsed.
Click to expand it.
bms.py
+
70
−
49
View file @
2a4ab4e8
#!/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.22 2005/08/11 22:17:58 customdesigned
# Consider SMTP AUTH connections internal.
#
# Revision 1.21 2005/08/04 21:21:31 customdesigned
# Revision 1.21 2005/08/04 21:21:31 customdesigned
# Treat fail like softfail for selected (braindead) domains.
# Treat fail like softfail for selected (braindead) domains.
# Treat mail according to extended processing results, but
# Treat mail according to extended processing results, but
...
@@ -726,8 +729,8 @@ class bmsMilter(Milter.Milter):
...
@@ -726,8 +729,8 @@ class bmsMilter(Milter.Milter):
q
.
set_default_explanation
(
q
.
set_default_explanation
(
'
SPF fail: see http://openspf.com/why.html?sender=%s&ip=%s
'
%
(
q
.
s
,
q
.
i
))
'
SPF fail: see http://openspf.com/why.html?sender=%s&ip=%s
'
%
(
q
.
s
,
q
.
i
))
res
,
code
,
txt
=
q
.
check
()
res
,
code
,
txt
=
q
.
check
()
if
res
==
'
unknown
'
and
q
.
perm_error
:
q
.
result
=
res
q
.
result
=
res
if
res
==
'
unknown
'
and
q
.
perm_error
:
self
.
cbv_needed
=
q
# report SPF syntax error to sender
self
.
cbv_needed
=
q
# report SPF syntax error to sender
res
,
code
,
txt
=
q
.
perm_error
.
ext
# extended (lax processing) result
res
,
code
,
txt
=
q
.
perm_error
.
ext
# extended (lax processing) result
txt
=
'
EXT:
'
+
txt
txt
=
'
EXT:
'
+
txt
...
@@ -774,11 +777,9 @@ class bmsMilter(Milter.Milter):
...
@@ -774,11 +777,9 @@ class bmsMilter(Milter.Milter):
)
)
return
Milter
.
REJECT
return
Milter
.
REJECT
if
self
.
mailfrom
!=
'
<>
'
:
if
self
.
mailfrom
!=
'
<>
'
:
q
.
result
=
res
self
.
cbv_needed
=
q
self
.
cbv_needed
=
q
if
res
in
(
'
deny
'
,
'
fail
'
):
if
res
in
(
'
deny
'
,
'
fail
'
):
if
hres
==
'
pass
'
and
q
.
o
in
spf_accept_fail
:
if
hres
==
'
pass
'
and
q
.
o
in
spf_accept_fail
:
q
.
result
=
res
self
.
cbv_needed
=
q
self
.
cbv_needed
=
q
else
:
else
:
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
self
.
log
(
'
REJECT: SPF %s %i %s
'
%
(
res
,
code
,
txt
))
...
@@ -800,7 +801,6 @@ class bmsMilter(Milter.Milter):
...
@@ -800,7 +801,6 @@ class bmsMilter(Milter.Milter):
)
)
return
Milter
.
REJECT
return
Milter
.
REJECT
if
self
.
mailfrom
!=
'
<>
'
:
if
self
.
mailfrom
!=
'
<>
'
:
q
.
result
=
res
self
.
cbv_needed
=
q
self
.
cbv_needed
=
q
if
res
==
'
neutral
'
and
q
.
o
in
spf_reject_neutral
:
if
res
==
'
neutral
'
and
q
.
o
in
spf_reject_neutral
:
self
.
log
(
'
REJECT: SPF neutral for
'
,
q
.
s
)
self
.
log
(
'
REJECT: SPF neutral for
'
,
q
.
s
)
...
@@ -823,6 +823,7 @@ class bmsMilter(Milter.Milter):
...
@@ -823,6 +823,7 @@ class bmsMilter(Milter.Milter):
self
.
setreply
(
str
(
code
),
'
4.3.0
'
,
txt
)
self
.
setreply
(
str
(
code
),
'
4.3.0
'
,
txt
)
return
Milter
.
TEMPFAIL
return
Milter
.
TEMPFAIL
self
.
add_header
(
'
Received-SPF
'
,
q
.
get_header
(
res
,
receiver
))
self
.
add_header
(
'
Received-SPF
'
,
q
.
get_header
(
res
,
receiver
))
self
.
spf
=
q
return
Milter
.
CONTINUE
return
Milter
.
CONTINUE
# hide_path causes a copy of the message to be saved - until we
# hide_path causes a copy of the message to be saved - until we
...
@@ -1075,6 +1076,7 @@ class bmsMilter(Milter.Milter):
...
@@ -1075,6 +1076,7 @@ class bmsMilter(Milter.Milter):
# this will give a fast start to stats
# this will give a fast start to stats
def
check_spam
(
self
):
def
check_spam
(
self
):
"
return True/False if self.fp, else return Milter.REJECT/TEMPFAIL/etc
"
if
not
dspam_userdir
:
return
False
if
not
dspam_userdir
:
return
False
ds
=
Dspam
.
DSpamDirectory
(
dspam_userdir
)
ds
=
Dspam
.
DSpamDirectory
(
dspam_userdir
)
ds
.
log
=
self
.
log
ds
.
log
=
self
.
log
...
@@ -1094,7 +1096,7 @@ class bmsMilter(Milter.Milter):
...
@@ -1094,7 +1096,7 @@ class bmsMilter(Milter.Milter):
ds
.
add_spam
(
sender
,
txt
)
ds
.
add_spam
(
sender
,
txt
)
txt
=
None
txt
=
None
self
.
fp
=
None
self
.
fp
=
None
return
False
return
Milter
.
DISCARD
elif
user
==
'
falsepositive
'
and
self
.
internal_connection
:
elif
user
==
'
falsepositive
'
and
self
.
internal_connection
:
sender
=
dspam_users
.
get
(
self
.
canon_from
)
sender
=
dspam_users
.
get
(
self
.
canon_from
)
if
sender
:
if
sender
:
...
@@ -1111,16 +1113,23 @@ class bmsMilter(Milter.Milter):
...
@@ -1111,16 +1113,23 @@ class bmsMilter(Milter.Milter):
return
False
return
False
if
user
==
'
honeypot
'
and
Dspam
.
VERSION
>=
'
1.1.9
'
:
if
user
==
'
honeypot
'
and
Dspam
.
VERSION
>=
'
1.1.9
'
:
keep
=
False
# keep honeypot mail
keep
=
False
# keep honeypot mail
self
.
fp
=
None
if
len
(
self
.
recipients
)
>
1
:
if
len
(
self
.
recipients
)
>
1
:
self
.
log
(
"
HONEYPOT:
"
,
rcpt
,
'
SCREENED
'
)
if
self
.
spf
:
# check that sender accepts quarantine DSN
msg
=
mime
.
message_from_file
(
StringIO
.
StringIO
(
txt
))
rc
=
self
.
send_dsn
(
self
.
spf
,
msg
,
'
quarantine.txt
'
)
del
msg
if
rc
!=
Milter
.
CONTINUE
:
return
rc
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
,
quarantine
=
True
,
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
,
quarantine
=
True
,
force_result
=
dspam
.
DSR_ISSPAM
)
force_result
=
dspam
.
DSR_ISSPAM
)
self
.
log
(
"
HONEYPOT:
"
,
rcpt
,
'
SCREENED
'
)
else
:
else
:
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
,
quarantine
=
keep
,
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
,
quarantine
=
keep
,
force_result
=
dspam
.
DSR_ISSPAM
)
force_result
=
dspam
.
DSR_ISSPAM
)
self
.
log
(
"
HONEYPOT:
"
,
rcpt
)
self
.
log
(
"
HONEYPOT:
"
,
rcpt
)
self
.
fp
=
None
return
Milter
.
DISCARD
return
False
txt
=
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
)
txt
=
ds
.
check_spam
(
user
,
txt
,
self
.
recipients
)
if
not
txt
:
if
not
txt
:
# DISCARD if quarrantined for any recipient. It
# DISCARD if quarrantined for any recipient. It
...
@@ -1128,7 +1137,7 @@ class bmsMilter(Milter.Milter):
...
@@ -1128,7 +1137,7 @@ class bmsMilter(Milter.Milter):
# as a false positive.
# as a false positive.
self
.
log
(
"
DSPAM:
"
,
user
,
rcpt
)
self
.
log
(
"
DSPAM:
"
,
user
,
rcpt
)
self
.
fp
=
None
self
.
fp
=
None
return
False
return
Milter
.
DISCARD
self
.
fp
=
StringIO
.
StringIO
(
txt
)
self
.
fp
=
StringIO
.
StringIO
(
txt
)
modified
=
True
modified
=
True
except
Exception
,
x
:
except
Exception
,
x
:
...
@@ -1143,18 +1152,25 @@ class bmsMilter(Milter.Milter):
...
@@ -1143,18 +1152,25 @@ class bmsMilter(Milter.Milter):
self
.
log
(
"
Large message:
"
,
len
(
txt
))
self
.
log
(
"
Large message:
"
,
len
(
txt
))
return
False
return
False
screener
=
dspam_screener
[
self
.
id
%
len
(
dspam_screener
)]
screener
=
dspam_screener
[
self
.
id
%
len
(
dspam_screener
)]
# FIXME: if screener is 'honeypot', classify with no quarantine.
# If spam, send DSN and reject if not accepted. Otherwise, use
# force_result to quarantine.
if
not
ds
.
check_spam
(
screener
,
txt
,
self
.
recipients
,
if
not
ds
.
check_spam
(
screener
,
txt
,
self
.
recipients
,
classify
=
True
,
quarantine
=
not
self
.
reject_spam
):
classify
=
True
,
quarantine
=
False
):
self
.
fp
=
None
self
.
fp
=
None
if
self
.
reject_spam
:
if
self
.
reject_spam
:
self
.
log
(
"
DSPAM:
"
,
screener
,
self
.
log
(
"
DSPAM:
"
,
screener
,
'
REJECT: X-DSpam-Score: %f
'
%
ds
.
probability
)
'
REJECT: X-DSpam-Score: %f
'
%
ds
.
probability
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
'
Your Message looks spammy
'
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
'
Your Message looks spammy
'
)
return
True
return
Milter
.
REJECT
self
.
log
(
"
DSPAM:
"
,
screener
,
"
SCREENED
"
)
self
.
log
(
"
DSPAM:
"
,
screener
,
"
SCREENED
"
)
if
self
.
spf
:
# check that sender accepts quarantine DSN
msg
=
mime
.
message_from_file
(
StringIO
.
StringIO
(
txt
))
rc
=
self
.
send_dsn
(
self
.
spf
,
msg
,
'
quarantine.txt
'
)
del
msg
if
rc
!=
Milter
.
CONTINUE
:
return
rc
ds
.
check_spam
(
screener
,
txt
,
self
.
recipients
,
quarantine
=
True
,
force_result
=
dspam
.
DSR_ISSPAM
)
return
Milter
.
DISCARD
return
modified
return
modified
def
eom
(
self
):
def
eom
(
self
):
...
@@ -1165,8 +1181,7 @@ class bmsMilter(Milter.Milter):
...
@@ -1165,8 +1181,7 @@ class bmsMilter(Milter.Milter):
# analyze external mail for spam
# analyze external mail for spam
spam_checked
=
self
.
check_spam
()
# tag or quarantine for spam
spam_checked
=
self
.
check_spam
()
# tag or quarantine for spam
if
not
self
.
fp
:
if
not
self
.
fp
:
if
spam_checked
:
return
Milter
.
REJECT
return
spam_checked
return
Milter
.
DISCARD
# message quarantined for all recipients
# analyze all mail for dangerous attachments and scripts
# analyze all mail for dangerous attachments and scripts
self
.
fp
.
seek
(
0
)
self
.
fp
.
seek
(
0
)
...
@@ -1233,41 +1248,15 @@ class bmsMilter(Milter.Milter):
...
@@ -1233,41 +1248,15 @@ class bmsMilter(Milter.Milter):
if
self
.
cbv_needed
:
if
self
.
cbv_needed
:
q
=
self
.
cbv_needed
q
=
self
.
cbv_needed
sender
=
q
.
s
cached
=
cbv_cache
.
has_key
(
sender
)
if
cached
:
self
.
log
(
'
CBV:
'
,
sender
,
'
(cached)
'
)
res
=
cbv_cache
[
sender
]
else
:
self
.
log
(
'
CBV:
'
,
sender
)
try
:
if
q
.
result
in
(
'
softfail
'
,
'
fail
'
,
'
deny
'
):
if
q
.
result
in
(
'
softfail
'
,
'
fail
'
,
'
deny
'
):
template
=
file
(
'
softfail.txt
'
).
read
()
template
_name
=
'
softfail.txt
'
elif
q
.
result
==
'
unknown
'
:
elif
q
.
result
==
'
unknown
'
:
template
=
file
(
'
permerror.txt
'
).
read
()
template
_name
=
'
permerror.txt
'
else
:
else
:
template
=
file
(
'
strike3.txt
'
).
read
()
template_name
=
'
strike3.txt
'
except
IOError
:
template
=
None
rc
=
self
.
send_dsn
(
q
,
msg
,
template_name
)
m
=
dsn
.
create_msg
(
q
,
self
.
recipients
,
msg
,
template
)
m
=
m
.
as_string
()
print
>>
open
(
'
last_dsn
'
,
'
w
'
),
m
res
=
dsn
.
send_dsn
(
sender
,
self
.
receiver
,
m
)
if
res
:
desc
=
"
CBV: %d %s
"
%
res
[:
2
]
if
400
<=
res
[
0
]
<
500
:
self
.
log
(
'
TEMPFAIL:
'
,
desc
)
self
.
setreply
(
'
450
'
,
'
4.2.0
'
,
*
desc
.
splitlines
())
return
Milter
.
TEMPFAIL
if
len
(
res
)
<
3
:
res
+=
time
.
time
(),
cbv_cache
[
sender
]
=
res
self
.
log
(
'
REJECT:
'
,
desc
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
*
desc
.
splitlines
())
return
Milter
.
REJECT
cbv_cache
[
sender
]
=
res
if
not
cached
:
s
=
time
.
strftime
(
time_format
,
time
.
localtime
())
print
>>
open
(
'
send_dsn.log
'
,
'
a
'
),
sender
,
s
# log who we sent DSNs to
self
.
cbv_needed
=
None
self
.
cbv_needed
=
None
if
rc
!=
Milter
.
CONTINUE
:
return
rc
if
not
defanged
and
not
spam_checked
:
if
not
defanged
and
not
spam_checked
:
os
.
remove
(
self
.
tempname
)
os
.
remove
(
self
.
tempname
)
...
@@ -1301,6 +1290,38 @@ class bmsMilter(Milter.Milter):
...
@@ -1301,6 +1290,38 @@ class bmsMilter(Milter.Milter):
out
.
close
()
out
.
close
()
return
Milter
.
TEMPFAIL
return
Milter
.
TEMPFAIL
def
send_dsn
(
self
,
q
,
msg
,
template_name
):
sender
=
q
.
s
cached
=
cbv_cache
.
has_key
(
sender
)
if
cached
:
self
.
log
(
'
CBV:
'
,
sender
,
'
(cached)
'
)
res
=
cbv_cache
[
sender
]
else
:
self
.
log
(
'
CBV:
'
,
sender
)
try
:
template
=
file
(
template_name
).
read
()
except
IOError
:
template
=
None
m
=
dsn
.
create_msg
(
q
,
self
.
recipients
,
msg
,
template
)
m
=
m
.
as_string
()
print
>>
open
(
'
last_dsn
'
,
'
w
'
),
m
res
=
dsn
.
send_dsn
(
sender
,
self
.
receiver
,
m
)
if
res
:
desc
=
"
CBV: %d %s
"
%
res
[:
2
]
if
400
<=
res
[
0
]
<
500
:
self
.
log
(
'
TEMPFAIL:
'
,
desc
)
self
.
setreply
(
'
450
'
,
'
4.2.0
'
,
*
desc
.
splitlines
())
return
Milter
.
TEMPFAIL
if
len
(
res
)
<
3
:
res
+=
time
.
time
(),
cbv_cache
[
sender
]
=
res
self
.
log
(
'
REJECT:
'
,
desc
)
self
.
setreply
(
'
550
'
,
'
5.7.1
'
,
*
desc
.
splitlines
())
return
Milter
.
REJECT
cbv_cache
[
sender
]
=
res
if
not
cached
:
s
=
time
.
strftime
(
time_format
,
time
.
localtime
())
print
>>
open
(
'
send_dsn.log
'
,
'
a
'
),
sender
,
s
# log who we sent DSNs to
return
Milter
.
CONTINUE
def
close
(
self
):
def
close
(
self
):
sys
.
stdout
.
flush
()
# make log messages visible
sys
.
stdout
.
flush
()
# make log messages visible
if
self
.
tempname
:
if
self
.
tempname
:
...
...
This diff is collapsed.
Click to expand it.
milter.spec
+
6
−
3
View file @
2a4ab4e8
...
@@ -160,9 +160,10 @@ rm -rf $RPM_BUILD_ROOT
...
@@ -160,9 +160,10 @@ rm -rf $RPM_BUILD_ROOT
%dir /var/log/milter/save
%dir /var/log/milter/save
%config /var/log/milter/start.sh
%config /var/log/milter/start.sh
%config /var/log/milter/bms.py
%config /var/log/milter/bms.py
%config /var/log/milter/strike3.txt
%config(noreplace) /var/log/milter/strike3.txt
%config /var/log/milter/softfail.txt
%config(noreplace) /var/log/milter/softfail.txt
%config /var/log/milter/quarantine.txt
%config(noreplace) /var/log/milter/quarantine.txt
%config(noreplace) /var/log/milter/permerror.txt
%config(noreplace) /etc/mail/pymilter.cfg
%config(noreplace) /etc/mail/pymilter.cfg
/usr/share/sendmail-cf/hack/rhsbl.m4
/usr/share/sendmail-cf/hack/rhsbl.m4
...
@@ -171,6 +172,8 @@ rm -rf $RPM_BUILD_ROOT
...
@@ -171,6 +172,8 @@ rm -rf $RPM_BUILD_ROOT
- Keep screened honeypot mail, but optionally discard honeypot only mail.
- Keep screened honeypot mail, but optionally discard honeypot only mail.
- spf_accept_fail option for braindead SPF senders (treats fail like softfail)
- spf_accept_fail option for braindead SPF senders (treats fail like softfail)
- Consider SMTP AUTH connections internal.
- Consider SMTP AUTH connections internal.
- Send DSN for SPF errors corrected by extended processing.
- Send DSN before SCREENED mail is quarantined
* Fri Jul 15 2005 Stuart Gathman <stuart@bmsi.com> 0.8.2-4
* Fri Jul 15 2005 Stuart Gathman <stuart@bmsi.com> 0.8.2-4
- Limit each CNAME chain independently like PTR and MX
- Limit each CNAME chain independently like PTR and MX
* Fri Jul 15 2005 Stuart Gathman <stuart@bmsi.com> 0.8.2-3
* Fri Jul 15 2005 Stuart Gathman <stuart@bmsi.com> 0.8.2-3
...
...
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