[Pymilter] simple milter design needed

Stuart D. Gathman stuart at bmsi.com
Sat Apr 3 18:05:44 EST 2004


On Sat, 3 Apr 2004, Eric S. Johansson wrote:

> (www.camram.org) into a milter.  What I need at invocation is a list of 
> all recipients and the sender, and the message  passing through the 
> milter either the form of a string or a email.message object.  It would 
> be wonderful if I could find out what interface the message came in on 
> but I have a backup plan in case that's not possible.

import Milter
import mime
import rfc822

class camramMilter(Milter.Milter):

# The connect callback tells you connecting IP.  Furthermore, the connect
# interface is available as a "macro".

  def connect(self,hostname,unused,hostaddr):
    self.receiver = self.getsymval('j')
    self.if_name = self.getsymval('if_name')
    self.if_addr = self.getsymval('if_addr')
    if hostaddr and len(hostaddr) > 0:
      ipaddr = hostaddr[0]
      self.connectip = ipaddr
    else:
      self.connectip = None
    self.log("connect from %s at %s" % (hostname,hostaddr))
    return Milter.CONTINUE

  def hello(self,hostname):
    self.hello_name = hostname
    self.log("hello from %s" % hostname)
    return Milter.CONTINUE

# The envfrom callback tells you who the message is (purportedly) from.
# multiple messages can be received on a single connection
# envfrom (MAIL FROM in the SMTP protocol) marks the start
# of each message.
  def envfrom(self,f,*str):
    self.log("mail from",f,str)
    self.fp = StringIO.StringIO()	# file to save message in
    self.mailfrom = f
    self.recipients = []
    return Milter.CONTINUE

  def envrcpt(self,to,*str):
    self.log("rcpt to",to,str)
    self.recipients.append(to)
    return Milter.CONTINUE

  def header(self,name,val):
    self.fp.write("%s: %s\n" % (name,val))	# add header to buffer
    return Milter.CONTINUE

  def eoh(self):
    # possibly camram would know by this time whether to discard the
    # message
    if self.check_camram():
      return Milter.DISCARD
    return Milter.CONTINUE

  def body(self,chunk):		# copy body to temp file
    if self.fp:
      self.fp.write(chunk)	# IOError causes TEMPFAIL in milter
      self.bodysize += len(chunk)
    return Milter.CONTINUE

  def _headerChange(self,msg,name,value):
    if value:	# add header
      self.addheader(name,value)
    else:	# delete all headers with name
      h = msg.getheaders(name)
      if h:
	for i in range(len(h),0,-1):
	  self.chgheader(name,i-1,'')

> on return, I would either pass the message back (modified) or nothing at 
> all (i.e. message has been spamtrapped).

  def eom(self):
    #msgtxt = self.fp.getvalue() # get message as string

    #
    # get message as enhanced email.Message with bug fixes and support
    #
    # for changing attachments and propagating header changes
    self.fp.seek(0)
    msg = mime.MimeMessage(self.fp)
    # pass header changes in top level message to sendmail
    msg.headerchange = self._headerChange

    # crunch message with camram
    if self.camram(msg):
      return Milter.DISCARD	# camram says to ignore message

    if not msg.ismodified():
      return Milter.CONTINUE

    # pass modified message to sendmail
    out = StringIO.StringIO()
    try:
      msg.dump(out) # flatten modified message
      out.seek(0)
      msg = rfc822.Message(out) # just need to skip headers
      msg.rewindbody()	  # skip headers
      while True:
	buf = out.read(8192)
	if len(buf) == 0: break
	self.replacebody(buf)	# feed modified message to sendmail
      if spam_checked: self.log("dspam")
      return Milter.CONTINUE
    except:
      return Milter.TEMPFAIL
    finally:
      out.close()

  def abort(self):
    self.log('connection aborted!')
    return Milter.CONTINUE

  def close(self):
    # do any cleanup here
    return Milter.CONTINUE

> is there any glaring problems with these needs?  Or will I have no 
> problem creating a wrapper bridging between the milter data model and 
> the camram data model?

  Hope the quick tutorial helped.  
  
This really brings home the need to create a plugin structure for Python milter
addons.  Sendmail can, of course, run several milters in series.  And that is
the best approach for C milters.  However, with Python it would be better to
handle lots of optional features within the same VM.

-- 
		      Stuart D. Gathman <stuart at bmsi.com>
    Business Management Systems Inc.  Phone: 703 591-0911 Fax: 703 591-6154
    "Very few of our customers are going to have a pure Unix
    or pure Windows environment." - Dennis Oldroyd, Microsoft Corporation




More information about the Pymilter mailing list