0
|
1 # Simple milter deamon that handles email messages off to spamd for
|
|
2 # classification. Messages classified as spam are rejected. Other
|
|
3 # are augmented with Spamassassin additional headers.
|
|
4
|
|
5 import email.parser
|
|
6 from cStringIO import StringIO
|
|
7
|
|
8 import Milter as milter
|
|
9 import spamc
|
|
10
|
|
11
|
|
12 # headers to add to scanned messages
|
|
13 HEADERS = ('X-Spam-Checker-Version', 'X-Spam-Level', 'X-Spam-Status', )
|
|
14
|
|
15
|
|
16 class Milter(milter.Base):
|
|
17
|
|
18 def __init__(self):
|
|
19 # integer incremented with each call
|
|
20 self.id = milter.uniqueID()
|
|
21 self.fd = StringIO()
|
|
22
|
|
23 @milter.noreply
|
|
24 def connect(self, hostname, family, hostaddr):
|
|
25 self.hostname = hostname
|
|
26 self.hostaddr = hostaddr
|
|
27 return milter.CONTINUE
|
|
28
|
|
29 def hello(self, heloname):
|
|
30 self.helo = heloname
|
|
31 return milter.CONTINUE
|
|
32
|
|
33 def envfrom(self, mailfrom, *str):
|
|
34 self.mailfrom = mailfrom
|
|
35 return milter.CONTINUE
|
|
36
|
|
37 @milter.noreply
|
|
38 def envrcpt(self, recipient, *str):
|
|
39 self.recipient = recipient
|
|
40 return milter.CONTINUE
|
|
41
|
|
42 @milter.noreply
|
|
43 def header(self, name, val):
|
|
44 # add header to buffer
|
|
45 self.fd.write('%s: %s\n' % (name, val))
|
|
46 return milter.CONTINUE
|
|
47
|
|
48 @milter.noreply
|
|
49 def eoh(self):
|
|
50 # terminate headers
|
|
51 self.fd.write('\n')
|
|
52 return milter.CONTINUE
|
|
53
|
|
54 @milter.noreply
|
|
55 def body(self, chunk):
|
|
56 # add body to buffer
|
|
57 self.fd.write(chunk)
|
|
58 return milter.CONTINUE
|
|
59
|
|
60 def eom(self):
|
|
61 # ask spamd about the received message
|
|
62 self.conn = spamc.Client('localhost')
|
|
63 resp = self.conn.request('HEADERS', self.fd.getvalue())
|
|
64 # reject spam messages
|
|
65 if resp.result:
|
|
66 return milter.REJECT
|
|
67 # parse returned message headers
|
|
68 data = resp.read()
|
|
69 print data
|
|
70 headers = email.parser.Parser().parsestr(data)
|
|
71 # add headers
|
|
72 for name in HEADERS:
|
|
73 # transform multiple line headers to single line ones
|
|
74 value = ' '.join(headers.get(name, '').split())
|
|
75 self.addheader(name, value)
|
|
76 # accept message
|
|
77 return milter.ACCEPT
|
|
78
|
|
79 def close(self):
|
|
80 # clean up
|
|
81 self.fd.close()
|
|
82 self.conn.close()
|
|
83 return milter.CONTINUE
|
|
84
|
|
85 def abort(self):
|
|
86 # client disconnected prematurely
|
|
87 return milter.CONTINUE
|
|
88
|
|
89
|
|
90 def main():
|
|
91 socketname = "/var/spool/postfix/private/spamc"
|
|
92 timeout = 600
|
|
93
|
|
94 # register the milter factory
|
|
95 milter.factory = Milter
|
|
96 # ask milter features we use
|
|
97 milter.set_flags(milter.CHGHDRS + milter.ADDHDRS)
|
|
98 # run
|
|
99 milter.runmilter("spamc-milter", socketname, timeout)
|
|
100
|
|
101
|
|
102 if __name__ == "__main__":
|
|
103 main()
|