Abhishek Dujari
Abhishek Dujari

Reputation: 2443

twisted and smtp tls client

from OpenSSL.SSL import SSLv3_METHOD, TLSv1_METHOD

from twisted.mail.smtp import ESMTPSenderFactory
from twisted.python.usage import Options, UsageError
from twisted.internet.ssl import ClientContextFactory
from twisted.internet.defer import Deferred
from twisted.internet import reactor


def sendmail(
    authenticationUsername, authenticationSecret,
    fromAddress, toAddress,
    messageFile,
    smtpHost="email-smtp.us-east-1.amazonaws.com", smtpPort=587
    ):
    """
    @param authenticationUsername: The username with which to authenticate.
    @param authenticationSecret: The password with which to authenticate.
    @param fromAddress: The SMTP reverse path (ie, MAIL FROM)
    @param toAddress: The SMTP forward path (ie, RCPT TO)
    @param messageFile: A file-like object containing the headers and body of
    the message to send.
    @param smtpHost: The MX host to which to connect.
    @param smtpPort: The port number to which to connect.

    @return: A Deferred which will be called back when the message has been
    sent or which will errback if it cannot be sent.
    """

    # Create a context factory which only allows SSLv3 and does not verify
    # the peer's certificate.
    contextFactory = ClientContextFactory()
    contextFactory.method = TLSv1_METHOD

    resultDeferred = Deferred()

    senderFactory = ESMTPSenderFactory(
        authenticationUsername,
        authenticationSecret,
        fromAddress,
        toAddress,
        messageFile,
        resultDeferred,
        contextFactory=contextFactory,heloFallback=True
        )

    reactor.connectTCP(smtpHost, smtpPort, senderFactory)

    return resultDeferred

Note I have tried both SSLv3 and TLSv1 so you will both imported, but that is not the issue. The error I keep getting is this. Traceback:

2013-05-23 01:19:17+0800 [ESMTPSender,client] SMTP Client retrying server. Retry: 5
2013-05-23 01:19:20+0800 [ESMTPSender,client] SMTP Client retrying server. Retry: 4
2013-05-23 01:19:22+0800 [ESMTPSender,client] SMTP Client retrying server. Retry: 3
2013-05-23 01:19:25+0800 [ESMTPSender,client] SMTP Client retrying server. Retry: 2
2013-05-23 01:19:28+0800 [ESMTPSender,client] SMTP Client retrying server. Retry: 1
2013-05-23 01:19:30+0800 [ESMTPSender,client] Failed to deliver mail [Failure instance: Traceback (failure with no frames): <class 'twisted.mail.smtp.TLSError'>: 454 Could not complete the SSL/TLS handshake
2013-05-23 01:19:30+0800 [ESMTPSender,client] <<< 250-AUTH PLAIN LOGIN
2013-05-23 01:19:30+0800 [ESMTPSender,client] <<< 250 Ok
2013-05-23 01:19:30+0800 [ESMTPSender,client] >>> STARTTLS
2013-05-23 01:19:30+0800 [ESMTPSender,client] <<< 454 TLS not available due to temporary reason: TLS already active
2013-05-23 01:19:30+0800 [ESMTPSender,client]
2013-05-23 01:19:30+0800 [ESMTPSender,client] ]
2013-05-23 01:19:30+0800 [ESMTPSender,client] Stopping factory <twisted.mail.smtp.ESMTPSenderFactory instance at 0x2119950>

Amazon Ses supports wrapper and TLS but on different ports. SImilar to how GMail behaves.

I tried to just remove ContextFactory altogether. The error is same.

I have tried with smtplib to ensure it's not my system or auth etc. It works fine.

To be honest I do not fully understand twisted so I may be doing something dumb. The above code is similar to examples elsewhere and should work. Btw, I do not call reactor.stop() as I just ctrl-c after test. Any clues?

Update for completion: I call the method above like so

sendmail('username','password',from, to,StringIO.StringIO(mail)).addCallbacks(self.delivered, self.failed)

Upvotes: 1

Views: 1560

Answers (1)

Jean-Paul Calderone
Jean-Paul Calderone

Reputation: 48325

Host email-smtp.us-east-1.amazonaws.com port 587 speaks unencrypted ESMTP (call this, perhaps, "TCP"). It supports negotiating up to encrypted ESMTP via the STARTTLS command.

Testing this manually, I find that it works as expected.

The error from the log you pasted (TLS already active) suggests that instead you have a connection over which TLS has already been negotiated (either because STARTTLS was used over a TCP connection or because you connected to a different server where TLS is negotiated automatically at the start of the connection).

The server is refusing to run TLS over TLS over TCP, which is probably sensible. However, from the code you pasted, I don't see how TLS could be negotiated twice. Perhaps if you can include more details about the context the answer will become clear.

It is possible that you're experiencing http://tm.tl/3989. If that is the case, upgrading to Twisted 13.0.0 or newer will resolve the issue. However, I don't see how this could be the case since I don't see how TLS could be negotiated twice by your code.

Actually, on further investigation, it seems you're experiencing a regression introduced by http://tm.tl/3989. I've filed http://tm.tl/6524 to track this.

Upvotes: 1

Related Questions