共用方式為


"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

  • Anonymous
    April 16, 2008
    PingBack from http://microsoftnews.askpcdoc.com/?p=2810

  • Anonymous
    April 16, 2008
    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

  • Anonymous
    April 16, 2008
    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

  • Anonymous
    June 22, 2008
    I have the same code but I get "The operation has timed out."

  • Anonymous
    September 23, 2008
    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!

  • Anonymous
    March 30, 2009
    The comment has been removed