5

I'm trying to connect to smtp.live.com using the TcpClient class. There is a wonderful example of connecting to Gmail here: Testing SMTP server is running via C#

Unfortunately, when updating this to work with smtp.live.com, I'm getting an "IOException: The handshake failed due to an unexpected packet format" when the AuthenticateAsClient method is called.

How can I work around this?

class Program
{
    static void Main(string[] args)
    {
        using (var client = new TcpClient())
        {
            var server = "smtp.live.com";
            var port = 25;
            client.Connect(server, port);
            using (var stream = client.GetStream())
            using (var sslStream = new SslStream(stream))
            {
                // Getting an IOException here
                sslStream.AuthenticateAsClient(server);
                using (var writer = new StreamWriter(sslStream))
                using (var reader = new StreamReader(sslStream))
                {
                    writer.WriteLine("EHLO " + server);
                    writer.Flush();
                    Console.WriteLine(reader.ReadLine());
                }
            }
        }
        Console.WriteLine("Press Enter to exit...");
        Console.ReadLine();
    }
}

I tried specifying the SslProtocol in AuthenticateAsClient. Neither Tls or Ssl3 worked.

Also tried providing a callback for RemoteCertificateValidation that alway returned true just in case the server cert was invalid. That didn't work either.

NOTE: Please do not suggest that I use SmtpClient; I need more control than it provides.

Community
  • 1
  • 1
Doug Clutter
  • 3,646
  • 2
  • 29
  • 31
  • What kind of control do you need that `SmtpClient` do not provide? – jgauffin Jun 07 '11 at 18:51
  • I'm creating an SMTP proxy that will support an unencrypted connection from some programs we use internally that do not support TLS/SSL. The SMTP proxy server will accept connections from these programs and forward the info to the smtp.live.com servers which require TLS/SSL. – Doug Clutter Jun 08 '11 at 12:42
  • And why can't you use SmtpClient for the outbound connection? Sounds to me that only the inbount connection in the proxy needs to be something custom. – jgauffin Jun 08 '11 at 12:55
  • @jgauffin - If I used the SmtpClient, I would need to completely receive and dismantle the email. This could include multiple recipients, multiple CCs, multiple attachments, et al. Simply too much work. – Doug Clutter Jun 14 '11 at 02:48

2 Answers2

13

Thanks to nos for putting me on the right track. The smtp.live.com servers require the following sequence of events:

  1. Connect
  2. HELO - will not accept the STARTTLS until this has been sent
  3. STARTTLS - apparently this sets up the server to accept an encrypted connection
  4. SslStream.AuthenticateAsClient() - this seems to let the C# framework and the SMTP server come to an "understanding" :)
  5. Now that we have an encrypted connection, the usual SMTP commands work

Anyway, this code works for both smtp.live.com AND smtp.gmail.com on port 587:

class Program
{
    static void Main(string[] args)
    {
        const string server = "smtp.live.com";
        const int port = 587;
        using (var client = new TcpClient(server, port))
        {
            using (var stream = client.GetStream())
            using (var clearTextReader = new StreamReader(stream))
            using (var clearTextWriter = new StreamWriter(stream) { AutoFlush = true })
            using (var sslStream = new SslStream(stream))
            {
                var connectResponse = clearTextReader.ReadLine();
                if (!connectResponse.StartsWith("220"))
                    throw new InvalidOperationException("SMTP Server did not respond to connection request");

                clearTextWriter.WriteLine("HELO");
                var helloResponse = clearTextReader.ReadLine();
                if (!helloResponse.StartsWith("250"))
                    throw new InvalidOperationException("SMTP Server did not respond to HELO request");

                clearTextWriter.WriteLine("STARTTLS");
                var startTlsResponse = clearTextReader.ReadLine();
                if (!startTlsResponse.StartsWith("220"))
                    throw new InvalidOperationException("SMTP Server did not respond to STARTTLS request");

                sslStream.AuthenticateAsClient(server);

                using (var reader = new StreamReader(sslStream))
                using (var writer = new StreamWriter(sslStream) { AutoFlush = true })
                {
                    writer.WriteLine("EHLO " + server);
                    Console.WriteLine(reader.ReadLine());
                }
            }
        }
        Console.WriteLine("Press Enter to exit...");
        Console.ReadLine();
    }
}
Doug Clutter
  • 3,646
  • 2
  • 29
  • 31
  • 1
    @eFloh - thanks for the reminder. SO wouldn't let me do so right off...had to wait 24 hours or some such. – Doug Clutter Jun 14 '11 at 02:49
  • 1
    I created a project with a simple [SMTP Proxy](http://smtpproxy.codeplex.com) on CodePlex. Hope it helps someone else out of this pickle. – Doug Clutter Jun 14 '11 at 02:53
  • any sample without use SSL, for connect to Exchange server in intranet? – Kiquenet Oct 31 '12 at 15:16
  • Sending `EHLO` instead of `HELO` will normally get a response with multiple lines, showing all commands supported by the server, each on its own line starting with `250`. If you want to use `EHLO`, you will need to loop, calling `clearTextReader.ReadLine()` until the last line of the response starts with `250 ` (including the space after the response code). SMTP responses with more lines to follow in the response start with `250-`, while the last line starts with `250 ` (including the space). – nekno Oct 10 '14 at 20:20
2

If you want to use ssl on the connection. You should use port 465 for ssl or 587 for tls.

Tip: Try it first without ssl, and add it after you've got things working.

Link: http://www.checktls.com/tests.html to see some starttls examples.

albertjan
  • 7,739
  • 6
  • 44
  • 74