Ausgabe der neuen DB Einträge

This commit is contained in:
hubobel 2022-01-02 21:50:48 +01:00
parent bad48e1627
commit cfbbb9ee3d
2399 changed files with 843193 additions and 43 deletions

View file

@ -0,0 +1,6 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Twisted Mail: Servers and clients for POP3, ESMTP, and IMAP.
"""

View file

@ -0,0 +1,122 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Credential managers for L{twisted.mail}.
"""
from __future__ import absolute_import, division
import hmac
import hashlib
from zope.interface import implementer
from twisted.cred import credentials
from twisted.python.compat import nativeString
from twisted.mail._except import IllegalClientResponse
from twisted.mail.interfaces import IClientAuthentication, IChallengeResponse
@implementer(IClientAuthentication)
class CramMD5ClientAuthenticator:
def __init__(self, user):
self.user = user
def getName(self):
return b"CRAM-MD5"
def challengeResponse(self, secret, chal):
response = hmac.HMAC(secret, chal, digestmod=hashlib.md5).hexdigest()
return self.user + b' ' + response.encode('ascii')
@implementer(IClientAuthentication)
class LOGINAuthenticator:
def __init__(self, user):
self.user = user
self.challengeResponse = self.challengeUsername
def getName(self):
return b"LOGIN"
def challengeUsername(self, secret, chal):
# Respond to something like "Username:"
self.challengeResponse = self.challengeSecret
return self.user
def challengeSecret(self, secret, chal):
# Respond to something like "Password:"
return secret
@implementer(IClientAuthentication)
class PLAINAuthenticator:
def __init__(self, user):
self.user = user
def getName(self):
return b"PLAIN"
def challengeResponse(self, secret, chal):
return b'\0' + self.user + b'\0' + secret
@implementer(IChallengeResponse)
class LOGINCredentials(credentials.UsernamePassword):
def __init__(self):
self.challenges = [b'Password\0', b'User Name\0']
self.responses = [b'password', b'username']
credentials.UsernamePassword.__init__(self, None, None)
def getChallenge(self):
return self.challenges.pop()
def setResponse(self, response):
setattr(self, nativeString(self.responses.pop()), response)
def moreChallenges(self):
return bool(self.challenges)
@implementer(IChallengeResponse)
class PLAINCredentials(credentials.UsernamePassword):
def __init__(self):
credentials.UsernamePassword.__init__(self, None, None)
def getChallenge(self):
return b''
def setResponse(self, response):
parts = response.split(b'\0')
if len(parts) != 3:
raise IllegalClientResponse(
"Malformed Response - wrong number of parts")
useless, self.username, self.password = parts
def moreChallenges(self):
return False
__all__ = [
"CramMD5ClientAuthenticator",
"LOGINCredentials", "LOGINAuthenticator",
"PLAINCredentials", "PLAINAuthenticator",
]

View file

