Compare commits

..

32 Commits

Author SHA1 Message Date
cvs2svn 07503a8dea This commit was manufactured by cvs2svn to create tag 'pymilter-0_9_2'.
Sprout from master 2009-06-04 22:17:40 UTC Stuart Gathman <stuart@gathman.org> 'Release 0.9.2-3'
Cherrypick from bmsi 2005-05-31 18:23:49 UTC Stuart Gathman <stuart@gathman.org> 'Development changes since 0.7.2':
    sample.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
2009-06-04 22:17:41 +00:00
Stuart Gathman 6a1996117c Release 0.9.2-3 2009-06-04 22:17:40 +00:00
Stuart Gathman 77c0ce6b2e Avoid getpriv() overhead. 2009-06-04 22:16:32 +00:00
Stuart Gathman 7311f65150 Set milter_protocol attribute of noreply wrapper 2009-06-04 22:02:09 +00:00
Stuart Gathman 84bd61aac1 Wrap @noreply callbacks to return NOREPLY only when so negotiated. 2009-06-04 21:47:34 +00:00
Stuart Gathman 372fad6ac9 Release 0.9.2-2 2009-06-02 21:38:09 +00:00
Stuart Gathman 60963b3c37 Streamline negotiate 2009-06-02 17:49:49 +00:00
Stuart Gathman 6221f8b753 Validate methods passed to @noreply, @nocallback 2009-06-01 22:28:33 +00:00
Stuart Gathman 344ecc7c07 Typo SMFIP_NO constants. 2009-05-29 20:44:58 +00:00
Stuart Gathman ee14614c3e Typo SMFIS_ALL_OPTS 2009-05-29 19:53:36 +00:00
Stuart Gathman 4bb2403223 Typo calling helo instead of negotiate. 2009-05-29 19:49:40 +00:00
Stuart Gathman d58546930a Init future flags in negotiate. 2009-05-29 19:41:01 +00:00
Stuart Gathman f8efbb23df Create Milter on either connect or negotiate 2009-05-29 19:30:05 +00:00
Stuart Gathman 26b006455e Null terminate keyword list. 2009-05-29 18:25:59 +00:00
Stuart Gathman 9b7ca633f3 Release 0.9.2 2009-05-29 01:22:34 +00:00
Stuart Gathman 5928e99520 Remove amazon test since it contains copyrighted material. 2009-05-29 01:20:44 +00:00
Stuart Gathman 6d3833da72 Release 0.9.2 2009-05-29 01:16:27 +00:00
Stuart Gathman 2937935fea Comment updates 2009-05-29 01:14:44 +00:00
Stuart Gathman 31aa39034b Start with all symbols from milter module. 2009-05-28 18:54:48 +00:00
Stuart Gathman cb31963492 Support new callbacks, including negotiate 2009-05-28 18:36:43 +00:00
Stuart Gathman ed17f9cecf First cut at support unknown, data, negotiate callbacks. 2009-05-21 21:53:05 +00:00
Stuart Gathman 0e1a2de41f Support non-DSN CBV (non-empty MAIL FROM) 2009-05-20 20:08:44 +00:00
Stuart Gathman 9f419e3fc8 Release 0.9.1 2009-02-06 04:59:54 +00:00
Stuart Gathman 6913fd3e66 Release 0.9.1 2009-02-06 04:29:49 +00:00
Stuart Gathman 780ac63ebe Oops! Missing options argument pointer for addrcpt. 2009-02-06 04:28:08 +00:00
Stuart Gathman b51c08ba3a More changes from Fedora review. 2009-02-06 02:35:01 +00:00
Stuart Gathman 2e7805e531 Fedora core changes 2009-01-27 02:28:52 +00:00
Stuart Gathman b1eae98453 Changes for Fedora 2009-01-08 03:44:51 +00:00
Stuart Gathman 9118364164 Fedora release 2008-12-16 04:21:05 +00:00
Stuart Gathman 577c0bd134 Release 0.9.0 2008-12-14 03:01:43 +00:00
Stuart Gathman a97dbb8fd9 Release 0.8.12 2008-12-14 02:55:42 +00:00
Stuart Gathman df036eb55f Remove project docs 2008-12-14 02:54:46 +00:00
19 changed files with 503 additions and 1578 deletions
+4
View File
@@ -7,6 +7,10 @@ real, usable Python extension.
Other contributors (in random order): Other contributors (in random order):
arkanes@irc.freenode.net
for suggesting a class method to compute and cache protocol masks
habnabit@habnabit.org
for suggesting function attributes and decorators for protocol negotiation
Dwayne Litzenberger, B.A.Sc. Dwayne Litzenberger, B.A.Sc.
for library_dirs patch to compile on Debian for library_dirs patch to compile on Debian
Dave MacQuigg Dave MacQuigg
-154
View File
@@ -1,154 +0,0 @@
On Sun, 11 Feb 2007, Rick Saul wrote:
> Stuart I was planning to move to centos4.4 in a couple of weeks anyway...
> Your advice of where to go from here.
Oh - you are asking for a howto.
Step one. Which DSPAM is right for you?
The DSPAM project makes dspam part of the LDA (Local Delivery Agent).
Pydspam puts dspam into the MTA (Mail Transfer Agent - sendmail with pymilter).
The advantage of doing dspam in the LDA is that any aliasing has already been
resolved. You need only configure mailboxes.
The advantage of doing dspam in the MTA is it can screen an entire
company as a gateway with multiple domains. Unfortunately, this
means you have to tell it about all the aliases that comprise each
account. (Also, pydspam is still uses dspam-2.6.5.2 - the Dspam API
has changed for newer versions.)
If the LDA is right for you, you'll want to use the official Dspam
package. http://www.nuclearelephant.com/projects/dspam/
If the MTA approach is what you want, then pydspam is what you want.
In either case, you will still want pymilter to block forgeries, Windows
executables, etc.
So, lets assume you want to install pymilter, and may or may not
wish to install pydspam.
Step two. Obtaining RPMS.
For basic pymilter you'll need:
python-2.4
milter-0.8.10
sendmail-8.13.x (with milter support enabled)
and for SPF you'll need:
pydns-2.3.3-2.4
pyspf-2.0.5-1.py24
and for SRS you'll need:
pysrs-0.30.11-1.py24
I'm pretty sure you will want to have SPF and SRS available.
Step three. Activate basic milter.
Activate the basic milter and pysrs by editing /etc/mail/sendmail.mc and adding:
define(`NO_SRS_FILE',`/etc/mail/no-srs-mailers')dnl
dnl define(`NO_SRS_FROM_LOCAL')dnl
HACK(`pysrs',`/var/run/milter/pysrs')dnl
INPUT_MAIL_FILTER(`pythonfilter', `S=local:/var/run/milter/pythonsock, F=T, T=C:5m;S:20s;R:5m;E:5m')
You can then "make sendmail.cf" and restart sendmail.
Start milter and pysrs with "service milter start", "service pysrs start".
Tail /var/log/milter/milter.log while SMTP clients connect to your
sendmail instance. This should show you what the milter is doing.
By default, milter-0.8.10 rejects on SPF fail.
Step four. Tweaking the basic config.
Most pymilter configuration is in /etc/mail/pymilter.cfg. To activate
changes, "service milter restart".
By default, milter scans attachments for executable extensions. You can
turn this off by setting banned_exts to the empty list. There are options
to scan ZIP attachments and rfc822 attachments. When it finds a banned
file type, milter saves the original message in /var/log/milter/save,
and replaces the attachment with a plain text warning message.
Configure hello_blacklist with your own helo name and domains - which
you know cannot legitimately be used by external MTAs.
Configure trusted_relay with your secondary MX servers, if any. These
should also run pymilter with similar policies. (But this isn't
needed for initial testing.)
Configure internal_connect with subnets of your internal SMTP clients.
Internal connections skip SPF testing and other policies. You will
likely need to set this to allow outgoing mail if you have
an SPF policy already.
Configure internal_domains with domains used by your internal SMTP clients.
If they attempt to use any other domain, the attempt is blocked and the
client is logged as a "zombie". Conversely, any attempt by an external
MTA to use one of your internal domains is treated as a forgery and
blocked (a simplified form of local SPF).
Adjust porn_words and spam_words - these block emails with a Subject
containing the listed strings. They can be empty to disable Subject
string blocking.
Advanced SPF configuration.
The sendmail access file, or another readonly database with that
format, can be used for detail spf policy. SPF access policy
record are tagged with "SPF-{Result}:". Results are
Pass, Neutral, Softfail, Fail, PermError. Currently supported
policy keywords are OK, CBV, REJECT. Currently, TempError always
results in TEMPFAIL.
The default policies are set in pymilter.cfg. The defaults
if none of the config options are set are as follows:
SPF-Fail: REJECT
SPF-Softfail: CBV
SPF-Neutral: OK
SPF-PermError: REJECT
SPF-Pass: OK
The tag may be followed by a specific domain. For instance, to
require a Pass from aol.com:
SPF-Neutral:aol.com REJECT
SPF-Softfail:aol.com REJECT
The CBV policy requires a valid HELO name. If the EHLO name is
RFC2822 compliant, then a DSN is sent to the alleged sender. The
template for the DSN is selected according to the SPF result:
Fail: fail.txt
SoftFail: softfail.txt
Neutral: neutral.txt
PermError: permerror.txt
None: strike3.txt
An SPF-Pass is always accepted by the milter. Domains can be blacklisted
via sendmail in the access file or via a RHS DNS blacklist.
To be continued.
Forthcoming topics:
SRS config
pydspam config
wiretap config
--
Stuart D. Gathman <stuart@bmsi.com>
Business Management Systems Inc. Phone: 703 591-0911 Fax: 703 591-6154
"Confutatis maledictis, flammis acribus addictis" - background song for
a Microsoft sponsored "Where do you want to go from here?" commercial.
+184 -61
View File
@@ -4,19 +4,13 @@
# A thin OO wrapper for the milter module # A thin OO wrapper for the milter module
__version__ = '0.9.2'
import os import os
import milter import milter
import thread import thread
from milter import ACCEPT,CONTINUE,REJECT,DISCARD,TEMPFAIL, \ from milter import *
set_flags, setdbg, setbacklog, settimeout, error, \
ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS, \
V1_ACTS, V2_ACTS, CURR_ACTS
try: from milter import QUARANTINE
except: pass
__version__ = '0.8.5'
_seq_lock = thread.allocate_lock() _seq_lock = thread.allocate_lock()
_seq = 0 _seq = 0
@@ -30,30 +24,181 @@ def uniqueID():
_seq_lock.release() _seq_lock.release()
return seqno return seqno
class Milter: OPTIONAL_CALLBACKS = {
"""A simple class interface to the milter module. 'connect':(P_NR_CONN,P_NOCONNECT),
""" 'hello':(P_NR_HELO,P_NOHELO),
'envfrom':(P_NR_MAIL,P_NOMAIL),
'envrcpt':(P_NR_RCPT,P_NORCPT),
'data':(P_NR_DATA,P_NODATA),
'unknown':(P_NR_UNKN,P_NOUNKNOWN),
'eoh':(P_NR_EOH,P_NOEOH),
'body':(P_NR_BODY,P_NOBODY),
'header':(P_NR_HDR,P_NOHDRS)
}
def nocallback(func):
try:
func.milter_protocol = OPTIONAL_CALLBACKS[func.__name__][1]
except KeyError:
raise ValueError(
'@nocallback applied to non-optional method: '+func.__name__)
return func
def noreply(func):
try:
nr_mask = OPTIONAL_CALLBACKS[func.__name__][0]
except KeyErro:
raise ValueError(
'@noreply applied to non-optional method: '+func.__name__)
def wrapper(self,*args):
rc = func(self,*args)
if self._protocol & nr_mask: return NOREPLY
return rc
wrapper.milter_protocol = nr_mask
return wrapper
class DisabledAction(RuntimeError):
pass
# A do nothing Milter base class from which python milters should derive
# unless they are using the milter C module directly.
class Base(object):
"The core class interface to the milter module."
def _setctx(self,ctx): def _setctx(self,ctx):
self.__ctx = ctx self._ctx = ctx
self._actions = CURR_ACTS # all actions enabled by default
self._protocol = 0 # no protocol options by default
if ctx: if ctx:
ctx.setpriv(self) ctx.setpriv(self)
def log(self,*msg): pass
@nocallback
def connect(self,hostname,family,hostaddr): return CONTINUE
@nocallback
def hello(self,hostname): return CONTINUE
@nocallback
def envfrom(self,f,*str): return CONTINUE
@nocallback
def envrcpt(self,to,*str): return CONTINUE
@nocallback
def data(self): return CONTINUE
@nocallback
def header(self,field,value): return CONTINUE
@nocallback
def eoh(self): return CONTINUE
@nocallback
def body(self,unused): return CONTINUE
@nocallback
def unknown(self,cmd): return CONTINUE
def eom(self): return CONTINUE
def abort(self): return CONTINUE
def close(self): return CONTINUE
# Return mask of SMFIP_N.. protocol option bits to clear for this class
@classmethod
def protocol_mask(klass):
try:
return klass._protocol_mask
except AttributeError:
p = 0
for func,(nr,nc) in OPTIONAL_CALLBACKS.items():
func = getattr(klass,func)
ca = getattr(func,'milter_protocol',0)
#print func,hex(nr),hex(nc),hex(ca)
p |= (nr|nc) & ~ca
klass._protocol_mask = p
return p
# Default negotiation sets P_NO* and P_NR* for callbacks
# marked @nocallback and @noreply respectively
def negotiate(self,opts):
try:
self._actions,p,f1,f2 = opts
opts[1] = self._protocol = \
p & ~self.protocol_mask() & ~P_RCPT_REJ & ~P_HDR_LEADSPC
opts[2] = 0
opts[3] = 0
#self.log("Negotiated:",opts)
except:
# don't change anything if something went wrong
return ALL_OPTS
return CONTINUE
# Milter methods which can be invoked from most callbacks
def getsymval(self,sym):
return self._ctx.getsymval(sym)
# If sendmail does not support setmlreply, then only the
# first msg line is used.
def setreply(self,rcode,xcode=None,msg=None,*ml):
return self._ctx.setreply(rcode,xcode,msg,*ml)
# may only be called from negotiate callback
def setsmlist(self,stage,macros):
if not self._actions & SETSMLIST: raise DisabledAction("SETSMLIST")
if type(macros) in (list,tuple):
macros = ' '.join(macros)
return self._ctx.setsmlist(stage,macros)
# Milter methods which can only be called from eom callback.
def addheader(self,field,value,idx=-1):
if not self._actions & ADDHDRS: raise DisabledAction("ADDHDRS")
return self._ctx.addheader(field,value,idx)
def chgheader(self,field,idx,value):
if not self._actions & CHGHDRS: raise DisabledAction("CHGHDRS")
return self._ctx.chgheader(field,idx,value)
def addrcpt(self,rcpt,params=None):
if not self._actions & ADDRCPT: raise DisabledAction("ADDRCPT")
return self._ctx.addrcpt(rcpt,params)
def delrcpt(self,rcpt):
if not self._actions & DELRCPT: raise DisabledAction("DELRCPT")
return self._ctx.delrcpt(rcpt)
def replacebody(self,body):
if not self._actions & MODBODY: raise DisabledAction("MODBODY")
return self._ctx.replacebody(body)
def chgfrom(self,sender,params=None):
if not self._actions & CHGFROM: raise DisabledAction("CHGFROM")
return self._ctx.chgfrom(sender,params)
# When quarantined, a message goes into the mailq as if to be delivered,
# but delivery is deferred until the message is unquarantined.
def quarantine(self,reason):
if not self._actions & QUARANTINE: raise DisabledAction("QUARANTINE")
return self._ctx.quarantine(reason)
def progress(self):
return self._ctx.progress()
# A logging but otherwise do nothing Milter base class included
# for compatibility with previous versions of pymilter.
class Milter(Base):
"A simple class interface to the milter module."
# user replaceable callbacks
def log(self,*msg): def log(self,*msg):
print 'Milter:', print 'Milter:',
for i in msg: print i, for i in msg: print i,
print print
@noreply
def connect(self,hostname,family,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
@noreply
def hello(self,hostname): def hello(self,hostname):
"Called after the HELO command." "Called after the HELO command."
self.log("hello from %s" % hostname) self.log("hello from %s" % hostname)
return CONTINUE return CONTINUE
@noreply
def envfrom(self,f,*str): def envfrom(self,f,*str):
"""Called to begin each message. """Called to begin each message.
f -> string message sender f -> string message sender
@@ -62,25 +207,24 @@ class Milter:
self.log("mail from",f,str) self.log("mail from",f,str)
return CONTINUE return CONTINUE
@noreply
def envrcpt(self,to,*str): def envrcpt(self,to,*str):
"Called for each message recipient." "Called for each message recipient."
self.log("rcpt to",to,str) self.log("rcpt to",to,str)
return CONTINUE return CONTINUE
@noreply
def header(self,field,value): def header(self,field,value):
"Called for each message header." "Called for each message header."
self.log("%s: %s" % (field,value)) self.log("%s: %s" % (field,value))
return CONTINUE return CONTINUE
@noreply
def eoh(self): def eoh(self):
"Called after all headers are processed." "Called after all headers are processed."
self.log("eoh") self.log("eoh")
return CONTINUE return CONTINUE
def body(self,unused):
"Called to transfer the message body."
return CONTINUE
def eom(self): def eom(self):
"Called at the end of message." "Called at the end of message."
self.log("eom") self.log("eom")
@@ -96,54 +240,29 @@ class Milter:
self.log("close") self.log("close")
return CONTINUE return CONTINUE
# Milter methods which can be invoked from callbacks
def getsymval(self,sym):
return self.__ctx.getsymval(sym)
# If sendmail does not support setmlreply, then only the
# first msg line is used.
def setreply(self,rcode,xcode=None,msg=None,*ml):
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 chgheader(self,field,idx,value):
return self.__ctx.chgheader(field,idx,value)
def addrcpt(self,rcpt,params=None):
return self.__ctx.addrcpt(rcpt,params)
def delrcpt(self,rcpt):
return self.__ctx.delrcpt(rcpt)
def replacebody(self,body):
return self.__ctx.replacebody(body)
def chgfrom(self,sender,params=None):
return self.__ctx.chgfrom(sender,params)
# When quarantined, a message goes into the mailq as if to be delivered,
# but delivery is deferred until the message is unquarantined.
def quarantine(self,reason):
return self.__ctx.quarantine(reason)
def progress(self):
return self.__ctx.progress()
factory = Milter factory = Milter
def connectcallback(ctx,hostname,family,hostaddr): def negotiate_callback(ctx,opts):
m = factory() m = factory()
m._setctx(ctx) m._setctx(ctx)
return m.negotiate(opts)
def connect_callback(ctx,hostname,family,hostaddr,nr_mask=P_NR_CONN):
m = ctx.getpriv()
if not m:
# If not already created (because the current MTA doesn't support
# xmfi_negotiate), create the connection object.
m = factory()
m._setctx(ctx)
return m.connect(hostname,family,hostaddr) return m.connect(hostname,family,hostaddr)
def closecallback(ctx): def close_callback(ctx):
m = ctx.getpriv() m = ctx.getpriv()
if not m: return CONTINUE if not m: return CONTINUE
rc = m.close() try:
m._setctx(None) # release milterContext rc = m.close()
finally:
m._setctx(None) # release milterContext
return rc return rc
def dictfromlist(args): def dictfromlist(args):
@@ -194,7 +313,7 @@ def runmilter(name,socketname,timeout = 0):
# The default flags set include everything # The default flags set include everything
# milter.set_flags(milter.ADDHDRS) # milter.set_flags(milter.ADDHDRS)
milter.set_connect_callback(connectcallback) milter.set_connect_callback(connect_callback)
milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host)) milter.set_helo_callback(lambda ctx, host: ctx.getpriv().hello(host))
# For envfrom and envrcpt, we would like to convert ESMTP parms to keyword # For envfrom and envrcpt, we would like to convert ESMTP parms to keyword
# parms, but then all existing users would have to include **kw to accept # parms, but then all existing users would have to include **kw to accept
@@ -207,12 +326,16 @@ def runmilter(name,socketname,timeout = 0):
milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk)) milter.set_body_callback(lambda ctx,chunk: ctx.getpriv().body(chunk))
milter.set_eom_callback(lambda ctx: ctx.getpriv().eom()) milter.set_eom_callback(lambda ctx: ctx.getpriv().eom())
milter.set_abort_callback(lambda ctx: ctx.getpriv().abort()) milter.set_abort_callback(lambda ctx: ctx.getpriv().abort())
milter.set_close_callback(closecallback) milter.set_close_callback(close_callback)
milter.setconn(socketname) milter.setconn(socketname)
if timeout > 0: milter.settimeout(timeout) if timeout > 0: milter.settimeout(timeout)
# The name *must* match the X line in sendmail.cf (supposedly) # The name *must* match the X line in sendmail.cf (supposedly)
milter.register(name) milter.register(name,
data=lambda ctx: ctx.getpriv().data(),
unknown=lambda ctx,cmd: ctx.getpriv().unknown(cmd),
negotiate=negotiate_callback
)
start_seq = _seq start_seq = _seq
try: try:
milter.main() milter.main()
+7 -4
View File
@@ -5,6 +5,9 @@
# Send DSNs, do call back verification, # Send DSNs, do call back verification,
# and generate DSN messages from a template # and generate DSN messages from a template
# $Log$ # $Log$
# Revision 1.16 2007/09/25 01:24:59 customdesigned
# Allow arbitrary object, not just spf.query like, to provide data for create_msg
#
# Revision 1.15 2007/09/24 20:13:26 customdesigned # Revision 1.15 2007/09/24 20:13:26 customdesigned
# Remove explicit spf dependency. # Remove explicit spf dependency.
# #
@@ -31,7 +34,7 @@ import Milter
import time import time
import dns import dns
def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None): def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None,ourfrom=''):
"""Send DSN. If msg is None, do callback verification. """Send DSN. If msg is None, do callback verification.
Mailfrom is original sender we are sending DSN or CBV to. Mailfrom is original sender we are sending DSN or CBV to.
Receiver is the MTA sending the DSN. Receiver is the MTA sending the DSN.
@@ -62,14 +65,14 @@ def send_dsn(mailfrom,receiver,msg=None,timeout=600,session=None):
raise smtplib.SMTPHeloError(code, resp) raise smtplib.SMTPHeloError(code, resp)
if msg: if msg:
try: try:
smtp.sendmail('<>',mailfrom,msg) smtp.sendmail('<%s>'%ourfrom,mailfrom,msg)
except smtplib.SMTPSenderRefused: except smtplib.SMTPSenderRefused:
# does not accept DSN, try postmaster (at the risk of mail loops) # does not accept DSN, try postmaster (at the risk of mail loops)
smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg) smtp.sendmail('<postmaster@%s>'%receiver,mailfrom,msg)
else: # CBV else: # CBV
code,resp = smtp.docmd('MAIL FROM: <>') code,resp = smtp.docmd('MAIL FROM: <%s>'%ourfrom)
if code != 250: if code != 250:
raise smtplib.SMTPSenderRefused(code, resp, '<>') raise smtplib.SMTPSenderRefused(code, resp, '<%s>'%ourfrom)
code,resp = smtp.rcpt(mailfrom) code,resp = smtp.rcpt(mailfrom)
if code not in (250,251): if code not in (250,251):
return (code,resp) # permanent error return (code,resp) # permanent error
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

