serpent/mech_smtp.py

141 lines
4.5 KiB
Python

# -*- coding: utf-8 -*-
from serpent.config import conf
from serpent import rules
from serpent.queue import squeue
from email.Header import Header
from zope.interface import implements
from twisted.internet import defer, ssl
from twisted.mail import smtp
from twisted.mail.imap4 import LOGINCredentials, PLAINCredentials
from twisted.cred.portal import IRealm, Portal
class SmtpMessageDelivery:
implements(smtp.IMessageDelivery)
def __init__(self, avatarId = None):
self.avatarId = avatarId
def receivedHeader(self, helo, origin, recipients):
header = conf.smtp_header.format(
sender_ip = helo[1],
sender_host = helo[0],
srv_host = conf.smtp_hostname,
srv_info = conf.srv_version,
sender = conf.smtp_email_delim.join([origin.local, origin.domain]),
id = self.messageid,
rcpt = conf.smtp_email_delim.join([recipients[0].dest.local, recipients[0].dest.domain]),
date = smtp.rfc822date()
)
return 'Received: %s' % Header(header)
def validateFrom(self, helo, origin): # Надо воткнуть всякие проверки хоста по HELO
try:
rules.validateFrom(self, [origin.local, origin.domain])
except:
raise
else:
return origin
def validateTo(self, user):
self.messageid = smtp.messageid().split('@')[0].strip('<')
try:
rules.validateTo(self, user)
except:
raise
else:
msg = {
'from': [user.orig.local, user.orig.domain],
'rcpt': [user.dest.local, user.dest.domain],
'transaction_id': self.messageid,
'id': smtp.messageid().split('@')[0].strip('<')
}
return lambda: SmtpMessage(msg)
class SmtpMessage:
implements(smtp.IMessage)
def __init__(self, msg):
self.lines = []
self.size = 0
self.msg = msg
def lineReceived(self, line):
self.lines.append(line)
def eomReceived(self):
self.lines.append('')
self.msg['message'] = "\n".join(self.lines)
self.lines = None
return defer.succeed(squeue.add(self.msg))
def connectionLost(self):
# There was an error, throw away the stored lines
self.lines = None
class SerpentESMTP(smtp.ESMTP):
def ext_AUTH(self, rest):
if self.canStartTLS and not self.startedTLS:
self.sendCode(538, 'Unencrypted auth denied')
return
if self.authenticated:
self.sendCode(503, 'Already authenticated')
return
parts = rest.split(None, 1)
chal = self.challengers.get(parts[0].upper(), lambda: None)()
if not chal:
self.sendCode(504, 'Unrecognized authentication type')
return
self.mode = smtp.AUTH
self.challenger = chal
if len(parts) > 1:
chal.getChallenge() # Discard it, apparently the client does not
# care about it.
rest = parts[1]
else:
rest = None
self.state_AUTH(rest)
class SerpentSMTPFactory(smtp.SMTPFactory):
protocol = SerpentESMTP
def __init__(self, *a, **kw):
smtp.SMTPFactory.__init__(self, *a, **kw)
self.delivery = SmtpMessageDelivery()
def buildProtocol(self, addr):
contextFactory = None
if conf.tls:
tls_data = file(conf.tls_pem, 'rb').read()
cert = ssl.PrivateCertificate.loadPEM(tls_data)
contextFactory = cert.options()
p = smtp.SMTPFactory.buildProtocol(self, addr)
p.ctx = contextFactory
if conf.tls:
p.canStartTLS = True
p.host = conf.smtp_hostname
p.delivery = self.delivery
p.challengers = {"LOGIN": LOGINCredentials, "PLAIN": PLAINCredentials}
return p
class SmtpRealm:
implements(IRealm)
def requestAvatar(self, avatarId, mind, *interfaces):
if smtp.IMessageDelivery in interfaces:
return smtp.IMessageDelivery, SmtpMessageDelivery(avatarId), lambda: None
raise NotImplementedError()
smtp_portal = Portal(SmtpRealm())