@ -0,0 +1,392 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Exceptions in L{twisted.mail}.
"""
from __future__ import absolute_import, division
from twisted.python.compat import _PY3, unicode
class IMAP4Exception(Exception):
pass
class IllegalClientResponse(IMAP4Exception):
pass
class IllegalOperation(IMAP4Exception):
pass
class IllegalMailboxEncoding(IMAP4Exception):
pass
class MailboxException(IMAP4Exception):
pass
class MailboxCollision(MailboxException):
def __str__(self):
return 'Mailbox named %s already exists' % self.args
class NoSuchMailbox(MailboxException):
def __str__(self):
return 'No mailbox named %s exists' % self.args
class ReadOnlyMailbox(MailboxException):
def __str__(self):
return 'Mailbox open in read-only state'
class UnhandledResponse(IMAP4Exception):
pass
class NegativeResponse(IMAP4Exception):
pass
class NoSupportedAuthentication(IMAP4Exception):
def __init__(self, serverSupports, clientSupports):
IMAP4Exception.__init__(
self, 'No supported authentication schemes available')
self.serverSupports = serverSupports
self.clientSupports = clientSupports
def __str__(self):
return (IMAP4Exception.__str__(self)
+ ': Server supports %r, client supports %r'
% (self.serverSupports, self.clientSupports))
class IllegalServerResponse(IMAP4Exception):
pass
class IllegalIdentifierError(IMAP4Exception):
pass
class IllegalQueryError(IMAP4Exception):
pass
class MismatchedNesting(IMAP4Exception):
pass
class MismatchedQuoting(IMAP4Exception):
pass
class SMTPError(Exception):
pass
class SMTPClientError(SMTPError):
"""
Base class for SMTP client errors.
"""
def __init__(self, code, resp, log=None, addresses=None, isFatal=False,
retry=False):
"""
@param code: The SMTP response code associated with this error.
@param resp: The string response associated with this error.
@param log: A string log of the exchange leading up to and including
the error.
@type log: L{bytes}
@param isFatal: A boolean indicating whether this connection can
proceed or not. If True, the connection will be dropped.
@param retry: A boolean indicating whether the delivery should be
retried. If True and the factory indicates further retries are
desirable, they will be attempted, otherwise the delivery will be
failed.
"""
self.code = code
self.resp = resp
self.log = log
self.addresses = addresses
self.isFatal = isFatal
self.retry = retry
def __str__(self):
if _PY3:
return self.__bytes__().decode("utf-8")
else:
return self.__bytes__()
def __bytes__(self):
if self.code > 0:
res = [u"{:03d} {}".format(self.code, self.resp)]
else:
res = [self.resp]
if self.log:
res.append(self.log)
res.append(b'')
for (i, r) in enumerate(res):
if isinstance(r, unicode):
res[i] = r.encode('utf-8')
return b'\n'.join(res)
class ESMTPClientError(SMTPClientError):
"""
Base class for ESMTP client errors.
"""
class EHLORequiredError(ESMTPClientError):
"""
The server does not support EHLO.
This is considered a non-fatal error (the connection will not be dropped).
"""
class AUTHRequiredError(ESMTPClientError):
"""
Authentication was required but the server does not support it.
This is considered a non-fatal error (the connection will not be dropped).
"""
class TLSRequiredError(ESMTPClientError):
"""
Transport security was required but the server does not support it.
This is considered a non-fatal error (the connection will not be dropped).
"""
class AUTHDeclinedError(ESMTPClientError):
"""
The server rejected our credentials.
Either the username, password, or challenge response
given to the server was rejected.
This is considered a non-fatal error (the connection will not be
dropped).
"""
class AuthenticationError(ESMTPClientError):
"""
An error occurred while authenticating.
Either the server rejected our request for authentication or the
challenge received was malformed.
This is considered a non-fatal error (the connection will not be
dropped).
"""
class SMTPTLSError(ESMTPClientError):
"""
An error occurred while negiotiating for transport security.
This is considered a non-fatal error (the connection will not be dropped).
"""
class SMTPConnectError(SMTPClientError):
"""
Failed to connect to the mail exchange host.
This is considered a fatal error. A retry will be made.
"""
def __init__(self, code, resp, log=None, addresses=None, isFatal=True,
retry=True):
SMTPClientError.__init__(self, code, resp, log, addresses, isFatal,
retry)
class SMTPTimeoutError(SMTPClientError):
"""
Failed to receive a response from the server in the expected time period.
This is considered a fatal error. A retry will be made.
"""
def __init__(self, code, resp, log=None, addresses=None, isFatal=True,
retry=True):
SMTPClientError.__init__(self, code, resp, log, addresses, isFatal,
retry)
class SMTPProtocolError(SMTPClientError):
"""
The server sent a mangled response.
This is considered a fatal error. A retry will not be made.
"""
def __init__(self, code, resp, log=None, addresses=None, isFatal=True,
retry=False):
SMTPClientError.__init__(self, code, resp, log, addresses, isFatal,
retry)
class SMTPDeliveryError(SMTPClientError):
"""
Indicates that a delivery attempt has had an error.
"""
class SMTPServerError(SMTPError):
def __init__(self, code, resp):
self.code = code
self.resp = resp
def __str__(self):
return "%.3d %s" % (self.code, self.resp)
class SMTPAddressError(SMTPServerError):
def __init__(self, addr, code, resp):
from twisted.mail.smtp import Address
SMTPServerError.__init__(self, code, resp)
self.addr = Address(addr)
def __str__(self):
return "%.3d <%s>... %s" % (self.code, self.addr, self.resp)
class SMTPBadRcpt(SMTPAddressError):
def __init__(self, addr, code=550,
resp='Cannot receive for specified address'):
SMTPAddressError.__init__(self, addr, code, resp)
class SMTPBadSender(SMTPAddressError):
def __init__(self, addr, code=550, resp='Sender not acceptable'):
SMTPAddressError.__init__(self, addr, code, resp)
class AddressError(SMTPError):
"""
Parse error in address
"""
class POP3Error(Exception):
"""
The base class for POP3 errors.
"""
pass
class _POP3MessageDeleted(Exception):
"""
An internal control-flow error which indicates that a deleted message was
requested.
"""
class POP3ClientError(Exception):
"""
The base class for all exceptions raised by POP3Client.
"""
class InsecureAuthenticationDisallowed(POP3ClientError):
"""
An error indicating secure authentication was required but no mechanism
could be found.
"""
class TLSError(POP3ClientError):
"""
An error indicating secure authentication was required but either the
transport does not support TLS or no TLS context factory was supplied.
"""
class TLSNotSupportedError(POP3ClientError):
"""
An error indicating secure authentication was required but the server does
not support TLS.
"""
class ServerErrorResponse(POP3ClientError):
"""
An error indicating that the server returned an error response to a
request.
@ivar consumer: See L{__init__}
"""
def __init__(self, reason, consumer=None):
"""
@type reason: L{bytes}
@param reason: The server response minus the status indicator.
@type consumer: callable that takes L{object}
@param consumer: The function meant to handle the values for a
multi-line response.
"""
POP3ClientError.__init__(self, reason)
self.consumer = consumer
class LineTooLong(POP3ClientError):
"""
An error indicating that the server sent a line which exceeded the
maximum line length (L{LineOnlyReceiver.MAX_LENGTH}).
"""

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,404 @@
# -*- test-case-name: twisted.mail.test.test_mail -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Mail protocol support.
"""
from __future__ import absolute_import, division
from twisted.mail import pop3
from twisted.mail import smtp
from twisted.internet import protocol
from twisted.internet import defer
from twisted.copyright import longversion
from twisted.python import log
from twisted.cred.credentials import CramMD5Credentials, UsernamePassword
from twisted.cred.error import UnauthorizedLogin
from twisted.mail import relay
from zope.interface import implementer
@implementer(smtp.IMessageDelivery)
class DomainDeliveryBase:
"""
A base class for message delivery using the domains of a mail service.
@ivar service: See L{__init__}
@ivar user: See L{__init__}
@ivar host: See L{__init__}
@type protocolName: L{bytes}
@ivar protocolName: The protocol being used to deliver the mail.
Sub-classes should set this appropriately.
"""
service = None
protocolName = None
def __init__(self, service, user, host=smtp.DNSNAME):
"""
@type service: L{MailService}
@param service: A mail service.
@type user: L{bytes} or L{None}
@param user: The authenticated SMTP user.
@type host: L{bytes}
@param host: The hostname.
"""
self.service = service
self.user = user
self.host = host
def receivedHeader(self, helo, origin, recipients):
"""
Generate a received header string for a message.
@type helo: 2-L{tuple} of (L{bytes}, L{bytes})
@param helo: The client's identity as sent in the HELO command and its
IP address.
@type origin: L{Address}
@param origin: The origination address of the message.
@type recipients: L{list} of L{User}
@param recipients: The destination addresses for the message.
@rtype: L{bytes}
@return: A received header string.
"""
authStr = heloStr = b""
if self.user:
authStr = b" auth=" + self.user.encode('xtext')
if helo[0]:
heloStr = b" helo=" + helo[0]
fromUser = (b"from " + helo[0] + b" ([" + helo[1] + b"]" +
heloStr + authStr)
by = (b"by " + self.host + b" with " + self.protocolName +
b" (" + longversion.encode("ascii") + b")")
forUser = (b"for <" + b' '.join(map(bytes, recipients)) + b"> " +
smtp.rfc822date())
return (b"Received: " + fromUser + b"\n\t" + by +
b"\n\t" + forUser)
def validateTo(self, user):
"""
Validate the address for which a message is destined.
@type user: L{User}
@param user: The destination address.
@rtype: L{Deferred <defer.Deferred>} which successfully fires with
no-argument callable which returns L{IMessage <smtp.IMessage>}
provider.
@return: A deferred which successfully fires with a no-argument
callable which returns a message receiver for the destination.
@raise SMTPBadRcpt: When messages cannot be accepted for the
destination address.
"""
# XXX - Yick. This needs cleaning up.
if self.user and self.service.queue:
d = self.service.domains.get(user.dest.domain, None)
if d is None:
d = relay.DomainQueuer(self.service, True)
else:
d = self.service.domains[user.dest.domain]
return defer.maybeDeferred(d.exists, user)
def validateFrom(self, helo, origin):
"""
Validate the address from which a message originates.
@type helo: 2-L{tuple} of (L{bytes}, L{bytes})
@param helo: The client's identity as sent in the HELO command and its
IP address.
@type origin: L{Address}
@param origin: The origination address of the message.
@rtype: L{Address}
@return: The origination address.
@raise SMTPBadSender: When messages cannot be accepted from the
origination address.
"""
if not helo:
raise smtp.SMTPBadSender(origin, 503,
"Who are you? Say HELO first.")
if origin.local != b'' and origin.domain == b'':
raise smtp.SMTPBadSender(origin, 501,
"Sender address must contain domain.")
return origin
class SMTPDomainDelivery(DomainDeliveryBase):
"""
A domain delivery base class for use in an SMTP server.
"""
protocolName = b'smtp'
class ESMTPDomainDelivery(DomainDeliveryBase):
"""
A domain delivery base class for use in an ESMTP server.
"""
protocolName = b'esmtp'
class SMTPFactory(smtp.SMTPFactory):
"""
An SMTP server protocol factory.
@ivar service: See L{__init__}
@ivar portal: See L{__init__}
@type protocol: no-argument callable which returns a L{Protocol
<protocol.Protocol>} subclass
@ivar protocol: A callable which creates a protocol. The default value is
L{SMTP}.
"""
protocol = smtp.SMTP
portal = None
def __init__(self, service, portal = None):
"""
@type service: L{MailService}
@param service: An email service.
@type portal: L{Portal <twisted.cred.portal.Portal>} or
L{None}
@param portal: A portal to use for authentication.
"""
smtp.SMTPFactory.__init__(self)
self.service = service
self.portal = portal
def buildProtocol(self, addr):
"""
Create an instance of an SMTP server protocol.
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
@param addr: The address of the SMTP client.
@rtype: L{SMTP}
@return: An SMTP protocol.
"""
log.msg('Connection from %s' % (addr,))
p = smtp.SMTPFactory.buildProtocol(self, addr)
p.service = self.service
p.portal = self.portal
return p
class ESMTPFactory(SMTPFactory):
"""
An ESMTP server protocol factory.
@type protocol: no-argument callable which returns a L{Protocol
<protocol.Protocol>} subclass
@ivar protocol: A callable which creates a protocol. The default value is
L{ESMTP}.
@type context: L{IOpenSSLContextFactory
<twisted.internet.interfaces.IOpenSSLContextFactory>} or L{None}
@ivar context: A factory to generate contexts to be used in negotiating
encrypted communication.
@type challengers: L{dict} mapping L{bytes} to no-argument callable which
returns L{ICredentials <twisted.cred.credentials.ICredentials>}
subclass provider.
@ivar challengers: A mapping of acceptable authorization mechanism to
callable which creates credentials to use for authentication.
"""
protocol = smtp.ESMTP
context = None
def __init__(self, *args):
"""
@param args: Arguments for L{SMTPFactory.__init__}
@see: L{SMTPFactory.__init__}
"""
SMTPFactory.__init__(self, *args)
self.challengers = {
b'CRAM-MD5': CramMD5Credentials
}
def buildProtocol(self, addr):
"""
Create an instance of an ESMTP server protocol.
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
@param addr: The address of the ESMTP client.
@rtype: L{ESMTP}
@return: An ESMTP protocol.
"""
p = SMTPFactory.buildProtocol(self, addr)
p.challengers = self.challengers
p.ctx = self.context
return p
class VirtualPOP3(pop3.POP3):
"""
A virtual hosting POP3 server.
@type service: L{MailService}
@ivar service: The email service that created this server. This must be
set by the service.
@type domainSpecifier: L{bytes}
@ivar domainSpecifier: The character to use to split an email address into
local-part and domain. The default is '@'.
"""
service = None
domainSpecifier = b'@' # Gaagh! I hate POP3. No standardized way
# to indicate user@host. '@' doesn't work
# with NS, e.g.
def authenticateUserAPOP(self, user, digest):
"""
Perform APOP authentication.
Override the default lookup scheme to allow virtual domains.
@type user: L{bytes}
@param user: The name of the user attempting to log in.
@type digest: L{bytes}
@param digest: The challenge response.
@rtype: L{Deferred} which successfully results in 3-L{tuple} of
(L{IMailbox <pop3.IMailbox>}, L{IMailbox <pop3.IMailbox>}
provider, no-argument callable)
@return: A deferred which fires when authentication is complete.
If successful, it returns an L{IMailbox <pop3.IMailbox>} interface,
a mailbox and a logout function. If authentication fails, the
deferred fails with an L{UnauthorizedLogin
<twisted.cred.error.UnauthorizedLogin>} error.
"""
user, domain = self.lookupDomain(user)
try:
portal = self.service.lookupPortal(domain)
except KeyError:
return defer.fail(UnauthorizedLogin())
else:
return portal.login(
pop3.APOPCredentials(self.magic, user, digest),
None,
pop3.IMailbox
)
def authenticateUserPASS(self, user, password):
"""
Perform authentication for a username/password login.
Override the default lookup scheme to allow virtual domains.
@type user: L{bytes}
@param user: The name of the user attempting to log in.
@type password: L{bytes}
@param password: The password to authenticate with.
@rtype: L{Deferred} which successfully results in 3-L{tuple} of
(L{IMailbox <pop3.IMailbox>}, L{IMailbox <pop3.IMailbox>}
provider, no-argument callable)
@return: A deferred which fires when authentication is complete.
If successful, it returns an L{IMailbox <pop3.IMailbox>} interface,
a mailbox and a logout function. If authentication fails, the
deferred fails with an L{UnauthorizedLogin
<twisted.cred.error.UnauthorizedLogin>} error.
"""
user, domain = self.lookupDomain(user)
try:
portal = self.service.lookupPortal(domain)
except KeyError:
return defer.fail(UnauthorizedLogin())
else:
return portal.login(
UsernamePassword(user, password),
None,
pop3.IMailbox
)
def lookupDomain(self, user):
"""
Check whether a domain is among the virtual domains supported by the
mail service.
@type user: L{bytes}
@param user: An email address.
@rtype: 2-L{tuple} of (L{bytes}, L{bytes})
@return: The local part and the domain part of the email address if the
domain is supported.
@raise POP3Error: When the domain is not supported by the mail service.
"""
try:
user, domain = user.split(self.domainSpecifier, 1)
except ValueError:
domain = b''
if domain not in self.service.domains:
raise pop3.POP3Error(
"no such domain {}".format(domain.decode("utf-8")))
return user, domain
class POP3Factory(protocol.ServerFactory):
"""
A POP3 server protocol factory.
@ivar service: See L{__init__}
@type protocol: no-argument callable which returns a L{Protocol
<protocol.Protocol>} subclass
@ivar protocol: A callable which creates a protocol. The default value is
L{VirtualPOP3}.
"""
protocol = VirtualPOP3
service = None
def __init__(self, service):
"""
@type service: L{MailService}
@param service: An email service.
"""
self.service = service
def buildProtocol(self, addr):
"""
Create an instance of a POP3 server protocol.
@type addr: L{IAddress <twisted.internet.interfaces.IAddress>} provider
@param addr: The address of the POP3 client.
@rtype: L{POP3}
@return: A POP3 protocol.
"""
p = protocol.ServerFactory.buildProtocol(self, addr)
p.service = self.service
return p