-222
View File
@@ -1,222 +0,0 @@
Title: Recent Changes
<h2> Recent Changes </h2>
<h3> 0.8.10 </h3>
SRS rejections now log the recipient.
I have finally implemented plain CBV (no DSN). The CBV policy
will do a plain CBV from now on, and the DSN policy is required
if you want to send a DSN.
I started checking the MAIL FROM fullname (human readable part
of an email) for porn keywords. There is now a banned IP database.
IPs are banned for too many bad MAIL FROMs or RCPT TOs, and remain banned
for 7 days.
<h3> 0.8.9 </h3>
I use the <code>%ifarch</code> hack to build milter and milter-spf
packages as noarch, while pymilter is built as native.
I removed the spf dependency from dsn.py, so pymilter can be used without
installing pyspf, and added a Milter.dns module to let python milters do
general DNS lookups without loading pyspf.
<h3> 0.8.8 </h3>
Programs do not belong in the /var/log directory. I moved the
milter apps to /usr/lib/pymilter. Since having the programs and
data in the same directory is convenient for debugging, it will
still use an executable present in the datadir.
Several general utility classes and functions are now in the Milter package
for possible use by other python milters. In addition to the trivial example
milter, a simple SPF only milter is included as a realistic example.
The spec file now build 3 RPMs:
<ul>
<li> pymilter is the milter module and Milter package for use by all python
milters.
<li> milter is the all-singing, all-dancing python milter application, with
supporting <code>/etc/init.d</code>, logrotate and other scripts.
<li> milter-spf is the simple SPF only milter application.
</ul>
<h3> 0.8.7 </h3>
The spf module has been moved to the
<a href="http://cheeseshop.python.org/pypi/pyspf">pyspf</a> package.
Download <a href="http://sourceforge.net/project/showfiles.php?group_id=139894&package_id=191419">here</a>.
<h3> 0.8.6 </h3>
Python milter has been moved to
<a href="http://sourceforge.net/projects/pymilter/">pymilter Sourceforge
project</a> for development and release downloads.
<h3> 0.8.5 </h3>
Release 0.8.5 fixes some build bugs reported by Stephen Figgins. It
fixes many small things, like not auto-whitelisting recipients of
outgoing mail when the subject contains "autoreply:". There is a
simple trusted forwarder implementation. If you have more than
2 or so forwarders, we will need a way to "compile" SPF records into an
IP set and TTL for it to be efficient (like libspf2 does).
<h3> GOSSiP </h3>
An alpha release of <a href="pygossip.html">pygossip</a> has been commited to
CVS, module pygossip. A version of the bms.py milter has been commited to CVS
which supports calling GOSSiP to track domain reputation in a local database.
<h3> New website design </h3>
Hey, I'm no artist, so I just used the
<a href="http://ht2html.sourceforge.net/">ht2html</a> package
by <a href="http://barry.wooz.org/">Barry Warsaw</a>. The mascot
is by <a href="http://alphard.ethz.ch/hafner/lebl.htm">Christian Hafner</a>,
or maybe his wife. I chose Maxwell's daemon because it tirelessly
and invisibly sorts molecules, just as milters sort mail.
Christian has also provided a fun
<a href="http://alphard.ethz.ch/hafner/PPS/PPS2002/Maxwell/simulation.htm">
simulation</a> that lets you try your hand at sorting molecules.
<h3> 0.8.4 </h3>
Release 0.8.4 makes configuring SPF policy via access.db actually work.
The honeypot idea is enhanced by auto-whitelisting recipients of
email sent from selected domains. Whitelisted messages are then used
to train the honeypot. This makes the honeypot screener entirely self
training. The smfi_progress() API is now automatically supported when present.
An optional idx parameter to milter.addheader() invokes smfi_insheader().
<h3> 0.8.3 </h3>
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.
<h3> 0.8.2 </h3>
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>
support (the original SES without body hash) for pysrs-0.30.10, and honeypot
support for pydspam-1.1.9. There is a new method in the base milter module.
milter.set_exception_policy(i) lets you choose a policy of CONTINUE, REJECT, or
TEMPFAIL (default) for untrapped exceptions encountered in a milter callback.
<h3> 0.8.0 </h3>
Release 0.8.0 is the first <a href="http://sourceforge.net/">Sourceforge</a>
release. It supports Python-2.4, and provides an option to accept mail
that gets an SPF softfail or fails the 3 strikes rule, provided the
alleged sender accepts a DSN explaining the problem. Python-2.3 is
no longer supported by the reworked mime.py module, although API changes
could be backported. There are too many incompatible changes to the
python email package.
<h3> Older Releases </h3>
Release 0.7.2 tightens the authentication screws with a "3 strikes and
you're out" policy. A sender must have a valid PTR, HELO, or SPF record
to send email. Specific senders can be whitelisted using the
"delegate" option in the spf configuration section by adding a
default SPF record for them. The PTR and HELO are required
by RFC anyway, so this is not an unreasonable requirement.
There is now a coherent policy for an SPF softfail result. A softfail
is accepted if there is a valid PTR or HELO, or if the domain
is listed in the "accept_softfail" option of the spf configuration section.
A neutral result is accepted by default if there is a valid PTR or
HELO, (and the SPF record was not guessed), unless the domain is listed in the
"reject_neutral" option. Common forms of PTR records for dynamic IPs are
recognized, and do not count as a valid PTR. This does not prevent anyone
from sending mail from a dynamic IP - they just need to configure a
valid HELO name or publish an SPF record.
<p>
As SPF adoption continues to rise, forged spam is not getting through. So
spammers are publishing their SPF records as predicted. The 0.7.2 RPM
now provides the <code>rhsbl</code> sendmail hack so that spammer domains
can be blacklisted. With the RPM installed, add a line like the following
to your <code>sendmail.mc</code>.
<pre>
HACK(rhsbl,`blackholes.example.com',"550 Rejected: " $&{RHS} " has been spamming our customers.")dnl
</pre>
<p>
Of course, spammers are now starting to register
throwaway domains. The next thing we need is a custom DNS server,
in Python, that
can recognize patterns. For instance, one spammer registers ded304.com,
ded305.com, ded306.com, etc. We also need the custom DNS server to
let SPF classic clients check SES (which will be part of pysrs).
The <a href="http://twistedmatrix.com/products/twisted">Twisted Python</a>
framework provides a custom DNS server - but I
would like a smaller implementation for our use.
<p>
The RPM for release 0.7.0 moves the config file and socket locations to
/etc/mail and /var/run/milter respectively. We now parse Microsoft CID records
- but only hotmail.com uses them. They seem to have applied for a patent on
the brilliant idea of examining the mail headers to see who the message is
from. We aren't doing that here, so not to worry - but I am not a lawyer, so
if you are worried, change spf.py around line 626 to return None instead of
calling CIDParser(). There is a new option to reject mail with no PTR
and no SPF.
<p>
Microsoft is pushing an anti-opensource license for their pending patent
along with their sender-ID proposal before the IETF.
It is royalty free - but requires anyone distributing a binary they've
compiled from source to sign a license agreement. The Apache Software
Foundation <a
href="http://www.apache.org/foundation/docs/sender-id-position.html"> explains
the problem with sender-ID</a>, and Debian <a
href="http://www.debian.org/News/2004/20040904">concurs</a>. Since
the <a href="http://download.microsoft.com/download/4/3/9/439b024b-09fd-44ee-8ff0-10e834004c36/senderid_FAQ.PDF">Microsoft license</a> is
<a href="http://www.circleid.com/article/732_0_1_0_C/">incompatible with free
software in general</a> and the <a
href="http://www.imc.org/ietf-mxcomp/mail-archive/msg03678.html">GPL in
particular</a>, Python milter will not be able to implement sender-ID in its
current form. This was, no doubt, Microsoft's intent all along.
<p>
Sender-ID attempts to do for RFC2822 headers what SPF does for RFC2821 headers.
Unlike SPF, it has never been tried, and is encumbered by a stupid patent. I
recommend ignoring it and continuing to implement and improve SPF until a
working and unencumbered proposal for RFC2822 headers surfaces.
<p>
<a href="http://openspf.com">
<img src="SPF.gif" align=left alt="SPF logo"></a>
Release 0.6.6 adds support for <a href="http://openspf.com/">SPF</a>,
a protocol to prevent forging of the envelope from address.
SPF support requires <a href="http://pydns.sourceforge.net/">pydns</a>.
The included spf.py module is an updated version of the original 1.6
version at <a href="http://www.wayforward.net/spf/">wayforward.net</a>.
The updated version tracks the draft RFC and test suite.
<p>
The FAQ addresses <a href="faq.html#spf">how to get started with SPF</a>.
<p>
Release 0.6.1 adds a full milter based dspam application.
<p>
I have selected the <a href="http://www.nuclearelephant.com/projects/dspam/">
dspam bayes filter project</a> and <a href="dspam.html">
packaged it for python</a>.
Release 0.6.0 offers a simple application of dspam I call "header triage",
which rejects messages with spammy headers.
To use header triage, you must have <a href="dspam.html">DSPAM</a> installed,
and select a dictionary that is well moderated by someone who gets
lots of spam. That dictionary can be used to block spam that is
obvious from the headers (e.g. X-Mailer and Subject) before it ties
up any more resources. I have yet to see any false positives from this
approach (check the milter log), but if there are, the sender will
get a REJECT with the message "Your message looks spammy."
-55
View File
@@ -1,55 +0,0 @@
Title: Credits
<h1> CREDITS </h1>
<a href="mailto:Jim Niemira <urmane@urmane.org>">Jim Niemira</a>
wrote the original C module and some quick
and dirty python to use it.
<a href="http://gathman.org/vitae">Stuart D. Gathman</a>
took that kludge and added threading and context objects to it, wrote a proper
OO 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.
<h2>Other contributors (in random order):</h2>
<dl>
<dt> <a href="http://alphard.ethz.ch/hafner/lebl.htm">Christian Hafner</a>
<dd>for the pymilter mascot image of
<a href="http://maxwelld.netfirms.com/">
Maxwell's daemon</a>
<dt>Stephen Figgins
<dd>for reporting problems building with sendmail-8.12, and when
building milter.so for the first time.
<dt>Dave MacQuigg
<dd>for noticing that smfi_insheader wasn't supported, and creating
a template to help first time pymilter users create their own milter.
<dt>Terence Way
<dd>for providing a Python port of SPF
<dt>Scott Kitterman
<dd>for doing lots of testing and debugging of SPF against draft standard,
and for putting up a <a href="http://www.kitterman.com/spf/validate.html">
web page that validates SPF</a> records using spf.py
<dt>Alexander Kourakos
<dd>for plugging several memory leaks
<dt>George Graf at Vienna University of Economics and Business Administration
<dd>for handling None passed to setreply and chgheader.
<dt>Deron Meranda
<dd>for IPv6 patches
<dt>Jason Erikson
<dd>for handling NULL hostaddr in connect callback.
<dt>John Draper
<dd>for porting Python milter to OpenBSD, and starting to work on tutorials
then pointing out that it would be easier to just write the MTA in Python.
<dt>Eric S. Johansson
<dd>for helpful design discussions while working on camram
<dt>Alex Savguira
<dd>for finding bugs with international headers and
suggesting the scan_zip option.
<dt><a href="http://www.bmsi.com">Business Management Systems</a>
<dd>for hosting the website, and providing paying clients who need milter
service so I can work on it as part of my day job.
</dl>
If I have left anybody out, send me a reminder:
<a href="mailto:Stuart Gathman <stuart@bmsi.com>">stuart@bmsi.com</a>
-293
View File
@@ -1,293 +0,0 @@
Title: Python Milter FAQ
<h1> Python Milter <a name=faq>FAQ</a> </h1>
<menu>
<li> <a href="#compiling">Compiling Python Milter</a>
<li> <a href="#running">Running Python Milter</a>
<li> <a href="#spf">Using SPF</a>
<li> <a href="#srs">Using SRS</a>
</menu>
<ol>
<h3> <a name="compiling">Compiling Python Milter </a> </h3>
<li> Q. I have tried to download the current milter code and my virus scan
traps several viruses in the download.
<p> A. The milter source includes a number of deactivated viruses in
the test directory. All but the first and last lines of the base64
encoded virus data has been removed. I suppose I should randomize
the first and last lines as well, since pymilter just deletes executables,
and doesn't look for signatures.
<li> Q. I have installed sendmail from source, but Python milter won't
compile.
<p> A. Even though libmilter is officially supported in sendmail-8.12,
you need to build and install it in separate steps. Take a look
at the <a href="/aix/sendmail12.spec">RPM spec file</a> for sendmail-8.12.
The %prep section shows you how to create
a site.config.m4 that enables MILTER. The %build section shows you how
to build libmilter in a separate invocation of make. The %install section
shows you how to install libmilter with a separate invocation of make.
<p>
<li> Q. Why is mfapi.h not found when I try to compile Python milter on
RedHat 7.2?
<p> A. RedHat forgot to include the header in the RPM. See the
<a href="requirements.html#rh72">RedHat 7.2 requirements</a>.
<p>
<li> Q. Python milter compiles ok, but I get an error like this when
I try to import the milter module:
<pre>
ImportError: /usr/lib/python2.4/site-packages/milter.so: undefined symbol: smfi_setmlreply
</pre>
<p> A. Your libmilter.a is from sendmail-8.12 or earlier. You need
sendmail-8.13 or later to support setmlreply. You can disable
setmlreply by changing setup.py. Change:
<pre>
define_macros = [ ('MAX_ML_REPLY',32) ]
</pre>
in setup.py to
<pre>
define_macros = [ ('MAX_ML_REPLY',1) ]
</pre>
<h3> <a name="running">Running Python Milter </a></h3>
<li> Q. The sample.py milter prints a message, then just sits there.
<pre>
To use this with sendmail, add the following to sendmail.cf:
O InputMailFilters=pythonfilter
Xpythonfilter, S=local:inet:1030@localhost
See the sendmail README for libmilter.
sample milter startup
</pre>
<p> A. You need to tell sendmail to connect to your milter. The
sample milter tells you what to add to your sendmail.cf to tell
sendmail to use the milter. You can also add an INPUT_MAIL_FILTER
macro to your sendmail.mc file and rebuild sendmail.cf - see the sendmail
README for milters.
<p>
<li> Q. I've configured sendmail properly, but still nothing happens
when I send myself mail!
<p> A. Sendmail only milters SMTP mail. Local mail is not miltered.
You can pipe a raw message through sendmail to test your milter:
<pre>
$ cat rawtextmsg | sendmail myname@my.full.domain
</pre>
Now check your milter log.
<p>
<li> Q. Why do I get this ImportError exception?
<pre>
File "mime.py", line 370, in ?
from sgmllib import declstringlit, declname
ImportError: cannot import name declstringlit
</pre>
<p> A. <code>declstringlit</code> is not provided by sgmllib in all versions
of python. For instance, python-2.2 does not have it. Upgrade to
milter-0.4.5 or later to remove this dependency.
<p>
<li> Q. Why do I get <code>milter.error: cannot add recipient</code>?
<pre>
</pre>
<p> A. You must tell libmilter how you might mutate the message with
<code>set_flags()</code> before calling <code>runmilter()</code>. For
instance, <code>Milter.set_flags(Milter.ADDRCPT)</code>. You must add together
all of <code>ADDHDRS, CHGBODY, ADDRCPT, DELRCPT, CHGHDRS</code> that apply.
<p> NOTE - recent versions default flags to enabling all features. You
must now call <code>set_flags()</code> if you wish to disable features for
efficiency.
<p>
<li> Q. Why does sendmail sometimes print something like:
"...write(D) returned -1, expected 5: Broken pipe"
in the sendmail log?
<p> A. Libmilter expects "rcpt to" shortly after getting "mail from".
"Shortly" is defined by the timeout parameter you passed to
<code>Milter.runmilter()
</code> or <code>milter.settimeout()</code>. If the timeout is 10 seconds,
and looking up the first recipient in DNS takes more than
10 seconds, libmilter will give up and break the connection.
<code>Milter.runmilter()</code> defaulted to 10 seconds in 0.3.4. In 0.3.5
it will keep the libmilter default of 2 hours.
<p>
<li> Q. Why does milter block messages with big5 encoding? What if I
want to receive them?
<p> A. sample.py is a sample. It is supposed to be easily modified
for your specific needs. We will of course continue to move generic
code out of the sample as the project evolves. Think of sample.py as
an active config file.
<p>
If you are running bms.py, then the block_chinese option in
<code>/etc/mail/pymilter.cfg</code> controls this feature.
<p>
<li> Q. Why does sendmail coredump with milters on OpenBSD?
<p> A. Sendmail has a problem with unix sockets on old versions of OpenBSD.
OpenBSD users report that this problem has been fixed, so upgrading
OpenBSD will fix this. Otherwise, you can
use an internet domain socket instead. For example, in
<code>sendmail.cf</code> use
<pre>
Xpythonfilter, S=inet:1234@localhost
</pre>
and change sample.py accordingly.
<p>
<li> Q. How can I change the bounce message for an invalid recipient?
I can only change the recipient in the eom callback, but the eom callback
is never called when the recipient is invalid!
<p> A. Configure sendmail to use virtusertable, and send all unknown
addresses to /dev/null. For example,
<h4>/etc/mail/virtusertable</h4>
<pre>
@mycorp.com dev-null
dan@mycorp.com dan
sally@mycorp.com sally
</pre>
<h4>/etc/aliases</h4>
<pre>
dev-null: /dev/null
</pre>
Now your milter will get to the eom callback, and can change the
envelope recipient at will. Thanks to Dredd at
<a href=http://www.milter.org/>milter.org</a> for this solution.
<p>
<li> Q. I am having trouble with the setreply method. It always outputs
"milter.error: cannot set reply".
<p> A. Check the sendmail log for errors. If sendmail is getting
milter timeouts, then your milter is taking too long and sendmail gave
up waiting. You can adjust the timeouts in your sendmail config. Here
is a milter declaration for sendmail.cf with all timeouts specified:
<pre>
Xpythonfilter, S=local:/var/log/milter/pythonsock, F=T, T=C:5m;S:20s;R:60s;E:5m
</pre>
<li> Q. There is a Python traceback in the log file! What happened to
my email?
<p> A. By default, when the milter fails with an untrapped exception, a
TEMPFAIL result (451) is returned to the sender. The sender will then retry
every hour or so for several days. Hopefully, someone will notice the
traceback, and workaround or fix the problem. Beginning with milter-0.8.2,
you can call <code>milter.set_exception_policy(milter.CONTINUE)</code>
to cause an untrapped exception to continue processing with the
next callback or milter instead. For
completeness, you can also set the exception policy to
<code>milter.REJECT</code>.
<li> Q. I read some notes such as "Check valid domains allowed by internal
senders to detect PCs infected with spam trojans." but could not
understand the idea. Could you clarify the content ?
<p> A. The <code>internal_domains</code> configuration specifies which
MAIL FROM domains are used by internal connections. If an internal
PC tries to use some other domain, it is assumed to be a "Zombie".
<p>
Here is a sample log line:
<pre>
2005Jun22 12:01:04 [12430] REJECT: zombie PC at 192.168.100.171 sending MAIL FROM debby@fedex.com
</pre>
No, fedex.com does not use pymilter, and there is no one named debby at my
client. But the idiot using the PC at 192.168.100.171 has downloaded and
installed some stupid weatherbar/hotbar/aquariumscreensaver that is actually a
spam bot.
<p>
The <code>internal_domains</code> option is simplistic, it assumes all
valid senders of the domains are internal. SPF provides a much more general
check of IP and MAIL FROM for external email. Pymilter should soon
have a local policy feature for more general checking of internal mail.
<li> Q. <code>mail_archive</code> isn't working. Or I don't understand how
it's suppose to work. I have
<code>mail_archive = /var/mail/mail_archive</code>
in <code>pymilter.cfg</code> but nothing ever gets dumped into
<code>/var/mail/mail_archive</code>.
<p> A. The 'mail' user needs to have write access. Permission failures
should be logged as a traceback in milter.log if it doesn't.
<h3> <a name="spf">Using SPF </a></h3>
<li> Q. So how do I use the SPF support? The sample.py milter doesn't seem
to use it.
<p> A. The bms.py milter supports spf. The RedHat RPMs will set almost
everything up for you. For other systems:
<ol type=i>
<li> Arrange to run bms.py in the background (as a service perhaps) and
redirect output and errors to a logfile. For instance, on AIX you'll want
to use SRC (System Resource Controller).
<li> Copy pymilter.cfg to the /etc/mail or the directory you run bms.py in,
and edit it. The comments should explain the options.
<li> Start bms.py in the background as arranged.
<li> Add Xpythonfilter to sendmail.cf or add an INPUT_MAIL_FILTER to
sendmail.mc. Regen sendmail.cf if you use sendmail.mc and restart
sendmail.
<li> Arrange to rotate log files and remove old defang files in
<code>tempdir</code>. The RedHat RPM uses <code>logrotate</code> for
logfiles and a simple cron script using <code>find</code> to clean
<code>tempdir</code>.
</ol>
In CVS, there is <code>spfmilter.py</code>. Run that as a service,
and it does just SPF. It uses the sendmail <code>access</code>
file to configure SPF responses just like <code>bms.py</code>, but
supports only REJECT and OK.
<li> Q. The SPF DSN is sent at least once for domains that don't publish a SPF.
How do I stop this behavior?
<p> A. The SPF response is controlled by <code>/etc/mail/access</code>
(actually the file you specify with <code>access_file</code> in
the <code>[spf]</code> section of <code>pymilter.cfg</code>).
Responses are OK, CBV, and REJECT. CBV sends the DSN.
<p>
You can change the defaults. For instance, I have:
<pre>
SPF-None: REJECT
SPF-Neutral: CBV
SPF-Softfail: CBV
SPF-Permerror: CBV
</pre>
I have best_guess = 1, so SPF none is converted to PASS/NEUTRAL for policy
lookup, and 3 strikes (no PTR, no HELO, no SPF) becomes "SPF NONE" for local
policy purposes (the Received-SPF header always shows the official SPF
result.)
<p>
You can change the default for specific domains:
<pre>
# these guys aren't going to pay attention to CBVs anyway...
SPF-None:cia.gov REJECT
SPF-None:fbi.gov REJECT
SPF-Neutral:aol.com REJECT
SPF-Softfail:ebay.com REJECT
</pre>
<h3> <a name="srs">Using SRS </a></h3>
<li> Q. The SRS part doesn't seem to work as whenever I try to start
<code>/etc/init.d/pysrs</code>, I get this in
<code>/var/log/milter/pysrs.log</code>:
<pre>
ConfigParser.NoOptionError: No option 'fwdomain' in section: 'srs'
</pre>
<p> A. You need to specify the forward domain - i.e. the domain you want
SRS to rewrite stuff too.
<p>
For instance, I have:
<pre>
# sample SRS configuration
[srs]
secret = don't you wish
maxage = 8
hashlength = 5
;database=/var/log/milter/srs.db
fwdomain = bmsi.com
sign=bmsi.com,mail.bmsi.com,gathman.org
srs=bmsaix.bmsi.com,bmsred.bmsi.com,stl.gathman.org,bampa.gathman.org
</pre>
The <code>sign</code> is for local domains which are signed.
The <code>srs</code> list is for other domains which you are relaying,
and which need to have SRS checked/undone for bounces.
</ol>
-24
View File
@@ -1,24 +0,0 @@
<!-- -*- html -*- -->
<h3>Subsections</h3>
<li><a href="milter.html">Introduction</a>
<li><a href="changes.html">Changes</a>
<li><a href="requirements.html">Requirements</a>
<li><a href="http://sourceforge.net/project/showfiles.php?group_id=139894">Download</a>
<li><a href="RPM-GPG-KEY-bms">GPG-KEY</a>
<li><a href="faq.html">FAQ</a>
<li><a href="policy.html">Policies</a>
<li><a href="logmsgs.html">Log&nbsp;Messages</a>
<li><a href="http://bmsi.com/mailman/listinfo/pymilter">Mailing&nbsp;List</a>
<li><a href="credits.html">CREDITS</a>
<li><a href="http://sourceforge.net"><img src="http://sflogo.sourceforge.net/sflogo.php?group_id=139894&amp;type=1" width="88" height="31" border="0" alt="SourceForge.net Logo" /></a>
<h3>Links</h3>
<li><a href="https://www.milter.org/developers/api/index">C&nbsp;API</a>
<li><a href="http://www.milter.org/">Milter.Org</a>
<li><a href="http://www.python.org/">Python.Org</a>
<li><a href="http://www.sendmail.org/">Sendmail.Org</a>
<li><a href="http://www.openspf.org/">SPF</a>
<li><a href="pysrs.html">pysrs</a>
<li><a href="http://cheeseshop.python.org/pypi/pyspf">pyspf</a>
<li><a href="http://bmsi.com/python/pygossip.html">pygossip</a>
<li><a href="http://bmsi.com/python/dspam.html">pydspam</a>
<li><a href="http://bmsi.com/libdspam/dspam.html">libdspam</a>
-91
View File
@@ -1,91 +0,0 @@
Title: Python Milter Log Documentation
<style>
DT { font-weight: bolder; padding-top: 1em }
</style>
<h1> Milter Log Documentation </h1>
The milter log from the bms.py application 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: pooh@bwicorp.com
<dd> message was sent to a honeypot address (pooh@bwicorp.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> REJECT: zombie PC at 192.168.3.37 sending MAIL FROM seajdr@amritind.com
<dd> message was rejected because the connect ip was internal, but the
sender was not. This is usually because a Windows PC is infected with
malware.
<dt> X-Guessed-SPF: pass
<dd> When the SPF result is NONE, we guess a result based on the generic
SPF policy "v=spf1 a/24 mx/24 ptr".
<dt> DSPAM: tonyc tonyc@example.com
<dd> message was sent to tonyc@example.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
<dt> Auto-Whitelist: user@example.com
<dd> recipient has been added to auto_whitelist.log because the message
was sent from an internal IP and the recipient is not internal.
<dt> WHITELIST user@example.com
<dd> message is whitelisted because sender appears in auto_whitelist.log
<dt> BLACKLIST user@example.com
<dd> message is blacklisted because sender appears in blacklist.log or
failed a CBV test.
<dt> TRAINSPAM: honeypot X-Dspam-Score: 0.002278
<dd> message was used to train screener dictionary as spam
<dt> TRAIN: honeypot X-Dspam-Score: 0.980203
<dd> message was used to train screener dictionary as ham
</dl>
<br>
-264
View File
@@ -1,264 +0,0 @@
Title: Python Milters
<P ALIGN="CENTER"><A HREF="http://www.anybrowser.org/campaign/">
<IMG SRC="http://bmsi.com/art/brain1.gif"
ALT="Viewable With Any Browser" BORDER="0"></A>
<img src="http://bmsi.com/art/banner_4.gif" width="468" height="60" border="0"
usemap="#banner_4" alt="Your vote?">
<map name="banner_4">
<area shape="rect" coords="330,25,426,59"
href="http://education-survey.org/" alt="I Disagree">
<area shape="rect" coords="234,28,304,57" href="http://www.honestEd.com/" alt="I Agree">
</map>
</P>
<table rules="none">
<tr><td>
<img src="Maxwells.gif" alt="Maxwell's Daemon: pymilter mascot" align="top">
Mascot by <a href="http://alphard.ethz.ch/hafner/lebl.htm">Christian Hafner</a>
</td>
<td>
<h1 align=center>Sendmail Milters in Python</h1>
<h4 align=center>by <a href="mailto:%75%72%6D%61%6E%65%40%6E%65%75%72%61l%61%63%63%65%73%73%2E%63%6F%6D">Jim Niemira</a>
and <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">
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 26, 2008</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="http://bmsi.com/mailman/listinfo/pymilter">Subscribe to mailing list</a> |
<a href="#overview">Overview</a> |
<a href="/python/dspam.html">pydspam</a> |
<a href="/libdspam/dspam.html">libdspam</a>
<p>
<a href="//www.python.org">
<img src="python55.gif" align=left alt="A Python"></a>
<a href="//www.sendmail.org/">Sendmail</a> introduced a
<a href="https://www.milter.org/developers/api/index"> new API</a> beginning with version 8.10 -
libmilter. The milter module for <a href="//www.python.org">Python</a>
provides a python interface to libmilter that exploits all its features.
<p>
Sendmail 8.12 officially releases libmilter.
Version 8.12 seems to be more robust, and includes new privilege
separation features to enhance security. Even better, sendmail 8.13
supports socket maps, which makes <a href="pysrs.html">pysrs</a> much more
efficient and secure. Sendmail 8.14 finally supports modifying
MAIL FROM via the milter API. Unfortunately, I haven't gotten around
to supporting that yet in python milter.
</td></tr>
</table>
<h3><a name=overview>Overview</a></h3>
This package provides a robust toolkit for Python <a
href="#milter">milters</a>, and the beginnings of a general purpose mail
filtering system written in Python.
<p>
At the lowest level, the 'milter' module provides a thin wrapper around the
<a href="https://www.milter.org/developers/api/index">
sendmail libmilter API</a>. This API lets you register callbacks for
a number of events in the
<a href="http://www.cs.concordia.ca/~group/fig/public/email/relay/milter+ruleset-checks.html">process of sendmail receiving a message via SMTP</a>.
These events include the initial connection from a MTA,
the envelope sender and recipients, the top level mail headers, and
the message body. There are options to mangle all of these components
of the message as it passes through the milter.
<p>
At the next level, the 'Milter' module (note the case difference) provides a
Python friendly object oriented wrapper for the low level API. To use the
Milter module, an application registers a 'factory' to create an object
for each connection from a MTA to sendmail. These connection objects
must provide methods corresponding to the libmilter callback events.
<p>
Each event method returns a code to tell sendmail whether to proceed
with processing the message. This is a big advantage of milters over
other mail filtering systems. Unwanted mail can be stopped in its
tracks at the earliest possible point.
<p>
The Milter.Milter class provides default implementations for event
methods that
do nothing, and also provides wrappers for the libmilter methods to mutate
the message.
<p>
The 'spf' module provides an implementation of <a href="http://openspf.com">
SPF</a> useful for detecting email forgery.
<p>
The 'mime' module provides a wrapper for the Python email package that
fixes some bugs, and simplifies modifying selected parts of a MIME message.
<p>
Finally, the bms.py application is both a sample of how to use the
Milter and spf modules, and the beginnings of a general purpose SPAM filtering,
wiretapping, SPF checking, and Win32 virus protecting milter. It can
make use of the <a href="pysrs.html">pysrs</a> package when available for
SRS/SES checking and the <a href="dspam.html">pydspam</a> package for Bayesian
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.
<h3>What is a <a name="milter">milter</a>?</h3>
Milters can run on the same machine as sendmail, or another machine. The
milter can even run with a different operating system or processor than
sendmail.
Sendmail talks to the milter via a local or internet socket.
Sendmail keeps the
milter informed of events as it processes a mail connection. At any
point, the milter can cut the conversation short by telling sendmail
to ACCEPT, REJECT, or DISCARD the message. After receiving a complete
message from sendmail, the milter can again REJECT or DISCARD it, but it
can also ACCEPT it with changes to the headers or body.
<h3> What can you do with a milter? </h3>
<menu>
<li> A milter can DISCARD or REJECT spam based based on algorithms scripted
in python rather than sendmail's cryptic "cf" language.
<li> A milter can alter or remove attachments from mail that are poisonous to
Windows.
<li> A milter can scan for viruses and clean them when detected.
<li> A milter scans outgoing as well as incoming mail.
<li> A milter can add and delete recipients to forward or secretly
copy mail.
<li> For more ideas, check the <a href="//www.milter.org">Milter Web Page</a>.
</menu>
<a href="https://www.milter.org/developers/api/index">
Documentation</a> for the C API is provided with sendmail. Miltermodule
provides a thin python wrapper for the C API. Milter.py provides a simple
OO wrapper on top of that.
<p>
The Python milter package includes a sample milter that replaces dangerous
attachments with a warning message, discards mail addressed to
MAILER-DAEMON, and demonstrates several SPAM abatement strategies.
The MimeMessage class to do this used to be based on the
<code>mimetools</code> and <code>multifile</code> standard python packages.
As of milter version 0.6.0, it is based on the email standard
python packages, which were derived from the
<a href="http://sourceforge.net/projects/mimelib">mimelib</a> project.
The MimeMessage class patches several bugs in the email package,
and provides some backward compatibility.
<p>
The "defang" function of the sample milter was inspired by
<a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>,
a Perl milter with flexible attachment processing options. The latest
version of MIMEDefang uses an apache style process pool to avoid reloading
the Perl interpreter for each message. This makes it fast enough for
production without using Perl threading.
<p>
<a href="http://sourceforge.net/projects/mailchecker">mailchecker</a> is
a Python project to provide flexible attachment processing for mail. I
will be looking at plugging mailchecker into a milter.
<p>
<a href="http://software.libertine.org/tmda/">TMDA</a> is a Python project
to require confirmation the first time someone tries to send to your
mailbox. This would be a nice feature to have in a milter.
<p>
There is also a <a href="http://www.milter.org/">Milter community website</a>
where milter software and gory details of the API are discussed.
<h3> Is a milter written in python efficient? </h3>
The python milter process is multi-threaded and startup cost is incurred
only once. This is much more efficient than some implementations that
start a new interpreter for each connection. Testing in a production
environment did not use a significant percentage of the CPU. Furthermore,
python is easily extended in C for any step requiring expensive CPU
processing.
<p>
For example, the HTML parsing feature to remove scripts from HTML attachments
is rather CPU intensive in pure python. Using the C replacement for sgmllib
greatly speeds things up.
<h3> Goals </h3>
<menu>
<li> Implement RRS - a backdoor for non-SRS forwarders. User lists non-SRS
forwarder accounts (perhaps in <code>~/.forwarders</code>), and a util
provides a special local alias for the 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. Requires
milter to have read access to <code>~/.forwarders</code> or else
a way for user to submit entries to milter database.
<li> The bms.py milter has too many features. Create a framework where
numerous small feature modules can be plugged together in the
configuration.
<li> Create a pure python substitute for miltermodule and libmilter that
implements the <a
href="http://www.duh.org/cvsweb.cgi/~checkout~/pmilter/doc/milter-protocol.txt?rev=1">
libmilter protocol</a> in python.
<li> Find or write a faster implementation of sgmllib. The
<a href="http://www.effbot.org/zone/sgmlop-index.htm">sgmlop package</a>
is not very compatible with
<a href="http://www.python.org/doc/2.1.3/lib/module-sgmllib.html">
Python-2.1 sgmllib</a>, but it is a start, and is supported in
milter-0.4.5 or later.
<li> Implement all or most of the features of
<a href="http://www.roaringpenguin.com/mimedefang/">MIMEDefang</a>.
<li> Follow the official <a href="http://www.python.org/peps/pep-0008.html">
Python coding standards</a> more closely.
<li> Make unit test code more like other python modules.
</menu>
<h3> Confirmed Installations </h3>
Please <a href="mailto:%73%74%75%61%72%74%40%62%6D%73%69%2E%63%6F%6D">email</a>
me if you do <i>not</i> successfully install milter. The confirmed
installations are too numerous to list at this point.
<h2> Enough Already! </h2>
Nearly a dozen people have emailed me begging for a feature to copy
outgoing and/or incoming mail to a backup directory by user. Ok, it
looks like this is a most requested feature for 0.5.6. In the meantime,
here are some things to consider:
<ul>
<li> If you want to equivalent of a Bcc added to each message, this
is very easy to do in the python code for bms.py. See below.
<li> If you want to copy to a file in a directory (thus avoiding having to
set up aliases), this is slightly more involved. The bms.py milter already
copies the message to a temporary file for use in replacing the message body
when banned attachments are found. You have to open a file, and copy the
Mesage object to it in eom().
<li> Finally, you are probably aware that most email clients already
keep a copy of outgoing mail? Presumably there is a good reason for
keeping another copy on the server.
</ul>
<p>
To Bcc a message, call <code>self.add_recipient(rcpt)</code> in envfrom after
determining whether you want to copy (e.g. whether the sender is local). For
example,
<pre>
def envfrom(...
...
if len(t) == 2:
self.rejectvirus = t[1] in reject_virus_from
if t[0] in wiretap_users.get(t[1],()):
self.add_recipient(wiretap_dest)
if t[1] == 'mydomain.com':
self.add_recipient('&lt;copy-%s&gt;' % t[0])
...
</pre>
<p>
To make this a generic feature requires thinking about how the configuration
would look. Feel free to make specific suggestions about config file
entries. Be sure to handle both Bcc and file copies, and designating what
mail should be copied. How should "outgoing" be defined? Implementing it is
easy once the configuration is designed.
<hr>
<p>
<a href="http://validator.w3.org/check/referer">
<img border=0 src="http://bmsi.com/vh32.png" alt=" [ Valid HTML 3.2! ] " height=31 width=88></a>
<a href="http://www.redhat.com">
<img src="http://bmsi.com/art/powered_by.gif" width="88" height="31" alt=" [ Powered By Red Hat Linux ] " border="0"></a>
</p>
-249
View File
@@ -1,249 +0,0 @@
Title: Python Milter Mail Policy
<h1> Python Milter Mail Policy </h1>
These are the policies implemented by the <code>bms.py</code> milter
application. The milter and Milter modules do not implement any policies
by themselves.
<h3> Classify connection </h3>
When the SMTP client connects, the connection IP address is
saved for later verification, and the connection
is classified as INTERNAL or EXTERNAL by matching the ip
address against the <code>internal_connect</code> configuration.
IP addresses with no PTR, and PTR names that look like
the kind assigned to dynamic IPs (as determined by a heuristic
algorithm) are flagged as DYNAMIC. IPs that match the
<code>trusted_relay</code> configuration are flagged as TRUSTED.
<p>
Examples from the log file (<i>not</i> the SMTP error message returned):
<pre>
2005Jul29 13:56:53 [71207] connect from p50863492.dip0.t-ipconnect.de at ('80.134.52.146', 1858) EXTERNAL DYN
2005Jul29 18:10:15 [74511] connect from foopub at ('1.2.3.4', 46513) EXTERNAL TRUSTED
2005Jul29 14:41:00 [71805] connect from foobar at ('192.168.0.1', 41205) INTERNAL
2005Jul29 14:41:15 [71806] connect from cncln.online.ln.cn at ('218.25.240.137', 35992) EXTERNAL
</pre>
<p>
Certain obviously evil PTR names are blocked at this point:
"localhost" (when IP is not 127.*) and ".".
<pre>
2005Jul29 14:49:50 [71918] connect from localhost at ('221.132.0.6', 50507) EXTERNAL
2005Jul29 14:49:50 [71918] REJECT: PTR is localhost
</pre>
<h3> HELO Check </h3>
The HELO name provided by the client is saved for later verification
(for example by SPF). We could validate the HELO at this point
by verifying that an A record for the HELO name matches the connect ip.
However, currently we only block certain obvious problems.
HELO names that look like an IP4 address
and ones that match the <code>hello_blacklist</code> configuration
are immediately rejected. The hello_blacklist typically contains
the current MTAs own HELO name or email domains.
Clients that attempt to skip HELO are immediately rejected.
<pre>
2005Jul29 18:10:15 [74512] hello from example.com
2005Jul29 18:10:15 [74512] REJECT: spam from self: example.com
2005Jul29 18:17:09 [74581] hello from 80.191.244.69
2005Jul29 18:17:09 [74581] REJECT: numeric hello name: 80.191.244.69
</pre>
<h3> MAIL FROM Check </h3>
Before calling our milter, sendmail checks a DNS blacklist to
block banned sender domains. We never see a blocked domain.
<p>
The MAIL FROM address is saved for possible use by the smart-alias
feature. First, the <code>internal_domains</code> is used for
a simple screening if defined. If the MAIL FROM for an INTERNAL connection
is NOT in <code>internal_domains</code>, then it is rejected (the
PC is most likely infected and attempting to send out spam).
If the MAIL FROM for an EXTERNAL connection IS in
<code>internal_domains</code>, then the message is immediately rejected.
This is quick and effective for most small company MTAs. For more
complex mail networks, it is too simplistic, and should not be defined.
SPF will handle the complex cases.
<h4> wiretap </h4>
The wiretap feature can screen and/or monitor mail to/from certain
users. If the MAIL FROM is being wiretapped, the recipients are
altered accordingly.
<!--table-stop-->
<h2> SPF check </h2>
The MAIL FROM, connect IP, and HELO name are checked against
any SPF records published via DNS for the alleged sender (MAIL FROM)
to determine the official SPF policy result.
The offical SPF result is then logged in the Received-SPF header field,
but certain results are subjected to further processing to create
an effective result for policy purposes.
<p>
If the official result is 'none', we try to turn it into an effective result of
'pass' or 'fail'. First, we check for a local substitute SPF record
under the domain defined in the <code>[spf]delegate</code> configuration.
It is often useful to add local SPF records for correspondents that are
too clueless to add their own. If there is no local substitute, we use a "best
guess" SPF record of "v=spf1 a/24 mx/24 ptr" for MAIL FROM or "v=spf1 a/24
mx/24" for HELO. In addition, a HELO that is a subdomain of MAIL FROM and
resolves to the connect IP results in an effective result of 'pass'.
<p>
If there is no local SPF record, and the effective result is still not
'pass', we check for either a valid HELO name or a valid PTR record for
the connect IP. A valid HELO or PTR cannot look like a dynamic name
as determined by the heuristic in <code>Milter.dynip</code>.
<p>
If HELO has an SPF record, and the result is anything but pass, we reject
the connection:
<pre>
2005Jul30 19:45:16 [93991] connect from [221.200.41.54] at ('221.200.41.54', 3581) EXTERNAL DYN
2005Jul30 19:45:18 [93991] hello from adelphia.net
2005Jul30 19:45:19 [93991] mail from <wendy.stubbsua@link-it.com> ()
2005Jul30 19:45:19 [93991] REJECT: hello SPF: fail 550 access denied
</pre>
Note that HELO does not have any forwarding issues like MAIL FROM, and so
any result other than 'pass' or 'none' should be treated like 'fail'.
<p>
Only if nothing about the SMTP envelope can be validated does the effective
result remain 'none. I call this the "3 strikes" rule.
<p>
If the official result is 'permerror' (a syntax error in the sender's
policy), we use the 'lax' option in pyspf to try various heuristics to guess
what they really meant. For instance, the invalid mechanism "ip:1.2.3.4" is
treated as "ip4:1.2.3.4". The result of lax processing is then used
as the effective result for policy purposes.
<p>
With an effective SPF result in hand, we consult the sendmail access
database to find our receiver policy for the sender.
<table border=1>
<tr><th>REJECT</th><td>
Reject the sender with a 550 5.7.1 SMTP code. The SMTP rejection
includes a detailed description of the problem.
</td></tr>
<tr><th>CBV</th><td>
Do a Call Back Validation by connecting to an MX of the sender
and checking that using the sender as the RCPT TO is not rejected.
We quit the CBV connection before actualling sending a message.
If the CBV is rejected, our SMTP connection is rejected with the
same error code and message. CBV results are cached.
</td></tr>
<tr><th>DSN</th><td>
Do a Call Back Validation by connecting to an MX of the sender
and checking that using the sender as the RCPT TO is not rejected.
Unlike a CBV, we continue on to data and send a detailed message
explaining the problem. This can be useful for reporting PermError
or SoftFail to the sender. Keep in mind that for any result other
than 'pass', the sender could be forged, and your DSN could annoy the
wrong person. However, a SoftFail result is requesting such feedback
for debugging and a PermError result needs to be fixed by the sender ASAP
whether forged or not. DSN results are cached so that senders are
annoyed only weekly.
</td></tr>
<tr><th>OK</th><td>
Accept the sender. The message may still be rejected via reputation
or content filtering.
</td></tr>
</table>
<h3> SPF policy syntax </h3>
First, the full sender is checked:
<pre>
SPF-Fail:abeb@adelphia.net DSN
</pre>
This says to accept mail from that adelphia.net user despite the
SPF fail, but only after annoying them with a DSN about their ISP's broken
policy.
<p>
If there is no match on the full sender, the domain is checked:
<pre>
SPF-Neutral:aol.com REJECT
</pre>
This says to reject mail from AOL with an SPF result of neutral.
This means AOL users can't use their AOL address with another mail service
to send us mail. This is good because the other mail service is
likely a badly configured greeting card site or a virus.
<p>
Finally, a default policy for the result is checked. While there are program
defaults, you should have defaults in the access database for SPF results:
<pre>
SPF-Neutral: CBV
SPF-Softfail: DSN
SPF-PermError: DSN
SPF-TempError: REJECT
SPF-None: REJECT
SPF-Fail: REJECT
SPF-Pass: OK
</pre>
<h2> Reputation </h2>
If the sender has not been rejected by this point, and if a GOSSiP server is
configured, we consult GOSSiP for the reputation score of the sender and
SPF result. The score is a number from -100 to 100 with a confidence
percentage from 0 to 100. A really bad reputation (less than -50 with
confidence greater than 3) is rejected. Note that the reputation is tracked
independently for each SPF result and sender combination. So aol.com:neutral
might have a really bad reputation, while aol.com:pass would be ok.
Furthermore, when a sender finally publishes an SPF policy and starts
getting SPF pass, their reputation is effectively reset.
<h2> Whitelists and Blacklists </h2>
The administrator can whitelist or blacklist senders and sending domains by
appending them to <code>${datadir}/auto_whitelist.log</code> or
<code>${datadir}/blacklist.log</code> respectively. In addition,
recipients of internal senders (except for automatic replies like vacation
messages and return receipts) are automatically whitelisted for 60 days, and
senders that fail CBV or DSN checks are automatically blacklisted for 30 days.
Whitelisted and blacklisted senders are used to automatically train the
bayesian content filter before being delivered or rejected, respectively.
<p>
Real Soon Now users will be able to maintain their own whitelist and
blacklist that applies only when they are the recipient.
<h2> Content Filter </h2>
Most messages have been rejected or delivered by now, but spammers
are always finding new places to send their junk from. For instance,
we get around 10000 emails a day, of which around 500 are first time
spam senders. A bayesian filter is trained by the whitelists and
blacklists, and scores the message. What is likely spam is either
rejected or quarantined. If the sender is an effective SPF pass,
then they get a DSN notifying them that their message has been
quarantined. (A DSN failure gets the sender auto blacklisted.)
Else, if the reject_spam option is set, the message is rejected.
Otherwise, a CBV is done (failure gets the sender auto blacklisted)
and the message is silently quarantined.
<p>
Normally, you don't want email messages to silently disappear into
a black hole, so you should set the reject_spam option. However,
if you don't want your correspondent's email to get rejected, you can
check your quarantine frequently instead.
<h3> Honeypot </h3>
You can also blacklist recipients by listing them as aliases of the
'honeypot' dspam user. These are collectively called
the honeypot. Any email to these recipients is used to train the
spam filter as spam and chalk up a reputation demerit for the sender, then
discarded. It might be a good idea to blacklist the sender if it has SPF pass
as well, but I'm afraid of accidents.
<h3> Reputation </h3>
Reputation is tracked by sending domain and effective SPF result.
The GOSSiP server tracks the spam/ham status of the last 1024 messages
for each domain:result combination. When the server is queried during
the SMTP envelope phase (MAIL FROM), it also queries any configured
peers, and the scores are combined. Domains with a history of spam for
a given SPF result are rejected at MAIL FROM. The GOSSiP system has
a command line utility to reset (delete) a reputation for cases where a
sender that was infected with malware is repaired. In addition,
the confidence score of a reputation decays with time, so a bad sender
will eventually be able to try again without manual intervention.
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

-99
View File
@@ -1,99 +0,0 @@
Title: Requirements
<h2> Requirements </h2>
<menu>
<li> While the miltermodule will work with python 1.5, you probably
want to use python 2.0 or better. The python code uses a number of
python 2 features. The email support requires python 2.4.
<li> Python must be configured with thread support. This is because
pymilter uses sendmail's libmilter which requires thread support.
<li> You must compile sendmail with libmilter enabled. In versions of
sendmail prior to 8.12 libmilter is marked FFR (For Future Release) and
is not installed by default.
Sendmail 8.12 still does not enable libmilter by default. You must
explicitly select the "MILTER" option when compiling.
<li> When compiling Python milter against sendmail versions earlier than
8.13, you must set MAX_ML_REPLY to 1 in setup.py. There is no way to tell from
the libmilter includes that smfi_setmlreply is not supported.
<li> You probably want to use sendmail-8.13, since that supports multi-line
SMTP error descriptions and SOCKETMAP. You want SOCKETMAP for use with
pysrs.
<li> Python milter has been tested against sendmail-8.11 through sendmail-8.13.
<li> Python milter must be compiled for the specific version of sendmail
it will run with. (Since the result is dynamically loaded, there could
conceivably be multiple versions available and selected at startup - but
that will have to wait.) This situation may only exist for sendmail
versions prior to 8.12. The protocol seems designed for backward
compatibility - and 8.12 is the first official milter release.
<li> Mea Culpa! After reading the Python Style guide, I realize that
my Python code is not up to snuff. Apparently mixed tabs and spaces
are anathema to those using Windows editors, where tabs can be expanded using
any arbitrary algorithm. Other than that, my
intuition matched Guido's pretty well - although I like to indent by 2
rather than 4. I will arrange to have tabs expanded to spaces when
exporting new versions. Until then, beware!
</menu>
<h3> <a name="aix4"> AIX 4.1.5 Requirements </a> </h3>
To create sendmail RPMs for AIX, you can download my AIX 4.1.5 spec files
for <a href="/aix/sendmail.spec">sendmail-8.11.5</a>
or <a href="/aix/sendmail12.spec">sendmail-8.12.3</a>. If you have
not already set it up, I use a <a href="/aix/aix.spec">dummy RPM package</a>
to represent the stuff that comes with AIX. You might also want
my <a href="/aix/python.spec">python-2.1.1</a> spec file for AIX. It
does not include Tk or curses modules, sorry. If y'all trust me, you can
download rpms for AIX 4.x from my <a href="/aix">AIX RPM directory</a>.
<p>
Sendmail-8.12 renames
libsmutil.a to libsm.a. Unfortunately, libsm.a is an important AIX system
shared library. Therefore, I rename libsm.a back to libsmutil.a for
AIX. This presents a problem for setup.py.
<h3> <a name="rh72"> RedHat 7.2 Requirements </a> </h3>
If you are running Redhat 7.2, the distributed version of sendmail
now enables libmilter by default. RedHat 7.2 bundles
the development libraries with the main sendmail package, so
there is no sendmail-devel package. However, they forgot to include the
headers! So you'll have to get the SRPM and modify it. I suggest
moving the static libs to a devel package and adding the headers. If
this is too much trouble, you can get the <a href="mfapi.h">mfapi.h</a>
header for sendmail-8.6.11 from here and manually install it as
<code>/usr/include/libmilter/mfapi.h</code>.
<p>
If you do modify the SRPM, I suggest renaming libsmutil.a
to libsm.a - just like sendmail-8.12 will. If you manually install
mfapi.h or don't rename libsmutil.a, you'll
need to force <code>libs = ["milter", "smutil"]</code> in setup.py.
<p>
If you have installed python2, and want
python-milter to use python2, add <code>python=python2</code> to setup.cfg
and build with <code>python2 setup.py bdist_rpm</code>.
<h3> <a name="rh62"> Redhat 6.2 Requirements </a> </h3>
If you are running Redhat 6.2, the distributed version of sendmail
does not enable libmilter. You can download the Redhat 7.2 sendmail.spec
modified to compile on RedHat 6.2:
<a href="http://www.bmsi.com/linux/rh62/sendmail-rhmilter.spec">
sendmail-rhmilter.spec</a>. The <a
href="ftp://updates.redhat.com/7.0/en/os/SRPMS/sendmail-8.11.6-1.7.0.src.rpm">
SRPM for sendmail-8.11.6</a> is available from
<a href="http://www.redhat.com">Redhat</a> under
<a href="http://www.redhat.com/support/errata/RHSA-2001-106.html">
Errata for RH6.2</a>. But that doesn't include the latest security
patches since RH6.2 is no longer supported.
<p>
If y'all trust me, you can pick up source and binary sendmail RPMs for RH6.2
from my <a href="http://www.bmsi.com/linux/rh62">linux downloads</a> directory.
The lastest RPMs were built by taking a RH7.2 SRPMS and removing some
RPM features from the spec file that RH6.2 doesn't support, then
recompiling on RH6.2. You can check this by installing the RH7.2 SRPM,
then diffing my sendmail.spec with theirs. Then run
"rpm -bb sendmail-rhmilter.spec" when you are satisfied.
<p>
If you have installed python2, and want
python-milter to use python2, add <code>python=python2</code> to setup.cfg
and build with <code>python2 setup.py bdist_rpm</code>.
You'll need to install the sendmail-devel package to compile milter.
+222 -14
View File
@@ -35,6 +35,30 @@ $ python setup.py help
libraries=["milter","smutil","resolv"] libraries=["milter","smutil","resolv"]
* $Log$ * $Log$
* Revision 1.22 2009/05/29 19:53:36 customdesigned
* Typo SMFIS_ALL_OPTS
*
* Revision 1.21 2009/05/29 19:49:40 customdesigned
* Typo calling helo instead of negotiate.
*
* Revision 1.20 2009/05/29 18:25:59 customdesigned
* Null terminate keyword list.
*
* Revision 1.19 2009/05/28 18:36:42 customdesigned
* Support new callbacks, including negotiate
*
* Revision 1.18 2009/05/21 21:53:05 customdesigned
* First cut at support unknown, data, negotiate callbacks.
*
* Revision 1.17 2009/02/06 04:28:08 customdesigned
* Oops! Missing options argument pointer for addrcpt.
*
* Revision 1.16 2008/12/16 04:21:05 customdesigned
* Fedora release
*
* Revision 1.15 2008/12/13 20:29:56 customdesigned
* Split off milter applications.
*
* Revision 1.14 2008/12/04 19:43:00 customdesigned * Revision 1.14 2008/12/04 19:43:00 customdesigned
* Doc updates. * Doc updates.
* *
@@ -375,7 +399,7 @@ generic_set_callback(PyObject *args,char *t,PyObject **cb) {
callback = 0; callback = 0;
else { else {
if (!PyCallable_Check(callback)) { if (!PyCallable_Check(callback)) {
PyErr_SetString(PyExc_TypeError, "parameter must be callable"); PyErr_SetString(PyExc_TypeError, "callback parameter must be callable");
return NULL; return NULL;
} }
Py_INCREF(callback); Py_INCREF(callback);
@@ -777,6 +801,87 @@ milter_wrap_abort(SMFICTX *ctx) {
return generic_noarg_wrapper(ctx,abort_callback); return generic_noarg_wrapper(ctx,abort_callback);
} }
#ifdef SMFIS_ALL_OPTS
static PyObject *unknown_callback = NULL;
static PyObject *data_callback = NULL;
static PyObject *negotiate_callback = NULL;
static int
milter_wrap_unknown(SMFICTX *ctx, const char *cmd) {
PyObject *arglist;
milter_ContextObject *c;
if (unknown_callback == NULL) return SMFIS_CONTINUE;
c = _get_context(ctx);
if (!c) return SMFIS_TEMPFAIL;
arglist = Py_BuildValue("(Os)", c, cmd);
return _generic_wrapper(c, unknown_callback, arglist);
}
static int
milter_wrap_data(SMFICTX *ctx) {
return generic_noarg_wrapper(ctx,data_callback);
}
static int
milter_wrap_negotiate(SMFICTX *ctx,
unsigned long f0,
unsigned long f1,
unsigned long f2,
unsigned long f3,
unsigned long *pf0,
unsigned long *pf1,
unsigned long *pf2,
unsigned long *pf3) {
PyObject *arglist, *optlist;
milter_ContextObject *c;
int rc;
if (negotiate_callback == NULL) return SMFIS_ALL_OPTS;
c = _get_context(ctx);
if (!c)
return SMFIS_REJECT; // do not contact us again for current connection
optlist = Py_BuildValue("[kkkk]",f0,f1,f2,f3);
if (optlist == NULL)
arglist = NULL;
else
arglist = Py_BuildValue("(OO)", c, optlist);
PyThreadState *t = c->t;
c->t = 0; // do not release thread in _generic_wrapper
rc = _generic_wrapper(c, negotiate_callback, arglist);
c->t = t;
if (rc == SMFIS_CONTINUE) {
#if 0 // PyArgs_Parse deprecated and going away
if (!PyArgs_Parse(optlist,"[kkkk]",pf0,pf1,pf2,pf3)) {
PyErr_Print();
PyErr_Clear(); /* must clear since not returning to python */
rc = SMFIS_REJECT;
}
#else
unsigned long *pa[4] = { pf0,pf1,pf2,pf3 };
unsigned long fa[4] = { f0,f1,f2,f3 };
int len = PyList_Size(optlist);
int i;
for (i = 0; i < 4; ++i) {
*pa[i] = (i <= len)
? PyInt_AsUnsignedLongMask(PyList_GET_ITEM(optlist,i))
: fa[i];
}
if (PyErr_Occurred()) {
PyErr_Print();
PyErr_Clear();
rc = SMFIS_REJECT;
}
#endif
}
else if (rc != SMFIS_ALL_OPTS)
rc = SMFIS_REJECT;
Py_DECREF(optlist);
_release_thread(t);
return rc;
}
#endif
static int static int
milter_wrap_close(SMFICTX *ctx) { milter_wrap_close(SMFICTX *ctx) {
/* xxfi_close can be called out of order - even before connect. /* xxfi_close can be called out of order - even before connect.
@@ -808,15 +913,61 @@ milter_wrap_close(SMFICTX *ctx) {
} }
static char milter_register__doc__[] = static char milter_register__doc__[] =
"register(name) -> None\n\ "register(name,unknown=,data=,negotiate=) -> None\n\
Registers the milter name with current callbacks, and flags.\n\ Registers the milter name with current callbacks, and flags.\n\
Required before main() is called."; Required before main() is called.";
static PyObject * static PyObject *
milter_register(PyObject *self, PyObject *args) { milter_register(PyObject *self, PyObject *args, PyObject *kwds) {
if (!PyArg_ParseTuple(args, "s:register", &description.xxfi_name)) static char *kwlist[] = { "name","unknown","data","negotiate", NULL };
static PyObject** const cbp[3] =
{ &unknown_callback, &data_callback, &negotiate_callback };
PyObject *cb[3] = { NULL, NULL, NULL };
int i;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|OOO:register", kwlist,
&description.xxfi_name, &cb[0],&cb[1],&cb[2]))
return NULL;
for (i = 0; i < 3; ++i) {
PyObject *callback = cb[i];
if (callback != NULL && callback != Py_None) {
if (!PyCallable_Check(callback)) {
char err[80];
sprintf(err,"%s parameter must be callable",kwlist[i]);
PyErr_SetString(PyExc_TypeError, err);
return NULL;
}
}
}
for (i = 0; i < 3; ++i) {
PyObject *callback = cb[i];
if (callback != NULL) { // if keyword specified
if (callback == Py_None) {
callback = NULL;
}
else {
Py_INCREF(callback);
}
PyObject *oldval = *cbp[i];
*cbp[i] = callback;
if (oldval) {
Py_DECREF(oldval);
}
}
}
return _generic_return(smfi_register(description), "cannot register");
}
static char milter_opensocket__doc__[] =
"opensocket(rmsock) -> None\n\
Attempts to create and open the socket provided with setconn.\n\
Removes the socket first if rmsock is True.";
static PyObject *
milter_opensocket(PyObject *self, PyObject *args) {
char rmsock = 0;
if (!PyArg_ParseTuple(args, "b:opensocket", &rmsock))
return NULL; return NULL;
return _generic_return(smfi_register(description), "cannot register"); return _generic_return(smfi_opensocket(rmsock), "cannot opensocket");
} }
static char milter_main__doc__[] = static char milter_main__doc__[] =
@@ -1094,7 +1245,7 @@ milter_addrcpt(PyObject *self, PyObject *args) {
PyThreadState *t; PyThreadState *t;
int rc; int rc;
if (!PyArg_ParseTuple(args, "s|z:addrcpt", &rcpt)) return NULL; if (!PyArg_ParseTuple(args, "s|z:addrcpt", &rcpt, &params)) 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();
@@ -1124,8 +1275,7 @@ milter_delrcpt(PyObject *self, PyObject *args) {
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_delrcpt(ctx, rcpt), return _thread_return(t,smfi_delrcpt(ctx, rcpt), "cannot delete recipient");
"cannot delete recipient");
} }
static char milter_replacebody__doc__[] = static char milter_replacebody__doc__[] =
@@ -1146,8 +1296,8 @@ milter_replacebody(PyObject *self, PyObject *args) {
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_replacebody(ctx, bodyp, bodylen), return _thread_return(t,smfi_replacebody(ctx,
"cannot replace message body"); (unsigned char *)bodyp, bodylen), "cannot replace message body");
} }
static char milter_setpriv__doc__[] = static char milter_setpriv__doc__[] =
@@ -1232,6 +1382,27 @@ milter_progress(PyObject *self, PyObject *args) {
} }
#endif #endif
#ifdef SMFIF_SETSMLIST
static char milter_setsmlist__doc__[] =
"setsmlist(stage,macrolist) -> None\n\
Tell the MTA which macro values we are interested in for a given stage";
static PyObject *
milter_setsmlist(PyObject *self, PyObject *args) {
SMFICTX *ctx;
PyThreadState *t;
int stage = 0;
char *smlist = 0;
if (!PyArg_ParseTuple(args, "is:setsmlist",&stage, &smlist)) return NULL;
ctx = _find_context(self);
if (ctx == NULL) return NULL;
t = PyEval_SaveThread();
return _thread_return(t,smfi_setsmlist(ctx,stage,smlist),
"cannot set macro list");
}
#endif
static PyMethodDef context_methods[] = { static PyMethodDef context_methods[] = {
{ "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__}, { "getsymval", milter_getsymval, METH_VARARGS, milter_getsymval__doc__},
{ "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__}, { "setreply", milter_setreply, METH_VARARGS, milter_setreply__doc__},
@@ -1250,6 +1421,9 @@ static PyMethodDef context_methods[] = {
#endif #endif
#ifdef SMFIF_CHGFROM #ifdef SMFIF_CHGFROM
{ "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__}, { "chgfrom", milter_chgfrom, METH_VARARGS, milter_chgfrom__doc__},
#endif
#ifdef SMFIF_SETSMLIST
{ "setsmlist", milter_setsmlist, METH_VARARGS, milter_setsmlist__doc__},
#endif #endif
{ NULL, NULL } { NULL, NULL }
}; };
@@ -1272,7 +1446,12 @@ static struct smfiDesc description = { /* Set some reasonable defaults */
milter_wrap_body, milter_wrap_body,
milter_wrap_eom, milter_wrap_eom,
milter_wrap_abort, milter_wrap_abort,
milter_wrap_close milter_wrap_close,
#ifdef SMFIS_ALL_OPTS
milter_wrap_unknown,
milter_wrap_data,
milter_wrap_negotiate
#endif
}; };
static PyMethodDef milter_methods[] = { static PyMethodDef milter_methods[] = {
@@ -1287,9 +1466,9 @@ static PyMethodDef milter_methods[] = {
{ "set_eom_callback", milter_set_eom_callback, METH_VARARGS, milter_set_eom_callback__doc__}, { "set_eom_callback", milter_set_eom_callback, METH_VARARGS, milter_set_eom_callback__doc__},
{ "set_abort_callback", milter_set_abort_callback, METH_VARARGS, milter_set_abort_callback__doc__}, { "set_abort_callback", milter_set_abort_callback, METH_VARARGS, milter_set_abort_callback__doc__},
{ "set_close_callback", milter_set_close_callback, METH_VARARGS, milter_set_close_callback__doc__}, { "set_close_callback", milter_set_close_callback, METH_VARARGS, milter_set_close_callback__doc__},
{ "set_exception_policy", milter_set_exception_policy,METH_VARARGS, milter_set_exception_policy__doc__}, { "set_exception_policy", milter_set_exception_policy, METH_VARARGS, milter_set_exception_policy__doc__},
{ "register", milter_register, METH_VARARGS, milter_register__doc__}, { "register", (PyCFunction)milter_register,METH_VARARGS|METH_KEYWORDS, milter_register__doc__},
{ "register", milter_register, METH_VARARGS, milter_register__doc__}, { "opensocket", milter_opensocket, METH_VARARGS, milter_opensocket__doc__},
{ "main", milter_main, METH_VARARGS, milter_main__doc__}, { "main", milter_main, METH_VARARGS, milter_main__doc__},
{ "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__}, { "setdbg", milter_setdbg, METH_VARARGS, milter_setdbg__doc__},
{ "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__}, { "settimeout", milter_settimeout, METH_VARARGS, milter_settimeout__doc__},
@@ -1364,6 +1543,35 @@ initmilter(void) {
#endif #endif
#ifdef SMFIF_CHGFROM #ifdef SMFIF_CHGFROM
setitem(d,"CHGFROM",SMFIF_CHGFROM); setitem(d,"CHGFROM",SMFIF_CHGFROM);
#endif
#ifdef SMFIF_SETSMLIST
setitem(d,"SETSMLIST",SMFIF_SETSMLIST);
#endif
#ifdef SMFIS_ALL_OPTS
setitem(d,"P_RCPT_REJ",SMFIP_RCPT_REJ);
setitem(d,"P_NR_CONN",SMFIP_NR_CONN);
setitem(d,"P_NR_HELO",SMFIP_NR_HELO);
setitem(d,"P_NR_MAIL",SMFIP_NR_MAIL);
setitem(d,"P_NR_RCPT",SMFIP_NR_RCPT);
setitem(d,"P_NR_DATA",SMFIP_NR_DATA);
setitem(d,"P_NR_UNKN",SMFIP_NR_UNKN);
setitem(d,"P_NR_EOH",SMFIP_NR_EOH);
setitem(d,"P_NR_BODY",SMFIP_NR_BODY);
setitem(d,"P_NR_HDR",SMFIP_NR_HDR);
setitem(d,"P_NOCONNECT",SMFIP_NOCONNECT);
setitem(d,"P_NOHELO",SMFIP_NOHELO);
setitem(d,"P_NOMAIL",SMFIP_NOMAIL);
setitem(d,"P_NORCPT",SMFIP_NORCPT);
setitem(d,"P_NODATA",SMFIP_NODATA);
setitem(d,"P_NOUNKNOWN",SMFIP_NOUNKNOWN);
setitem(d,"P_NOEOH",SMFIP_NOEOH);
setitem(d,"P_NOBODY",SMFIP_NOBODY);
setitem(d,"P_NOHDRS",SMFIP_NOHDRS);
setitem(d,"P_HDR_LEADSPC",SMFIP_HDR_LEADSPC);
setitem(d,"P_SKIP",SMFIP_SKIP);
setitem(d,"ALL_OPTS",SMFIS_ALL_OPTS);
setitem(d,"SKIP",SMFIS_SKIP);
setitem(d,"NOREPLY",SMFIS_NOREPLY);
#endif #endif
setitem(d,"CONTINUE", SMFIS_CONTINUE); setitem(d,"CONTINUE", SMFIS_CONTINUE);
setitem(d,"REJECT", SMFIS_REJECT); setitem(d,"REJECT", SMFIS_REJECT);
+75 -40
View File
@@ -1,23 +1,32 @@
# EL 3,4,5 supported, set to 0 for Fedora
%define el4 1
%define dist .el4
%if 0%{?el3} || 0%{?el4}
%define __python python2.4 %define __python python2.4
%define version 0.8.12 %endif
%define release 1%{?dist}.py24
%define libdir %{_libdir}/pymilter %define libdir %{_libdir}/pymilter
%define name pymilter %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")}
%define redhat7 0 %define pythonbase %(basename %{__python})
Summary: Python interface to sendmail milter API Summary: Python interface to sendmail milter API
Name: %{name} Name: pymilter
Version: %{version} Version: 0.9.2
Release: %{release} Release: 3%{dist}
Source: %{name}-%{version}.tar.gz Source: http://downloads.sourceforge.net/pymilter/%{name}-%{version}.tar.gz
#Patch: %{name}-%{version}.patch
License: GPLv2+ License: GPLv2+
Group: Development/Libraries Group: Development/Libraries
BuildRoot: %{_tmppath}/%{name}-buildroot BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
Vendor: Stuart D. Gathman <stuart@bmsi.com>
Url: http://www.bmsi.com/python/milter.html Url: http://www.bmsi.com/python/milter.html
Requires: %{__python} >= 2.4, sendmail >= 8.13 Requires: %{pythonbase} >= 2.4, sendmail >= 8.13
BuildRequires: %{__python}-devel >= 2.4, sendmail-devel >= 8.13 %if 0%{?el3} || 0%{?el4}
# Need python2.4 specific pydns, not the version for system python
Requires: pydns
%else
# Needed for callbacks, not a core function but highly useful for milters
Requires: python-pydns
%endif
BuildRequires: ed, %{pythonbase}-devel >= 2.4, sendmail-devel >= 8.13
%description %description
This is a python extension module to enable python scripts to This is a python extension module to enable python scripts to
@@ -27,30 +36,30 @@ DSNs, and doing CBV.
%prep %prep
%setup -q %setup -q
#patch -p0 -b .bms
%build %build
%if %{redhat7} env CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build
LDFLAGS="-s"
%else # Redhat builds debug packages after 7.3
LDFLAGS="-g"
%endif
env CFLAGS="$RPM_OPT_FLAGS" LDFLAGS="$LDFLAGS" %{__python} setup.py build
%install %install
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
%{__python} setup.py install --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES %{__python} setup.py install --root=$RPM_BUILD_ROOT
mkdir -p $RPM_BUILD_ROOT/var/run/milter mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/run/milter
mkdir -p $RPM_BUILD_ROOT%{_localstatedir}/log/milter
mkdir -p $RPM_BUILD_ROOT%{libdir} mkdir -p $RPM_BUILD_ROOT%{libdir}
%ifos aix4.1
cat >$RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF'
#!/bin/sh
cd /var/log/milter
exec /usr/local/bin/python bms.py >>milter.log 2>&1
EOF
%else # not aix4.1
cp start.sh $RPM_BUILD_ROOT%{libdir} cp start.sh $RPM_BUILD_ROOT%{libdir}
ed $RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF' ed $RPM_BUILD_ROOT%{libdir}/start.sh <<'EOF'
/^datadir=/
c
datadir="%{_localstatedir}/log/milter"
.
/^piddir=/
c
piddir="%{_localstatedir}/run/milter"
.
/^libdir=/
c
libdir="%{libdir}"
.
/^python=/ /^python=/
c c
python="%{__python}" python="%{__python}"
@@ -58,43 +67,69 @@ python="%{__python}"
w w
q q
EOF EOF
%endif
chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh chmod a+x $RPM_BUILD_ROOT%{libdir}/start.sh
%if !%{redhat7}
#grep '.pyc$' INSTALLED_FILES | sed -e 's/c$/o/' >>INSTALLED_FILES
%endif
# start.sh is used by spfmilter and milter, and could be used by # start.sh is used by spfmilter, srsmilter, and milter, and could be used by
# other milters running on redhat # other milters using pymilter.
%files -f INSTALLED_FILES %files
%defattr(-,root,root) %defattr(-,root,root,-)
%doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py %doc README ChangeLog NEWS TODO CREDITS sample.py milter-template.py
%config %{libdir}/start.sh %{python_sitearch}/*
%dir %attr(0755,mail,mail) /var/run/milter %{libdir}
%dir %attr(0755,mail,mail) %{_localstatedir}/run/milter
%dir %attr(0755,mail,mail) %{_localstatedir}/log/milter
%clean %clean
rm -rf $RPM_BUILD_ROOT rm -rf $RPM_BUILD_ROOT
%changelog %changelog
* Mon Nov 24 2008 Stuart Gathman <stuart@bmsi.com> 0.8.12-1 * Tue Jun 02 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-3
- Change result of @noreply callbacks to NOREPLY when so negotiated.
* Tue Jun 02 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-2
- Cache callback negotiation
* Thu May 28 2009 Stuart Gathman <stuart@bmsi.com> 0.9.2-1
- Add new callback support: data,negotiate,unknown
- Auto-negotiate protocol steps
* Thu Feb 05 2009 Stuart Gathman <stuart@bmsi.com> 0.9.1-1
- Fix missing address of optional param to addrcpt
* Wed Jan 07 2009 Stuart Gathman <stuart@bmsi.com> 0.9.0-4
- Stop using INSTALLED_FILES to make Fedora happy
- Remove config flag from start.sh glue
- Own /var/log/milter
- Use _localstatedir
* Wed Jan 07 2009 Stuart Gathman <stuart@bmsi.com> 0.9.0-2
- Changes to meet Fedora standards
* Mon Nov 24 2008 Stuart Gathman <stuart@bmsi.com> 0.9.0-1
- Split pymilter into its own CVS module - Split pymilter into its own CVS module
- Support chgfrom and addrcpt_par - Support chgfrom and addrcpt_par
- Support NS records in Milter.dns - Support NS records in Milter.dns
* Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-2 * Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-2
- /var/run/milter directory must be owned by mail - /var/run/milter directory must be owned by mail
* Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-1 * Mon Aug 25 2008 Stuart Gathman <stuart@bmsi.com> 0.8.10-1
- improved parsing into email and fullname (still 2 self test failures) - improved parsing into email and fullname (still 2 self test failures)
- implement no-DSN CBV, reduce full DSNs - implement no-DSN CBV, reduce full DSNs
* Mon Sep 24 2007 Stuart Gathman <stuart@bmsi.com> 0.8.9-1 * Mon Sep 24 2007 Stuart Gathman <stuart@bmsi.com> 0.8.9-1
- Use ifarch hack to build milter and milter-spf packages as noarch - Use ifarch hack to build milter and milter-spf packages as noarch
- Remove spf dependency from dsn.py, add dns.py - Remove spf dependency from dsn.py, add dns.py
* Fri Jan 05 2007 Stuart Gathman <stuart@bmsi.com> 0.8.8-1 * Fri Jan 05 2007 Stuart Gathman <stuart@bmsi.com> 0.8.8-1
- move AddrCache, parse_addr, iniplist to Milter package - move AddrCache, parse_addr, iniplist to Milter package
- move parse_header to Milter.utils - move parse_header to Milter.utils
- fix plock for missing source and can't change owner/group - fix plock for missing source and can't change owner/group
- split out pymilter and pymilter-spf packages - split out pymilter and pymilter-spf packages
- move milter apps to /usr/lib/pymilter - move milter apps to /usr/lib/pymilter
* Sat Nov 04 2006 Stuart Gathman <stuart@bmsi.com> 0.8.7-1 * Sat Nov 04 2006 Stuart Gathman <stuart@bmsi.com> 0.8.7-1
- SPF moved to pyspf RPM - SPF moved to pyspf RPM
* Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2 * Tue May 23 2006 Stuart Gathman <stuart@bmsi.com> 0.8.6-2
- Support CBV timeout - Support CBV timeout
+5 -4
View File
@@ -2,10 +2,11 @@ import os
import sys import sys
from distutils.core import setup, Extension from distutils.core import setup, Extension
# FIXME: on some versions of sendmail, smutil is renamed to sm # FIXME: on some versions of sendmail, smutil is renamed to sm.
# on slackware and debian, leave it out entirely. It depends # On slackware and debian, leave it out entirely. It depends
# on how libmilter was built by the sendmail package. # on how libmilter was built by the sendmail package.
libs = ["milter", "smutil"] #libs = ["milter", "smutil"]
libs = ["milter"]
libdirs = ["/usr/lib/libmilter"] # needed for Debian libdirs = ["/usr/lib/libmilter"] # needed for Debian
# patch distutils if it can't cope with the "classifiers" or # patch distutils if it can't cope with the "classifiers" or
@@ -16,7 +17,7 @@ if sys.version < '2.2.3':
DistributionMetadata.download_url = None DistributionMetadata.download_url = None
# NOTE: importing Milter to obtain version fails when milter.so not built # NOTE: importing Milter to obtain version fails when milter.so not built
setup(name = "pymilter", version = '0.8.12', setup(name = "pymilter", version = '0.9.2',
description="Python interface to sendmail milter API", description="Python interface to sendmail milter API",
long_description="""\ long_description="""\
This is a python extension module to enable python scripts to This is a python extension module to enable python scripts to
+5 -3
View File
@@ -1,14 +1,16 @@
#!/bin/sh #!/bin/sh
appname="$1" appname="$1"
script="${2:-${appname}}" script="${2:-${appname}}"
datadir=/var/log/milter datadir="/var/log/milter"
piddir="/var/run/milter"
libdir="/usr/lib/pymilter"
python="python2.4" python="python2.4"
exec >>${datadir}/${appname}.log 2>&1 exec >>${datadir}/${appname}.log 2>&1
if test -s ${datadir}/${script}.py; then if test -s ${datadir}/${script}.py; then
cd ${datadir} # use version in log dir if it exists for debugging cd ${datadir} # use version in log dir if it exists for debugging
else else
cd /usr/lib/pymilter cd ${libdir}
fi fi
${python} ${script}.py & ${python} ${script}.py &
echo $! >/var/run/milter/${appname}.pid echo $! >${piddir}/${appname}.pid