Пример #1
0
 def getCanonicalDomain(self, domain):
     try:
         canonical = canonicalizeEmailDomain(domain, self.context.canon)
     except (UnsupportedDomain, BadEmail) as error:
         logging.warn(error)
     else:
         return canonical
Пример #2
0
 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')
Пример #3
0
 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')
Пример #4
0
    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
Пример #5
0
    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
Пример #6
0
    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
Пример #7
0
 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.
Пример #8
0
    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
Пример #9
0
    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