def getCanonicalDomain(self, domain): try: canonical = canonicalizeEmailDomain(domain, self.context.canon) except (UnsupportedDomain, BadEmail) as error: logging.warn(error) else: return canonical
def test_permitted(self): """A domain in the domainmap of allowed domains should return the canonical domain. """ domainmap = {'foo.example.com': 'example.com'} domain = 'foo.example.com' canonical = addr.canonicalizeEmailDomain(domain, domainmap) self.assertEquals(canonical, 'example.com')
def test_permitted(self): """A domain in the domainmap of allowed domains should return the canonical domain. """ domainmap = {'foo.example.com': 'example.com'} domain = 'foo.example.com' canonical = addr.canonicalizeEmailDomain(domain, domainmap) self.assertEquals(canonical, 'example.com')
def runChecks(self, client): """Run checks on the incoming message, and only reply if they pass. 1. Check that the domain names, taken from the SMTP ``MAIL FROM:`` command and the email ``'From:'`` header, can be :func:`canonicalized <addr.canonicalizeEmailDomain>`. 2. Check that those canonical domains match, 3. If the incoming message is from a domain which supports DKIM signing, then run :func:`bridgedb.email.dkim.checkDKIM` as well. .. note:: Calling this method sets the ``canonicalFromEmail`` and :ivar:``canonicalDomainRules`` attributes of the :ivar:`incoming` message. :param client: An :api:`twisted.mail.smtp.Address`, which contains the client's email address, extracted from the ``'From:'`` header from the incoming email. :rtype: bool :returns: ``False`` if the checks didn't pass, ``True`` otherwise. """ # If the SMTP ``RCPT TO:`` domain name couldn't be canonicalized, then # we *should* have bailed at the SMTP layer, but we'll reject this # email again nonetheless: if not self.incoming.canonicalFromSMTP: logging.warn(("SMTP 'MAIL FROM' wasn't from a canonical domain " "for email from %s") % str(client)) return False logging.debug("Canonicalizing client email domain...") # The client's address was already checked to see if it came from a # supported domain and is a valid email address in :meth:`getMailTo`, # so we should just be able to re-extract the canonical domain safely # here: canonicalFromEmail = addr.canonicalizeEmailDomain( client.domain, self.incoming.canon) logging.debug("Canonical email domain: %s" % canonicalFromEmail) # The canonical domains from the SMTP ``MAIL FROM:`` and the email # ``From:`` header should match: if self.incoming.canonicalFromSMTP != canonicalFromEmail: logging.error("SMTP/Email canonical domain mismatch!") return False domainRules = self.incoming.context.domainRules.get( canonicalFromEmail, list()) # If the domain's ``domainRules`` say to check DKIM verification # results, and those results look bad, reject this email: if not dkim.checkDKIM(self.incoming.message, domainRules): return False self.incoming.canonicalDomainRules = domainRules self.incoming.canonicalFromEmail = canonicalFromEmail return True
def validateFrom(self, helo, origin): """Validate the ``MAIL FROM:`` address on the incoming SMTP connection. This is done at the SMTP layer. Meaning that if a Postfix or other email server is proxying emails from the outside world to BridgeDB, the :api:`origin.domain <twisted.email.smtp.Address.domain>` will be set to the local hostname. Therefore, if the SMTP ``MAIL FROM:`` domain name is our own hostname (as returned from :func:`socket.gethostname`) or our own FQDN, allow the connection. Otherwise, if the ``MAIL FROM:`` domain has a canonical domain in our mapping (taken from our :data:`context.canon`, which is taken in turn from the ``EMAIL_DOMAIN_MAP``), then our :data:`fromCanonicalSMTP` is set to that domain. :type helo: tuple :param helo: The lines received during SMTP client HELO. :type origin: :api:`twisted.mail.smtp.Address` :param origin: The email address we received this message from. :raises: :api:`twisted.mail.smtp.SMTPBadSender` if the ``origin.domain`` was neither our local hostname, nor one of the canonical domains listed in :attr:`context.canon`. :rtype: :api:`twisted.mail.smtp.Address` :returns: The ``origin``. We *must* return some non-``None`` data from this method, or else Twisted will reply to the sender with a 503 error. """ try: if str(origin) in self.context.whitelist.keys(): logging.warn("Got SMTP 'MAIL FROM:' whitelisted address: %s." % str(origin)) # We need to be certain later that when the fromCanonicalSMTP # domain is checked again the email 'From:' canonical domain, # that we allow whitelisted addresses through the check. self.fromCanonicalSMTP = origin.domain return origin if ((origin.domain == self.context.hostname) or (origin.domain == smtp.DNSNAME)): self.fromCanonicalSMTP = origin.domain else: logging.debug("Canonicalizing client SMTP domain...") canonical = canonicalizeEmailDomain(origin.domain, self.context.canon) logging.debug("Canonical SMTP domain: %r" % canonical) self.fromCanonicalSMTP = canonical except UnsupportedDomain as error: logging.info(error) raise smtp.SMTPBadSender(origin) except Exception as error: logging.exception(error) # This method **cannot** return None, or it'll cause a 503 error. return origin
def validateFrom(self, helo, origin): """Validate the ``MAIL FROM:`` address on the incoming SMTP connection. This is done at the SMTP layer. Meaning that if a Postfix or other email server is proxying emails from the outside world to BridgeDB, the :api:`origin.domain <twisted.email.smtp.Address.domain` will be set to the local hostname. Therefore, if the SMTP ``MAIL FROM:`` domain name is our own hostname (as returned from :func:`socket.gethostname`) or our own FQDN, allow the connection. Otherwise, if the ``MAIL FROM:`` domain has a canonical domain in our mapping (taken from :ivar:`context.canon <MailServerContext.canon>`, which is taken in turn from the ``EMAIL_DOMAIN_MAP``), then our :ivar:`fromCanonicalSMTP` is set to that domain. :type helo: tuple :param helo: The lines received during SMTP client HELO. :type origin: :api:`twisted.mail.smtp.Address` :param origin: The email address we received this message from. :raises: :api:`twisted.mail.smtp.SMTPBadSender` if the ``origin.domain`` was neither our local hostname, nor one of the canonical domains listed in :ivar:`context.canon`. :rtype: :api:`twisted.mail.smtp.Address` :returns: The ``origin``. We *must* return some non-``None`` data from this method, or else Twisted will reply to the sender with a 503 error. """ try: if str(origin) in self.context.whitelist.keys(): logging.warn("Got SMTP 'MAIL FROM:' whitelisted address: %s." % str(origin)) # We need to be certain later that when the fromCanonicalSMTP # domain is checked again the email 'From:' canonical domain, # that we allow whitelisted addresses through the check. self.fromCanonicalSMTP = origin.domain return origin if ((origin.domain == self.context.hostname) or (origin.domain == smtp.DNSNAME)): self.fromCanonicalSMTP = origin.domain else: logging.debug("Canonicalizing client SMTP domain...") canonical = canonicalizeEmailDomain(origin.domain, self.context.canon) logging.debug("Canonical SMTP domain: %r" % canonical) self.fromCanonicalSMTP = canonical except UnsupportedDomain as error: logging.info(error) raise smtp.SMTPBadSender(origin) except Exception as error: logging.exception(error) # This method **cannot** return None, or it'll cause a 503 error. return origin
def validateFrom(self, helo, origin): try: logging.debug("ORIGIN: %r" % repr(origin.addrstr)) canonical = canonicalizeEmailDomain(origin.domain, self.context.canon) except UnsupportedDomain as error: logging.info(error) raise smtp.SMTPBadSender(origin.domain) except Exception as error: logging.exception(error) else: logging.debug("Got canonical domain: %r" % canonical) self.fromCanonical = canonical return origin # This method *cannot* return None, or it'll cause a 503.
def runChecks(self, client): """Run checks on the incoming message, and only reply if they pass. 1. Check if the client's address is whitelisted. 2. If it's not whitelisted, check that the domain names, taken from the SMTP ``MAIL FROM:`` command and the email ``'From:'`` header, can be :func:`canonicalized <addr.canonicalizeEmailDomain>`. 3. Check that those canonical domains match. 4. If the incoming message is from a domain which supports DKIM signing, then run :func:`bridgedb.email.dkim.checkDKIM` as well. .. note:: Calling this method sets the ``canonicalFromEmail`` and :data:``canonicalDomainRules`` attributes of the :data:`incoming` message. :param client: An :api:`twisted.mail.smtp.Address`, which contains the client's email address, extracted from the ``'From:'`` header from the incoming email. :rtype: bool :returns: ``False`` if the checks didn't pass, ``True`` otherwise. """ # If the SMTP ``RCPT TO:`` domain name couldn't be canonicalized, then # we *should* have bailed at the SMTP layer, but we'll reject this # email again nonetheless: if not self.incoming.canonicalFromSMTP: logging.warn(("SMTP 'MAIL FROM' wasn't from a canonical domain " "for email from %s") % str(client)) return False # Allow whitelisted addresses through the canonicalization check: if str(client) in self.incoming.context.whitelist.keys(): self.incoming.canonicalFromEmail = client.domain logging.info("'From:' header contained whitelisted address: %s" % str(client)) # Straight up reject addresses in the EMAIL_BLACKLIST config option: elif str(client) in self.incoming.context.blacklist: logging.info("'From:' header contained blacklisted address: %s") return False else: logging.debug("Canonicalizing client email domain...") try: # The client's address was already checked to see if it came # from a supported domain and is a valid email address in # :meth:`getMailTo`, so we should just be able to re-extract # the canonical domain safely here: self.incoming.canonicalFromEmail = canonicalizeEmailDomain( client.domain, self.incoming.canon) logging.debug("Canonical email domain: %s" % self.incoming.canonicalFromEmail) except addr.UnsupportedDomain as error: logging.info("Domain couldn't be canonicalized: %s" % safelog.logSafely(client.domain)) return False # The canonical domains from the SMTP ``MAIL FROM:`` and the email # ``From:`` header should match: if self.incoming.canonicalFromSMTP != self.incoming.canonicalFromEmail: logging.error("SMTP/Email canonical domain mismatch!") logging.debug("Canonical domain mismatch: %s != %s" % (self.incoming.canonicalFromSMTP, self.incoming.canonicalFromEmail)) #return False self.incoming.domainRules = self.incoming.context.domainRules.get( self.incoming.canonicalFromEmail, list()) # If the domain's ``domainRules`` say to check DKIM verification # results, and those results look bad, reject this email: if not dkim.checkDKIM(self.incoming.message, self.incoming.domainRules): return False # If fuzzy matching is enabled via the EMAIL_FUZZY_MATCH setting, then # calculate the Levenshtein String Distance (see # :func:`~bridgedb.util.levenshteinDistance`): if self.incoming.context.fuzzyMatch != 0: for blacklistedAddress in self.incoming.context.blacklist: distance = levenshteinDistance(str(client), blacklistedAddress) if distance <= self.incoming.context.fuzzyMatch: logging.info( "Fuzzy-matched %s to blacklisted address %s!" % (self.incoming.canonicalFromEmail, blacklistedAddress)) return False return True
def runChecks(self, client): """Run checks on the incoming message, and only reply if they pass. 1. Check if the client's address is whitelisted. 2. If it's not whitelisted, check that the domain names, taken from the SMTP ``MAIL FROM:`` command and the email ``'From:'`` header, can be :func:`canonicalized <addr.canonicalizeEmailDomain>`. 3. Check that those canonical domains match. 4. If the incoming message is from a domain which supports DKIM signing, then run :func:`bridgedb.email.dkim.checkDKIM` as well. .. note:: Calling this method sets the ``canonicalFromEmail`` and :data:``canonicalDomainRules`` attributes of the :data:`incoming` message. :param client: An :api:`twisted.mail.smtp.Address`, which contains the client's email address, extracted from the ``'From:'`` header from the incoming email. :rtype: bool :returns: ``False`` if the checks didn't pass, ``True`` otherwise. """ # If the SMTP ``RCPT TO:`` domain name couldn't be canonicalized, then # we *should* have bailed at the SMTP layer, but we'll reject this # email again nonetheless: if not self.incoming.canonicalFromSMTP: logging.warn(("SMTP 'MAIL FROM' wasn't from a canonical domain " "for email from %s") % str(client)) return False # Allow whitelisted addresses through the canonicalization check: if str(client) in self.incoming.context.whitelist.keys(): self.incoming.canonicalFromEmail = client.domain logging.info("'From:' header contained whitelisted address: %s" % str(client)) # Straight up reject addresses in the EMAIL_BLACKLIST config option: elif str(client) in self.incoming.context.blacklist: logging.info("'From:' header contained blacklisted address: %s") return False else: logging.debug("Canonicalizing client email domain...") try: # The client's address was already checked to see if it came # from a supported domain and is a valid email address in # :meth:`getMailTo`, so we should just be able to re-extract # the canonical domain safely here: self.incoming.canonicalFromEmail = canonicalizeEmailDomain( client.domain, self.incoming.canon) logging.debug("Canonical email domain: %s" % self.incoming.canonicalFromEmail) except addr.UnsupportedDomain as error: logging.info("Domain couldn't be canonicalized: %s" % safelog.logSafely(client.domain)) return False # The canonical domains from the SMTP ``MAIL FROM:`` and the email # ``From:`` header should match: if self.incoming.canonicalFromSMTP != self.incoming.canonicalFromEmail: logging.error("SMTP/Email canonical domain mismatch!") logging.debug("Canonical domain mismatch: %s != %s" % (self.incoming.canonicalFromSMTP, self.incoming.canonicalFromEmail)) #return False self.incoming.domainRules = self.incoming.context.domainRules.get( self.incoming.canonicalFromEmail, list()) # If the domain's ``domainRules`` say to check DKIM verification # results, and those results look bad, reject this email: if not dkim.checkDKIM(self.incoming.message, self.incoming.domainRules): return False # If fuzzy matching is enabled via the EMAIL_FUZZY_MATCH setting, then # calculate the Levenshtein String Distance (see # :func:`~bridgedb.util.levenshteinDistance`): if self.incoming.context.fuzzyMatch != 0: for blacklistedAddress in self.incoming.context.blacklist: distance = levenshteinDistance(str(client), blacklistedAddress) if distance <= self.incoming.context.fuzzyMatch: logging.info("Fuzzy-matched %s to blacklisted address %s!" % (self.incoming.canonicalFromEmail, blacklistedAddress)) return False return True