View file

@ -0,0 +1,180 @@
# -*- test-case-name: twisted.mail.test.test_mail -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Support for relaying mail.
"""
from twisted.mail import smtp
from twisted.python import log
from twisted.internet.address import UNIXAddress
import os
try:
import cPickle as pickle
except ImportError:
import pickle
class DomainQueuer:
"""
An SMTP domain which add messages to a queue intended for relaying.
"""
def __init__(self, service, authenticated=False):
self.service = service
self.authed = authenticated
def exists(self, user):
"""
Check whether mail can be relayed to a user.
@type user: L{User}
@param user: A user.
@rtype: no-argument callable which returns L{IMessage <smtp.IMessage>}
provider
@return: A function which takes no arguments and returns a message
receiver for the user.
@raise SMTPBadRcpt: When mail cannot be relayed to the user.
"""
if self.willRelay(user.dest, user.protocol):
# The most cursor form of verification of the addresses
orig = filter(None, str(user.orig).split('@', 1))
dest = filter(None, str(user.dest).split('@', 1))
if len(orig) == 2 and len(dest) == 2:
return lambda: self.startMessage(user)
raise smtp.SMTPBadRcpt(user)
def willRelay(self, address, protocol):
"""
Check whether we agree to relay.
The default is to relay for all connections over UNIX
sockets and all connections from localhost.
"""
peer = protocol.transport.getPeer()
return (self.authed or isinstance(peer, UNIXAddress) or
peer.host == '127.0.0.1')
def startMessage(self, user):
"""
Create an envelope and a message receiver for the relay queue.
@type user: L{User}
@param user: A user.
@rtype: L{IMessage <smtp.IMessage>}
@return: A message receiver.
"""
queue = self.service.queue
envelopeFile, smtpMessage = queue.createNewMessage()
with envelopeFile:
log.msg('Queueing mail %r -> %r' % (str(user.orig),
str(user.dest)))
pickle.dump([str(user.orig), str(user.dest)], envelopeFile)
return smtpMessage
class RelayerMixin:
# XXX - This is -totally- bogus
# It opens about a -hundred- -billion- files
# and -leaves- them open!
def loadMessages(self, messagePaths):
self.messages = []
self.names = []
for message in messagePaths:
with open(message + '-H') as fp:
messageContents = pickle.load(fp)
fp = open(message + '-D')
messageContents.append(fp)
self.messages.append(messageContents)
self.names.append(message)
def getMailFrom(self):
if not self.messages:
return None
return self.messages[0][0]
def getMailTo(self):
if not self.messages:
return None
return [self.messages[0][1]]
def getMailData(self):
if not self.messages:
return None
return self.messages[0][2]
def sentMail(self, code, resp, numOk, addresses, log):
"""Since we only use one recipient per envelope, this
will be called with 0 or 1 addresses. We probably want
to do something with the error message if we failed.
"""
if code in smtp.SUCCESS:
# At least one, i.e. all, recipients successfully delivered
os.remove(self.names[0] + '-D')
os.remove(self.names[0] + '-H')
del self.messages[0]
del self.names[0]
class SMTPRelayer(RelayerMixin, smtp.SMTPClient):
"""
A base class for SMTP relayers.
"""
def __init__(self, messagePaths, *args, **kw):
"""
@type messagePaths: L{list} of L{bytes}
@param messagePaths: The base filename for each message to be relayed.
@type args: 1-L{tuple} of (0) L{bytes} or 2-L{tuple} of
(0) L{bytes}, (1) L{int}
@param args: Positional arguments for L{SMTPClient.__init__}
@type kw: L{dict}
@param kw: Keyword arguments for L{SMTPClient.__init__}
"""
smtp.SMTPClient.__init__(self, *args, **kw)
self.loadMessages(messagePaths)
class ESMTPRelayer(RelayerMixin, smtp.ESMTPClient):
"""
A base class for ESMTP relayers.
"""
def __init__(self, messagePaths, *args, **kw):
"""
@type messagePaths: L{list} of L{bytes}
@param messagePaths: The base filename for each message to be relayed.
@type args: 3-L{tuple} of (0) L{bytes}, (1) L{None} or
L{ClientContextFactory
<twisted.internet.ssl.ClientContextFactory>},
(2) L{bytes} or 4-L{tuple} of (0) L{bytes}, (1) L{None}
or L{ClientContextFactory
<twisted.internet.ssl.ClientContextFactory>}, (2) L{bytes},
(3) L{int}
@param args: Positional arguments for L{ESMTPClient.__init__}
@type kw: L{dict}
@param kw: Keyword arguments for L{ESMTPClient.__init__}
"""
smtp.ESMTPClient.__init__(self, *args, **kw)
self.loadMessages(messagePaths)

View file

@ -0,0 +1,402 @@
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Implementation module for the I{mailmail} command.
"""
from __future__ import print_function
import email.utils
import os
import sys
import getpass
try:
# Python 3
from configparser import ConfigParser
except ImportError:
# Python 2
from ConfigParser import ConfigParser
from twisted.copyright import version
from twisted.internet import reactor
from twisted.logger import Logger, textFileLogObserver
from twisted.mail import smtp
from twisted.python.compat import NativeStringIO
GLOBAL_CFG = "/etc/mailmail"
LOCAL_CFG = os.path.expanduser("~/.twisted/mailmail")
SMARTHOST = '127.0.0.1'
ERROR_FMT = """\
Subject: Failed Message Delivery
Message delivery failed. The following occurred:
%s
--
The Twisted sendmail application.
"""
_logObserver = textFileLogObserver(sys.stderr)
_log = Logger(observer=_logObserver)
class Options:
"""
Store the values of the parsed command-line options to the I{mailmail}
script.
@type to: L{list} of L{str}
@ivar to: The addresses to which to deliver this message.
@type sender: L{str}
@ivar sender: The address from which this message is being sent.
@type body: C{file}
@ivar body: The object from which the message is to be read.
"""
def getlogin():
try:
return os.getlogin()
except:
return getpass.getuser()
_unsupportedOption = SystemExit("Unsupported option.")
def parseOptions(argv):
o = Options()
o.to = [e for e in argv if not e.startswith('-')]
o.sender = getlogin()
# Just be very stupid
# Skip -bm -- it is the default
# Add a non-standard option for querying the version of this tool.
if '--version' in argv:
print('mailmail version:', version)
raise SystemExit()
# -bp lists queue information. Screw that.
if '-bp' in argv:
raise _unsupportedOption
# -bs makes sendmail use stdin/stdout as its transport. Screw that.
if '-bs' in argv:
raise _unsupportedOption
# -F sets who the mail is from, but is overridable by the From header
if '-F' in argv:
o.sender = argv[argv.index('-F') + 1]
o.to.remove(o.sender)
# -i and -oi makes us ignore lone "."
if ('-i' in argv) or ('-oi' in argv):
raise _unsupportedOption
# -odb is background delivery
if '-odb' in argv:
o.background = True
else:
o.background = False
# -odf is foreground delivery
if '-odf' in argv:
o.background = False
else:
o.background = True
# -oem and -em cause errors to be mailed back to the sender.
# It is also the default.
# -oep and -ep cause errors to be printed to stderr
if ('-oep' in argv) or ('-ep' in argv):
o.printErrors = True
else:
o.printErrors = False
# -om causes a copy of the message to be sent to the sender if the sender
# appears in an alias expansion. We do not support aliases.
if '-om' in argv:
raise _unsupportedOption
# -t causes us to pick the recipients of the message from
# the To, Cc, and Bcc headers, and to remove the Bcc header
# if present.
if '-t' in argv:
o.recipientsFromHeaders = True
o.excludeAddresses = o.to
o.to = []
else:
o.recipientsFromHeaders = False
o.exludeAddresses = []
requiredHeaders = {
'from': [],
'to': [],
'cc': [],
'bcc': [],
'date': [],
}
buffer = NativeStringIO()
while 1:
write = 1
line = sys.stdin.readline()
if not line.strip():
break
hdrs = line.split(': ', 1)
hdr = hdrs[0].lower()
if o.recipientsFromHeaders and hdr in ('to', 'cc', 'bcc'):
o.to.extend([
email.utils.parseaddr(hdrs[1])[1]
])
if hdr == 'bcc':
write = 0
elif hdr == 'from':
o.sender = email.utils.parseaddr(hdrs[1])[1]
if hdr in requiredHeaders:
requiredHeaders[hdr].append(hdrs[1])
if write:
buffer.write(line)
if not requiredHeaders['from']:
buffer.write('From: {}\r\n'.format(o.sender))
if not requiredHeaders['to']:
if not o.to:
raise SystemExit("No recipients specified.")
buffer.write('To: {}\r\n'.format(', '.join(o.to)))
if not requiredHeaders['date']:
buffer.write('Date: {}\r\n'.format(smtp.rfc822date()))
buffer.write(line)
if o.recipientsFromHeaders:
for a in o.excludeAddresses:
try:
o.to.remove(a)
except:
pass
buffer.seek(0, 0)
o.body = NativeStringIO(buffer.getvalue() + sys.stdin.read())
return o
class Configuration:
"""
@ivar allowUIDs: A list of UIDs which are allowed to send mail.
@ivar allowGIDs: A list of GIDs which are allowed to send mail.
@ivar denyUIDs: A list of UIDs which are not allowed to send mail.
@ivar denyGIDs: A list of GIDs which are not allowed to send mail.
@type defaultAccess: L{bool}
@ivar defaultAccess: L{True} if access will be allowed when no other access
control rule matches or L{False} if it will be denied in that case.
@ivar useraccess: Either C{'allow'} to check C{allowUID} first
or C{'deny'} to check C{denyUID} first.
@ivar groupaccess: Either C{'allow'} to check C{allowGID} first or
C{'deny'} to check C{denyGID} first.
@ivar identities: A L{dict} mapping hostnames to credentials to use when
sending mail to that host.
@ivar smarthost: L{None} or a hostname through which all outgoing mail will
be sent.
@ivar domain: L{None} or the hostname with which to identify ourselves when
connecting to an MTA.
"""
def __init__(self):
self.allowUIDs = []
self.denyUIDs = []
self.allowGIDs = []
self.denyGIDs = []
self.useraccess = 'deny'
self.groupaccess = 'deny'
self.identities = {}
self.smarthost = None
self.domain = None
self.defaultAccess = True
def loadConfig(path):
# [useraccess]
# allow=uid1,uid2,...
# deny=uid1,uid2,...
# order=allow,deny
# [groupaccess]
# allow=gid1,gid2,...
# deny=gid1,gid2,...
# order=deny,allow
# [identity]
# host1=username:password
# host2=username:password
# [addresses]
# smarthost=a.b.c.d
# default_domain=x.y.z
c = Configuration()
if not os.access(path, os.R_OK):
return c
p = ConfigParser()
p.read(path)
au = c.allowUIDs
du = c.denyUIDs
ag = c.allowGIDs
dg = c.denyGIDs
for (section, a, d) in (('useraccess', au, du), ('groupaccess', ag, dg)):
if p.has_section(section):
for (mode, L) in (('allow', a), ('deny', d)):
if p.has_option(section, mode) and p.get(section, mode):
for sectionID in p.get(section, mode).split(','):
try:
sectionID = int(sectionID)
except ValueError:
_log.error(
"Illegal {prefix}ID in "
"[{section}] section: {sectionID}",
prefix=section[0].upper(),
section=section, sectionID=sectionID)
else:
L.append(sectionID)
order = p.get(section, 'order')
order = [s.split()
for s in [s.lower()
for s in order.split(',')]]
if order[0] == 'allow':
setattr(c, section, 'allow')
else:
setattr(c, section, 'deny')
if p.has_section('identity'):
for (host, up) in p.items('identity'):
parts = up.split(':', 1)
if len(parts) != 2:
_log.error("Illegal entry in [identity] section: {section}",
section=up)
continue
c.identities[host] = parts
if p.has_section('addresses'):
if p.has_option('addresses', 'smarthost'):
c.smarthost = p.get('addresses', 'smarthost')
if p.has_option('addresses', 'default_domain'):
c.domain = p.get('addresses', 'default_domain')
return c
def success(result):
reactor.stop()
failed = None
def failure(f):
global failed
reactor.stop()
failed = f
def sendmail(host, options, ident):
d = smtp.sendmail(host, options.sender, options.to, options.body)
d.addCallbacks(success, failure)
reactor.run()
def senderror(failure, options):
recipient = [options.sender]
sender = '"Internally Generated Message ({})"<postmaster@{}>'.format(
sys.argv[0], smtp.DNSNAME.decode("ascii"))
error = NativeStringIO()
failure.printTraceback(file=error)
body = NativeStringIO(ERROR_FMT % error.getvalue())
d = smtp.sendmail('localhost', sender, recipient, body)
d.addBoth(lambda _: reactor.stop())
def deny(conf):
uid = os.getuid()
gid = os.getgid()
if conf.useraccess == 'deny':
if uid in conf.denyUIDs:
return True
if uid in conf.allowUIDs:
return False
else:
if uid in conf.allowUIDs:
return False
if uid in conf.denyUIDs:
return True
if conf.groupaccess == 'deny':
if gid in conf.denyGIDs:
return True
if gid in conf.allowGIDs:
return False
else:
if gid in conf.allowGIDs:
return False
if gid in conf.denyGIDs:
return True
return not conf.defaultAccess
def run():
o = parseOptions(sys.argv[1:])
gConf = loadConfig(GLOBAL_CFG)
lConf = loadConfig(LOCAL_CFG)
if deny(gConf) or deny(lConf):
_log.error("Permission denied")
return
host = lConf.smarthost or gConf.smarthost or SMARTHOST
ident = gConf.identities.copy()
ident.update(lConf.identities)
if lConf.domain:
smtp.DNSNAME = lConf.domain
elif gConf.domain:
smtp.DNSNAME = gConf.domain
sendmail(host, o, ident)
if failed:
if o.printErrors:
failed.printTraceback(file=sys.stderr)
raise SystemExit(1)
else:
senderror(failed, o)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
"Tests for twistd.mail"

