Attached is a patch for tmda 1.0.2 to add lmtp support. I didn't write the actually lmtp client code I just hooked it into tmda. To use lmtp you set the DELIVERY variable to "=unix:/path/to/lmtp/socket" for unix sockets or "=host:port" for tcp sockets. I have not tested tcp sockets because I don't run cyrus imapd that way.
Pleas let me know if you find any problems with my implementation.
-Craig Forbes
--On Thursday, March 11, 2004 12:24 PM -0700 tmda <[EMAIL PROTECTED]> wrote:
On Thu, Mar 11, 2004 at 11:56:53AM -0500, Craig Forbes wrote:Jared said: > I did what you said, and it didn't fix the errors. Perhaps the error I > am having is not that deliver isn't being run as cyrus. If that's not > it, then I'm at a loss. I don't know how else to get the messages to my > inbox from TMDA. The mailbox format is not plain text, but rather a > binary database. I have to have cyrus handle the mail some how. And > like you, I don't know much about cyrus. > > Anyone else doing something similar to this?
I have modified tmda 1.0.2 to support lmtp for delivery to cyrus imap. If there is interest I will make the patches available.
Please do! I've been waiting for native LMTP support in tmda for a long time now. I would have coded one myself if I didn't have other priorities. In an effort to cut down on the number of resources required I'm trying to make my postfix-amavis-clamav-spamassassin-tmda-cyrus chain as "lean" as possible, and the easiest way I can see that happening is cutting out YET another program (ie., procmail) to sit as the delivery agent between tmda and my virtual users in cyrus. Please make this patch available! Lots of beer and thanks!
Index: TMDA/Deliver.py =================================================================== RCS file: /usr/local/cvsroot/tmda/TMDA/Deliver.py,v retrieving revision 1.1.1.1 retrieving revision 1.2 diff -u -r1.1.1.1 -r1.2 --- TMDA/Deliver.py 24 Feb 2004 05:07:05 -0000 1.1.1.1 +++ TMDA/Deliver.py 25 Feb 2004 04:40:51 -0000 1.2 @@ -33,6 +33,7 @@ import Defaults import Errors import Util +import LMTP def alarm_handler(signum, frame): @@ -105,6 +106,10 @@ self.delivery_dest = self.option if firstchar == '~': self.delivery_dest = os.path.expanduser(self.delivery_dest) + # A lmtp line begins with = + elif (firstchar == '='): + self.delivery_type = 'lmtp' + self.delivery_dest = self.option[1:].strip() elif self.option == '_filter_': self.delivery_type = 'filter' self.delivery_dest = 'stdout' @@ -162,6 +167,8 @@ else: # don't wrap headers, don't escape From, don't add From_ line self.__deliver_maildir(Util.msg_as_string(self.msg), dest) + elif type == 'lmtp': + self.__deliver_lmtp(Util.msg_as_string(self.msg), dest) elif type == 'filter': sys.stdout.write(Util.msg_as_string(self.msg)) @@ -390,3 +397,9 @@ # Delivery is done, cancel the alarm. signal.alarm(0) signal.signal(signal.SIGALRM, signal.SIG_DFL) + + def __deliver_lmtp(self, message, lmtp_host): + LMTP.sendlmtp(lmtp_host, self.env_sender,os.environ.get('TMDA_RECIPIENT'), message); + + + Index: TMDA/LMTP.py =================================================================== RCS file: TMDA/LMTP.py diff -N TMDA/LMTP.py --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ TMDA/LMTP.py 25 Feb 2004 04:41:21 -0000 1.2 @@ -0,0 +1,129 @@ +# -*- python -*- + +# LMTP protocol support for tmda +# +# Based on spamcheck.py, Copyright (C) 2002 James Henstridge +# Which was distributed with the following license" +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import sys, string +import re, getopt +import smtplib, socket + +import Defaults +import Errors + +# this class hacks smtplib's SMTP class into a shape where it will +# successfully pass a message off to Cyrus's LMTP daemon. +# Also adds support for connecting to a unix domain socket. +class LMTP(smtplib.SMTP): + lhlo_resp = None + def __init__(self, host=''): + self.lmtp_features = {} + self.esmtp_features = self.lmtp_features + + if host: + (code, msg) = self.connect(host) + if code != 220: + raise smtplib.SMTPConnectError(code, msg) + + def connect(self, host='localhost'): + """Connect to a host on a given port. + + If the hostname starts with `unix:', the remainder of the string + is assumed to be a unix domain socket. + """ + + if host[:5] == 'unix:': + host = host[5:] + self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + if self.debuglevel > 0: print 'connect:', host + self.sock.connect(host) + else: + i = string.find(host, ':') + if i >= 0: + host, port = host[:i], host[i+1:] + try: port = int(port) + except string.atoi_error: + raise socket.error, "non numeric port" + if not port: port = LMTP_PORT + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if self.debuglevel > 0: print 'connect:', (host, port) + self.sock.connect((host, port)) + (code, msg) = self.getreply() + if self.debuglevel > 0: print 'connect:', msg + return (code, msg) + + def putcmd(self, cmd, args=""): + """Send a command to the server.""" + if args: + str = '%s %s%s' % (cmd, args, smtplib.CRLF) + else: + str = '%s%s' % (cmd, smtplib.CRLF) + self.send(str) + + def lhlo(self, name='localhost'): + """ LMTP 'lhlo' command. + Hostname to send for this command defaults to localhost. + """ + self.putcmd("lhlo",name) + (code, msg) = self.getreply() + if code == -1 and len(msg) == 0: + raise smtplib.SMTPServerDisconnected("Server not connected") + self.lhlo_resp = msg + self.ehlo_resp = msg + if code != 250: + return (code, msg) + self.does_esmtp = 1 + # parse the lhlo response + resp = string.split(self.lhlo_resp, '\n') + del resp[0] + for each in resp: + m = re.match(r'(?P<feature>[A-Za-z0-9][A-Za-z0-9\-]*)',each) + if m: + feature = string.lower(m.group("feature")) + params = string.strip(m.string[m.end("feature"):]) + self.lmtp_features[feature] = params + return (code, msg) + + # make sure bits of code that tries to EHLO actually LHLO instead + ehlo = lhlo + + def mail(self, sender, options=[]): + optionlist = '' + if options and self.does_esmtp: + optionlist = ' ' + string.join(options, ' ') + self.putcmd('mail', 'FROM:%s%s' % (smtplib.quoteaddr(sender), optionlist)) + return self.getreply() + def rcpt(self, recip, options=[]): + optionlist = '' + if options and self.does_esmtp: + optionlist = ' ' + string.join(options, ' ') + self.putcmd('rcpt', 'TO:%s%s' % (smtplib.quoteaddr(recip), optionlist)) + return self.getreply() + +def sendlmtp(lmtp_host, sender, recipient, data): + """Send mail on via lmtp for cyrus imapd""" + try: + lmtp = LMTP(lmtp_host) + code, msg = lmtp.lhlo() + if code != 250: + raise Errors.DeliveryError, 'LMTP lhlo error' + lmtp.sendmail(sender, recipient, data) + except smtplib.SMTPRecipientsRefused: + raise Errors.DeliveryError, 'LMTP User does not exist' + + except smtplib.SMTPDataError, errors: + raise Errors.DeliveryError, 'LMTP Data error'
_____________________________________________ tmda-users mailing list ([EMAIL PROTECTED]) http://tmda.net/lists/listinfo/tmda-users