def getMailTo(self): """Attempt to get the client's email address from an incoming email. :rtype: list :returns: A list containing the client's :func:`normalized <bridgedb.parse.addr.normalizeEmail>` email :api:`Address <twisted.mail.smtp.Address>`, if it originated from a domain that we accept and the address was well-formed. Otherwise, returns ``None``. Even though we're likely to respond to only one client at a time, the return value of this method must be a list in order to hook into the rest of :api:`twisted.mail.smtp.SMTPClient` correctly. """ clients = [] addrHeader = None try: fromAddr = email.utils.parseaddr( self.incoming.message.get("From"))[1] except (IndexError, TypeError, AttributeError): pass else: addrHeader = fromAddr if not addrHeader: logging.warn("No From header on incoming mail.") try: senderHeader = email.utils.parseaddr( self.incoming.message.get("Sender"))[1] except (IndexError, TypeError, AttributeError): pass else: addrHeader = senderHeader if not addrHeader: logging.warn("No Sender header on incoming mail.") return clients client = None try: if addrHeader in self.incoming.context.whitelist.keys(): logging.debug("Email address was whitelisted: %s." % addrHeader) client = smtp.Address(addrHeader) else: normalized = addr.normalizeEmail( addrHeader, self.incoming.context.domainMap, self.incoming.context.domainRules) client = smtp.Address(normalized) except (addr.UnsupportedDomain) as error: logging.warn(error) except (addr.BadEmail, smtp.AddressError) as error: logging.warn(error) if client: clients.append(client) return clients
def testQuoteAddr(self): cases = [ ['*****@*****.**', '<*****@*****.**>'], ['"User Name" <*****@*****.**>', '<*****@*****.**>'], [smtp.Address('someguy@someplace'), '<someguy@someplace>'], ['', '<>'], [smtp.Address(''), '<>'], ] for (c, e) in cases: self.assertEquals(smtp.quoteaddr(c), e)
def setUp(self): self.settings = SettingsMock() self.settings.load() self.factory = smtp2web.ESMTPFactory(self.settings) self.smtp = self.factory.buildProtocol(None) # Cheating here to bypass testing smtp.ESMTP self.delivery = self.smtp.deliveryFactory.getMessageDelivery() self.sender = smtp.Address("*****@*****.**") self.test_message = """From: Me <*****@*****.**>
def getMailFrom(self): """Find our address in the recipients list of the **incoming** message. :rtype: str :return: Our address from the recipients list. If we can't find it return our default ``EMAIL_FROM_ADDRESS`` from the config file. """ logging.debug("Searching for our email address in 'To:' header...") ours = None try: ourAddress = smtp.Address(self.incoming.context.fromAddr) allRecipients = self.incoming.message.get_all("To") for address in allRecipients: recipient = smtp.Address(address) if not ourAddress.domain in recipient.domain: logging.debug(("Not our domain (%s) or subdomain, skipping" " email address: %s") % (ourAddress.domain, str(recipient))) continue # The recipient's username should at least start with ours, # but it still might be a '+' address. if not recipient.local.startswith(ourAddress.local): logging.debug(("Username doesn't begin with ours, skipping" " email address: %s") % str(recipient)) continue # Only check the username before the first '+': beforePlus = recipient.local.split(b'+', 1)[0] if beforePlus == ourAddress.local: ours = str(recipient) if not ours: raise addr.BadEmail( 'No email address accepted, please see log', allRecipients) except Exception as error: logging.error(("Couldn't find our email address in incoming email " "headers: %r" % error)) # Just return the email address that we're configured to use: ours = self.incoming.context.fromAddr logging.debug("Found our email address: %s." % ours) return ours
def __init__(self, domains, original): """ @type domains: L{dict} mapping L{bytes} to L{IDomain} provider @param domains: A mapping of domain name to domain object. @type original: L{bytes} @param original: The original address being aliased. """ self.domains = domains self.original = smtp.Address(original)
def test_validateFromAuthenticatedNonLocal(self): """ Test that using a non-local address as the sender address after authenticating as a user is rejected. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') delivery = smtp.IMessageDeliveryFactory(avatar).getMessageDelivery() addr = smtp.Address('*****@*****.**') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) return self.assertFailure(d, smtp.SMTPBadSender)
def test_validateFromAuthenticatedDisallowedLocal(self): """ Test that using a local address as the sender address after authenticating as a user who does /not/ own that address is rejected. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') delivery = smtp.IMessageDeliveryFactory(avatar).getMessageDelivery() addr = smtp.Address('admistrator@localhost') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) return self.assertFailure(d, smtp.SMTPBadSender)
def test_validateFromAuthenticatedLocal(self): """ Test that using a local address as the sender address after authenticating as the user who owns that address is accepted. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') delivery = smtp.IMessageDeliveryFactory(avatar).getMessageDelivery() addr = smtp.Address('testuser@localhost') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) return d.addCallback(self.assertEquals, addr)
def test_validateFromUnauthenticatedNonLocal(self): """ Test that using a non-local address as the sender address without authenticating first is accepted. """ factory = mail.MailTransferAgent(store=self.store) installOn(factory, self.store) delivery = factory.getMessageDelivery() addr = smtp.Address('*****@*****.**') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) return d.addCallback(self.assertEquals, addr)
def __init__(self, alias, *args): """ @type alias: L{Address}, L{User}, L{bytes} or object which can be converted into L{bytes} @param alias: The destination address. @type args: 2-L{tuple} of (0) L{dict} mapping L{bytes} to L{IDomain} provider, (1) L{bytes} @param args: Arguments for L{AliasBase.__init__}. """ AliasBase.__init__(self, *args) self.alias = smtp.Address(alias)
def loadAliasFile(domains, filename=None, fp=None): """Load a file containing email aliases. Lines in the file should be formatted like so:: username: alias1,alias2,...,aliasN Aliases beginning with a | will be treated as programs, will be run, and the message will be written to their stdin. Aliases without a host part will be assumed to be addresses on localhost. If a username is specified multiple times, the aliases for each are joined together as if they had all been on one line. @type domains: C{dict} of implementor of C{IDomain} @param domains: The domains to which these aliases will belong. @type filename: C{str} @param filename: The filename from which to load aliases. @type fp: Any file-like object. @param fp: If specified, overrides C{filename}, and aliases are read from it. @rtype: C{dict} @return: A dictionary mapping usernames to C{AliasGroup} objects. """ result = {} if fp is None: fp = file(filename) else: filename = getattr(fp, 'name', '<unknown>') i = 0 prev = '' for line in fp: i += 1 line = line.rstrip() if line.lstrip().startswith('#'): continue elif line.startswith(' ') or line.startswith('\t'): prev = prev + line else: if prev: handle(result, prev, filename, i) prev = line if prev: handle(result, prev, filename, i) for (u, a) in result.items(): addr = smtp.Address(u) result[u] = AliasGroup(a, domains, u) return result
def test_validateFromUnauthenticatedLocal(self): """ Test that using a local address as the sender address without authenticating as that user raises an exception to prevent the delivery. """ factory = mail.MailTransferAgent(store=self.store) installOn(factory, self.store) delivery = factory.getMessageDelivery() d = delivery.validateFrom( ('home.example.net', '192.168.1.1'), smtp.Address('testuser@localhost')) return self.assertFailure(d, smtp.SMTPBadSender)
def test_authenticatedReceivedHeader(self): """ Test that something at least minimally reasonable comes back from the receivedHeader method of L{AuthenticatedMessageDelivery}. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') composer = IMessageSender(avatar) delivery = mail.AuthenticatedMessageDelivery(composer.store, composer) header = delivery.receivedHeader( ("example.com", "192.168.123.45"), smtp.Address("testuser@localhost"), [smtp.User("*****@*****.**", None, None, None), smtp.User("*****@*****.**", None, None, None)]) self.failUnless( isinstance(header, str), "Got %r instead of a string" % (header,))
def test_validateToAuthenticatedNonLocal(self): """ Test that using a non-local address as the recipient address after authenticating as anyone is accepted. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') delivery = smtp.IMessageDeliveryFactory(avatar).getMessageDelivery() addr = smtp.Address('testuser@localhost') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('administrator', 'example.com'), None, None, None)) return d d.addCallback(validatedFrom) return d
def test_validateToAuthenticatedNonExistentLocal(self): """ Test that using as the recipient address a non-existent address which would exist locally if it existed at all is rejected. """ avatar = self.login.accountByAddress(u'testuser', u'localhost') delivery = smtp.IMessageDeliveryFactory(avatar).getMessageDelivery() addr = smtp.Address('testuser@localhost') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('nonexistent', 'localhost'), None, None, None)) return self.assertFailure(d, smtp.SMTPBadRcpt) d.addCallback(validatedFrom) return d
def test_validateToUnauthenticatedLocal(self): """ Test that using a local address as the recipient address without authenticating is accepted. """ factory = mail.MailTransferAgent(store=self.store) installOn(factory, self.store) delivery = factory.getMessageDelivery() addr = smtp.Address('*****@*****.**') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('testuser', 'localhost'), None, None, None)) return d d.addCallback(validatedFrom) return d
def test_validateToUnauthenticatedNonExistentLocal(self): """ Test that using as the recipient address a non-existent address which would exist locally if it existed at all is rejected. """ factory = mail.MailTransferAgent(store=self.store) installOn(factory, self.store) delivery = factory.getMessageDelivery() addr = smtp.Address('*****@*****.**') d = delivery.validateFrom(('home.example.net', '192.168.1.1'), addr) def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('nonexistent', 'localhost'), None, None, None)) return self.assertFailure(d, smtp.SMTPBadRcpt) d.addCallback(validatedFrom) return d
def validateTo(self, user): """Validate the SMTP ``RCPT TO:`` address for the incoming connection. The local username and domain name to which this SMTP message is addressed, after being stripped of any ``'+'`` aliases, **must** be identical to those in the email address set our ``EMAIL_SMTP_FROM_ADDR`` configuration file option. :type user: :api:`twisted.mail.smtp.User` :param user: Information about the user this SMTP message was addressed to. :raises: A :api:`twisted.mail.smtp.SMTPBadRcpt` if any of the above conditions weren't met. :rtype: callable :returns: A parameterless function which returns an instance of :class:`SMTPMessage`. """ logging.debug("Validating SMTP 'RCPT TO:' email address...") recipient = user.dest ourAddress = smtp.Address(self.context.smtpFromAddr) if not ((ourAddress.domain in recipient.domain) or (recipient.domain == b"bridgedb")): logging.debug(("Not our domain (%s) or subdomain, skipping" " SMTP 'RCPT TO' address: %s") % (ourAddress.domain.decode('utf-8'), str(recipient))) raise smtp.SMTPBadRcpt(str(recipient)) # The recipient's username should at least start with ours, # but it still might be a '+' address. if not recipient.local.startswith(ourAddress.local): logging.debug(("Username doesn't begin with ours, skipping" " SMTP 'RCPT TO' address: %s") % str(recipient)) raise smtp.SMTPBadRcpt(str(recipient)) # Ignore everything after the first '+', if there is one. beforePlus = recipient.local.split(b'+', 1)[0] if beforePlus != ourAddress.local: raise smtp.SMTPBadRcpt(str(recipient)) return lambda: SMTPMessage(self.context, self.fromCanonicalSMTP)
def test_authenticatedMultipleRecipientsOneRecord(self): """ Test that only one sent message object is created even if a messages is destined for multiple recipients. """ self.installStubSender(u'testuser', u'localhost') account = self.login.accountByAddress(u'testuser', u'localhost') factory = smtp.IMessageDeliveryFactory(account) delivery = factory.getMessageDelivery() d = delivery.validateFrom( ('home.example.net', '192.168.1.1'), smtp.Address('testuser@localhost')) def validatedFrom(ign): return gatherResults([ delivery.validateTo( smtp.User( smtp.Address(addr), None, None, None)) for addr in self.MULTI_RECIPIENT_ADDRESSES]) d.addCallback(validatedFrom) d.addCallback(self.deliverMessageAndVerify, u'testuser', u'localhost') return d
def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('testuser', 'localhost'), None, None, None)) return d
def loadAliasFile(domains, filename=None, fp=None): """ Load a file containing email aliases. Lines in the file should be formatted like so:: username: alias1, alias2, ..., aliasN Aliases beginning with a C{|} will be treated as programs, will be run, and the message will be written to their stdin. Aliases beginning with a C{:} will be treated as a file containing additional aliases for the username. Aliases beginning with a C{/} will be treated as the full pathname to a file to which the message will be appended. Aliases without a host part will be assumed to be addresses on localhost. If a username is specified multiple times, the aliases for each are joined together as if they had all been on one line. Lines beginning with a space or a tab are continuations of the previous line. Lines beginning with a C{#} are comments. @type domains: L{dict} mapping L{bytes} to L{IDomain} provider @param domains: A mapping of domain name to domain object. @type filename: L{bytes} or L{NoneType <types.NoneType>} @param filename: The full or relative path to a file from which to load aliases. If omitted, the C{fp} parameter must be specified. @type fp: file-like object or L{NoneType <types.NoneType>} @param fp: The file from which to load aliases. If specified, the C{filename} parameter is ignored. @rtype: L{dict} mapping L{bytes} to L{AliasGroup} @return: A mapping from username to group of aliases. """ result = {} if fp is None: fp = file(filename) else: filename = getattr(fp, 'name', '<unknown>') i = 0 prev = '' for line in fp: i += 1 line = line.rstrip() if line.lstrip().startswith('#'): continue elif line.startswith(' ') or line.startswith('\t'): prev = prev + line else: if prev: handle(result, prev, filename, i) prev = line if prev: handle(result, prev, filename, i) for (u, a) in result.items(): addr = smtp.Address(u) result[u] = AliasGroup(a, domains, u) return result
def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('administrator', 'localhost'), None, None, None)) return d
def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('*****@*****.**'), None, None, None)) return self.assertFailure(d, smtp.SMTPBadRcpt)
def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('administrator', 'example.com'), None, None, None)) return d
def validatedFrom(ign): d = delivery.validateTo( smtp.User( smtp.Address('nonexistent', 'localhost'), None, None, None)) return self.assertFailure(d, smtp.SMTPBadRcpt)
def __init__(self, domains, original): self.domains = domains self.original = smtp.Address(original)
def validatedFrom(ign): return gatherResults([ delivery.validateTo( smtp.User( smtp.Address(addr), None, None, None)) for addr in self.MULTI_RECIPIENT_ADDRESSES])
def __init__(self, alias, *args): AliasBase.__init__(self, *args) self.alias = smtp.Address(alias)