1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
|
## To roll your own milter, create a class that extends Milter.
# See the pymilter project at http://bmsi.com/python/milter.html
# based on Sendmail's milter API http://www.milter.org/milter_api/api.html
# This code is open-source on the same terms as Python.
## Milter calls methods of your class at milter events.
## Return REJECT,TEMPFAIL,ACCEPT to short circuit processing for a message.
## You can also add/del recipients, replacebody, add/del headers, etc.
import Milter
import StringIO
import time
import email
import sys
from socket import AF_INET, AF_INET6
from Milter.utils import parse_addr
if True:
from multiprocessing import Process as Thread, Queue
else:
from threading import Thread
from Queue import Queue
logq = Queue(maxsize=4)
class myMilter(Milter.Base):
def __init__(self): # A new instance with each new connection.
self.id = Milter.uniqueID() # Integer incremented with each call.
# each connection runs in its own thread and has its own myMilter
# instance. Python code must be thread safe. This is trivial if only stuff
# in myMilter instances is referenced.
@Milter.noreply
def connect(self, IPname, family, hostaddr):
# (self, 'ip068.subnet71.example.com', AF_INET, ('215.183.71.68', 4720) )
# (self, 'ip6.mxout.example.com', AF_INET6,
# ('3ffe:80e8:d8::1', 4720, 1, 0) )
self.IP = hostaddr[0]
self.port = hostaddr[1]
if family == AF_INET6:
self.flow = hostaddr[2]
self.scope = hostaddr[3]
else:
self.flow = None
self.scope = None
self.IPname = IPname # Name from a reverse IP lookup
self.H = None
self.fp = None
self.receiver = self.getsymval('j')
self.log("connect from %s at %s" % (IPname, hostaddr) )
return Milter.CONTINUE
## def hello(self,hostname):
def hello(self, heloname):
# (self, 'mailout17.dallas.texas.example.com')
self.H = heloname
self.log("HELO %s" % heloname)
if heloname.find('.') < 0: # illegal helo name
# NOTE: example only - too many real braindead clients to reject on this
self.setreply('550','5.7.1','Sheesh people! Use a proper helo name!')
return Milter.REJECT
return Milter.CONTINUE
## def envfrom(self,f,*str):
def envfrom(self, mailfrom, *str):
self.F = mailfrom
self.R = [] # list of recipients
self.fromparms = Milter.dictfromlist(str) # ESMTP parms
self.user = self.getsymval('{auth_authen}') # authenticated user
self.log("mail from:", mailfrom, *str)
self.fp = StringIO.StringIO()
self.canon_from = '@'.join(parse_addr(mailfrom))
self.fp.write('From %s %s\n' % (self.canon_from,time.ctime()))
return Milter.CONTINUE
## def envrcpt(self, to, *str):
@Milter.noreply
def envrcpt(self, recipient, *str):
rcptinfo = to,Milter.dictfromlist(str)
self.R.append(rcptinfo)
return Milter.CONTINUE
@Milter.noreply
def header(self, name, hval):
self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer
return Milter.CONTINUE
@Milter.noreply
def eoh(self):
self.fp.write("\n") # terminate headers
return Milter.CONTINUE
@Milter.noreply
def body(self, chunk):
self.fp.write(chunk)
return Milter.CONTINUE
def eom(self):
self.fp.seek(0)
msg = email.message_from_file(self.fp)
self.setreply('250','2.5.1','Grokked by pymilter')
# many milter functions can only be called from eom()
# example of adding a Bcc:
self.addrcpt('<%s>' % 'spy@example.com')
return Milter.ACCEPT
def close(self):
# always called, even when abort is called. Clean up
# any external resources here.
return Milter.CONTINUE
def abort(self):
# client disconnected prematurely
return Milter.CONTINUE
## === Support Functions ===
def log(self,*msg):
logq.put((msg,self.id,time.time()))
def background():
while True:
t = logq.get()
if not t: break
msg,id,ts = t
print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
for i in msg: print i,
print
## ===
def main():
bt = Thread(target=background)
bt.start()
socketname = "/home/stuart/pythonsock"
timeout = 600
# Register to have the Milter factory create instances of your class:
Milter.factory = myMilter
flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS
flags += Milter.ADDRCPT
flags += Milter.DELRCPT
Milter.set_flags(flags) # tell Sendmail which features we use
print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S')
sys.stdout.flush()
Milter.runmilter("pythonfilter",socketname,timeout)
logq.put(None)
bt.join()
print "%s bms milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')
if __name__ == "__main__":
main()
|