Remove List-* headers before adding them. Thanks to Johannes Berg.
[eoc.git] / qmqp.py
1 #!/usr/bin/python
2
3 # This module that implements sending mail via QMQP. See
4 #
5 #       http://cr.yp.to/proto/qmqp.html
6 #
7 # for a description of the protocol.
8 #
9 # This module was written by Jaakko Niemi <liiwi@lonesom.pp.fi> for
10 # Enemies of Carlotta. It is licensed the same way as Enemies of Carlotta:
11 # GPL version 2.
12
13 import socket, string
14
15 class QMQPException(Exception):
16     '''Base class for all exceptions raised by this module.'''
17
18 class QMQPTemporaryError(QMQPException):
19     '''Class for temporary errors'''
20     def __init__(self, msg):
21         self.msg = msg
22         
23     def __str__(self):
24         return "QMQP-Server said: %s" % self.msg
25         
26 class QMQPPermanentError(QMQPException):
27     '''Class for permanent errors'''
28     def __init__(self, msg):
29         self.msg = msg
30         
31     def __str__(self):
32         return "QMQP-Server said: %s" % self.msg
33
34 class QMQPConnectionError(QMQPException):
35     '''Class for connection errors'''
36     def __init__(self, msg):
37         self.msg = msg
38
39     def __str__(self):
40         return "Error was: %s" % self.msg
41
42 class QMQP:
43     '''I handle qmqp connection to a server'''
44
45     file = None
46
47     def __init__(self, host = 'localhost'):
48         '''Start'''
49         if host:
50             resp = self.connect(host)
51
52     def encode(self, stringi):
53             ret = '%d:%s,' % (len(stringi), stringi) 
54             return ret
55
56     def decode(self, stringi):
57             stringi = string.split(stringi, ':', 1)
58             stringi[1] = string.rstrip(stringi[1], ',')
59             if len(stringi[1]) is not int(stringi[0]):
60                     print 'malformed netstring encounterd'
61             return stringi[1]
62
63     def connect(self, host = 'localhost'):
64         for sres in socket.getaddrinfo(host, 628, 0, socket.SOCK_STREAM):
65             af, socktype, proto, canonname, sa = sres
66             try:
67                 self.sock = socket.socket(af, socktype, proto)
68                 self.sock.connect(sa)
69             except socket.error:
70                 print 'connect failed'
71                 if self.sock:
72                     self.sock.close()
73                 self.sock = None
74                 continue
75             break
76         if not self.sock:
77             raise socket.error
78         return 0
79         
80     def send(self, stringi):
81         if self.sock:
82             try:
83                 self.sock.sendall(stringi)
84             except socket.error, err:
85                 self.sock.close()
86                 raise QMQPConnectionError, err
87         else:
88             print 'not connected'
89
90     def getreply(self):
91         if self.file is None:
92             self.file = self.sock.makefile('rb')
93         while 1:
94             line = self.file.readline()
95             if line == '':
96                 self.sock.close()
97                 print 'ERORORR'
98             break
99         line = self.decode(line)
100         return line
101
102     def quit(self):
103         self.sock.close()
104
105     def sendmail(self, from_addr, to_addrs, msg):
106             recipients = ''
107             msg = self.encode(msg)
108             from_addr = self.encode(from_addr)
109
110 # I don't understand why len(to_addrs) <= 1 needs to be handled differently.
111 # Anyway, it doesn't seem to work with Postfix. --liw
112 #           if len(to_addrs) > 1:
113 #                   for t in to_addrs:
114 #                           recipients = recipients + self.encode(t)
115 #           else:
116 #                   recipients = self.encode(to_addrs[0])
117
118             for t in to_addrs:
119                     recipients = recipients + self.encode(t)
120             output = self.encode(msg + from_addr + recipients)
121             self.send(output)
122             ret = self.getreply()
123             if ret[0] == 'K':
124                     return ret[1:]
125             if ret[0] == 'Z':
126                     raise QMQPTemporaryError, ret[1:]
127             if ret[0] == 'D':
128                     raise QMQPPermanentError, ret[1:]
129
130 if __name__ == '__main__':
131     a = QMQP()
132     maili = 'asfasdfsfdasfasd'
133     envelope_sender = 'liw@liw.iki.fi'
134     recips = [ 'liw@liw.iki.fi' ]    
135     retcode = a.sendmail(envelope_sender, recips, maili)
136     print retcode
137     a.quit()