Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3ff607eb84 | |||
| c6ac3ddad8 | |||
| b3dce26928 | |||
| fcd85dbfb5 | |||
| 3a1c964f0d | |||
| 36ae390f01 | |||
| 4c0cf4fb95 | |||
| 8f8de8fa97 | |||
| bc516456c1 |
@@ -5,8 +5,11 @@ wrapper (Milter.py) that handles attachments, did lots of testing, packaged
|
||||
it with distutils, and generally transformed it from a quick hack to a
|
||||
real, usable Python extension.
|
||||
|
||||
Other contributors:
|
||||
Other contributors (in random order):
|
||||
|
||||
Dave MacQuigg
|
||||
for noticing that smfi_insheader wasn't supported, and creating
|
||||
a template to help first time pymilter users create their own milter.
|
||||
Terence Way
|
||||
for providing a Python port of SPF
|
||||
Scott Kitterman
|
||||
|
||||
+4
-4
@@ -16,7 +16,7 @@ from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
|
||||
try: from milter import QUARANTINE
|
||||
except: pass
|
||||
|
||||
__version__ = '0.8.3'
|
||||
__version__ = '0.8.4'
|
||||
|
||||
_seq_lock = thread.allocate_lock()
|
||||
_seq = 0
|
||||
@@ -44,7 +44,7 @@ class Milter:
|
||||
for i in msg: print i,
|
||||
print
|
||||
|
||||
def connect(self,hostname,unused,hostaddr):
|
||||
def connect(self,hostname,family,hostaddr):
|
||||
"Called for each connection to sendmail."
|
||||
self.log("connect from %s at %s" % (hostname,hostaddr))
|
||||
return CONTINUE
|
||||
@@ -106,8 +106,8 @@ class Milter:
|
||||
return self.__ctx.setreply(rcode,xcode,msg,*ml)
|
||||
|
||||
# Milter methods which can only be called from eom callback.
|
||||
def addheader(self,field,value):
|
||||
return self.__ctx.addheader(field,value)
|
||||
def addheader(self,field,value,idx=-1):
|
||||
return self.__ctx.addheader(field,value,idx)
|
||||
|
||||
def chgheader(self,field,idx,value):
|
||||
return self.__ctx.chgheader(field,idx,value)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Here is a history of user visible changes to Python milter.
|
||||
|
||||
0.8.4 Auto-whitelist recipients of outgoing email.
|
||||
0.8.3 Keep screened honeypot mail, but optionally discard honeypot only mail.
|
||||
spf_accept_fail option for braindead SPF senders
|
||||
(treats fail like softfail)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
Send DSN for permerror before processing extended result. An additional
|
||||
DSN may be sent based on extended result.
|
||||
|
||||
Rescind whitelist for banned extensions, in case sender is infected.
|
||||
|
||||
Train honeypot on error only.
|
||||
|
||||
Find rfc2822 policy for MFROM quoting.
|
||||
|
||||
Use /etc/mail/access for domain specific SPF policies.
|
||||
|
||||
SPF-Fail: REJECT
|
||||
SPF-Softfail: OK
|
||||
SPF-Neutral: OK
|
||||
Support explicit errors for SPF policy in access file:
|
||||
SPF-Neutral:aol.com ERROR:"550 AOL mail must get SPF PASS"
|
||||
|
||||
Defer TEMPERROR in SPF evaluation - give precedence to security
|
||||
@@ -16,9 +19,6 @@ Option to add Received-SPF header, but never reject on SPF.
|
||||
Create null config that does nothing - except maybe add Received-SPF
|
||||
headers. Many admins would like to turn features on one at a time.
|
||||
|
||||
Auto whitelist based on outgoing email - perhaps with magic subject
|
||||
or recipient prefix.
|
||||
|
||||
Can't output messages with malformed rfc822 attachments.
|
||||
|
||||
Move milter,Milter,mime,spf modules to pymilter
|
||||
@@ -33,7 +33,8 @@ check spam keywords with character classes, e.g.
|
||||
|
||||
Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS
|
||||
forwarder accounts, and a util provides a special local alias for the
|
||||
user to give to the forwarder. Alias only works for mail from that
|
||||
user to give to the forwarder. (Or user just adds arbitrary alias
|
||||
unique to that forwarder to a database.) Alias only works for mail from that
|
||||
forwarder. Milter gets forwarder domain from alias and uses it to
|
||||
SPF check forwarder.
|
||||
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
#!/usr/bin/env python
|
||||
# A simple milter that has grown quite a bit.
|
||||
# $Log$
|
||||
# Revision 1.34 2005/10/19 21:07:49 customdesigned
|
||||
# access.db stores keys in lower case
|
||||
#
|
||||
# Revision 1.33 2005/10/19 19:37:50 customdesigned
|
||||
# Train screener on whitelisted messages.
|
||||
#
|
||||
# Revision 1.32 2005/10/14 16:17:31 customdesigned
|
||||
# Auto whitelist refinements.
|
||||
#
|
||||
# Revision 1.31 2005/10/14 01:14:08 customdesigned
|
||||
# Auto whitelist feature.
|
||||
#
|
||||
# Revision 1.30 2005/10/12 16:36:30 customdesigned
|
||||
# Release 0.8.3
|
||||
#
|
||||
# Revision 1.29 2005/10/11 22:50:07 customdesigned
|
||||
# Always check HELO except for SPF pass, temperror.
|
||||
#
|
||||
@@ -337,6 +352,7 @@ dspam_users = {}
|
||||
dspam_userdir = None
|
||||
dspam_exempt = {}
|
||||
dspam_whitelist = {}
|
||||
whitelist_senders = {}
|
||||
dspam_screener = ()
|
||||
dspam_internal = True # True if internal mail should be dspammed
|
||||
dspam_reject = ()
|
||||
@@ -352,9 +368,8 @@ spf_best_guess = False
|
||||
spf_reject_noptr = False
|
||||
supply_sender = False
|
||||
access_file = None
|
||||
time_format = '%Y%b%d %H:%M:%S %Z'
|
||||
timeout = 600
|
||||
cbv_cache = {}
|
||||
|
||||
logging.basicConfig(
|
||||
stream=sys.stdout,
|
||||
level=logging.INFO,
|
||||
@@ -363,19 +378,6 @@ logging.basicConfig(
|
||||
)
|
||||
milter_log = logging.getLogger('milter')
|
||||
|
||||
try:
|
||||
too_old = time.time() - 7*24*60*60 # 7 days
|
||||
for ln in open('send_dsn.log'):
|
||||
try:
|
||||
rcpt,ts = ln.strip().split(None,1)
|
||||
l = time.strptime(ts,time_format)
|
||||
t = time.mktime(l)
|
||||
if t > too_old:
|
||||
cbv_cache[rcpt] = None
|
||||
except:
|
||||
cbv_cache[ln.strip()] = None
|
||||
except IOError: pass
|
||||
|
||||
class MilterConfigParser(ConfigParser.ConfigParser):
|
||||
|
||||
def getlist(self,sect,opt):
|
||||
@@ -499,6 +501,8 @@ def read_config(list):
|
||||
# dspam section
|
||||
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal
|
||||
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit
|
||||
global whitelist_senders
|
||||
whitelist_senders = cp.getaddrset('dspam','whitelist_senders')
|
||||
dspam_dict = cp.getdefault('dspam','dspam_dict')
|
||||
dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
|
||||
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
|
||||
@@ -614,7 +618,7 @@ class SPFPolicy(object):
|
||||
return None
|
||||
|
||||
def getFailPolicy(self):
|
||||
policy = self.getPolicy('SPF-Fail:')
|
||||
policy = self.getPolicy('spf-fail:')
|
||||
if not policy:
|
||||
if self.domain in spf_accept_fail:
|
||||
policy = 'CBV'
|
||||
@@ -623,7 +627,7 @@ class SPFPolicy(object):
|
||||
return policy
|
||||
|
||||
def getNonePolicy(self):
|
||||
policy = self.getPolicy('SPF-None:')
|
||||
policy = self.getPolicy('spf-none:')
|
||||
if not policy:
|
||||
if spf_reject_noptr:
|
||||
policy = 'REJECT'
|
||||
@@ -632,7 +636,7 @@ class SPFPolicy(object):
|
||||
return policy
|
||||
|
||||
def getSoftfailPolicy(self):
|
||||
policy = self.getPolicy('SPF-Softfail:')
|
||||
policy = self.getPolicy('spf-softfail:')
|
||||
if not policy:
|
||||
if self.domain in spf_accept_softfail:
|
||||
policy = 'OK'
|
||||
@@ -643,7 +647,7 @@ class SPFPolicy(object):
|
||||
return policy
|
||||
|
||||
def getNeutralPolicy(self):
|
||||
policy = self.getPolicy('SPF-Neutral:')
|
||||
policy = self.getPolicy('spf-neutral:')
|
||||
if not policy:
|
||||
if self.domain in spf_reject_neutral:
|
||||
policy = 'REJECT'
|
||||
@@ -651,17 +655,59 @@ class SPFPolicy(object):
|
||||
return policy
|
||||
|
||||
def getPermErrorPolicy(self):
|
||||
policy = self.getPolicy('SPF-PermError:')
|
||||
policy = self.getPolicy('spf-permerror:')
|
||||
if not policy:
|
||||
policy = 'REJECT'
|
||||
return policy
|
||||
|
||||
def getPassPolicy(self):
|
||||
policy = self.getPolicy('SPF-Pass:')
|
||||
policy = self.getPolicy('spf-pass:')
|
||||
if not policy:
|
||||
policy = 'OK'
|
||||
return policy
|
||||
|
||||
class AddrCache(object):
|
||||
time_format = '%Y%b%d %H:%M:%S %Z'
|
||||
|
||||
def load(self,fname,age=7):
|
||||
self.fname = fname
|
||||
cache = {}
|
||||
self.cache = cache
|
||||
try:
|
||||
too_old = time.time() - age*24*60*60 # max age in days
|
||||
for ln in open(self.fname):
|
||||
try:
|
||||
rcpt,ts = ln.strip().split(None,1)
|
||||
l = time.strptime(ts,AddrCache.time_format)
|
||||
t = time.mktime(l)
|
||||
if t > too_old:
|
||||
cache[rcpt.lower()] = None
|
||||
except:
|
||||
cache[ln.strip().lower()] = None
|
||||
except IOError: pass
|
||||
|
||||
def has_key(self,sender):
|
||||
return self.cache.has_key(sender.lower())
|
||||
|
||||
def __getitem__(self,sender):
|
||||
return self.cache[sender.lower()]
|
||||
|
||||
def __setitem__(self,sender,res):
|
||||
lsender = sender.lower()
|
||||
cached = lsender in self.cache
|
||||
self.cache[lsender] = res
|
||||
if not cached and not res:
|
||||
s = time.strftime(AddrCache.time_format,time.localtime())
|
||||
print >>open(self.fname,'a'),sender,s # log who we sent DSNs to
|
||||
|
||||
def __len__(self):
|
||||
return len(self.cache)
|
||||
|
||||
cbv_cache = AddrCache()
|
||||
cbv_cache.load('send_dsn.log',age=7)
|
||||
auto_whitelist = AddrCache()
|
||||
auto_whitelist.load('auto_whitelist.log',age=30)
|
||||
|
||||
class bmsMilter(Milter.Milter):
|
||||
"""Milter to replace attachments poisonous to Windows with a WARNING message,
|
||||
check SPF, and other anti-forgery features, and implement wiretapping
|
||||
@@ -778,6 +824,7 @@ class bmsMilter(Milter.Milter):
|
||||
self.hidepath = False
|
||||
self.discard = False
|
||||
self.dspam = True
|
||||
self.whitelist = False
|
||||
self.reject_spam = True
|
||||
self.data_allowed = True
|
||||
self.trust_received = self.trusted_relay
|
||||
@@ -787,6 +834,7 @@ class bmsMilter(Milter.Milter):
|
||||
self.new_headers = []
|
||||
self.recipients = []
|
||||
self.cbv_needed = None
|
||||
self.whitelist_sender = False
|
||||
t = parse_addr(f)
|
||||
if len(t) == 2: t[1] = t[1].lower()
|
||||
self.canon_from = '@'.join(t)
|
||||
@@ -831,17 +879,22 @@ class bmsMilter(Milter.Milter):
|
||||
self.log("REJECT: spam from self",pat)
|
||||
self.setreply('550','5.7.1','I hate talking to myself.')
|
||||
return Milter.REJECT
|
||||
elif internal_domains:
|
||||
else:
|
||||
if internal_domains:
|
||||
for pat in internal_domains:
|
||||
if fnmatchcase(domain,pat): break
|
||||
else:
|
||||
self.log("REJECT: zombie PC at ",self.connectip," sending MAIL FROM ",
|
||||
self.canon_from)
|
||||
self.log("REJECT: zombie PC at ",self.connectip,
|
||||
" sending MAIL FROM ",self.canon_from)
|
||||
self.setreply('550','5.7.1',
|
||||
'Your PC is using an unauthorized MAIL FROM.',
|
||||
'It is either badly misconfigured or controlled by organized crime.'
|
||||
)
|
||||
return Milter.REJECT
|
||||
wl_users = whitelist_senders.get(domain,())
|
||||
if user in wl_users or '' in wl_users:
|
||||
self.whitelist_sender = True
|
||||
|
||||
self.rejectvirus = domain in reject_virus_from
|
||||
if user in wiretap_users.get(domain,()):
|
||||
self.add_recipient(wiretap_dest)
|
||||
@@ -987,6 +1040,9 @@ class bmsMilter(Milter.Milter):
|
||||
return Milter.TEMPFAIL
|
||||
self.add_header('Received-SPF',q.get_header(res,receiver))
|
||||
self.spf = q
|
||||
if res == 'pass' and auto_whitelist.has_key(self.canon_from):
|
||||
self.whitelist = True
|
||||
self.log("WHITELIST",self.canon_from)
|
||||
return Milter.CONTINUE
|
||||
|
||||
# hide_path causes a copy of the message to be saved - until we
|
||||
@@ -998,8 +1054,9 @@ class bmsMilter(Milter.Milter):
|
||||
self.log('DISCARD: RCPT TO:',to,str)
|
||||
return Milter.DISCARD
|
||||
self.log("rcpt to",to,str)
|
||||
t = parse_addr(to.lower())
|
||||
t = parse_addr(to)
|
||||
if len(t) == 2:
|
||||
t[1] = t[1].lower()
|
||||
user,domain = t
|
||||
if self.is_bounce and srs and domain in srs_domain:
|
||||
oldaddr = '@'.join(parse_addr(to))
|
||||
@@ -1027,7 +1084,8 @@ class bmsMilter(Milter.Milter):
|
||||
self.data_allowed = not srs_reject_spoofed
|
||||
|
||||
# non DSN mail to SRS address will bounce due to invalid local part
|
||||
self.recipients.append('@'.join(t))
|
||||
canon_to = '@'.join(t)
|
||||
self.recipients.append(canon_to)
|
||||
users = check_user.get(domain)
|
||||
if self.discard:
|
||||
self.del_recipient(to)
|
||||
@@ -1043,6 +1101,14 @@ class bmsMilter(Milter.Milter):
|
||||
self.hidepath = True
|
||||
if not domain in dspam_reject:
|
||||
self.reject_spam = False
|
||||
if self.internal_connection and self.whitelist_sender:
|
||||
if internal_domains:
|
||||
for pat in internal_domains:
|
||||
if fnmatchcase(domain,pat): break
|
||||
else:
|
||||
auto_whitelist[canon_to] = None
|
||||
else:
|
||||
auto_whitelist[canon_to] = None
|
||||
self.smart_alias(to)
|
||||
#rcpt = self.getsymval("{rcpt_addr}")
|
||||
#self.log("rcpt-addr",rcpt);
|
||||
@@ -1161,7 +1227,7 @@ class bmsMilter(Milter.Milter):
|
||||
self.fp.write("%s: %s\n" % (name,val)) # add new headers to buffer
|
||||
self.fp.write("\n") # terminate headers
|
||||
# log when neither sender nor from domains matches mail from domain
|
||||
if supply_sender and self.mailfrom != '<>':
|
||||
if supply_sender and self.mailfrom != '<>' and not self.internal_connection:
|
||||
mf_domain = self.canon_from.split('@')[-1]
|
||||
self.fp.seek(0)
|
||||
msg = rfc822.Message(self.fp)
|
||||
@@ -1194,7 +1260,7 @@ class bmsMilter(Milter.Milter):
|
||||
dspam.DSF_CHAINED|dspam.DSF_CLASSIFY)
|
||||
try:
|
||||
ds.process(headers)
|
||||
if ds.probability > 0.93 and self.dspam:
|
||||
if ds.probability > 0.93 and self.dspam and not self.whitelist:
|
||||
self.log('REJECT: X-DSpam-HeaderScore: %f' % ds.probability)
|
||||
self.setreply('550','5.7.1','Your Message looks spammy')
|
||||
return Milter.REJECT
|
||||
@@ -1301,6 +1367,9 @@ class bmsMilter(Milter.Milter):
|
||||
self.fp = None
|
||||
if len(self.recipients) > 1:
|
||||
self.log("HONEYPOT:",rcpt,'SCREENED')
|
||||
if self.whitelist:
|
||||
# don't train when recipients includes honeypot
|
||||
return False
|
||||
if self.spf:
|
||||
# check that sender accepts quarantine DSN
|
||||
msg = mime.message_from_file(StringIO.StringIO(txt))
|
||||
@@ -1315,6 +1384,12 @@ class bmsMilter(Milter.Milter):
|
||||
force_result=dspam.DSR_ISSPAM)
|
||||
self.log("HONEYPOT:",rcpt)
|
||||
return Milter.DISCARD
|
||||
if self.whitelist:
|
||||
# Sender whitelisted: tag, but force as ham.
|
||||
# User can change if actually spam.
|
||||
txt = ds.check_spam(user,txt,self.recipients,
|
||||
force_result=dspam.DSR_ISINNOCENT)
|
||||
else:
|
||||
txt = ds.check_spam(user,txt,self.recipients)
|
||||
if not txt:
|
||||
# DISCARD if quarrantined for any recipient. It
|
||||
@@ -1339,6 +1414,13 @@ class bmsMilter(Milter.Milter):
|
||||
screener = dspam_screener[self.id % len(dspam_screener)]
|
||||
if not ds.check_spam(screener,txt,self.recipients,
|
||||
classify=True,quarantine=False):
|
||||
if self.whitelist:
|
||||
# messages is whitelisted but looked like spam, Train on Error
|
||||
self.log("TRAIN:",screener,'X-Dspam-Score: %f' % ds.probability)
|
||||
# user can't correct anyway if really spam, so discard tag
|
||||
ds.check_spam(screener,txt,self.recipients,
|
||||
force_result=dspam.DSR_ISINNOCENT)
|
||||
return False
|
||||
if self.reject_spam:
|
||||
self.log("DSPAM:",screener,
|
||||
'REJECT: X-DSpam-Score: %f' % ds.probability)
|
||||
@@ -1507,9 +1589,6 @@ class bmsMilter(Milter.Milter):
|
||||
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):
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.1 Final//EN">
|
||||
<html>
|
||||
<head>
|
||||
<title>Python Milter Log Documentation</title>
|
||||
<style>
|
||||
DT { font-weight: bolder; padding-top: 1em }
|
||||
</style>
|
||||
</head><body>
|
||||
|
||||
<h1> Milter Log Documentation </h1>
|
||||
|
||||
The milter log has a variety of "tags" in it that indicate what it did.
|
||||
|
||||
<dl>
|
||||
<dt> DSPAM: honeypot SCREENED
|
||||
<dd> message was quarantined to the honeypot quarantine
|
||||
|
||||
<dt> REJECT: hello SPF: fail 550 access denied
|
||||
<dt> REJECT: hello SPF: softfail 550 domain in transition
|
||||
<dt> REJECT: hello SPF: neutral 550 access neither permitted nor denied
|
||||
<dd> message was rejected because there was an SPF policy for the
|
||||
HELO name, and it did not pass.
|
||||
|
||||
<dt> CBV: sender-17-44662668-643@bluepenmagic.com
|
||||
<dd> we performed a call back verification
|
||||
|
||||
<dt> dspam
|
||||
<dd> dspam identifier was added to the message
|
||||
|
||||
<dt> REJECT: spam from self: jsconnor.com
|
||||
<dd> message was reject because HELO was us (jsconnor.com)
|
||||
|
||||
<dt> INNOC: richh
|
||||
<dd> message was used to update richh's dspam dictionary
|
||||
|
||||
<dt> HONEYPOT: michaelb@jsconnor.com
|
||||
<dd> message was sent to a honeypot address (michaelb@jsconnor.com), the
|
||||
message was added to the honeypot dspam dictionary as spam
|
||||
|
||||
<dt> REJECT: numeric hello name: 63.217.19.146
|
||||
<dd> message was rejected because helo name was invalid (numeric)
|
||||
|
||||
<dt> eom
|
||||
<dd> message was successfully received
|
||||
|
||||
<dt> TEMPFAIL: CBV: 450 No MX servers available
|
||||
<dd> we tried to do a call back verification but could not look up
|
||||
MX record, we told the sender to try again later
|
||||
|
||||
<dt> CBV: info@emailpizzahut.com (cached)
|
||||
<dd> call back verification was needed, we had already done it recently
|
||||
|
||||
<dt> abort after 0 body chars
|
||||
<dd> sender hung up on us
|
||||
|
||||
<dt> REJECT: SPF fail 550 SPF fail: see
|
||||
http://openspf.com/why.html?sender=m.hendersonxk@163.net&ip=213.47.161.100
|
||||
<dd> message was reject because its sender's spf policy said to
|
||||
|
||||
<dt> REJECT: Subject: Cialis - No prescription needed!
|
||||
<dd> message was rejected because its subject contained a bad expression
|
||||
|
||||
<dt> DSPAM: tonyc tonyc@jsconnor.com
|
||||
<dd> message was sent to tonyc@jsconnor.com and it was identified as spam
|
||||
and placed in the tonyc dspam quarantine
|
||||
|
||||
<dt> REJECT: CBV: 550 calvinalstonis@ix.netcom.com...User unknown
|
||||
<dt> REJECT: CBV: 553 sorry, that domain isn't in my list
|
||||
<dt> REJECT: CBV: 554 delivery error: dd This user doesn't have an account
|
||||
<dd> message was rejected because call back verification gave us a fatal
|
||||
error
|
||||
</dl>
|
||||
|
||||
Please add more tags to this list if you know of any. Thanks.
|
||||
</body>
|
||||
</html>
|
||||
+9
-3
@@ -8,7 +8,7 @@ tempdir = /var/log/milter/save
|
||||
log_headers = 0
|
||||
# connection ips and hostnames are matched against this glob style list
|
||||
# to recognize internal senders.
|
||||
;internal_connect = 192.168.*.*
|
||||
;internal_connect = 192.168.*.*,127.*
|
||||
|
||||
# mail that is not an internal_connect and claims to be from an
|
||||
# internal domain is rejected. Furthermore, internal mail that
|
||||
@@ -17,7 +17,7 @@ log_headers = 0
|
||||
# flexible. However, SPF is not currently checked for outgoing
|
||||
# (internal_connect) mail because it doesn't yet handle authorizing
|
||||
# internal IPs locally.
|
||||
;internal_domains = mycorp.com
|
||||
;internal_domains = mycorp.com,localhost.localdomain
|
||||
|
||||
# connections from a trusted relay can trust the first Received header
|
||||
# SPF checks are bypassed for internal connections and trusted relays.
|
||||
@@ -146,7 +146,13 @@ blind = 1
|
||||
# only EXTERNAL messages are dspam filtered
|
||||
;dspam_dict=/var/lib/dspam/moderator.dict
|
||||
|
||||
# Opt-opt recipients from dspam screening and header triage
|
||||
# Recipients of mail sent from these senders are added to the auto_whitelist.
|
||||
# Auto_whitelisted senders with an SPF PASS are never rejected by dspam, and
|
||||
# messages from auto_whitelisted senders will be used to train screener
|
||||
# dictionaries as innocent mail.
|
||||
;whitelist_senders = @mycorp.com
|
||||
|
||||
# Opt-out recipients entirely from dspam screening and header triage
|
||||
;dspam_exempt=getitall@mycorp.com
|
||||
# Do not scan mail (ostensibly) from these senders
|
||||
;dspam_whitelist=getitall@sender.com
|
||||
|
||||
+19
-2
@@ -24,7 +24,7 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
|
||||
Stuart D. Gathman</a><br>
|
||||
This web page is written by Stuart D. Gathman<br>and<br>sponsored by
|
||||
<a href="http://www.bmsi.com">Business Management Systems, Inc.</a> <br>
|
||||
Last updated Aug 28, 2005</h4>
|
||||
Last updated Oct 12, 2005</h4>
|
||||
|
||||
See the <a href="faq.html">FAQ</a> | <a href="http://sourceforge.net/project/showfiles.php?group_id=139894">Download now</a> |
|
||||
<a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
|
||||
@@ -47,10 +47,25 @@ efficient and secure. I recommend upgrading.
|
||||
|
||||
<h2> Recent Changes </h2>
|
||||
|
||||
Python milter is being moved to
|
||||
Python milter has been moved to
|
||||
<a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge
|
||||
project</a> for development and release downloads.
|
||||
<p>
|
||||
Release 0.8.3 uses the standard logging module, and supports configuring
|
||||
more detailed SPF policy via the sendmail access map. SMTP AUTH connections
|
||||
are considered INTERNAL. Preventing forgery between internal domains is
|
||||
just a matter of specifying the user-domain map - I'll define something
|
||||
for the next version. We now send DSNs when mail is quarantined (rejecting
|
||||
if DSN fails) and for SPF syntax errors (PermError). There is an
|
||||
experimental option to add a Sender header when it is missing and the From
|
||||
domain doesn't match the MAIL FROM domain. Next release, we may start
|
||||
renaming and replacing an existing Sender header when neither it nor the
|
||||
From domain matches MAIL FROM. Since bogus MAIL FROMs are rejected
|
||||
(to varying degrees depending on the configured SPF policy), and
|
||||
both Sender and From and displayed by default in many email clients,
|
||||
this provides some phishing protection without rejecting mail based
|
||||
on headers.
|
||||
<p>
|
||||
Release 0.8.2 has changes to <a href="http://openspf.net">SPF</a> to bring it
|
||||
in line with the newly official RFC. It adds
|
||||
<a href="http://ses.codeshare.ca/">SES</a>
|
||||
@@ -245,6 +260,8 @@ content filtering. SPF checking
|
||||
requires <a href="http://pydns.sourceforge.net/">
|
||||
pydns</a>. Configuration documentation is currently included as comments
|
||||
in the <a href="milter.cfg">sample config file</a> for the bms.py milter.
|
||||
See also the <a href="HOWTO">HOWTO</a> and <a href="logmsgs.html">
|
||||
Milter Log Message Tags</a>.
|
||||
<p>
|
||||
Python milter is under GPL. The authors can probably be convinced to
|
||||
change this to LGPL if needed.
|
||||
|
||||
+7
-2
@@ -1,5 +1,5 @@
|
||||
%define name milter
|
||||
%define version 0.8.3
|
||||
%define version 0.8.4
|
||||
%define release 1.RH7
|
||||
# what version of RH are we building for?
|
||||
%define redhat9 0
|
||||
@@ -169,7 +169,12 @@ rm -rf $RPM_BUILD_ROOT
|
||||
/usr/share/sendmail-cf/hack/rhsbl.m4
|
||||
|
||||
%changelog
|
||||
* Fri Jul 15 2005 Stuart Gathman <stuart@bmsi.com> 0.8.3-1
|
||||
* Thu Oct 20 2005 Stuart Gathman <stuart@bmsi.com> 0.8.4-1
|
||||
- Fix SPF policy via sendmail access map (case insensitive keys).
|
||||
- Auto whitelist senders, train screener on whitelisted messages
|
||||
- Optional idx parameter to addheader to invoke smfi_insheader
|
||||
- Activate progress when SMFIR_PROGRESS defined
|
||||
* Wed Oct 12 2005 Stuart Gathman <stuart@bmsi.com> 0.8.3-1
|
||||
- Keep screened honeypot mail, but optionally discard honeypot only mail.
|
||||
- spf_accept_fail option for braindead SPF senders (treats fail like softfail)
|
||||
- Consider SMTP AUTH connections internal.
|
||||
|
||||
+15
-6
@@ -34,6 +34,12 @@ $ python setup.py help
|
||||
libraries=["milter","smutil","resolv"]
|
||||
|
||||
* $Log$
|
||||
* Revision 1.7 2005/10/20 23:04:46 customdesigned
|
||||
* Add optional idx for position of added header.
|
||||
*
|
||||
* Revision 1.6 2005/07/15 22:18:17 customdesigned
|
||||
* Support callback exception policy
|
||||
*
|
||||
* Revision 1.5 2005/06/24 04:20:07 customdesigned
|
||||
* Report context allocation error.
|
||||
*
|
||||
@@ -967,28 +973,31 @@ milter_setreply(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
static char milter_addheader__doc__[] =
|
||||
"addheader(field, value) -> None\n\
|
||||
"addheader(field, value, idx=-1) -> None\n\
|
||||
Add a header to the message. This header is not passed to other\n\
|
||||
filters. It is not checked for standards compliance;\n\
|
||||
the mail filter must ensure that no protocols are violated\n\
|
||||
as a result of adding this header.\n\
|
||||
field - header field name\n\
|
||||
value - header field value\n\
|
||||
idx - optional position in internal header list to insert new header\n\
|
||||
Both are strings. This function can only be called from the EOM callback.";
|
||||
|
||||
static PyObject *
|
||||
milter_addheader(PyObject *self, PyObject *args) {
|
||||
char *headerf;
|
||||
char *headerv;
|
||||
int idx = -1;
|
||||
SMFICTX *ctx;
|
||||
PyThreadState *t;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "ss:addheader", &headerf, &headerv)) return NULL;
|
||||
if (!PyArg_ParseTuple(args, "ss|i:addheader", &headerf, &headerv, &idx))
|
||||
return NULL;
|
||||
ctx = _find_context(self);
|
||||
if (ctx == NULL) return NULL;
|
||||
t = PyEval_SaveThread();
|
||||
return _thread_return(t,smfi_addheader(ctx, headerf, headerv),
|
||||
"cannot add header");
|
||||
return _thread_return(t, (idx < 0) ? smfi_addheader(ctx, headerf, headerv) :
|
||||
smfi_insheader(ctx, idx, headerf, headerv), "cannot add header");
|
||||
}
|
||||
|
||||
static char milter_chgheader__doc__[] =
|
||||
@@ -1143,7 +1152,7 @@ milter_quarantine(PyObject *self, PyObject *args) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if _FFR_SMFI_PROGRESS
|
||||
#ifdef SMFIR_PROGRESS
|
||||
static char milter_progress__doc__[] =
|
||||
"progress() -> None\n\
|
||||
Notify the MTA that we are working on a message so it will reset timeouts.";
|
||||
@@ -1174,7 +1183,7 @@ static PyMethodDef context_methods[] = {
|
||||
#ifdef SMFIF_QUARANTINE
|
||||
{ "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__},
|
||||
#endif
|
||||
#if _FFR_SMFI_PROGRESS
|
||||
#ifdef SMFIR_PROGRESS
|
||||
{ "progress", milter_progress, METH_VARARGS, milter_progress__doc__},
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ sent via an authorized SMTP server, but may still be legitimate. Since there
|
||||
is no positive confirmation that the message is really from you, we have
|
||||
to give it extra scrutiny - including verifying that the sender really
|
||||
exists by sending you this DSN. We will remember this sender and not
|
||||
bother you again for while. You can avoid this message entirely for
|
||||
bother you again for a while. You can avoid this message entirely for
|
||||
legitimate mail by using an authorized SMTP server. Contact your mail
|
||||
administrator and ask how to configure your email client to use an
|
||||
authorized server.
|
||||
|
||||
Reference in New Issue
Block a user