import sys
import re, getopt
import smtplib, socket
import spamd
EX_OK = 0
EX_USAGE = 64
EX_DATAERR = 65
EX_NOUSER = 67
EX_TEMPFAIL = 75
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:
port = LMTP_PORT
if ':' in host:
hose, port = host.split(':', 1)
port = int(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 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
resp = self.lhlo_resp.split('\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 = m.group("feature").lower()
params = m.string[m.end("feature"):].strip()
self.lmtp_features[feature] = params
return (code, msg)
ehlo = lhlo
def process_message(spamd_host, lmtp_host, sender, recipient):
try:
lmtp = LMTP(lmtp_host)
except:
sys.exit(EX_TEMPFAIL)
code, msg = lmtp.lhlo()
if code != 250: sys.exit(EX_TEMPFAIL)
code, msg = lmtp.mail(sender)
if code != 250: sys.exit(1)
code, msg = lmtp.rcpt(recipient)
if code == 550: sys.exit(EX_NOUSER)
if code != 250: sys.exit(EX_TEMPFAIL)
CHUNKSIZE = 256 * 1024
data = sys.stdin.read(CHUNKSIZE)
if len(data) < CHUNKSIZE:
connection = spamd.SpamdConnection(spamd_host)
connection.addheader('User', recipient)
try:
connection.check(spamd.PROCESS, data)
data = connection.response_message
except spamd.error, e:
sys.stderr.write('spamcheck: %s' % str(e))
lmtp.putcmd("data")
code, msg = lmtp.getreply()
if code != 354: sys.exit(EX_TEMPFAIL)
lmtp.send(smtplib.quotedata(data))
data = sys.stdin.read(CHUNKSIZE)
while data != '':
lmtp.send(smtplib.quotedata(data))
data = sys.stdin.read(CHUNKSIZE)
lmtp.send('\r\n.\r\n')
code, msg = lmtp.getreply()
if code != 250: sys.exit(EX_TEMPFAIL)
def main(argv):
spamd_host = ''
lmtp_host = None
sender = None
recipient = None
try:
opts, args = getopt.getopt(argv[1:], 's:r:l:')
except getopt.error, err:
sys.stderr.write('%s: %s\n' % (argv[0], err))
sys.exit(EX_USAGE)
for opt, arg in opts:
if opt == '-s': sender = arg
elif opt == '-r': recipient = arg.lower()
elif opt == '-l': lmtp_host = arg
else:
sys.stderr.write('unexpected argument\n')
sys.exit(EX_USAGE)
if args:
sys.stderr.write('unexpected argument\n')
sys.exit(EX_USAGE)
if not lmtp_host or not sender or not recipient:
sys.stderr.write('required argument missing\n')
sys.exit(EX_USAGE)
try:
process_message(spamd_host, lmtp_host, sender, recipient)
except SystemExit:
raise except:
sys.stderr.write('%s: %s\n' % sys.exc_info()[:2])
sys.exit(1)
if __name__ == '__main__':
main(sys.argv)