"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:
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.
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!
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);
Guess which smtp authentication module will be used now :-)
Comments
Anonymous
April 16, 2008
PingBack from http://microsoftnews.askpcdoc.com/?p=2810Anonymous
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 TryAnonymous
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? PhilAnonymous
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