From 4df74038a89dccff213c07795795154831330ace Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 7 Nov 2013 16:44:59 -0200 Subject: Cleanup code and fix tests. --- src/leap/mail/smtp/__init__.py | 33 +--- src/leap/mail/smtp/smtprelay.py | 260 +++++++++++++---------------- src/leap/mail/smtp/tests/__init__.py | 4 +- src/leap/mail/smtp/tests/test_smtprelay.py | 52 ++++-- 4 files changed, 156 insertions(+), 193 deletions(-) (limited to 'src/leap/mail/smtp') diff --git a/src/leap/mail/smtp/__init__.py b/src/leap/mail/smtp/__init__.py index be568b8..753ef34 100644 --- a/src/leap/mail/smtp/__init__.py +++ b/src/leap/mail/smtp/__init__.py @@ -46,7 +46,7 @@ def setup_smtp_relay(port, userid, keymanager, smtp_host, smtp_port, :type keymanager: leap.common.keymanager.KeyManager :param smtp_host: The hostname of the remote SMTP server. :type smtp_host: str - :param smtp_port: The port of the remote SMTP server. + :param smtp_port: The port of the remote SMTP server. :type smtp_port: int :param smtp_cert: The client certificate for authentication. :type smtp_cert: str @@ -58,36 +58,17 @@ def setup_smtp_relay(port, userid, keymanager, smtp_host, smtp_port, :returns: tuple of SMTPFactory, twisted.internet.tcp.Port """ - # The configuration for the SMTP relay is a dict with the following - # format: - # - # { - # 'host': '', - # 'port': , - # 'cert': '', - # 'key': '', - # 'encrypted_only': - # } - config = { - 'host': smtp_host, - 'port': smtp_port, - 'cert': smtp_cert, - 'key': smtp_key, - 'encrypted_only': encrypted_only - } - # configure the use of this service with twistd - factory = SMTPFactory(userid, keymanager, config) + factory = SMTPFactory(userid, keymanager, smtp_host, smtp_port, smtp_cert, + smtp_key, encrypted_only) try: - tport = reactor.listenTCP(port, factory, - interface="localhost") - signal(proto.SMTP_SERVICE_STARTED, str(smtp_port)) + tport = reactor.listenTCP(port, factory, interface="localhost") + signal(proto.SMTP_SERVICE_STARTED, str(port)) return factory, tport except CannotListenError: logger.error("STMP Service failed to start: " - "cannot listen in port %s" % ( - smtp_port,)) - signal(proto.SMTP_SERVICE_FAILED_TO_START, str(smtp_port)) + "cannot listen in port %s" % port) + signal(proto.SMTP_SERVICE_FAILED_TO_START, str(port)) except Exception as exc: logger.error("Unhandled error while launching smtp relay service") logger.exception(exc) diff --git a/src/leap/mail/smtp/smtprelay.py b/src/leap/mail/smtp/smtprelay.py index 14de849..474fc3b 100644 --- a/src/leap/mail/smtp/smtprelay.py +++ b/src/leap/mail/smtp/smtprelay.py @@ -67,68 +67,16 @@ from email import generator generator.Generator = RFC3156CompliantGenerator -# -# Exceptions -# - -class MalformedConfig(Exception): - """ - Raised when the configuration dictionary passed as parameter is malformed. - """ - pass - - # # Helper utilities # -HOST_KEY = 'host' -PORT_KEY = 'port' -CERT_KEY = 'cert' -KEY_KEY = 'key' -ENCRYPTED_ONLY_KEY = 'encrypted_only' - - -def assert_config_structure(config): - """ - Assert that C{config} is a dict with the following structure: - - { - HOST_KEY: '', - PORT_KEY: , - CERT_KEY: '', - KEY_KEY: '', - ENCRYPTED_ONLY_KEY: , - } - - @param config: The dictionary to check. - @type config: dict - """ - # assert smtp config structure is valid - leap_assert_type(config, dict) - leap_assert(HOST_KEY in config) - leap_assert_type(config[HOST_KEY], str) - leap_assert(PORT_KEY in config) - leap_assert_type(config[PORT_KEY], int) - leap_assert(CERT_KEY in config) - leap_assert_type(config[CERT_KEY], (str, unicode)) - leap_assert(KEY_KEY in config) - leap_assert_type(config[KEY_KEY], (str, unicode)) - leap_assert(ENCRYPTED_ONLY_KEY in config) - leap_assert_type(config[ENCRYPTED_ONLY_KEY], bool) - # assert received params are not empty - leap_assert(config[HOST_KEY] != '') - leap_assert(config[PORT_KEY] is not 0) - leap_assert(config[CERT_KEY] != '') - leap_assert(config[KEY_KEY] != '') - - def validate_address(address): """ Validate C{address} as defined in RFC 2822. - @param address: The address to be validated. - @type address: str + :param address: The address to be validated. + :type address: str @return: A valid address. @rtype: str @@ -153,45 +101,60 @@ class SMTPFactory(ServerFactory): Factory for an SMTP server with encrypted relaying capabilities. """ - def __init__(self, userid, keymanager, config): + def __init__(self, userid, keymanager, host, port, cert, key, + encrypted_only): """ Initialize the SMTP factory. - @param keymanager: A KeyManager for retrieving recipient's keys. - @type keymanager: leap.common.keymanager.KeyManager - @param config: A dictionary with smtp configuration. Should have - the following structure: - { - HOST_KEY: '', - PORT_KEY: , - CERT_KEY: '', - KEY_KEY: '', - ENCRYPTED_ONLY_KEY: , - } - @type config: dict - @param userid: The user currently logged in - @type userid: unicode + :param userid: The user currently logged in + :type userid: unicode + :param keymanager: A KeyManager for retrieving recipient's keys. + :type keymanager: leap.common.keymanager.KeyManager + :param host: The hostname of the remote SMTP server. + :type host: str + :param port: The port of the remote SMTP server. + :type port: int + :param cert: The client certificate for authentication. + :type cert: str + :param key: The client key for authentication. + :type key: str + :param encrypted_only: Whether the SMTP relay should send unencrypted mail + or not. + :type encrypted_only: bool """ # assert params leap_assert_type(keymanager, KeyManager) - assert_config_structure(config) + leap_assert_type(host, str) + leap_assert(host != '') + leap_assert_type(port, int) + leap_assert(port is not 0) + leap_assert_type(cert, str) + leap_assert(cert != '') + leap_assert_type(key, str) + leap_assert(key != '') + leap_assert_type(encrypted_only, bool) # and store them self._userid = userid self._km = keymanager - self._config = config + self._host = host + self._port = port + self._cert = cert + self._key = key + self._encrypted_only = encrypted_only def buildProtocol(self, addr): """ Return a protocol suitable for the job. - @param addr: An address, e.g. a TCP (host, port). - @type addr: twisted.internet.interfaces.IAddress + :param addr: An address, e.g. a TCP (host, port). + :type addr: twisted.internet.interfaces.IAddress @return: The protocol. @rtype: SMTPDelivery """ - smtpProtocol = smtp.SMTP(SMTPDelivery(self._userid, self._km, - self._config)) + smtpProtocol = smtp.SMTP(SMTPDelivery( + self._userid, self._km, self._host, self._port, self._cert, + self._key, self._encrypted_only)) smtpProtocol.factory = self return smtpProtocol @@ -207,49 +170,53 @@ class SMTPDelivery(object): implements(smtp.IMessageDelivery) - def __init__(self, userid, keymanager, config): + def __init__(self, userid, keymanager, host, port, cert, key, + encrypted_only): """ Initialize the SMTP delivery object. - @param keymanager: A KeyManager for retrieving recipient's keys. - @type keymanager: leap.common.keymanager.KeyManager - @param config: A dictionary with smtp configuration. Should have - the following structure: - { - HOST_KEY: '', - PORT_KEY: , - CERT_KEY: '', - KEY_KEY: '', - ENCRYPTED_ONLY_KEY: , - } - @type config: dict - @param userid: The user currently logged in - @type userid: unicode + :param userid: The user currently logged in + :type userid: unicode + :param keymanager: A KeyManager for retrieving recipient's keys. + :type keymanager: leap.common.keymanager.KeyManager + :param host: The hostname of the remote SMTP server. + :type host: str + :param port: The port of the remote SMTP server. + :type port: int + :param cert: The client certificate for authentication. + :type cert: str + :param key: The client key for authentication. + :type key: str + :param encrypted_only: Whether the SMTP relay should send unencrypted mail + or not. + :type encrypted_only: bool """ - # assert params - leap_assert_type(keymanager, KeyManager) - assert_config_structure(config) - # and store them self._userid = userid self._km = keymanager - self._config = config + self._userid = userid + self._km = keymanager + self._host = host + self._port = port + self._cert = cert + self._key = key + self._encrypted_only = encrypted_only self._origin = None def receivedHeader(self, helo, origin, recipients): """ Generate the 'Received:' header for a message. - @param helo: The argument to the HELO command and the client's IP + :param helo: The argument to the HELO command and the client's IP address. - @type helo: (str, str) - @param origin: The address the message is from. - @type origin: twisted.mail.smtp.Address - @param recipients: A list of the addresses for which this message is + :type helo: (str, str) + :param origin: The address the message is from. + :type origin: twisted.mail.smtp.Address + :param recipients: A list of the addresses for which this message is bound. - @type: list of twisted.mail.smtp.User + :type: list of twisted.mail.smtp.User @return: The full "Received" header string. - @type: str + :type: str """ myHostname, clientIP = helo headerValue = "by %s from %s with ESMTP ; %s" % ( @@ -269,8 +236,8 @@ class SMTPDelivery(object): In the end, it returns an encrypted message object that is able to send itself to the C{user}'s address. - @param user: The user whose address we wish to validate. - @type: twisted.mail.smtp.User + :param user: The user whose address we wish to validate. + :type: twisted.mail.smtp.User @return: A Deferred which becomes, or a callable which takes no arguments and returns an object implementing IMessage. This will @@ -290,7 +257,7 @@ class SMTPDelivery(object): signal(proto.SMTP_RECIPIENT_ACCEPTED_ENCRYPTED, user.dest.addrstr) except KeyNotFound: # if key was not found, check config to see if will send anyway. - if self._config[ENCRYPTED_ONLY_KEY]: + if self._encrypted_only: signal(proto.SMTP_RECIPIENT_REJECTED, user.dest.addrstr) raise smtp.SMTPBadRcpt(user.dest.addrstr) log.msg("Warning: will send an unencrypted message (because " @@ -298,17 +265,18 @@ class SMTPDelivery(object): signal( proto.SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED, user.dest.addrstr) return lambda: EncryptedMessage( - self._origin, user, self._km, self._config) + self._origin, user, self._km, self._host, self._port, self._cert, + self._key) def validateFrom(self, helo, origin): """ Validate the address from which the message originates. - @param helo: The argument to the HELO command and the client's IP + :param helo: The argument to the HELO command and the client's IP address. - @type: (str, str) - @param origin: The address the message is from. - @type origin: twisted.mail.smtp.Address + :type: (str, str) + :param origin: The address the message is from. + :type origin: twisted.mail.smtp.Address @return: origin or a Deferred whose callback will be passed origin. @rtype: Deferred or Address @@ -360,36 +328,36 @@ class EncryptedMessage(object): """ implements(smtp.IMessage) - def __init__(self, fromAddress, user, keymanager, config): + def __init__(self, fromAddress, user, keymanager, host, port, cert, key): """ Initialize the encrypted message. - @param fromAddress: The address of the sender. - @type fromAddress: twisted.mail.smtp.Address - @param user: The recipient of this message. - @type user: twisted.mail.smtp.User - @param keymanager: A KeyManager for retrieving recipient's keys. - @type keymanager: leap.common.keymanager.KeyManager - @param config: A dictionary with smtp configuration. Should have - the following structure: - { - HOST_KEY: '', - PORT_KEY: , - CERT_KEY: '', - KEY_KEY: '', - ENCRYPTED_ONLY_KEY: , - } - @type config: dict + :param fromAddress: The address of the sender. + :type fromAddress: twisted.mail.smtp.Address + :param user: The recipient of this message. + :type user: twisted.mail.smtp.User + :param keymanager: A KeyManager for retrieving recipient's keys. + :type keymanager: leap.common.keymanager.KeyManager + :param host: The hostname of the remote SMTP server. + :type host: str + :param port: The port of the remote SMTP server. + :type port: int + :param cert: The client certificate for authentication. + :type cert: str + :param key: The client key for authentication. + :type key: str """ # assert params leap_assert_type(user, smtp.User) leap_assert_type(keymanager, KeyManager) - assert_config_structure(config) # and store them self._fromAddress = fromAddress self._user = user self._km = keymanager - self._config = config + self._host = host + self._port = port + self._cert = cert + self._key = key # initialize list for message's lines self.lines = [] @@ -401,8 +369,8 @@ class EncryptedMessage(object): """ Handle another line. - @param line: The received line. - @type line: str + :param line: The received line. + :type line: str """ self.lines.append(line) @@ -441,8 +409,8 @@ class EncryptedMessage(object): """ Callback for a successful send. - @param r: The result from the last previous callback in the chain. - @type r: anything + :param r: The result from the last previous callback in the chain. + :type r: anything """ log.msg(r) signal(proto.SMTP_SEND_MESSAGE_SUCCESS, self._user.dest.addrstr) @@ -451,8 +419,8 @@ class EncryptedMessage(object): """ Callback for an unsuccessfull send. - @param e: The result from the last errback. - @type e: anything + :param e: The result from the last errback. + :type e: anything """ log.msg(e) log.err() @@ -471,8 +439,7 @@ class EncryptedMessage(object): """ msg = self._msg.as_string(False) - log.msg("Connecting to SMTP server %s:%s" % (self._config[HOST_KEY], - self._config[PORT_KEY])) + log.msg("Connecting to SMTP server %s:%s" % (self._host, self._port)) d = defer.Deferred() # we don't pass an ssl context factory to the ESMTPSenderFactory @@ -488,11 +455,8 @@ class EncryptedMessage(object): requireTransportSecurity=True) signal(proto.SMTP_SEND_MESSAGE_START, self._user.dest.addrstr) reactor.connectSSL( - self._config[HOST_KEY], - self._config[PORT_KEY], - factory, - contextFactory=SSLContextFactory(self._config[CERT_KEY], - self._config[KEY_KEY])) + self._host, self._port, factory, + contextFactory=SSLContextFactory(self._cert, self._key)) d.addCallback(self.sendSuccess) d.addErrback(self.sendError) return d @@ -506,10 +470,10 @@ class EncryptedMessage(object): Create an RFC 3156 compliang PGP encrypted and signed message using C{pubkey} to encrypt and C{signkey} to sign. - @param pubkey: The public key used to encrypt the message. - @type pubkey: leap.common.keymanager.openpgp.OpenPGPKey - @param signkey: The private key used to sign the message. - @type signkey: leap.common.keymanager.openpgp.OpenPGPKey + :param pubkey: The public key used to encrypt the message. + :type pubkey: leap.common.keymanager.openpgp.OpenPGPKey + :param signkey: The private key used to sign the message. + :type signkey: leap.common.keymanager.openpgp.OpenPGPKey """ # create new multipart/encrypted message with 'pgp-encrypted' protocol newmsg = MultipartEncrypted('application/pgp-encrypted') @@ -534,8 +498,8 @@ class EncryptedMessage(object): """ Create an RFC 3156 compliant PGP signed MIME message using C{signkey}. - @param signkey: The private key used to sign the message. - @type signkey: leap.common.keymanager.openpgp.OpenPGPKey + :param signkey: The private key used to sign the message. + :type signkey: leap.common.keymanager.openpgp.OpenPGPKey """ # create new multipart/signed message newmsg = MultipartSigned('application/pgp-signature', 'pgp-sha512') diff --git a/src/leap/mail/smtp/tests/__init__.py b/src/leap/mail/smtp/tests/__init__.py index 9b54de3..ee6de9b 100644 --- a/src/leap/mail/smtp/tests/__init__.py +++ b/src/leap/mail/smtp/tests/__init__.py @@ -59,7 +59,7 @@ class TestCaseWithKeyManager(BaseLeapTest): # setup our own stuff address = 'leap@leap.se' # user's address in the form user@provider uuid = 'leap@leap.se' - passphrase = '123' + passphrase = u'123' secrets_path = os.path.join(self.tempdir, 'secret.gpg') local_db_path = os.path.join(self.tempdir, 'soledad.u1db') server_url = 'http://provider/' @@ -88,6 +88,8 @@ class TestCaseWithKeyManager(BaseLeapTest): get_doc = Mock(return_value=None) put_doc = Mock(side_effect=_put_doc_side_effect) + lock = Mock(return_value=('atoken', 300)) + unlock = Mock(return_value=True) def __call__(self): return self diff --git a/src/leap/mail/smtp/tests/test_smtprelay.py b/src/leap/mail/smtp/tests/test_smtprelay.py index 7fefe77..25c780e 100644 --- a/src/leap/mail/smtp/tests/test_smtprelay.py +++ b/src/leap/mail/smtp/tests/test_smtprelay.py @@ -102,8 +102,10 @@ class TestSmtpRelay(TestCaseWithKeyManager): '250 Sender address accepted', '250 Recipient address accepted', '354 Continue'] - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + self._config['encrypted_only']).buildProtocol(('127.0.0.1', 0)) transport = proto_helpers.StringTransport() proto.makeConnection(transport) for i, line in enumerate(self.EMAIL_DATA): @@ -117,11 +119,15 @@ class TestSmtpRelay(TestCaseWithKeyManager): """ Test if message gets encrypted to destination email. """ - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + self._config['encrypted_only']).buildProtocol(('127.0.0.1', 0)) fromAddr = Address(ADDRESS_2) dest = User(ADDRESS, 'relay.leap.se', proto, ADDRESS) - m = EncryptedMessage(fromAddr, dest, self._km, self._config) + m = EncryptedMessage( + fromAddr, dest, self._km, self._config['host'], + self._config['port'], self._config['cert'], self._config['key']) for line in self.EMAIL_DATA[4:12]: m.lineReceived(line) m.eomReceived() @@ -149,11 +155,15 @@ class TestSmtpRelay(TestCaseWithKeyManager): Test if message gets encrypted to destination email and signed with sender key. """ - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + self._config['encrypted_only']).buildProtocol(('127.0.0.1', 0)) user = User(ADDRESS, 'relay.leap.se', proto, ADDRESS) fromAddr = Address(ADDRESS_2) - m = EncryptedMessage(fromAddr, user, self._km, self._config) + m = EncryptedMessage( + fromAddr, user, self._km, self._config['host'], + self._config['port'], self._config['cert'], self._config['key']) for line in self.EMAIL_DATA[4:12]: m.lineReceived(line) # trigger encryption and signing @@ -185,11 +195,15 @@ class TestSmtpRelay(TestCaseWithKeyManager): """ # mock the key fetching self._km.fetch_keys_from_server = Mock(return_value=[]) - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + self._config['encrypted_only']).buildProtocol(('127.0.0.1', 0)) user = User('ihavenopubkey@nonleap.se', 'relay.leap.se', proto, ADDRESS) fromAddr = Address(ADDRESS_2) - m = EncryptedMessage(fromAddr, user, self._km, self._config) + m = EncryptedMessage( + fromAddr, user, self._km, self._config['host'], + self._config['port'], self._config['cert'], self._config['key']) for line in self.EMAIL_DATA[4:12]: m.lineReceived(line) # trigger signing @@ -237,8 +251,10 @@ class TestSmtpRelay(TestCaseWithKeyManager): # mock the key fetching self._km.fetch_keys_from_server = Mock(return_value=[]) # prepare the SMTP factory - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + self._config['encrypted_only']).buildProtocol(('127.0.0.1', 0)) transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.lineReceived(self.EMAIL_DATA[0] + '\r\n') @@ -263,11 +279,11 @@ class TestSmtpRelay(TestCaseWithKeyManager): pgp.delete_key(pubkey) # mock the key fetching self._km.fetch_keys_from_server = Mock(return_value=[]) - # change the configuration - self._config['encrypted_only'] = False - # prepare the SMTP factory - proto = SMTPFactory( - self._km, self._config).buildProtocol(('127.0.0.1', 0)) + # prepare the SMTP factory with encrypted only equal to false + proto = SMTPFactory(u'anotheruser@leap.se', + self._km, self._config['host'], self._config['port'], + self._config['cert'], self._config['key'], + False).buildProtocol(('127.0.0.1', 0)) transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.lineReceived(self.EMAIL_DATA[0] + '\r\n') -- cgit v1.2.3