View file

@ -0,0 +1,326 @@
#!/usr/bin/env python
# -*- test-case-name: twisted.mail.test.test_pop3client -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from __future__ import print_function
from twisted.internet.protocol import Factory
from twisted.protocols import basic
from twisted.internet import reactor
import sys
USER = "test"
PASS = "twisted"
PORT = 1100
SSL_SUPPORT = True
UIDL_SUPPORT = True
INVALID_SERVER_RESPONSE = False
INVALID_CAPABILITY_RESPONSE = False
INVALID_LOGIN_RESPONSE = False
DENY_CONNECTION = False
DROP_CONNECTION = False
BAD_TLS_RESPONSE = False
TIMEOUT_RESPONSE = False
TIMEOUT_DEFERRED = False
SLOW_GREETING = False
"""Commands"""
CONNECTION_MADE = b"+OK POP3 localhost v2003.83 server ready"
CAPABILITIES = [
b"TOP",
b"LOGIN-DELAY 180",
b"USER",
b"SASL LOGIN"
]
CAPABILITIES_SSL = b"STLS"
CAPABILITIES_UIDL = b"UIDL"
INVALID_RESPONSE = b"-ERR Unknown request"
VALID_RESPONSE = b"+OK Command Completed"
AUTH_DECLINED = b"-ERR LOGIN failed"
AUTH_ACCEPTED = b"+OK Mailbox open, 0 messages"
TLS_ERROR = b"-ERR server side error start TLS handshake"
LOGOUT_COMPLETE = b"+OK quit completed"
NOT_LOGGED_IN = b"-ERR Unknown AUHORIZATION state command"
STAT = b"+OK 0 0"
UIDL = b"+OK Unique-ID listing follows\r\n."
LIST = b"+OK Mailbox scan listing follows\r\n."
CAP_START = b"+OK Capability list follows:"
class POP3TestServer(basic.LineReceiver):
def __init__(self, contextFactory = None):
self.loggedIn = False
self.caps = None
self.tmpUser = None
self.ctx = contextFactory
def sendSTATResp(self, req):
self.sendLine(STAT)
def sendUIDLResp(self, req):
self.sendLine(UIDL)
def sendLISTResp(self, req):
self.sendLine(LIST)
def sendCapabilities(self):
if self.caps is None:
self.caps = [CAP_START]
if UIDL_SUPPORT:
self.caps.append(CAPABILITIES_UIDL)
if SSL_SUPPORT:
self.caps.append(CAPABILITIES_SSL)
for cap in CAPABILITIES:
self.caps.append(cap)
resp = b'\r\n'.join(self.caps)
resp += b'\r\n.'
self.sendLine(resp)
def connectionMade(self):
if DENY_CONNECTION:
self.disconnect()
return
if SLOW_GREETING:
reactor.callLater(20, self.sendGreeting)
else:
self.sendGreeting()
def sendGreeting(self):
self.sendLine(CONNECTION_MADE)
def lineReceived(self, line):
"""Error Conditions"""
uline = line.upper()
find = lambda s: uline.find(s) != -1
if TIMEOUT_RESPONSE:
# Do not respond to clients request
return
if DROP_CONNECTION:
self.disconnect()
return
elif find(b"CAPA"):
if INVALID_CAPABILITY_RESPONSE:
self.sendLine(INVALID_RESPONSE)
else:
self.sendCapabilities()
elif find(b"STLS") and SSL_SUPPORT:
self.startTLS()
elif find(b"USER"):
if INVALID_LOGIN_RESPONSE:
self.sendLine(INVALID_RESPONSE)
return
resp = None
try:
self.tmpUser = line.split(" ")[1]
resp = VALID_RESPONSE
except:
resp = AUTH_DECLINED
self.sendLine(resp)
elif find(b"PASS"):
resp = None
try:
pwd = line.split(" ")[1]
if self.tmpUser is None or pwd is None:
resp = AUTH_DECLINED
elif self.tmpUser == USER and pwd == PASS:
resp = AUTH_ACCEPTED
self.loggedIn = True
else:
resp = AUTH_DECLINED
except:
resp = AUTH_DECLINED
self.sendLine(resp)
elif find(b"QUIT"):
self.loggedIn = False
self.sendLine(LOGOUT_COMPLETE)
self.disconnect()
elif INVALID_SERVER_RESPONSE:
self.sendLine(INVALID_RESPONSE)
elif not self.loggedIn:
self.sendLine(NOT_LOGGED_IN)
elif find(b"NOOP"):
self.sendLine(VALID_RESPONSE)
elif find(b"STAT"):
if TIMEOUT_DEFERRED:
return
self.sendLine(STAT)
elif find(b"LIST"):
if TIMEOUT_DEFERRED:
return
self.sendLine(LIST)
elif find(b"UIDL"):
if TIMEOUT_DEFERRED:
return
elif not UIDL_SUPPORT:
self.sendLine(INVALID_RESPONSE)
return
self.sendLine(UIDL)
def startTLS(self):
if self.ctx is None:
self.getContext()
if SSL_SUPPORT and self.ctx is not None:
self.sendLine(b'+OK Begin TLS negotiation now')
self.transport.startTLS(self.ctx)
else:
self.sendLine(b'-ERR TLS not available')
def disconnect(self):
self.transport.loseConnection()
def getContext(self):
try:
from twisted.internet import ssl
except ImportError:
self.ctx = None
else:
self.ctx = ssl.ClientContextFactory()
self.ctx.method = ssl.SSL.TLSv1_METHOD
usage = """popServer.py [arg] (default is Standard POP Server with no messages)
no_ssl - Start with no SSL support
no_uidl - Start with no UIDL support
bad_resp - Send a non-RFC compliant response to the Client
bad_cap_resp - send a non-RFC compliant response when the Client sends a 'CAPABILITY' request
bad_login_resp - send a non-RFC compliant response when the Client sends a 'LOGIN' request
deny - Deny the connection
drop - Drop the connection after sending the greeting
bad_tls - Send a bad response to a STARTTLS
timeout - Do not return a response to a Client request
to_deferred - Do not return a response on a 'Select' request. This
will test Deferred callback handling
slow - Wait 20 seconds after the connection is made to return a Server Greeting
"""
def printMessage(msg):
print("Server Starting in %s mode" % msg)
def processArg(arg):
if arg.lower() == 'no_ssl':
global SSL_SUPPORT
SSL_SUPPORT = False
printMessage("NON-SSL")
elif arg.lower() == 'no_uidl':
global UIDL_SUPPORT
UIDL_SUPPORT = False
printMessage("NON-UIDL")
elif arg.lower() == 'bad_resp':
global INVALID_SERVER_RESPONSE
INVALID_SERVER_RESPONSE = True
printMessage("Invalid Server Response")
elif arg.lower() == 'bad_cap_resp':
global INVALID_CAPABILITY_RESPONSE
INVALID_CAPABILITY_RESPONSE = True
printMessage("Invalid Capability Response")
elif arg.lower() == 'bad_login_resp':
global INVALID_LOGIN_RESPONSE
INVALID_LOGIN_RESPONSE = True
printMessage("Invalid Capability Response")
elif arg.lower() == 'deny':
global DENY_CONNECTION
DENY_CONNECTION = True
printMessage("Deny Connection")
elif arg.lower() == 'drop':
global DROP_CONNECTION
DROP_CONNECTION = True
printMessage("Drop Connection")
elif arg.lower() == 'bad_tls':
global BAD_TLS_RESPONSE
BAD_TLS_RESPONSE = True
printMessage("Bad TLS Response")
elif arg.lower() == 'timeout':
global TIMEOUT_RESPONSE
TIMEOUT_RESPONSE = True
printMessage("Timeout Response")
elif arg.lower() == 'to_deferred':
global TIMEOUT_DEFERRED
TIMEOUT_DEFERRED = True
printMessage("Timeout Deferred Response")
elif arg.lower() == 'slow':
global SLOW_GREETING
SLOW_GREETING = True
printMessage("Slow Greeting")
elif arg.lower() == '--help':
print(usage)
sys.exit()
else:
print(usage)
sys.exit()
def main():
if len(sys.argv) < 2:
printMessage("POP3 with no messages")
else:
args = sys.argv[1:]
for arg in args:
processArg(arg)
f = Factory()
f.protocol = POP3TestServer
reactor.listenTCP(PORT, f)
reactor.run()
if __name__ == '__main__':
main()

