\section{milter-\/template.\+py}

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