Exemple #1
0
    def test_email_metrics(self):

        config = _createConfig()
        context = _createMailServerContext(config)
        message = SMTPMessage(context)
        message.lines = [
            "From: [email protected]",
            "To: [email protected]",
            "Subject: testing",
            "",
            "get transport obfs4",
        ]

        message.message = message.getIncomingMessage()
        responder = message.responder
        tr = proto_helpers.StringTransportWithDisconnection()
        tr.protocol = responder
        responder.makeConnection(tr)

        email_metrix = metrics.EmailMetrics()

        key1 = "email.obfs4.gmail.success.none"
        email_metrix.recordValidEmailRequest(responder)
        self.assertTrue(email_metrix.hotMetrics[key1] == 1)

        key2 = "email.obfs4.gmail.fail.none"
        email_metrix.recordInvalidEmailRequest(responder)
        self.assertTrue(email_metrix.hotMetrics[key2] == 1)
Exemple #2
0
 def setUp(self):
     self.config = _createConfig()
     self.context = _createMailServerContext(self.config)
     self.message = SMTPMessage(self.context)
Exemple #3
0
class SMTPAutoresponderTests(unittest.TestCase):
    """Unittests for :class:`bridgedb.distributors.email.autoresponder.SMTPAutoresponder`."""

    timeout = 10

    def setUp(self):
        self.config = _createConfig()
        self.context = _createMailServerContext(self.config)
        self.message = SMTPMessage(self.context)

    def _getIncomingLines(self, clientAddress="*****@*****.**"):
        """Generate the lines of an incoming email from **clientAddress**."""
        lines = [
            "From: %s" % clientAddress,
            "To: bridges@localhost",
            "Subject: testing",
            "",
            "get bridges",
        ]
        self.message.lines = lines

    def _setUpResponder(self):
        """Set up the incoming message of our autoresponder.

        This is necessary because normally our SMTP server acts as a line
        protocol, waiting for an EOM which sets off a chain of deferreds
        resulting in the autoresponder sending out the response. This should
        be called after :meth:`_getIncomingLines` so that we can hook into the
        SMTP protocol without actually triggering all the deferreds.
        """
        self.message.message = self.message.getIncomingMessage()
        self.responder = self.message.responder
        # The following are needed to provide client disconnection methods for
        # the call to ``twisted.mail.smtp.SMTPClient.sendError`` in
        # ``bridgedb.distributors.email.autoresponder.SMTPAutoresponder.sendError``:
        #protocol = proto_helpers.AccumulatingProtocol()
        #transport = proto_helpers.StringTransportWithDisconnection()
        self.tr = proto_helpers.StringTransportWithDisconnection()
        # Set the transport's protocol, because
        # StringTransportWithDisconnection is a bit janky:
        self.tr.protocol = self.responder
        self.responder.makeConnection(self.tr)

    def test_SMTPAutoresponder_getMailFrom_notbridgedb_at_yikezors_dot_net(
            self):
        """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
        address other than the one we're listening for should return our
        configured address, not the one in the incoming email.
        """
        self._getIncomingLines()
        self.message.lines[1] = 'To: [email protected]'
        self._setUpResponder()
        recipient = str(self.responder.getMailFrom())
        self.assertEqual(recipient, self.context.fromAddr)

    def test_SMTPAutoresponder_getMailFrom_givemebridges_at_seriously(self):
        """SMTPAutoresponder.getMailFrom() for an incoming email sent to any email
        address other than the one we're listening for should return our
        configured address, not the one in the incoming email.
        """
        self._getIncomingLines()
        self.message.lines[1] = 'To: [email protected]'
        self._setUpResponder()
        recipient = str(self.responder.getMailFrom())
        self.assertEqual(recipient, self.context.fromAddr)

    def test_SMTPAutoresponder_getMailFrom_bad_address(self):
        """SMTPAutoresponder.getMailFrom() for an incoming email sent to a malformed
        email address should log an smtp.AddressError and then return our
        configured email address.
        """
        self._getIncomingLines()
        self.message.lines[1] = 'To: ><@><<<>>.foo'
        self._setUpResponder()
        recipient = str(self.responder.getMailFrom())
        self.assertEqual(recipient, self.context.fromAddr)

    def test_SMTPAutoresponder_getMailFrom_plus_address(self):
        """SMTPAutoresponder.getMailFrom() for an incoming email sent with a valid
        plus address should respond.
        """
        self._getIncomingLines()
        ours = Address(self.context.fromAddr)
        plus = '@'.join([
            ours.local.decode('utf-8') + '+zh_cn',
            ours.domain.decode('utf-8')
        ])
        self.message.lines[1] = 'To: {0}'.format(plus)
        self._setUpResponder()
        recipient = str(self.responder.getMailFrom())
        self.assertEqual(recipient, plus)

    def test_SMTPAutoresponder_getMailFrom_getbridges_at_localhost(self):
        """SMTPAutoresponder.getMailFrom() for an incoming email sent with
        'getbridges+zh_cn@localhost' should be responded to from the default
        address.
        """
        self._getIncomingLines()
        ours = Address(self.context.fromAddr)
        plus = '@'.join([
            'get' + ours.local.decode('utf-8') + '+zh_cn',
            ours.domain.decode('utf-8')
        ])
        self.message.lines[1] = 'To: {0}'.format(plus)
        self._setUpResponder()
        recipient = str(self.responder.getMailFrom())
        self.assertEqual(recipient, self.context.fromAddr)

    def test_SMTPAutoresponder_getMailTo_UnsupportedDomain(self):
        """getMailTo() should catch emails from UnsupportedDomains."""
        emailFrom = '*****@*****.**'
        self._getIncomingLines(emailFrom)
        self._setUpResponder()
        clients = self.responder.getMailTo()
        self.assertIsInstance(
            clients, list,
            ("Returned value of SMTPAutoresponder.getMailTo() isn't a list! "
             "Type: %s" % type(clients)))
        self.assertTrue(emailFrom not in clients)
        # The client was from an unsupported domain; they shouldn't be in the
        # clients list:
        self.assertEqual(len(clients), 0, "clients = %s" % repr(clients))

    def test_SMTPAutoresponder_reply_noFrom(self):
        """A received email without a "From:" or "Sender:" header shouldn't
        receive a response.
        """
        self._getIncomingLines()
        self.message.lines[0] = ""
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_badAddress(self):
        """Don't respond to RFC2822 malformed source addresses."""
        self._getIncomingLines("testing*.?\"@example.com")
        self._setUpResponder()
        ret = self.responder.reply()
        # This will call ``self.responder.reply()``:
        #ret = self.responder.incoming.eomReceived()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_anotherBadAddress(self):
        """Don't respond to RFC2822 malformed source addresses."""
        self._getIncomingLines("Mallory <>>@example.com")
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_invalidDomain(self):
        """Don't respond to RFC2822 malformed source addresses."""
        self._getIncomingLines("testing@exa#mple.com")
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_anotherInvalidDomain(self):
        """Don't respond to RFC2822 malformed source addresses."""
        self._getIncomingLines("testing@exam+ple.com")
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_DKIM_badDKIMheader(self):
        """An email with an 'X-DKIM-Authentication-Result:' header appended
        after the body should not receive a response.
        """
        self._getIncomingLines("*****@*****.**")
        self.message.lines.append("X-DKIM-Authentication-Result: ")
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_goodDKIMheader(self):
        """An email with a good DKIM header should be responded to."""
        self._getIncomingLines("*****@*****.**")
        self.message.lines.insert(3, "X-DKIM-Authentication-Result: pass")
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)

    def test_SMTPAutoresponder_reply_transport_invalid(self):
        """An invalid request for 'transport obfs3' should get help text."""

        #self.skip = True
        #raise unittest.SkipTest("We need to fake the reactor for this one")

        def cb(success):
            pass

        self._getIncomingLines("*****@*****.**")
        self.message.lines[4] = "transport obfs3"
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)
        #self.assertSubstring("COMMANDs", ret)
        print(self.tr.value())
        return ret

    def test_SMTPAutoresponder_reply_transport_valid(self):
        """An valid request for 'get transport obfs3' should get obfs3."""
        #self.skip = True
        #raise unittest.SkipTest("We need to fake the reactor for this one")

        self._getIncomingLines("*****@*****.**")
        self.message.lines[4] = "transport obfs3"
        self._setUpResponder()
        ret = self.responder.reply()
        self.assertIsInstance(ret, defer.Deferred)
        #self.assertSubstring("obfs3", ret)
        print(self.tr.value())
        return ret

    def test_SMTPAutoresponder_sentMail(self):
        """``SMTPAutoresponder.sendMail()`` should handle successes from an
        :api:`twisted.mail.smtp.SMTPSenderFactory`.
        """
        success = (1, [(
            '*****@*****.**',
            250,
            'OK',
        )])
        self._getIncomingLines()
        self._setUpResponder()
        self.responder.sentMail(success)

    def test_SMTPAutoresponder_sendError_fail(self):
        """``SMTPAutoresponder.sendError()`` should handle failures."""
        fail = Failure(ValueError('This failure was sent on purpose.'))
        self._getIncomingLines()
        self._setUpResponder()
        self.responder.sendError(fail)

    def test_SMTPAutoresponder_sendError_exception(self):
        """``SMTPAutoresponder.sendError()`` should handle exceptions."""
        error = ValueError('This error was sent on purpose.')
        self._getIncomingLines()
        self._setUpResponder()
        self.responder.sendError(error)

    def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_domain(self):
        """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
        reported being from an email address at one supported domain and the
        email's 'From:' header reported another domain.
        """
        smtpFrom = '*****@*****.**'
        emailFrom = Address('*****@*****.**')
        self._getIncomingLines(str(emailFrom))
        self._setUpResponder()
        self.responder.incoming.canonicalFromSMTP = smtpFrom
        self.assertFalse(self.responder.runChecks(emailFrom))

    def test_SMTPAutoresponder_runChecks_RCPTTO_From_mismatched_username(self):
        """runChecks() should catch emails where the SMTP 'MAIL FROM:' command
        reported being from an email address and the email's 'From:' header
        reported another email address, even if the only the username part is
        mismatched.
        """
        smtpFrom = '*****@*****.**'
        emailFrom = Address('*****@*****.**')
        self._getIncomingLines(str(emailFrom))
        self._setUpResponder()
        self.responder.incoming.canonicalFromSMTP = smtpFrom
        self.assertFalse(self.responder.runChecks(emailFrom))

    def test_SMTPAutoresponder_runChecks_DKIM_dunno(self):
        """runChecks() should catch emails with bad DKIM headers
        (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
        which we're configured to check DKIM verification results for.
        """
        emailFrom = Address('*****@*****.**')
        header = "X-DKIM-Authentication-Results: dunno"
        self._getIncomingLines(str(emailFrom))
        self.message.lines.insert(3, header)
        self._setUpResponder()
        self.assertFalse(self.responder.runChecks(emailFrom))

    def test_SMTPAutoresponder_runChecks_DKIM_bad(self):
        """runChecks() should catch emails with bad DKIM headers
        (``"X-DKIM-Authentication-Results: dunno"``) for canonical domains
        which we're configured to check DKIM verification results for.
        """
        emailFrom = Address('*****@*****.**')
        header = "X-DKIM-Authentication-Results: wowie zowie there's a sig here"
        self._getIncomingLines(str(emailFrom))
        self.message.lines.insert(3, header)
        self._setUpResponder()
        self.assertFalse(self.responder.runChecks(emailFrom))

    def test_SMTPAutoresponder_runChecks_blacklisted(self):
        """runChecks() on an blacklisted email address should return False."""
        emailFrom = Address('*****@*****.**')
        self._getIncomingLines(str(emailFrom))
        self._setUpResponder()
        self.assertFalse(self.responder.runChecks(emailFrom))