View file

@ -0,0 +1,86 @@
Return-Path: <twisted-commits-admin@twistedmatrix.com>
Delivered-To: exarkun@meson.dyndns.org
Received: from localhost [127.0.0.1]
by localhost with POP3 (fetchmail-6.2.1)
for exarkun@localhost (single-drop); Thu, 20 Mar 2003 14:50:20 -0500 (EST)
Received: from pyramid.twistedmatrix.com (adsl-64-123-27-105.dsl.austtx.swbell.net [64.123.27.105])
by intarweb.us (Postfix) with ESMTP id 4A4A513EA4
for <exarkun@meson.dyndns.org>; Thu, 20 Mar 2003 14:49:27 -0500 (EST)
Received: from localhost ([127.0.0.1] helo=pyramid.twistedmatrix.com)
by pyramid.twistedmatrix.com with esmtp (Exim 3.35 #1 (Debian))
id 18w648-0007Vl-00; Thu, 20 Mar 2003 13:51:04 -0600
Received: from acapnotic by pyramid.twistedmatrix.com with local (Exim 3.35 #1 (Debian))
id 18w63j-0007VK-00
for <twisted-commits@twistedmatrix.com>; Thu, 20 Mar 2003 13:50:39 -0600
To: twisted-commits@twistedmatrix.com
From: etrepum CVS <etrepum@twistedmatrix.com>
Reply-To: twisted-python@twistedmatrix.com
X-Mailer: CVSToys
Message-Id: <E18w63j-0007VK-00@pyramid.twistedmatrix.com>
Subject: [Twisted-commits] rebuild now works on python versions from 2.2.0 and up.
Sender: twisted-commits-admin@twistedmatrix.com
Errors-To: twisted-commits-admin@twistedmatrix.com
X-BeenThere: twisted-commits@twistedmatrix.com
X-Mailman-Version: 2.0.11
Precedence: bulk
List-Help: <mailto:twisted-commits-request@twistedmatrix.com?subject=help>
List-Post: <mailto:twisted-commits@twistedmatrix.com>
List-Subscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
<mailto:twisted-commits-request@twistedmatrix.com?subject=subscribe>
List-Id: <twisted-commits.twistedmatrix.com>
List-Unsubscribe: <http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits>,
<mailto:twisted-commits-request@twistedmatrix.com?subject=unsubscribe>
List-Archive: <http://twistedmatrix.com/pipermail/twisted-commits/>
Date: Thu, 20 Mar 2003 13:50:39 -0600
Modified files:
Twisted/twisted/python/rebuild.py 1.19 1.20
Log message:
rebuild now works on python versions from 2.2.0 and up.
ViewCVS links:
http://twistedmatrix.com/users/jh.twistd/viewcvs/cgi/viewcvs.cgi/twisted/python/rebuild.py.diff?r1=text&tr1=1.19&r2=text&tr2=1.20&cvsroot=Twisted
Index: Twisted/twisted/python/rebuild.py
diff -u Twisted/twisted/python/rebuild.py:1.19 Twisted/twisted/python/rebuild.py:1.20
--- Twisted/twisted/python/rebuild.py:1.19 Fri Jan 17 13:50:49 2003
+++ Twisted/twisted/python/rebuild.py Thu Mar 20 11:50:08 2003
@@ -206,15 +206,27 @@
clazz.__dict__.clear()
clazz.__getattr__ = __getattr__
clazz.__module__ = module.__name__
+ if newclasses:
+ import gc
+ if (2, 2, 0) <= sys.version_info[:3] < (2, 2, 2):
+ hasBrokenRebuild = 1
+ gc_objects = gc.get_objects()
+ else:
+ hasBrokenRebuild = 0
for nclass in newclasses:
ga = getattr(module, nclass.__name__)
if ga is nclass:
log.msg("WARNING: new-class %s not replaced by reload!" % reflect.qual(nclass))
else:
- import gc
- for r in gc.get_referrers(nclass):
- if isinstance(r, nclass):
+ if hasBrokenRebuild:
+ for r in gc_objects:
+ if not getattr(r, '__class__', None) is nclass:
+ continue
r.__class__ = ga
+ else:
+ for r in gc.get_referrers(nclass):
+ if getattr(r, '__class__', None) is nclass:
+ r.__class__ = ga
if doLog:
log.msg('')
log.msg(' (fixing %s): ' % str(module.__name__))
_______________________________________________
Twisted-commits mailing list
Twisted-commits@twistedmatrix.com
http://twistedmatrix.com/cgi-bin/mailman/listinfo/twisted-commits

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,379 @@
# -*- test-case-name: twisted.mail.test.test_mailmail -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.mail.scripts.mailmail}, the implementation of the
command line program I{mailmail}.
"""
import os
import sys
from twisted.copyright import version
from twisted.internet.defer import Deferred
from twisted.mail import smtp
from twisted.mail.scripts import mailmail
from twisted.mail.scripts.mailmail import parseOptions
from twisted.python.compat import NativeStringIO
from twisted.python.failure import Failure
from twisted.python.runtime import platformType
from twisted.test.proto_helpers import MemoryReactor
from twisted.trial.unittest import TestCase
class OptionsTests(TestCase):
"""
Tests for L{parseOptions} which parses command line arguments and reads
message text from stdin to produce an L{Options} instance which can be
used to send a message.
"""
memoryReactor = MemoryReactor()
def setUp(self):
"""
Override some things in mailmail, so that we capture C{stdout},
and do not call L{reactor.stop}.
"""
self.out = NativeStringIO()
# Override the mailmail logger, so we capture stderr output
from twisted.logger import textFileLogObserver, Logger
logObserver = textFileLogObserver(self.out)
self.patch(mailmail, '_log', Logger(observer=logObserver))
self.host = None
self.options = None
self.ident = None
# Override mailmail.sendmail, so we don't call reactor.stop()
def sendmail(host, options, ident):
self.host = host
self.options = options
self.ident = ident
return smtp.sendmail(host, options.sender, options.to,
options.body, reactor=self.memoryReactor)
self.patch(mailmail, 'sendmail', sendmail)
def test_unspecifiedRecipients(self):
"""
If no recipients are given in the argument list and there is no
recipient header in the message text, L{parseOptions} raises
L{SystemExit} with a string describing the problem.
"""
self.patch(sys, 'stdin', NativeStringIO(
'Subject: foo\n'
'\n'
'Hello, goodbye.\n'))
exc = self.assertRaises(SystemExit, parseOptions, [])
self.assertEqual(exc.args, ('No recipients specified.',))
def test_listQueueInformation(self):
"""
The I{-bp} option for listing queue information is unsupported and
if it is passed to L{parseOptions}, L{SystemExit} is raised.
"""
exc = self.assertRaises(SystemExit, parseOptions, ['-bp'])
self.assertEqual(exc.args, ("Unsupported option.",))
def test_stdioTransport(self):
"""
The I{-bs} option for using stdin and stdout as the SMTP transport
is unsupported and if it is passed to L{parseOptions}, L{SystemExit}
is raised.
"""
exc = self.assertRaises(SystemExit, parseOptions, ['-bs'])
self.assertEqual(exc.args, ("Unsupported option.",))
def test_ignoreFullStop(self):
"""
The I{-i} and I{-oi} options for ignoring C{"."} by itself on a line
are unsupported and if either is passed to L{parseOptions},
L{SystemExit} is raised.
"""
exc = self.assertRaises(SystemExit, parseOptions, ['-i'])
self.assertEqual(exc.args, ("Unsupported option.",))
exc = self.assertRaises(SystemExit, parseOptions, ['-oi'])
self.assertEqual(exc.args, ("Unsupported option.",))
def test_copyAliasedSender(self):
"""
The I{-om} option for copying the sender if they appear in an alias
expansion is unsupported and if it is passed to L{parseOptions},
L{SystemExit} is raised.
"""
exc = self.assertRaises(SystemExit, parseOptions, ['-om'])
self.assertEqual(exc.args, ("Unsupported option.",))
def test_version(self):
"""
The I{--version} option displays the version and raises
L{SystemExit} with L{None} as the exit code.
"""
out = NativeStringIO()
self.patch(sys, 'stdout', out)
systemExitCode = self.assertRaises(SystemExit, parseOptions,
'--version')
# SystemExit.code is None on success
self.assertEqual(systemExitCode.code, None)
data = out.getvalue()
self.assertEqual(data, "mailmail version: {}\n".format(version))
def test_backgroundDelivery(self):
"""
The I{-odb} flag specifies background delivery.
"""
stdin = NativeStringIO('\n')
self.patch(sys, 'stdin', stdin)
o = parseOptions("-odb")
self.assertTrue(o.background)
def test_foregroundDelivery(self):
"""
The I{-odf} flags specifies foreground delivery.
"""
stdin = NativeStringIO('\n')
self.patch(sys, 'stdin', stdin)
o = parseOptions("-odf")
self.assertFalse(o.background)
def test_recipientsFromHeaders(self):
"""
The I{-t} flags specifies that recipients should be obtained
from headers.
"""
stdin = NativeStringIO(
'To: Curly <invaliduser2@example.com>\n'
'Cc: Larry <invaliduser1@example.com>\n'
'Bcc: Moe <invaliduser3@example.com>\n'
'\n'
'Oh, a wise guy?\n')
self.patch(sys, 'stdin', stdin)
o = parseOptions("-t")
self.assertEqual(len(o.to), 3)
def test_setFrom(self):
"""
When a message has no I{From:} header, a I{From:} value can be
specified with the I{-F} flag.
"""
stdin = NativeStringIO(
'To: invaliduser2@example.com\n'
'Subject: A wise guy?\n\n')
self.patch(sys, 'stdin', stdin)
o = parseOptions(["-F", "Larry <invaliduser1@example.com>", "-t"])
self.assertEqual(o.sender, "Larry <invaliduser1@example.com>")
def test_overrideFromFlagByFromHeader(self):
"""
The I{-F} flag specifies the From: value. However, I{-F} flag is
overriden by the value of From: in the e-mail header.
"""
stdin = NativeStringIO(
'To: Curly <invaliduser4@example.com>\n'
'From: Shemp <invaliduser4@example.com>\n')
self.patch(sys, 'stdin', stdin)
o = parseOptions(["-F", "Groucho <invaliduser5@example.com>", "-t"])
self.assertEqual(o.sender, "invaliduser4@example.com")
def test_runErrorsToStderr(self):
"""
Call L{mailmail.run}, and specify I{-oep} to print errors
to stderr. The sender, to, and printErrors options should be
set and there should be no failure.
"""
argv = ("test_mailmail.py", "invaliduser2@example.com", "-oep")
stdin = NativeStringIO('\n')
self.patch(sys, 'argv', argv)
self.patch(sys, 'stdin', stdin)
mailmail.run()
self.assertEqual(self.options.sender, mailmail.getlogin())
self.assertEqual(self.options.to, ["invaliduser2@example.com"])
# We should have printErrors set because we specified "-oep"
self.assertTrue(self.options.printErrors)
# We should not have any failures.
self.assertIsNone(mailmail.failed)
if platformType == "win32":
test_runErrorsToStderr.skip = (
"mailmail.run() does not work on win32 due to lack of support for"
" getuid()")
def test_readInvalidConfig(self):
"""
Error messages for illegal UID value, illegal GID value, and illegal
identity entry will be sent to stderr.
"""
stdin = NativeStringIO('\n')
self.patch(sys, 'stdin', stdin)
filename = self.mktemp()
myUid = os.getuid()
myGid = os.getgid()
with open(filename, "w") as f:
# Create a config file with some invalid values
f.write("[useraccess]\n"
"allow=invaliduser2,invaliduser1\n"
"deny=invaliduser3,invaliduser4,{}\n"
"order=allow,deny\n"
"[groupaccess]\n"
"allow=invalidgid1,invalidgid2\n"
"deny=invalidgid1,invalidgid2,{}\n"
"order=deny,allow\n"
"[identity]\n"
"localhost=funny\n"
"[addresses]\n"
"smarthost=localhost\n"
"default_domain=example.com\n".format(myUid, myGid))
# The mailmail script looks in
# the twisted.mail.scripts.GLOBAL_CFG variable
# and then the twisted.mail.scripts.LOCAL_CFG
# variable for the path to it's config file.
#
# Override twisted.mail.scripts.LOCAL_CFG with the file we just
# created.
self.patch(mailmail, "LOCAL_CFG", filename)
argv = ("test_mailmail.py", "invaliduser2@example.com", "-oep")
self.patch(sys, 'argv', argv)
mailmail.run()
self.assertRegex(self.out.getvalue(),
"Illegal UID in \\[useraccess\\] section: "
"invaliduser1")
self.assertRegex(self.out.getvalue(),
"Illegal GID in \\[groupaccess\\] section: "
"invalidgid1")
self.assertRegex(self.out.getvalue(),
'Illegal entry in \\[identity\\] section: funny')
if platformType == "win32":
test_readInvalidConfig.skip = ("mailmail.run() does not work on win32"
" due to lack of support for getuid()")
def getConfigFromFile(self, config):
"""
Read a mailmail configuration file.
The mailmail script checks the twisted.mail.scripts.mailmail.GLOBAL_CFG
variable and then the twisted.mail.scripts.mailmail.LOCAL_CFG
variable for the path to its config file.
@param config: path to config file
@type config: L{str}
@return: A parsed config.
@rtype: L{twisted.mail.scripts.mailmail.Configuration}
"""
from twisted.mail.scripts.mailmail import loadConfig
filename = self.mktemp()
with open(filename, "w") as f:
f.write(config)
return loadConfig(filename)
def test_loadConfig(self):
"""
L{twisted.mail.scripts.mailmail.loadConfig}
parses the config file for mailmail.
"""
config = self.getConfigFromFile("""
[addresses]
smarthost=localhost""")
self.assertEqual(config.smarthost, "localhost")
config = self.getConfigFromFile("""
[addresses]
default_domain=example.com""")
self.assertEqual(config.domain, "example.com")
config = self.getConfigFromFile("""
[addresses]
smarthost=localhost
default_domain=example.com""")
self.assertEqual(config.smarthost, "localhost")
self.assertEqual(config.domain, "example.com")
config = self.getConfigFromFile("""
[identity]
host1=invalid
host2=username:password""")
self.assertNotIn("host1", config.identities)
self.assertEqual(config.identities["host2"], ["username", "password"])
config = self.getConfigFromFile("""
[useraccess]
allow=invalid1,35
order=allow""")
self.assertEqual(config.allowUIDs, [35])
config = self.getConfigFromFile("""
[useraccess]
deny=35,36
order=deny""")
self.assertEqual(config.denyUIDs, [35, 36])
config = self.getConfigFromFile("""
[useraccess]
allow=35,36
deny=37,38
order=deny""")
self.assertEqual(config.allowUIDs, [35, 36])
self.assertEqual(config.denyUIDs, [37, 38])
config = self.getConfigFromFile("""
[groupaccess]
allow=gid1,41
order=allow""")
self.assertEqual(config.allowGIDs, [41])
config = self.getConfigFromFile("""
[groupaccess]
deny=41
order=deny""")
self.assertEqual(config.denyGIDs, [41])
config = self.getConfigFromFile("""
[groupaccess]
allow=41,42
deny=43,44
order=allow,deny""")
self.assertEqual(config.allowGIDs, [41, 42])
self.assertEqual(config.denyGIDs, [43, 44])
def test_senderror(self):
"""
L{twisted.mail.scripts.mailmail.senderror} sends mail back to the
sender if an error occurs while sending mail to the recipient.
"""
def sendmail(host, sender, recipient, body):
self.assertRegex(sender, "postmaster@")
self.assertEqual(recipient, ["testsender"])
self.assertRegex(body.getvalue(), "ValueError")
return Deferred()
self.patch(smtp, "sendmail", sendmail)
opts = mailmail.Options()
opts.sender = "testsender"
fail = Failure(ValueError())
mailmail.senderror(fail, opts)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,686 @@
# -*- test-case-name: twisted.mail.test.test_pop3client -*-
# Copyright (c) 2001-2004 Divmod Inc.
# See LICENSE for details.
import sys
import inspect
from zope.interface import directlyProvides
from twisted.internet import reactor, defer, error, protocol, interfaces
from twisted.mail.pop3 import AdvancedPOP3Client as POP3Client
from twisted.mail.pop3 import InsecureAuthenticationDisallowed
from twisted.mail.pop3 import ServerErrorResponse
from twisted.mail.test import pop3testserver
from twisted.protocols import basic, loopback
from twisted.python import log
from twisted.python.compat import intToBytes
from twisted.test.proto_helpers import StringTransport
from twisted.trial import unittest
try:
from twisted.test.ssl_helpers import ClientTLSContext, ServerTLSContext
except ImportError:
ClientTLSContext = ServerTLSContext = None
class StringTransportWithConnectionLosing(StringTransport):
def loseConnection(self):
self.protocol.connectionLost(error.ConnectionDone())
capCache = {b"TOP": None, b"LOGIN-DELAY": b"180", b"UIDL": None, \
b"STLS": None, b"USER": None, b"SASL": b"LOGIN"}
def setUp(greet=True):
p = POP3Client()
# Skip the CAPA login will issue if it doesn't already have a
# capability cache
p._capCache = capCache
t = StringTransportWithConnectionLosing()
t.protocol = p
p.makeConnection(t)
if greet:
p.dataReceived(b'+OK Hello!\r\n')
return p, t
def strip(f):
return lambda result, f=f: f()
class POP3ClientLoginTests(unittest.TestCase):
def testNegativeGreeting(self):
p, t = setUp(greet=False)
p.allowInsecureLogin = True
d = p.login(b"username", b"password")
p.dataReceived(b'-ERR Offline for maintenance\r\n')
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0],
b"Offline for maintenance"))
def testOkUser(self):
p, t = setUp()
d = p.user(b"username")
self.assertEqual(t.value(), b"USER username\r\n")
p.dataReceived(b"+OK send password\r\n")
return d.addCallback(self.assertEqual, b"send password")
def testBadUser(self):
p, t = setUp()
d = p.user(b"username")
self.assertEqual(t.value(), b"USER username\r\n")
p.dataReceived(b"-ERR account suspended\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"account suspended"))
def testOkPass(self):
p, t = setUp()
d = p.password(b"password")
self.assertEqual(t.value(), b"PASS password\r\n")
p.dataReceived(b"+OK you're in!\r\n")
return d.addCallback(self.assertEqual, b"you're in!")
def testBadPass(self):
p, t = setUp()
d = p.password(b"password")
self.assertEqual(t.value(), b"PASS password\r\n")
p.dataReceived(b"-ERR go away\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"go away"))
def testOkLogin(self):
p, t = setUp()
p.allowInsecureLogin = True
d = p.login(b"username", b"password")
self.assertEqual(t.value(), b"USER username\r\n")
p.dataReceived(b"+OK go ahead\r\n")
self.assertEqual(t.value(), b"USER username\r\nPASS password\r\n")
p.dataReceived(b"+OK password accepted\r\n")
return d.addCallback(self.assertEqual, b"password accepted")
def testBadPasswordLogin(self):
p, t = setUp()
p.allowInsecureLogin = True
d = p.login(b"username", b"password")
self.assertEqual(t.value(), b"USER username\r\n")
p.dataReceived(b"+OK waiting on you\r\n")
self.assertEqual(t.value(), b"USER username\r\nPASS password\r\n")
p.dataReceived(b"-ERR bogus login\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"bogus login"))
def testBadUsernameLogin(self):
p, t = setUp()
p.allowInsecureLogin = True
d = p.login(b"username", b"password")
self.assertEqual(t.value(), b"USER username\r\n")
p.dataReceived(b"-ERR bogus login\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"bogus login"))
def testServerGreeting(self):
p, t = setUp(greet=False)
p.dataReceived(b"+OK lalala this has no challenge\r\n")
self.assertEqual(p.serverChallenge, None)
def testServerGreetingWithChallenge(self):
p, t = setUp(greet=False)
p.dataReceived(b"+OK <here is the challenge>\r\n")
self.assertEqual(p.serverChallenge, b"<here is the challenge>")
def testAPOP(self):
p, t = setUp(greet=False)
p.dataReceived(b"+OK <challenge string goes here>\r\n")
d = p.login(b"username", b"password")
self.assertEqual(t.value(),
b"APOP username f34f1e464d0d7927607753129cabe39a\r\n")
p.dataReceived(b"+OK Welcome!\r\n")
return d.addCallback(self.assertEqual, b"Welcome!")
def testInsecureLoginRaisesException(self):
p, t = setUp(greet=False)
p.dataReceived(b"+OK Howdy\r\n")
d = p.login(b"username", b"password")
self.assertFalse(t.value())
return self.assertFailure(
d, InsecureAuthenticationDisallowed)
def testSSLTransportConsideredSecure(self):
"""
If a server doesn't offer APOP but the transport is secured using
SSL or TLS, a plaintext login should be allowed, not rejected with
an InsecureAuthenticationDisallowed exception.
"""
p, t = setUp(greet=False)
directlyProvides(t, interfaces.ISSLTransport)
p.dataReceived(b"+OK Howdy\r\n")
d = p.login(b"username", b"password")
self.assertEqual(t.value(), b"USER username\r\n")
t.clear()
p.dataReceived(b"+OK\r\n")
self.assertEqual(t.value(), b"PASS password\r\n")
p.dataReceived(b"+OK\r\n")
return d
class ListConsumer:
def __init__(self):
self.data = {}
def consume(self, result):
(item, value) = result
self.data.setdefault(item, []).append(value)
class MessageConsumer:
def __init__(self):
self.data = []
def consume(self, line):
self.data.append(line)
class POP3ClientListTests(unittest.TestCase):
def testListSize(self):
p, t = setUp()
d = p.listSize()
self.assertEqual(t.value(), b"LIST\r\n")
p.dataReceived(b"+OK Here it comes\r\n")
p.dataReceived(b"1 3\r\n2 2\r\n3 1\r\n.\r\n")
return d.addCallback(self.assertEqual, [3, 2, 1])
def testListSizeWithConsumer(self):
p, t = setUp()
c = ListConsumer()
f = c.consume
d = p.listSize(f)
self.assertEqual(t.value(), b"LIST\r\n")
p.dataReceived(b"+OK Here it comes\r\n")
p.dataReceived(b"1 3\r\n2 2\r\n3 1\r\n")
self.assertEqual(c.data, {0: [3], 1: [2], 2: [1]})
p.dataReceived(b"5 3\r\n6 2\r\n7 1\r\n")
self.assertEqual(c.data, {0: [3], 1: [2], 2: [1], 4: [3], 5: [2],
6: [1]})
p.dataReceived(b".\r\n")
return d.addCallback(self.assertIdentical, f)
def testFailedListSize(self):
p, t = setUp()
d = p.listSize()
self.assertEqual(t.value(), b"LIST\r\n")
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0],
b"Fatal doom server exploded"))
def testListUID(self):
p, t = setUp()
d = p.listUID()
self.assertEqual(t.value(), b"UIDL\r\n")
p.dataReceived(b"+OK Here it comes\r\n")
p.dataReceived(b"1 abc\r\n2 def\r\n3 ghi\r\n.\r\n")
return d.addCallback(self.assertEqual, [b"abc", b"def", b"ghi"])
def testListUIDWithConsumer(self):
p, t = setUp()
c = ListConsumer()
f = c.consume
d = p.listUID(f)
self.assertEqual(t.value(), b"UIDL\r\n")
p.dataReceived(b"+OK Here it comes\r\n")
p.dataReceived(b"1 xyz\r\n2 abc\r\n5 mno\r\n")
self.assertEqual(c.data, {0: [b"xyz"], 1: [b"abc"], 4: [b"mno"]})
p.dataReceived(b".\r\n")
return d.addCallback(self.assertIdentical, f)
def testFailedListUID(self):
p, t = setUp()
d = p.listUID()
self.assertEqual(t.value(), b"UIDL\r\n")
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0],
b"Fatal doom server exploded"))
class POP3ClientMessageTests(unittest.TestCase):
def testRetrieve(self):
p, t = setUp()
d = p.retrieve(7)
self.assertEqual(t.value(), b"RETR 8\r\n")
p.dataReceived(b"+OK Message incoming\r\n")
p.dataReceived(b"La la la here is message text\r\n")
p.dataReceived(b"..Further message text tra la la\r\n")
p.dataReceived(b".\r\n")
return d.addCallback(
self.assertEqual,
[b"La la la here is message text",
b".Further message text tra la la"])
def testRetrieveWithConsumer(self):
p, t = setUp()
c = MessageConsumer()
f = c.consume
d = p.retrieve(7, f)
self.assertEqual(t.value(), b"RETR 8\r\n")
p.dataReceived(b"+OK Message incoming\r\n")
p.dataReceived(b"La la la here is message text\r\n")
p.dataReceived(b"..Further message text\r\n.\r\n")
return d.addCallback(self._cbTestRetrieveWithConsumer, f, c)
def _cbTestRetrieveWithConsumer(self, result, f, c):
self.assertIdentical(result, f)
self.assertEqual(c.data, [b"La la la here is message text",
b".Further message text"])
def testPartialRetrieve(self):
p, t = setUp()
d = p.retrieve(7, lines=2)
self.assertEqual(t.value(), b"TOP 8 2\r\n")
p.dataReceived(b"+OK 2 lines on the way\r\n")
p.dataReceived(b"Line the first! Woop\r\n")
p.dataReceived(b"Line the last! Bye\r\n")
p.dataReceived(b".\r\n")
return d.addCallback(
self.assertEqual,
[b"Line the first! Woop",
b"Line the last! Bye"])
def testPartialRetrieveWithConsumer(self):
p, t = setUp()
c = MessageConsumer()
f = c.consume
d = p.retrieve(7, f, lines=2)
self.assertEqual(t.value(), b"TOP 8 2\r\n")
p.dataReceived(b"+OK 2 lines on the way\r\n")
p.dataReceived(b"Line the first! Woop\r\n")
p.dataReceived(b"Line the last! Bye\r\n")
p.dataReceived(b".\r\n")
return d.addCallback(self._cbTestPartialRetrieveWithConsumer, f, c)
def _cbTestPartialRetrieveWithConsumer(self, result, f, c):
self.assertIdentical(result, f)
self.assertEqual(c.data, [b"Line the first! Woop",
b"Line the last! Bye"])
def testFailedRetrieve(self):
p, t = setUp()
d = p.retrieve(0)
self.assertEqual(t.value(), b"RETR 1\r\n")
p.dataReceived(b"-ERR Fatal doom server exploded\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0],
b"Fatal doom server exploded"))
def test_concurrentRetrieves(self):
"""
Issue three retrieve calls immediately without waiting for any to
succeed and make sure they all do succeed eventually.
"""
p, t = setUp()
messages = [
p.retrieve(i).addCallback(
self.assertEqual,
[b"First line of " + intToBytes(i + 1) + b".",
b"Second line of " + intToBytes(i + 1) + b"."])
for i
in range(3)]
for i in range(1, 4):
self.assertEqual(t.value(), b"RETR " + intToBytes(i) + b"\r\n")
t.clear()
p.dataReceived(b"+OK 2 lines on the way\r\n")
p.dataReceived(b"First line of " + intToBytes(i) + b".\r\n")
p.dataReceived(b"Second line of " + intToBytes(i) + b".\r\n")
self.assertEqual(t.value(), b"")
p.dataReceived(b".\r\n")
return defer.DeferredList(messages, fireOnOneErrback=True)
class POP3ClientMiscTests(unittest.TestCase):
def testCapability(self):
p, t = setUp()
d = p.capabilities(useCache=0)
self.assertEqual(t.value(), b"CAPA\r\n")
p.dataReceived(b"+OK Capabilities on the way\r\n")
p.dataReceived(b"X\r\nY\r\nZ\r\nA 1 2 3\r\nB 1 2\r\nC 1\r\n.\r\n")
return d.addCallback(
self.assertEqual,
{b"X": None, b"Y": None, b"Z": None,
b"A": [b"1", b"2", b"3"],
b"B": [b"1", b"2"],
b"C": [b"1"]})
def testCapabilityError(self):
p, t = setUp()
d = p.capabilities(useCache=0)
self.assertEqual(t.value(), b"CAPA\r\n")
p.dataReceived(b"-ERR This server is lame!\r\n")
return d.addCallback(self.assertEqual, {})
def testStat(self):
p, t = setUp()
d = p.stat()
self.assertEqual(t.value(), b"STAT\r\n")
p.dataReceived(b"+OK 1 1212\r\n")
return d.addCallback(self.assertEqual, (1, 1212))
def testStatError(self):
p, t = setUp()
d = p.stat()
self.assertEqual(t.value(), b"STAT\r\n")
p.dataReceived(b"-ERR This server is lame!\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!"))
def testNoop(self):
p, t = setUp()
d = p.noop()
self.assertEqual(t.value(), b"NOOP\r\n")
p.dataReceived(b"+OK No-op to you too!\r\n")
return d.addCallback(self.assertEqual, b"No-op to you too!")
def testNoopError(self):
p, t = setUp()
d = p.noop()
self.assertEqual(t.value(), b"NOOP\r\n")
p.dataReceived(b"-ERR This server is lame!\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!"))
def testRset(self):
p, t = setUp()
d = p.reset()
self.assertEqual(t.value(), b"RSET\r\n")
p.dataReceived(b"+OK Reset state\r\n")
return d.addCallback(self.assertEqual, b"Reset state")
def testRsetError(self):
p, t = setUp()
d = p.reset()
self.assertEqual(t.value(), b"RSET\r\n")
p.dataReceived(b"-ERR This server is lame!\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"This server is lame!"))
def testDelete(self):
p, t = setUp()
d = p.delete(3)
self.assertEqual(t.value(), b"DELE 4\r\n")
p.dataReceived(b"+OK Hasta la vista\r\n")
return d.addCallback(self.assertEqual, b"Hasta la vista")
def testDeleteError(self):
p, t = setUp()
d = p.delete(3)
self.assertEqual(t.value(), b"DELE 4\r\n")
p.dataReceived(b"-ERR Winner is not you.\r\n")
return self.assertFailure(
d, ServerErrorResponse).addCallback(
lambda exc: self.assertEqual(exc.args[0], b"Winner is not you."))
class SimpleClient(POP3Client):
def __init__(self, deferred, contextFactory=None):
self.deferred = deferred
self.allowInsecureLogin = True
def serverGreeting(self, challenge):
self.deferred.callback(None)
class POP3HelperMixin:
serverCTX = None
clientCTX = None
def setUp(self):
d = defer.Deferred()
self.server = pop3testserver.POP3TestServer(
contextFactory=self.serverCTX)
self.client = SimpleClient(d, contextFactory=self.clientCTX)
self.client.timeout = 30
self.connected = d
def tearDown(self):
del self.server
del self.client
del self.connected
def _cbStopClient(self, ignore):
self.client.transport.loseConnection()
def _ebGeneral(self, failure):
self.client.transport.loseConnection()
self.server.transport.loseConnection()
return failure
def loopback(self):
return loopback.loopbackTCP(self.server, self.client, noisy=False)
class TLSServerFactory(protocol.ServerFactory):
class protocol(basic.LineReceiver):
context = None
output = []
def connectionMade(self):
self.factory.input = []
self.output = self.output[:]
for line in self.output.pop(0):
self.sendLine(line)
def lineReceived(self, line):
self.factory.input.append(line)
[self.sendLine(l) for l in self.output.pop(0)]
if line == b'STLS':
self.transport.startTLS(self.context)
class POP3TLSTests(unittest.TestCase):
"""
Tests for POP3Client's support for TLS connections.
"""
def test_startTLS(self):
"""
POP3Client.startTLS starts a TLS session over its existing TCP
connection.
"""
sf = TLSServerFactory()
sf.protocol.output = [
[b'+OK'], # Server greeting
[b'+OK', b'STLS', b'.'], # CAPA response
[b'+OK'], # STLS response
[b'+OK', b'.'], # Second CAPA response
[b'+OK'] # QUIT response
]
sf.protocol.context = ServerTLSContext()
port = reactor.listenTCP(0, sf, interface='127.0.0.1')
self.addCleanup(port.stopListening)
H = port.getHost().host
P = port.getHost().port
connLostDeferred = defer.Deferred()
cp = SimpleClient(defer.Deferred(), ClientTLSContext())
def connectionLost(reason):
SimpleClient.connectionLost(cp, reason)
connLostDeferred.callback(None)
cp.connectionLost = connectionLost
cf = protocol.ClientFactory()
cf.protocol = lambda: cp
conn = reactor.connectTCP(H, P, cf)
def cbConnected(ignored):
log.msg("Connected to server; starting TLS")
return cp.startTLS()
def cbStartedTLS(ignored):
log.msg("Started TLS; disconnecting")
return cp.quit()
def cbDisconnected(ign):
log.msg("Disconnected; asserting correct input received")
self.assertEqual(
sf.input,
[b'CAPA', b'STLS', b'CAPA', b'QUIT'])
def cleanup(result):
log.msg("Asserted correct input; disconnecting "
"client and shutting down server")
conn.disconnect()
return connLostDeferred
cp.deferred.addCallback(cbConnected)
cp.deferred.addCallback(cbStartedTLS)
cp.deferred.addCallback(cbDisconnected)
cp.deferred.addBoth(cleanup)
return cp.deferred
class POP3TimeoutTests(POP3HelperMixin, unittest.TestCase):
def testTimeout(self):
def login():
d = self.client.login('test', 'twisted')
d.addCallback(loggedIn)
d.addErrback(timedOut)
return d
def loggedIn(result):
self.fail("Successfully logged in!? Impossible!")
def timedOut(failure):
failure.trap(error.TimeoutError)
self._cbStopClient(None)
def quit():
return self.client.quit()
self.client.timeout = 0.01
# Tell the server to not return a response to client. This
# will trigger a timeout.
pop3testserver.TIMEOUT_RESPONSE = True
methods = [login, quit]
map(self.connected.addCallback, map(strip, methods))
self.connected.addCallback(self._cbStopClient)
self.connected.addErrback(self._ebGeneral)
return self.loopback()
if ClientTLSContext is None:
for case in (POP3TLSTests,):
case.skip = "OpenSSL not present"
elif interfaces.IReactorSSL(reactor, None) is None:
for case in (POP3TLSTests,):
case.skip = "Reactor doesn't support SSL"
import twisted.mail.pop3client
class POP3ClientModuleStructureTests(unittest.TestCase):
"""
Miscellaneous tests more to do with module/package structure than
anything to do with the POP3 client.
"""
def test_all(self):
"""
twisted.mail.pop3client.__all__ should be empty because all classes
should be imported through twisted.mail.pop3.
"""
self.assertEqual(twisted.mail.pop3client.__all__, [])
def test_import(self):
"""
Every public class in twisted.mail.pop3client should be available as a
member of twisted.mail.pop3 with the exception of
twisted.mail.pop3client.POP3Client which should be available as
twisted.mail.pop3.AdvancedClient.
"""
publicClasses = [c[0] for c in inspect.getmembers(
sys.modules['twisted.mail.pop3client'],
inspect.isclass)
if not c[0][0] == '_']
for pc in publicClasses:
if not pc == 'POP3Client':
self.assertTrue(hasattr(twisted.mail.pop3, pc))
else:
self.assertTrue(hasattr(twisted.mail.pop3,
'AdvancedPOP3Client'))

File diff suppressed because it is too large Load diff