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