141 lines
4.5 KiB
Python
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())
|