From 0f7cd8b5551e2ec4333cb10561375872dd7e03d3 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Sat, 3 Dec 2005 20:41:56 +0200 Subject: Initial import. --- qmqp.py | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 qmqp.py (limited to 'qmqp.py') diff --git a/qmqp.py b/qmqp.py new file mode 100644 index 0000000..bc5d194 --- /dev/null +++ b/qmqp.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +# This module that implements sending mail via QMQP. See +# +# http://cr.yp.to/proto/qmqp.html +# +# for a description of the protocol. +# +# This module was written by Jaakko Niemi for +# Enemies of Carlotta. It is licensed the same way as Enemies of Carlotta: +# GPL version 2. + +import socket, string + +class QMQPException(Exception): + '''Base class for all exceptions raised by this module.''' + +class QMQPTemporaryError(QMQPException): + '''Class for temporary errors''' + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return "QMQP-Server said: %s" % self.msg + +class QMQPPermanentError(QMQPException): + '''Class for permanent errors''' + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return "QMQP-Server said: %s" % self.msg + +class QMQPConnectionError(QMQPException): + '''Class for connection errors''' + def __init__(self, msg): + self.msg = msg + + def __str__(self): + return "Error was: %s" % self.msg + +class QMQP: + '''I handle qmqp connection to a server''' + + file = None + + def __init__(self, host = 'localhost'): + '''Start''' + if host: + resp = self.connect(host) + + def encode(self, stringi): + ret = '%d:%s,' % (len(stringi), stringi) + return ret + + def decode(self, stringi): + stringi = string.split(stringi, ':', 1) + stringi[1] = string.rstrip(stringi[1], ',') + if len(stringi[1]) is not int(stringi[0]): + print 'malformed netstring encounterd' + return stringi[1] + + def connect(self, host = 'localhost'): + for sres in socket.getaddrinfo(host, 628, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = sres + try: + self.sock = socket.socket(af, socktype, proto) + self.sock.connect(sa) + except socket.error: + print 'connect failed' + if self.sock: + self.sock.close() + self.sock = None + continue + break + if not self.sock: + raise socket.error + return 0 + + def send(self, stringi): + if self.sock: + try: + self.sock.sendall(stringi) + except socket.error, err: + self.sock.close() + raise QMQPConnectionError, err + else: + print 'not connected' + + def getreply(self): + if self.file is None: + self.file = self.sock.makefile('rb') + while 1: + line = self.file.readline() + if line == '': + self.sock.close() + print 'ERORORR' + break + line = self.decode(line) + return line + + def quit(self): + self.sock.close() + + def sendmail(self, from_addr, to_addrs, msg): + recipients = '' + msg = self.encode(msg) + from_addr = self.encode(from_addr) + +# I don't understand why len(to_addrs) <= 1 needs to be handled differently. +# Anyway, it doesn't seem to work with Postfix. --liw +# if len(to_addrs) > 1: +# for t in to_addrs: +# recipients = recipients + self.encode(t) +# else: +# recipients = self.encode(to_addrs[0]) + + for t in to_addrs: + recipients = recipients + self.encode(t) + output = self.encode(msg + from_addr + recipients) + self.send(output) + ret = self.getreply() + if ret[0] == 'K': + return ret[1:] + if ret[0] == 'Z': + raise QMQPTemporaryError, ret[1:] + if ret[0] == 'D': + raise QMQPPermanentError, ret[1:] + +if __name__ == '__main__': + a = QMQP() + maili = 'asfasdfsfdasfasd' + envelope_sender = 'liw@liw.iki.fi' + recips = [ 'liw@liw.iki.fi' ] + retcode = a.sendmail(envelope_sender, recips, maili) + print retcode + a.quit() -- cgit v1.2.1