| OLD | NEW |
| (Empty) |
| 1 # -*- test-case-name: twisted.mail.test.test_mail -*- | |
| 2 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
| 3 # See LICENSE for details. | |
| 4 | |
| 5 | |
| 6 """Protocol support for twisted.mail.""" | |
| 7 | |
| 8 # twisted imports | |
| 9 from twisted.mail import pop3 | |
| 10 from twisted.mail import smtp | |
| 11 from twisted.internet import protocol | |
| 12 from twisted.internet import defer | |
| 13 from twisted.copyright import longversion | |
| 14 from twisted.python import log | |
| 15 | |
| 16 from twisted import cred | |
| 17 import twisted.cred.error | |
| 18 import twisted.cred.credentials | |
| 19 | |
| 20 from twisted.mail import relay | |
| 21 | |
| 22 from zope.interface import implements | |
| 23 | |
| 24 | |
| 25 class DomainDeliveryBase: | |
| 26 """A server that uses twisted.mail service's domains.""" | |
| 27 | |
| 28 implements(smtp.IMessageDelivery) | |
| 29 | |
| 30 service = None | |
| 31 protocolName = None | |
| 32 | |
| 33 def __init__(self, service, user, host=smtp.DNSNAME): | |
| 34 self.service = service | |
| 35 self.user = user | |
| 36 self.host = host | |
| 37 | |
| 38 def receivedHeader(self, helo, origin, recipients): | |
| 39 authStr = heloStr = "" | |
| 40 if self.user: | |
| 41 authStr = " auth=%s" % (self.user.encode('xtext'),) | |
| 42 if helo[0]: | |
| 43 heloStr = " helo=%s" % (helo[0],) | |
| 44 from_ = "from %s ([%s]%s%s)" % (helo[0], helo[1], heloStr, authStr) | |
| 45 by = "by %s with %s (%s)" % ( | |
| 46 self.host, self.protocolName, longversion | |
| 47 ) | |
| 48 for_ = "for <%s>; %s" % (' '.join(map(str, recipients)), smtp.rfc822date
()) | |
| 49 return "Received: %s\n\t%s\n\t%s" % (from_, by, for_) | |
| 50 | |
| 51 def validateTo(self, user): | |
| 52 # XXX - Yick. This needs cleaning up. | |
| 53 if self.user and self.service.queue: | |
| 54 d = self.service.domains.get(user.dest.domain, None) | |
| 55 if d is None: | |
| 56 d = relay.DomainQueuer(self.service, True) | |
| 57 else: | |
| 58 d = self.service.domains[user.dest.domain] | |
| 59 return defer.maybeDeferred(d.exists, user) | |
| 60 | |
| 61 def validateFrom(self, helo, origin): | |
| 62 if not helo: | |
| 63 raise smtp.SMTPBadSender(origin, 503, "Who are you? Say HELO first.
") | |
| 64 if origin.local != '' and origin.domain == '': | |
| 65 raise smtp.SMTPBadSender(origin, 501, "Sender address must contain d
omain.") | |
| 66 return origin | |
| 67 | |
| 68 def startMessage(self, users): | |
| 69 ret = [] | |
| 70 for user in users: | |
| 71 ret.append(self.service.domains[user.dest.domain].startMessage(user)
) | |
| 72 return ret | |
| 73 | |
| 74 | |
| 75 class SMTPDomainDelivery(DomainDeliveryBase): | |
| 76 protocolName = 'smtp' | |
| 77 | |
| 78 class ESMTPDomainDelivery(DomainDeliveryBase): | |
| 79 protocolName = 'esmtp' | |
| 80 | |
| 81 class DomainSMTP(SMTPDomainDelivery, smtp.SMTP): | |
| 82 service = user = None | |
| 83 | |
| 84 def __init__(self, *args, **kw): | |
| 85 import warnings | |
| 86 warnings.warn( | |
| 87 "DomainSMTP is deprecated. Use IMessageDelivery objects instead.", | |
| 88 DeprecationWarning, stacklevel=2, | |
| 89 ) | |
| 90 smtp.SMTP.__init__(self, *args, **kw) | |
| 91 if self.delivery is None: | |
| 92 self.delivery = self | |
| 93 | |
| 94 class DomainESMTP(ESMTPDomainDelivery, smtp.ESMTP): | |
| 95 service = user = None | |
| 96 | |
| 97 def __init__(self, *args, **kw): | |
| 98 import warnings | |
| 99 warnings.warn( | |
| 100 "DomainESMTP is deprecated. Use IMessageDelivery objects instead.", | |
| 101 DeprecationWarning, stacklevel=2, | |
| 102 ) | |
| 103 smtp.ESMTP.__init__(self, *args, **kw) | |
| 104 if self.delivery is None: | |
| 105 self.delivery = self | |
| 106 | |
| 107 class SMTPFactory(smtp.SMTPFactory): | |
| 108 """A protocol factory for SMTP.""" | |
| 109 | |
| 110 protocol = smtp.SMTP | |
| 111 portal = None | |
| 112 | |
| 113 def __init__(self, service, portal = None): | |
| 114 smtp.SMTPFactory.__init__(self) | |
| 115 self.service = service | |
| 116 self.portal = portal | |
| 117 | |
| 118 def buildProtocol(self, addr): | |
| 119 log.msg('Connection from %s' % (addr,)) | |
| 120 p = smtp.SMTPFactory.buildProtocol(self, addr) | |
| 121 p.service = self.service | |
| 122 p.portal = self.portal | |
| 123 return p | |
| 124 | |
| 125 class ESMTPFactory(SMTPFactory): | |
| 126 protocol = smtp.ESMTP | |
| 127 context = None | |
| 128 | |
| 129 def __init__(self, *args): | |
| 130 SMTPFactory.__init__(self, *args) | |
| 131 self.challengers = { | |
| 132 'CRAM-MD5': cred.credentials.CramMD5Credentials | |
| 133 } | |
| 134 | |
| 135 def buildProtocol(self, addr): | |
| 136 p = SMTPFactory.buildProtocol(self, addr) | |
| 137 p.challengers = self.challengers | |
| 138 p.ctx = self.context | |
| 139 return p | |
| 140 | |
| 141 class VirtualPOP3(pop3.POP3): | |
| 142 """Virtual hosting POP3.""" | |
| 143 | |
| 144 service = None | |
| 145 | |
| 146 domainSpecifier = '@' # Gaagh! I hate POP3. No standardized way | |
| 147 # to indicate user@host. '@' doesn't work | |
| 148 # with NS, e.g. | |
| 149 | |
| 150 def authenticateUserAPOP(self, user, digest): | |
| 151 # Override the default lookup scheme to allow virtual domains | |
| 152 user, domain = self.lookupDomain(user) | |
| 153 try: | |
| 154 portal = self.service.lookupPortal(domain) | |
| 155 except KeyError: | |
| 156 return defer.fail(cred.error.UnauthorizedLogin()) | |
| 157 else: | |
| 158 return portal.login( | |
| 159 pop3.APOPCredentials(self.magic, user, digest), | |
| 160 None, | |
| 161 pop3.IMailbox | |
| 162 ) | |
| 163 | |
| 164 def authenticateUserPASS(self, user, password): | |
| 165 user, domain = self.lookupDomain(user) | |
| 166 try: | |
| 167 portal = self.service.lookupPortal(domain) | |
| 168 except KeyError: | |
| 169 return defer.fail(cred.error.UnauthorizedLogin()) | |
| 170 else: | |
| 171 return portal.login( | |
| 172 cred.credentials.UsernamePassword(user, password), | |
| 173 None, | |
| 174 pop3.IMailbox | |
| 175 ) | |
| 176 | |
| 177 def lookupDomain(self, user): | |
| 178 try: | |
| 179 user, domain = user.split(self.domainSpecifier, 1) | |
| 180 except ValueError: | |
| 181 domain = '' | |
| 182 if domain not in self.service.domains: | |
| 183 raise pop3.POP3Error("no such domain %s" % domain) | |
| 184 return user, domain | |
| 185 | |
| 186 | |
| 187 class POP3Factory(protocol.ServerFactory): | |
| 188 """POP3 protocol factory.""" | |
| 189 | |
| 190 protocol = VirtualPOP3 | |
| 191 service = None | |
| 192 | |
| 193 def __init__(self, service): | |
| 194 self.service = service | |
| 195 | |
| 196 def buildProtocol(self, addr): | |
| 197 p = protocol.ServerFactory.buildProtocol(self, addr) | |
| 198 p.service = self.service | |
| 199 return p | |
| 200 | |
| 201 # | |
| 202 # It is useful to know, perhaps, that the required file for this to work can | |
| 203 # be created thusly: | |
| 204 # | |
| 205 # openssl req -x509 -newkey rsa:2048 -keyout file.key -out file.crt \ | |
| 206 # -days 365 -nodes | |
| 207 # | |
| 208 # And then cat file.key and file.crt together. The number of days and bits | |
| 209 # can be changed, of course. | |
| 210 # | |
| 211 class SSLContextFactory: | |
| 212 """An SSL Context Factory | |
| 213 | |
| 214 This loads a certificate and private key from a specified file. | |
| 215 """ | |
| 216 def __init__(self, filename): | |
| 217 self.filename = filename | |
| 218 | |
| 219 def getContext(self): | |
| 220 """Create an SSL context.""" | |
| 221 from OpenSSL import SSL | |
| 222 ctx = SSL.Context(SSL.SSLv23_METHOD) | |
| 223 ctx.use_certificate_file(self.filename) | |
| 224 ctx.use_privatekey_file(self.filename) | |
| 225 return ctx | |
| OLD | NEW |