Compare commits

..

1 Commits

Author SHA1 Message Date
cvs2svn 65c73f61c2 This commit was manufactured by cvs2svn to create tag 'milter-0_8_3'.
Sprout from master 2005-10-12 17:21:13 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.8.3'
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-12 17:21:14 +00:00
11 changed files with 68 additions and 265 deletions
+1 -4
View File
@@ -5,11 +5,8 @@ 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 (in random order):
Other contributors:
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
View File
@@ -16,7 +16,7 @@ from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \
try: from milter import QUARANTINE
except: pass
__version__ = '0.8.4'
__version__ = '0.8.3'
_seq_lock = thread.allocate_lock()
_seq = 0
@@ -44,7 +44,7 @@ class Milter:
for i in msg: print i,
print
def connect(self,hostname,family,hostaddr):
def connect(self,hostname,unused,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,idx=-1):
return self.__ctx.addheader(field,value,idx)
def addheader(self,field,value):
return self.__ctx.addheader(field,value)
def chgheader(self,field,idx,value):
return self.__ctx.chgheader(field,idx,value)
-1
View File
@@ -1,6 +1,5 @@
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)
+9 -10
View File
@@ -1,13 +1,10 @@
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.
Support explicit errors for SPF policy in access file:
Use /etc/mail/access for domain specific SPF policies.
SPF-Fail: REJECT
SPF-Softfail: OK
SPF-Neutral: OK
SPF-Neutral:aol.com ERROR:"550 AOL mail must get SPF PASS"
Defer TEMPERROR in SPF evaluation - give precedence to security
@@ -19,6 +16,9 @@ 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,8 +33,7 @@ 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. (Or user just adds arbitrary alias
unique to that forwarder to a database.) Alias only works for mail from that
user to give to the forwarder. Alias only works for mail from that
forwarder. Milter gets forwarder domain from alias and uses it to
SPF check forwarder.
+40 -119
View File
@@ -1,21 +1,6 @@
#!/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.
#
@@ -352,7 +337,6 @@ 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 = ()
@@ -368,8 +352,9 @@ 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,
@@ -378,6 +363,19 @@ 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):
@@ -501,8 +499,6 @@ 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')
@@ -618,7 +614,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'
@@ -627,7 +623,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'
@@ -636,7 +632,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'
@@ -647,7 +643,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'
@@ -655,59 +651,17 @@ 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
@@ -824,7 +778,6 @@ 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
@@ -834,7 +787,6 @@ 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)
@@ -879,22 +831,17 @@ 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
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.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
elif 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.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
self.rejectvirus = domain in reject_virus_from
if user in wiretap_users.get(domain,()):
self.add_recipient(wiretap_dest)
@@ -1040,9 +987,6 @@ 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
@@ -1054,9 +998,8 @@ class bmsMilter(Milter.Milter):
self.log('DISCARD: RCPT TO:',to,str)
return Milter.DISCARD
self.log("rcpt to",to,str)
t = parse_addr(to)
t = parse_addr(to.lower())
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))
@@ -1084,8 +1027,7 @@ class bmsMilter(Milter.Milter):
self.data_allowed = not srs_reject_spoofed
# non DSN mail to SRS address will bounce due to invalid local part
canon_to = '@'.join(t)
self.recipients.append(canon_to)
self.recipients.append('@'.join(t))
users = check_user.get(domain)
if self.discard:
self.del_recipient(to)
@@ -1101,14 +1043,6 @@ 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);
@@ -1227,7 +1161,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 != '<>' and not self.internal_connection:
if supply_sender and self.mailfrom != '<>':
mf_domain = self.canon_from.split('@')[-1]
self.fp.seek(0)
msg = rfc822.Message(self.fp)
@@ -1260,7 +1194,7 @@ class bmsMilter(Milter.Milter):
dspam.DSF_CHAINED|dspam.DSF_CLASSIFY)
try:
ds.process(headers)
if ds.probability > 0.93 and self.dspam and not self.whitelist:
if ds.probability > 0.93 and self.dspam:
self.log('REJECT: X-DSpam-HeaderScore: %f' % ds.probability)
self.setreply('550','5.7.1','Your Message looks spammy')
return Milter.REJECT
@@ -1367,9 +1301,6 @@ 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))
@@ -1384,13 +1315,7 @@ 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)
txt = ds.check_spam(user,txt,self.recipients)
if not txt:
# DISCARD if quarrantined for any recipient. It
# will be resent to all recipients if they submit
@@ -1414,13 +1339,6 @@ 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)
@@ -1589,6 +1507,9 @@ 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):
-76
View File
@@ -1,76 +0,0 @@
<!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>
+3 -9
View File
@@ -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.*.*,127.*
;internal_connect = 192.168.*.*
# 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,localhost.localdomain
;internal_domains = mycorp.com
# connections from a trusted relay can trust the first Received header
# SPF checks are bypassed for internal connections and trusted relays.
@@ -146,13 +146,7 @@ blind = 1
# only EXTERNAL messages are dspam filtered
;dspam_dict=/var/lib/dspam/moderator.dict
# 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
# Opt-opt recipients from dspam screening and header triage
;dspam_exempt=getitall@mycorp.com
# Do not scan mail (ostensibly) from these senders
;dspam_whitelist=getitall@sender.com
+2 -19
View File
@@ -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 Oct 12, 2005</h4>
Last updated Aug 28, 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,25 +47,10 @@ efficient and secure. I recommend upgrading.
<h2> Recent Changes </h2>
Python milter has been moved to
Python milter is being 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>
@@ -260,8 +245,6 @@ 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.
+2 -7
View File
@@ -1,5 +1,5 @@
%define name milter
%define version 0.8.4
%define version 0.8.3
%define release 1.RH7
# what version of RH are we building for?
%define redhat9 0
@@ -169,12 +169,7 @@ rm -rf $RPM_BUILD_ROOT
/usr/share/sendmail-cf/hack/rhsbl.m4
%changelog
* 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
* Fri Jul 15 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.
+6 -15
View File
@@ -34,12 +34,6 @@ $ 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.
*
@@ -973,31 +967,28 @@ milter_setreply(PyObject *self, PyObject *args) {
}
static char milter_addheader__doc__[] =
"addheader(field, value, idx=-1) -> None\n\
"addheader(field, value) -> 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|i:addheader", &headerf, &headerv, &idx))
return NULL;
if (!PyArg_ParseTuple(args, "ss:addheader", &headerf, &headerv)) return NULL;
ctx = _find_context(self);
if (ctx == NULL) return NULL;
t = PyEval_SaveThread();
return _thread_return(t, (idx < 0) ? smfi_addheader(ctx, headerf, headerv) :
smfi_insheader(ctx, idx, headerf, headerv), "cannot add header");
return _thread_return(t,smfi_addheader(ctx, headerf, headerv),
"cannot add header");
}
static char milter_chgheader__doc__[] =
@@ -1152,7 +1143,7 @@ milter_quarantine(PyObject *self, PyObject *args) {
}
#endif
#ifdef SMFIR_PROGRESS
#if _FFR_SMFI_PROGRESS
static char milter_progress__doc__[] =
"progress() -> None\n\
Notify the MTA that we are working on a message so it will reset timeouts.";
@@ -1183,7 +1174,7 @@ static PyMethodDef context_methods[] = {
#ifdef SMFIF_QUARANTINE
{ "quarantine", milter_quarantine, METH_VARARGS, milter_quarantine__doc__},
#endif
#ifdef SMFIR_PROGRESS
#if _FFR_SMFI_PROGRESS
{ "progress", milter_progress, METH_VARARGS, milter_progress__doc__},
#endif
{ 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
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 a while. You can avoid this message entirely for
bother you again for 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.