Compare commits

...

9 Commits

Author SHA1 Message Date
cvs2svn 3ff607eb84 This commit was manufactured by cvs2svn to create tag 'milter-0_8_4'.
Sprout from master 2005-10-20 23:36:11 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.8.4'
Cherrypick from bmsi 2005-05-31 18:23:49 UTC Stuart Gathman <stuart@gathman.org> 'Development changes since 0.7.2':
    README
    cid2spf.py
    milter.rc
    milter.rc7
    rejects.py
    rhsbl.m4
    sample.py
    test.py
    test/amazon
    test/big5
    test/bounce
    test/bounce1
    test/bound
    test/honey
    test/missingboundary
    test/samp1
    test/spam44
    test/spam7
    test/spam8
    test/test1
    test/test8
    test/virus1
    test/virus13
    test/virus2
    test/virus3
    test/virus4
    test/virus5
    test/virus6
    test/virus7
    testsample.py
2005-10-20 23:36:12 +00:00
Stuart Gathman c6ac3ddad8 Release 0.8.4 2005-10-20 23:36:11 +00:00
Stuart Gathman b3dce26928 Include smfi_progress is SMFIR_PROGRESS defined 2005-10-20 23:23:36 +00:00
Stuart Gathman fcd85dbfb5 Add optional idx for position of added header. 2005-10-20 23:04:49 +00:00
Stuart Gathman 3a1c964f0d Configure auto_whitelist senders. 2005-10-20 18:47:27 +00:00
Stuart Gathman 36ae390f01 access.db stores keys in lower case 2005-10-19 21:07:49 +00:00
Stuart Gathman 4c0cf4fb95 Train screener on whitelisted messages. 2005-10-19 19:37:50 +00:00
Stuart Gathman 8f8de8fa97 Auto whitelist refinements. 2005-10-14 16:17:31 +00:00
Stuart Gathman bc516456c1 Auto whitelist feature. 2005-10-14 01:14:08 +00:00
11 changed files with 265 additions and 68 deletions
+4 -1
View File
@@ -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 it with distutils, and generally transformed it from a quick hack to a
real, usable Python extension. 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 Terence Way
for providing a Python port of SPF for providing a Python port of SPF
Scott Kitterman Scott Kitterman
+4 -4
View File
@@ -16,7 +16,7 @@ from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
try: from milter import QUARANTINE try: from milter import QUARANTINE
except: pass except: pass
__version__ = '0.8.3' __version__ = '0.8.4'
_seq_lock = thread.allocate_lock() _seq_lock = thread.allocate_lock()
_seq = 0 _seq = 0
@@ -44,7 +44,7 @@ class Milter:
for i in msg: print i, for i in msg: print i,
print print
def connect(self,hostname,unused,hostaddr): def connect(self,hostname,family,hostaddr):
"Called for each connection to sendmail." "Called for each connection to sendmail."
self.log("connect from %s at %s" % (hostname,hostaddr)) self.log("connect from %s at %s" % (hostname,hostaddr))
return CONTINUE return CONTINUE
@@ -106,8 +106,8 @@ class Milter:
return self.__ctx.setreply(rcode,xcode,msg,*ml) return self.__ctx.setreply(rcode,xcode,msg,*ml)
# Milter methods which can only be called from eom callback. # Milter methods which can only be called from eom callback.
def addheader(self,field,value): def addheader(self,field,value,idx=-1):
return self.__ctx.addheader(field,value) return self.__ctx.addheader(field,value,idx)
def chgheader(self,field,idx,value): def chgheader(self,field,idx,value):
return self.__ctx.chgheader(field,idx,value) return self.__ctx.chgheader(field,idx,value)
+1
View File
@@ -1,5 +1,6 @@
Here is a history of user visible changes to Python milter. 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. 0.8.3 Keep screened honeypot mail, but optionally discard honeypot only mail.
spf_accept_fail option for braindead SPF senders spf_accept_fail option for braindead SPF senders
(treats fail like softfail) (treats fail like softfail)
+10 -9
View File
@@ -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. Find rfc2822 policy for MFROM quoting.
Use /etc/mail/access for domain specific SPF policies. Support explicit errors for SPF policy in access file:
SPF-Fail: REJECT
SPF-Softfail: OK
SPF-Neutral: OK
SPF-Neutral:aol.com ERROR:"550 AOL mail must get SPF PASS" SPF-Neutral:aol.com ERROR:"550 AOL mail must get SPF PASS"
Defer TEMPERROR in SPF evaluation - give precedence to security 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 Create null config that does nothing - except maybe add Received-SPF
headers. Many admins would like to turn features on one at a time. 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. Can't output messages with malformed rfc822 attachments.
Move milter,Milter,mime,spf modules to pymilter 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 Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS
forwarder accounts, and a util provides a special local alias for the 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 forwarder. Milter gets forwarder domain from alias and uses it to
SPF check forwarder. SPF check forwarder.
+110 -31
View File
@@ -1,6 +1,21 @@
#!/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.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 # Revision 1.29 2005/10/11 22:50:07 customdesigned
# Always check HELO except for SPF pass, temperror. # Always check HELO except for SPF pass, temperror.
# #
@@ -337,6 +352,7 @@ dspam_users = {}
dspam_userdir = None dspam_userdir = None
dspam_exempt = {} dspam_exempt = {}
dspam_whitelist = {} dspam_whitelist = {}
whitelist_senders = {}
dspam_screener = () dspam_screener = ()
dspam_internal = True # True if internal mail should be dspammed dspam_internal = True # True if internal mail should be dspammed
dspam_reject = () dspam_reject = ()
@@ -352,9 +368,8 @@ spf_best_guess = False
spf_reject_noptr = False spf_reject_noptr = False
supply_sender = False supply_sender = False
access_file = None access_file = None
time_format = '%Y%b%d %H:%M:%S %Z'
timeout = 600 timeout = 600
cbv_cache = {}
logging.basicConfig( logging.basicConfig(
stream=sys.stdout, stream=sys.stdout,
level=logging.INFO, level=logging.INFO,
@@ -363,19 +378,6 @@ logging.basicConfig(
) )
milter_log = logging.getLogger('milter') 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): class MilterConfigParser(ConfigParser.ConfigParser):
def getlist(self,sect,opt): def getlist(self,sect,opt):
@@ -499,6 +501,8 @@ def read_config(list):
# dspam section # dspam section
global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal global dspam_dict, dspam_users, dspam_userdir, dspam_exempt, dspam_internal
global dspam_screener,dspam_whitelist,dspam_reject,dspam_sizelimit 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_dict = cp.getdefault('dspam','dspam_dict')
dspam_exempt = cp.getaddrset('dspam','dspam_exempt') dspam_exempt = cp.getaddrset('dspam','dspam_exempt')
dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist') dspam_whitelist = cp.getaddrset('dspam','dspam_whitelist')
@@ -614,7 +618,7 @@ class SPFPolicy(object):
return None return None
def getFailPolicy(self): def getFailPolicy(self):
policy = self.getPolicy('SPF-Fail:') policy = self.getPolicy('spf-fail:')
if not policy: if not policy:
if self.domain in spf_accept_fail: if self.domain in spf_accept_fail:
policy = 'CBV' policy = 'CBV'
@@ -623,7 +627,7 @@ class SPFPolicy(object):
return policy return policy
def getNonePolicy(self): def getNonePolicy(self):
policy = self.getPolicy('SPF-None:') policy = self.getPolicy('spf-none:')
if not policy: if not policy:
if spf_reject_noptr: if spf_reject_noptr:
policy = 'REJECT' policy = 'REJECT'
@@ -632,7 +636,7 @@ class SPFPolicy(object):
return policy return policy
def getSoftfailPolicy(self): def getSoftfailPolicy(self):
policy = self.getPolicy('SPF-Softfail:') policy = self.getPolicy('spf-softfail:')
if not policy: if not policy:
if self.domain in spf_accept_softfail: if self.domain in spf_accept_softfail:
policy = 'OK' policy = 'OK'
@@ -643,7 +647,7 @@ class SPFPolicy(object):
return policy return policy
def getNeutralPolicy(self): def getNeutralPolicy(self):
policy = self.getPolicy('SPF-Neutral:') policy = self.getPolicy('spf-neutral:')
if not policy: if not policy:
if self.domain in spf_reject_neutral: if self.domain in spf_reject_neutral:
policy = 'REJECT' policy = 'REJECT'
@@ -651,17 +655,59 @@ class SPFPolicy(object):
return policy return policy
def getPermErrorPolicy(self): def getPermErrorPolicy(self):
policy = self.getPolicy('SPF-PermError:') policy = self.getPolicy('spf-permerror:')
if not policy: if not policy:
policy = 'REJECT' policy = 'REJECT'
return policy return policy
def getPassPolicy(self): def getPassPolicy(self):
policy = self.getPolicy('SPF-Pass:') policy = self.getPolicy('spf-pass:')
if not policy: if not policy:
policy = 'OK' policy = 'OK'
return policy 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): class bmsMilter(Milter.Milter):
"""Milter to replace attachments poisonous to Windows with a WARNING message, """Milter to replace attachments poisonous to Windows with a WARNING message,
check SPF, and other anti-forgery features, and implement wiretapping check SPF, and other anti-forgery features, and implement wiretapping
@@ -778,6 +824,7 @@ class bmsMilter(Milter.Milter):
self.hidepath = False self.hidepath = False
self.discard = False self.discard = False
self.dspam = True self.dspam = True
self.whitelist = False
self.reject_spam = True self.reject_spam = True
self.data_allowed = True self.data_allowed = True
self.trust_received = self.trusted_relay self.trust_received = self.trusted_relay
@@ -787,6 +834,7 @@ class bmsMilter(Milter.Milter):
self.new_headers = [] self.new_headers = []
self.recipients = [] self.recipients = []
self.cbv_needed = None self.cbv_needed = None
self.whitelist_sender = False
t = parse_addr(f) t = parse_addr(f)
if len(t) == 2: t[1] = t[1].lower() if len(t) == 2: t[1] = t[1].lower()
self.canon_from = '@'.join(t) self.canon_from = '@'.join(t)
@@ -831,17 +879,22 @@ class bmsMilter(Milter.Milter):
self.log("REJECT: spam from self",pat) self.log("REJECT: spam from self",pat)
self.setreply('550','5.7.1','I hate talking to myself.') self.setreply('550','5.7.1','I hate talking to myself.')
return Milter.REJECT return Milter.REJECT
elif internal_domains: else:
if internal_domains:
for pat in internal_domains: for pat in internal_domains:
if fnmatchcase(domain,pat): break if fnmatchcase(domain,pat): break
else: else:
self.log("REJECT: zombie PC at ",self.connectip," sending MAIL FROM ", self.log("REJECT: zombie PC at ",self.connectip,
self.canon_from) " sending MAIL FROM ",self.canon_from)
self.setreply('550','5.7.1', self.setreply('550','5.7.1',
'Your PC is using an unauthorized MAIL FROM.', 'Your PC is using an unauthorized MAIL FROM.',
'It is either badly misconfigured or controlled by organized crime.' 'It is either badly misconfigured or controlled by organized crime.'
) )
return Milter.REJECT 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 self.rejectvirus = domain in reject_virus_from
if user in wiretap_users.get(domain,()): if user in wiretap_users.get(domain,()):
self.add_recipient(wiretap_dest) self.add_recipient(wiretap_dest)
@@ -987,6 +1040,9 @@ class bmsMilter(Milter.Milter):
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 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 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
@@ -998,8 +1054,9 @@ class bmsMilter(Milter.Milter):
self.log('DISCARD: RCPT TO:',to,str) self.log('DISCARD: RCPT TO:',to,str)
return Milter.DISCARD return Milter.DISCARD
self.log("rcpt to",to,str) self.log("rcpt to",to,str)
t = parse_addr(to.lower()) t = parse_addr(to)
if len(t) == 2: if len(t) == 2:
t[1] = t[1].lower()
user,domain = t user,domain = t
if self.is_bounce and srs and domain in srs_domain: if self.is_bounce and srs and domain in srs_domain:
oldaddr = '@'.join(parse_addr(to)) oldaddr = '@'.join(parse_addr(to))
@@ -1027,7 +1084,8 @@ class bmsMilter(Milter.Milter):
self.data_allowed = not srs_reject_spoofed self.data_allowed = not srs_reject_spoofed
# non DSN mail to SRS address will bounce due to invalid local part # 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) users = check_user.get(domain)
if self.discard: if self.discard:
self.del_recipient(to) self.del_recipient(to)
@@ -1043,6 +1101,14 @@ class bmsMilter(Milter.Milter):
self.hidepath = True self.hidepath = True
if not domain in dspam_reject: if not domain in dspam_reject:
self.reject_spam = False 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) self.smart_alias(to)
#rcpt = self.getsymval("{rcpt_addr}") #rcpt = self.getsymval("{rcpt_addr}")
#self.log("rcpt-addr",rcpt); #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("%s: %s\n" % (name,val)) # add new headers to buffer
self.fp.write("\n") # terminate headers self.fp.write("\n") # terminate headers
# log when neither sender nor from domains matches mail from domain # 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] mf_domain = self.canon_from.split('@')[-1]
self.fp.seek(0) self.fp.seek(0)
msg = rfc822.Message(self.fp) msg = rfc822.Message(self.fp)
@@ -1194,7 +1260,7 @@ class bmsMilter(Milter.Milter):
dspam.DSF_CHAINED|dspam.DSF_CLASSIFY) dspam.DSF_CHAINED|dspam.DSF_CLASSIFY)
try: try:
ds.process(headers) 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.log('REJECT: X-DSpam-HeaderScore: %f' % ds.probability)
self.setreply('550','5.7.1','Your Message looks spammy') self.setreply('550','5.7.1','Your Message looks spammy')
return Milter.REJECT return Milter.REJECT
@@ -1301,6 +1367,9 @@ class bmsMilter(Milter.Milter):
self.fp = None self.fp = None
if len(self.recipients) > 1: if len(self.recipients) > 1:
self.log("HONEYPOT:",rcpt,'SCREENED') self.log("HONEYPOT:",rcpt,'SCREENED')
if self.whitelist:
# don't train when recipients includes honeypot
return False
if self.spf: if self.spf:
# check that sender accepts quarantine DSN # check that sender accepts quarantine DSN
msg = mime.message_from_file(StringIO.StringIO(txt)) msg = mime.message_from_file(StringIO.StringIO(txt))
@@ -1315,6 +1384,12 @@ class bmsMilter(Milter.Milter):
force_result=dspam.DSR_ISSPAM) force_result=dspam.DSR_ISSPAM)
self.log("HONEYPOT:",rcpt) self.log("HONEYPOT:",rcpt)
return Milter.DISCARD 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) 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
@@ -1339,6 +1414,13 @@ class bmsMilter(Milter.Milter):
screener = dspam_screener[self.id % len(dspam_screener)] screener = dspam_screener[self.id % len(dspam_screener)]
if not ds.check_spam(screener,txt,self.recipients, if not ds.check_spam(screener,txt,self.recipients,
classify=True,quarantine=False): 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: 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)
@@ -1507,9 +1589,6 @@ class bmsMilter(Milter.Milter):
self.setreply('550','5.7.1',*desc.splitlines()) self.setreply('550','5.7.1',*desc.splitlines())
return Milter.REJECT return Milter.REJECT
cbv_cache[sender] = res 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 return Milter.CONTINUE
def close(self): def close(self):
+76
View File
@@ -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
View File
@@ -8,7 +8,7 @@ tempdir = /var/log/milter/save
log_headers = 0 log_headers = 0
# connection ips and hostnames are matched against this glob style list # connection ips and hostnames are matched against this glob style list
# to recognize internal senders. # 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 # mail that is not an internal_connect and claims to be from an
# internal domain is rejected. Furthermore, internal mail that # internal domain is rejected. Furthermore, internal mail that
@@ -17,7 +17,7 @@ log_headers = 0
# flexible. However, SPF is not currently checked for outgoing # flexible. However, SPF is not currently checked for outgoing
# (internal_connect) mail because it doesn't yet handle authorizing # (internal_connect) mail because it doesn't yet handle authorizing
# internal IPs locally. # internal IPs locally.
;internal_domains = mycorp.com ;internal_domains = mycorp.com,localhost.localdomain
# connections from a trusted relay can trust the first Received header # connections from a trusted relay can trust the first Received header
# SPF checks are bypassed for internal connections and trusted relays. # SPF checks are bypassed for internal connections and trusted relays.
@@ -146,7 +146,13 @@ blind = 1
# only EXTERNAL messages are dspam filtered # only EXTERNAL messages are dspam filtered
;dspam_dict=/var/lib/dspam/moderator.dict ;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 ;dspam_exempt=getitall@mycorp.com
# Do not scan mail (ostensibly) from these senders # Do not scan mail (ostensibly) from these senders
;dspam_whitelist=getitall@sender.com ;dspam_whitelist=getitall@sender.com
+19 -2
View File
@@ -24,7 +24,7 @@ ALT="Viewable With Any Browser" BORDER="0"></A>
Stuart D. Gathman</a><br> Stuart D. Gathman</a><br>
This web page is written by Stuart D. Gathman<br>and<br>sponsored by 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> <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> | 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> | <a href="/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
@@ -47,10 +47,25 @@ efficient and secure. I recommend upgrading.
<h2> Recent Changes </h2> <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 <a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge
project</a> for development and release downloads. project</a> for development and release downloads.
<p> <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 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 in line with the newly official RFC. It adds
<a href="http://ses.codeshare.ca/">SES</a> <a href="http://ses.codeshare.ca/">SES</a>
@@ -245,6 +260,8 @@ content filtering. SPF checking
requires <a href="http://pydns.sourceforge.net/"> requires <a href="http://pydns.sourceforge.net/">
pydns</a>. Configuration documentation is currently included as comments pydns</a>. Configuration documentation is currently included as comments
in the <a href="milter.cfg">sample config file</a> for the bms.py milter. 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> <p>
Python milter is under GPL. The authors can probably be convinced to Python milter is under GPL. The authors can probably be convinced to
change this to LGPL if needed. change this to LGPL if needed.
+7 -2
View File
@@ -1,5 +1,5 @@
%define name milter %define name milter
%define version 0.8.3 %define version 0.8.4
%define release 1.RH7 %define release 1.RH7
# what version of RH are we building for? # what version of RH are we building for?
%define redhat9 0 %define redhat9 0
@@ -169,7 +169,12 @@ rm -rf $RPM_BUILD_ROOT
/usr/share/sendmail-cf/hack/rhsbl.m4 /usr/share/sendmail-cf/hack/rhsbl.m4
%changelog %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. - 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.
+15 -6
View File
@@ -34,6 +34,12 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $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 * Revision 1.5 2005/06/24 04:20:07 customdesigned
* Report context allocation error. * Report context allocation error.
* *
@@ -967,28 +973,31 @@ milter_setreply(PyObject *self, PyObject *args) {
} }
static char milter_addheader__doc__[] = 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\ Add a header to the message. This header is not passed to other\n\
filters. It is not checked for standards compliance;\n\ filters. It is not checked for standards compliance;\n\
the mail filter must ensure that no protocols are violated\n\ the mail filter must ensure that no protocols are violated\n\
as a result of adding this header.\n\ as a result of adding this header.\n\
field - header field name\n\ field - header field name\n\
value - header field value\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."; Both are strings. This function can only be called from the EOM callback.";
static PyObject * static PyObject *
milter_addheader(PyObject *self, PyObject *args) { milter_addheader(PyObject *self, PyObject *args) {
char *headerf; char *headerf;
char *headerv; char *headerv;
int idx = -1;
SMFICTX *ctx; SMFICTX *ctx;
PyThreadState *t; 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); ctx = _find_context(self);
if (ctx == NULL) return NULL; if (ctx == NULL) return NULL;
t = PyEval_SaveThread(); t = PyEval_SaveThread();
return _thread_return(t,smfi_addheader(ctx, headerf, headerv), return _thread_return(t, (idx < 0) ? smfi_addheader(ctx, headerf, headerv) :
"cannot add header"); smfi_insheader(ctx, idx, headerf, headerv), "cannot add header");
} }
static char milter_chgheader__doc__[] = static char milter_chgheader__doc__[] =
@@ -1143,7 +1152,7 @@ milter_quarantine(PyObject *self, PyObject *args) {
} }
#endif #endif
#if _FFR_SMFI_PROGRESS #ifdef SMFIR_PROGRESS
static char milter_progress__doc__[] = static char milter_progress__doc__[] =
"progress() -> None\n\ "progress() -> None\n\
Notify the MTA that we are working on a message so it will reset timeouts."; 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 #ifdef SMFIF_QUARANTINE
{ "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__}, { "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__},
#endif #endif
#if _FFR_SMFI_PROGRESS #ifdef SMFIR_PROGRESS
{ "progress", milter_progress, METH_VARARGS, milter_progress__doc__}, { "progress", milter_progress, METH_VARARGS, milter_progress__doc__},
#endif #endif
{ NULL, NULL } { NULL, NULL }
+1 -1
View File
@@ -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 is no positive confirmation that the message is really from you, we have
to give it extra scrutiny - including verifying that the sender really to give it extra scrutiny - including verifying that the sender really
exists by sending you this DSN. We will remember this sender and not 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 legitimate mail by using an authorized SMTP server. Contact your mail
administrator and ask how to configure your email client to use an administrator and ask how to configure your email client to use an
authorized server. authorized server.