"Hacking" System.Net.Mail.SmtpClient


In .NET 2.0 there is a cool class called SmtpClient within System.Net.Mail.

With this class you can send mail (at least I thought).
The requirement was to send an email over a server of one of our agencies.
The mail server was secured by username/password authentication. That’s why I used the following code:

SmtpClient _client = new SmtpClient();
_client = new SmtpClient("smtp.myserver.at");

_client.UseDefaultCredentials = false;
_client.Credentials = new NetworkCredential("username", "password");

_client.Send("from@test.at", "to@test.at", "Hallo Welt", "Hallo max!!");
At execution of the last statement an exceptíon occurred:
image
The inner exception said "Invalid lenght for a Base-64 char array."
That didn't make any sense for me (nor helped me to fix the problem).
I had a look at the stack trace and found out, that the exception had occurred in a class called
SmtpNtlmAuthenticationModule.

image

So I used telnet to manually connect to the smtp server.

S: 220 smtp.myserver.at ESMTP
C: ehlo asd.com
S: 250-smtp.myserver.at Hello mypc.microsoft.com [xxx.xxx.xxx.xxx]
S: 250-SIZE 20971520
S: 250-PIPELINING
S: 250-AUTH PLAIN LOGIN CRAM-MD5 NTLM
C: AUTH NTLM <base64 encoded string>

S: 334 NTLM supported

Seemed like the server would support NTLM.. But.. when I looked up the NTLM-SMTP specification,I found out that the server should respond with

334 <NTLM supported as base64 encoded string>

So the problem obviously is, that “NTLM supported” was not a valid Base-64 encoded string (as the inner exception above also pointed out).

So how could this problem be solved…

I digged into the private members of the SmtpClient object and found a member called transport (of type SmtpTransport).

The SmtpTransport object had private members as well, and one of them was called authenticationModules – bingo!
image

This is an array of ISmtpAuthenticationModules like Negotiate, NTLM, Digest and Login.

Unfortunately SmtpClient always picks the most effective supported method (in this case NTLM). As NTLM was not working I needed a way to kick out NTLM of the list of supported auth methods.

So I used reflection to modify the array and “disable” (override) NTLM in the array. Here’s what I did:

FieldInfo transport = _client.GetType().GetField("transport",
    BindingFlags.NonPublic | BindingFlags.Instance);

FieldInfo authModules = transport.GetValue(_client).GetType()
    .GetField("authenticationModules",
        BindingFlags.NonPublic | BindingFlags.Instance);

Array modulesArray = authModules.GetValue(transport.GetValue(_client)) as Array;
modulesArray.SetValue(modulesArray.GetValue(2), 0);
modulesArray.SetValue(modulesArray.GetValue(2), 1);
modulesArray.SetValue(modulesArray.GetValue(2), 3);

Voila!
image

Guess which smtp authentication module will be used now 🙂

Comments (6)

  1. Sean Gahan says:

    This is how I worked it.

    Best regards,

    Sean Gahan

    Public Sub SendMail(ByVal emailTo As String, ByVal emailSubject As String, _

    ByVal emailBody As String, ByVal nameOfFileOut As String, _

    ByVal stringAttachment As String)

    Dim a As String = String.Empty ‘//attachment

    Dim f As String = AppSettings("SendFewrMailFromThisAcct")

    Dim b As String = String.Empty ‘//body

    Dim s As String = String.Empty ‘//subject

    Dim t As String = String.Empty ‘//to

    Dim att As Attachment

    Dim MailServer As String = AppSettings("MailServer")

    Dim MailUser As String = AppSettings("MailUser")

    Dim MailPwd As String = AppSettings("MailPwd").ToString

    Dim MailUserDomain As String = AppSettings("MailUserDomain")

    Dim memStream As System.IO.MemoryStream

    Dim encoding As New Text.ASCIIEncoding

    memStream = New System.IO.MemoryStream

    Dim byteArray As Byte() = encoding.GetBytes(stringAttachment)

    Dim sm As New SmtpClient(MailServer)

    Dim SMTPUserInfo As System.Net.NetworkCredential = _

    New System.Net.NetworkCredential(MailUser, MailPwd, MailUserDomain)

    ‘//set up email stuff

    t = emailTo

    s = emailSubject

    b = emailBody

    Dim m As New MailMessage(f, t, s, b)

    Try

       If nameOfFileOut <> String.Empty Then

           memStream.Write(byteArray, 0, byteArray.Length)

           memStream.Seek(0, IO.SeekOrigin.Begin)

           att = New Attachment(memStream, nameOfFileOut, MediaTypeNames.Text.Plain)

           m.Attachments.Add(att)

       End If

       m.Priority = MailPriority.Normal

       sm.UseDefaultCredentials = False

       sm.Credentials = SMTPUserInfo

       sm.Send(m)

    Catch ex As Exception

       Throw New ApplicationException("Error: Message:" & ex.Message & vbCrLf & "Stack:" & ex.StackTrace)

    Finally

       memStream.Dispose()

       If Not att Is Nothing Then

           att.Dispose()

       End If

       m.Dispose()

       sm = Nothing

    End Try

  2. Phil says:

    Hi knom,

    Thanks for the useful article, I’m having a similar problem.

    I think I need to use this method to force the use of the "login" authenticationModule. How would I do this?

    Phil

  3. Lungisa says:

    I have the same code but I get "The operation has timed out."

  4. Diane says:

    Hugely helpful – I had been puzzling over this for ages.

    Actually I also needed the "login" authentication Module so I replaced ONLY the ntlm authentication module :       modulesArray.SetValue(modulesArray.GetValue(2), 1)

    Worked like a dream!

  5. DannyC says:

    **** In case this helps someone else. I had the same issue and in the end it was due to the password being case sensitive. Apparently an invalid password will also throw the "Invalid lenght for a Base-64 char array." exception.