Understanding how SmtpClient handles retries - asp.net

I'm working against a GoogleApps account for sending event notifications from my (mvc) web-app. Usually everything works fine. When the system is asked to send more than 75 messages or so I'm seeing replies from the SMTP server:
Service not available, closing
transmission channel. The server
response was: 4.7.0 Try again later,
closing connection. (MAIL)
uf10sm1153163icb.17
However, the system is auto-retrying and anything my system is asked to send eventually (by everything i can tell as this point) making it out. But given how the code generates and sends the emails I don't understand how these re-tries are handled.
I'd like to try to slow down the transmission in hopes that whatever's causing the 'Service Not Available' condition will be placated if the submissions occur asynchronously. But from the looks of the code, it already is since i'm using a Try | catch block.
Here's the relevant bit of my facade code:
foreach (string email in recipientEmails)
{
try
{
EmailGateway.Instance.SendEmail(email, notificationSubject, notificationMessage);
}
catch (Exception ex)
{
Logger.Instance.LogException(ex);
Logger.Instance.LogMessage("ERROR! Unsent email is to: " + email );
}
Logger.Instance.LogMessage("Sent " + notificationSubject + " to " + email);
}
And here's the Gateway code (using System.Net.Mail;):
public virtual void SendEmail(string replyToAddress, string toEmailAddress, string subject, string body)
{
string destinationEmailAddress = toEmailAddress;
string fromEmailAddress = "my#address.com";
bool useSSL = "true";
MailMessage message = new MailMessage(fromEmailAddress, destinationEmailAddress, subject, body);
message.IsBodyHtml = true;
SmtpClient smtp = new SmtpClient();
smtp.EnableSsl = useSSL;
smtp.Send(message);
}
So i'm catching both successes and fails into my logger table. What I don't understand is how I can see a log message for both a fail and then a success condition for the same email address. That indicates a 'retry' and, while i'm not surprised that the SmtpClient (the native .net assembly) can retry without explicit code asking it to, I don't see how my facade's code is being made to log both conditions.

SmtpClient is not retrying to send the email.
In your facade code as it is, you are always logging a success, whether you are getting an exception or not.
You should log success in the try block, otherwise you are catching the exception (logging failure), coming out of the catch block and logging success anyway, which is what you are observing.
foreach (string email in recipientEmails)
{
try
{
EmailGateway.Instance.SendEmail(email, notificationSubject, notificationMessage);
Logger.Instance.LogMessage("Sent " + notificationSubject + " to " + email);
}
catch (Exception ex)
{
Logger.Instance.LogException(ex);
Logger.Instance.LogMessage("ERROR! Unsent email is to: " + email );
}
}

Related

Sent an email from asp.net through 365 with a "from" email that is different than the username

I hope someone can help me out here:
Users generate emails through my asp.net website. Emails are to be sent out through the 365 server using an account that I have there with my domain (e.g. out#mydomain.com). I want the message "from" field to be the email of my user (e.g., myuser#anotherdomain.com) so that the receivers of the email will see his email as the sender and will reply to him directly.
But when I try this I get an error message (see below).
I get the same error message even if I try to send an email when the "from" field is another existing mailbox in my domain (e.g., myname#mydomain.com)
Apparently someone in the way (not sure if the asp.net or the 365 server) blocks emails if the "from" is not identical to the username.
Is there any way to address this>
here is my code
protected void btnSend_Click(object sender, System.EventArgs e)
{
AuditLog.Info("here");
try
{
string EmailContent = "test";
MailMessage msg = new MailMessage();
msg.IsBodyHtml = true;
msg.From = new MailAddress("myuser#anotherdomain.com");
msg.Bcc.Add(msg.From);
string email = "receiver#gmail.com";
msg.To.Add(email);
msg.Subject = "TEst 365";
System.Net.Mail.AlternateView plainTextView = System.Net.Mail.AlternateView.CreateAlternateViewFromString(EmailContent);
System.Net.Mail.AlternateView htmlView = System.Net.Mail.AlternateView.CreateAlternateViewFromString(EmailContent);
msg.AlternateViews.Add(plainTextView);
msg.AlternateViews.Add(htmlView);
System.Net.Mail.SmtpClient client = new SmtpClient();
client.Host = "smtp.office365.com";
client.DeliveryMethod = SmtpDeliveryMethod.Network;
client.UseDefaultCredentials = false;
client.Credentials = "out#mydomain.com", "MyPassword");
client.Port = 587;
client.EnableSsl = true;
if (client.Host.Trim() != "")
client.Send(msg);
}
catch (Exception ex)
{
AuditLog.Info(string.Format("Failed to send mail . Error={0} ", ex.Message));
}
finally
{
AuditLog.Info("end");
}
}
And this is the error:
{"Transaction failed. The server response was: 5.2.0 STOREDRV.Submission.Exception:SendAsDeniedException.MapiExceptionSendAsDenied; Failed to process message due to a permanent exception with message Cannot submit message. 0.35250:0A00A280, 1.36674:0A000000, 1.61250:00000000, 1.45378:02000000, 1.44866:FD1E0000, 1.36674:0E000000, 1.61250:00000000, 1.45378:021F0000, 1.44866:14030000, 16.55847:AD0F0000, 17.43559:0000000004020000000000000000000000000000, 20.52176:140F2A8A0A00101043050000, 20.50032:140F2A8A7A17000000000000, 0.35180:48050000, 255.23226:0A00A780, 255.27962:0A000000, 255.27962:0E000000, 255.31418:0A00A880, 0.35250:0A000000, 1.36674:0A000000, 1.61250:00000000, 1.45378:02000000, 1.44866:20000000, 1.36674:32000000, 1.61250:00000000, 1.45378:25000000, 1.44866:01000000, 16.55847:8C000000, 17.43559:0000000030030000000000007B00000000000000, 20.52176:140F2A8A0A0070200A00AD80, 20.50032:140F2A8A7A1710106B050000, 0.35180:0A00AE80, 255.23226:4800D13D, 255.27962:0A000000, 255.27962:32000000, 255.17082:DC040000, 0.27745:75050000, 4.21921:DC040000, 255.27962..."}
It's right there in the error message: Exception:SendAsDeniedException.MapiExceptionSendAsDenied;
You will need to grant your users SendAs permissions for out#mydomain.com. That's probably not what you really want to do though, as those users would then consume an office 365 license and if they were using a license, they could just send as themselves.
The real solution is to not use office 365 as an SMTP relay. You should sign up with another SMTP provider that is specifically set up to do what you are trying to do. We use SendGrid, but there are others out there if you search.

SmtpClient Email Not Sent, Not Caught and Not Logged?

Our website was meant to send out a particular email, it didnt send the email and it also did not log the error which is unusual as i have a try catch setup which should log the entry in the try or catch section.
1) Why did it not log?
Should i not use Exception and instead use SmtpException and SmptFailedException. If i have this would it have made a difference? If so can you please provide an example.
2) The port we are currently using is 587. As our website is SSL i read we should be using 443 port instead. Would this have made a difference?
The below code is called asynchronously by using
ThreadPool.QueueUserWorkItem(new WaitCallback(SendInstructions), guid);
Which then calls a function that then calls the SendEmail Function below
public static bool SendEmail(String strToAddress, String strFromAddress, String strFromName, String strSubject, String strRecipientName, String strBody, String strEmbeddedImagePath, String strEmbeddedImageName, String strCCReminderEmail)
{
try
{
SmtpClient client = new SmtpClient();
client.EnableSsl = true;
using (MailMessage message = new MailMessage(
new MailAddress(strFromAddress, strFromName),
new MailAddress(strToAddress, strRecipientName)))
{
message.IsBodyHtml = true;
message.Subject = strSubject;
message.Body = strBody;
if (!string.IsNullOrEmpty(strCCReminderEmail))
message.CC.Add(strCCReminderEmail);
client.Send(message);
LogEmail(strFromAddress, strToAddress, strSubject, "Sent", strBody);
return true;
}
}
catch (Exception ex)
{
//log email
LogEmail(strFromAddress, strToAddress, strSubject, "Error", strBody);
throw;
}
}
I had a problem similar when using
ThreadPool.QueueUserWorkItem( new WaitCallback( ...
and then
System.Net.Mail.SmtpClient sC = new System.Net.Mail.SmtpClient(SMTPHost, SMTPPort);
the thread died without throwing an exception.
In my case, the main application thread is exiting before the background threads in the threadpool have had a chance to finish.
The threadpool creates Background worker threads which die as soon as
the main application thread terminates.
so I made a function to sleep 5 seconds and it worked.

System.Net.WebClient The underlying connection was closed

The underlying connection was closed: The connection was closed unexpectedly
I'm receiving the error message when using WebClient. I had tested this with asp code and it is running pretty well. But when I try to run the exe version of the code it running the following error, and I able to access the request address from the server, and it is within the internal network.
My server setup :-
Windows Server 2003 Entrerprise SP2
asp.net 2.0.50727
iis v6.0
Here is some code:
Manually accessing the
protected static String cross_server_req(String strContent, String strPage, String strPrivateKey)
{
try
{
WebClient wc = new WebClient();
String strURL = SERVICE_PATH + strPage + "?r=" + DES_.DESEncrypt(strContent);
byte[] responseArray = wc.DownloadData(strURL);
String strResData = Encoding.UTF8.GetString(responseArray);
if (Config.RecordWebService == "1")
{
String strLogContent = "Request:" + strContent + "\r\nResponse:" + strResData + "\r\nDateCreated:" + D_Time.DNow + "\r\n\r\n";
ServiceLog.Logger(strPage, strLogContent);
}
if (null != strResData && String.Empty != strResData)
{
return strResData.Trim();
}
}
catch (Exception ex)
{
ServiceLog.Logger("cross_server_req() Exception:" + ex.Message + "\r\n" + ex.StackTrace);
}
return null;
}
Here is from log
cross_server_req() Exception:The underlying connection was closed: The connection was closed unexpectedly.
at System.Net.WebClient.DownloadDataInternal(Uri address, WebRequest& request)
at System.Net.WebClient.DownloadData(Uri address)
at System.Net.WebClient.DownloadData(String address)
at Account.cross_server_request_curl(String strContent, String strPage, String strPrivateKey)
Please let me know any suggestion help this problem? I had look around the site and no solution had found.
I believe this error may have something to do with TLS/SSL connectivity assuming your hitting a HTTPS url. Have a look at this thread. Essentially you need to provide some code to verify that the certificate is valid. This is needed for CA's that have thumbprint issues or that aren't fully trusted. Hopefully this helps to solve your issue.
System.Net.ServicePointManager.ServerCertificateValidationCallback += new System.Net.Security.RemoteCertificateValidationCallback(validateSSLCert);
private static bool validateSSLCert(object sender, X509Certificate cert, X509Chain chain, System.Net.Security.SslPolicyErrors error)
{
return true;
}

System.Net.Mail.SmtpFailedRecipientException: Mailbox name not allowed

I have written ASP.Net code to send mails from domain1.com mail account such as abc#domain1.com. This code work fine otherwise and the mails go. But when the same code executes on domain2.com, even with correct userid-pwd it gives the following error:
System.Net.Mail.SmtpFailedRecipientException: Mailbox name not allowed. The server response was: sorry, that domain isn't in my list of allowed rcpthosts (#5.7.1) at System.Net.Mail.SmtpClient.Send(MailMessage message)
Is there any way to fix this?
If we have to add this domain in the list of allowed rcphosts, how can that be done?
The code written is something like this:
MailMessage message;
bool success;
message = new MailMessage(from, to);
Attachment file;
SmtpClient lclient;
lclient = new SmtpClient("mail.domain1.com", 587);
lclient.EnableSsl = false;
message.Body = body;
message.BodyEncoding = System.Text.Encoding.UTF8;
message.IsBodyHtml = true;
message.Subject = subject;
message.SubjectEncoding = System.Text.Encoding.UTF8;
lclient.SendCompleted += new
SendCompletedEventHandler(SendCompletedCallback);
lclient.UseDefaultCredentials = false;
lclient.Credentials = new NetworkCredential(userID, password);
try
{
lclient.Send(message);
success = true;
if (message != null)
message.Dispose();
success = true;
return (success);
}
catch (Exception ex)
{
//...
}
Thanks
The code works fine. The error is a rejection from the SMTP server. It would seem that the server, when accessed from Domain1, allows you to forward mail through it. When accessed from Domain2, it does not. Changing this would be a configuration on the SMTP server.
Note that this is common practice for SMTP services. They generally don't allow anybody to send mail through them to any address. (That would leave them wide open for spammers and other such unwanted activities.) So, if you're trying to access Domain1's SMTP service from outside of Domain1, it's probably just rejecting that.

SMTP header injection in ASP.NET?

My ASP.NET website has a global error handler that sends an email to me (and another developer) when there is any kind of error in the web app. We recently received an error which contained a CC to an email address that we'd never heard of. The scary thing is that the list of developers that the error email is sent to is hard coded in compiled ASP.NET code. We don't see how the CC could have been added.
We're also very suspicious of foul play because the request that caused the error was an attempt to use one of our forms to send spam. The IP address that sent the request is also listed on http://www.projecthoneypot.org/.
Our best guess right now is that the request was malformed in some way that it injected a CC header into the email. The problem is that we can't figure out how this could be done. We're using System.Net.Mail to send the emails and it seems to protect against this sort of thing. The subject of the MailMessage object only accepts a single line so that you don't create a multiline subject with a CC line. Setting the to and cc addresses in the MailMessage seems pretty robust. And I can't see how you could add a CC header in the body of the message. I can't find any information on this and I'd love to know if this is a real problem.
EDIT: Someone requested the code. It's a little long, but here it is:
public class Global : System.Web.HttpApplication
{
protected void Application_Error(Object sender, EventArgs e)
{
// Get the last exception.
Exception objException = Server.GetLastError();
// Work out the error details based on the exception.
string ErrorType = "";
string ErrorDescription = "";
string ErrorHtml = "";
if (objException == null)
{
// This should never occur.
ErrorType = "Unknown Error";
ErrorDescription = "Unknown Error";
}
else if (objException.GetType() == typeof(HttpException))
{
// This will occur when the ASP.NET engine throws a HttpException.
HttpException objHttpException = objException as HttpException;
if (objHttpException.GetHttpCode() == 404)
{
string Resource = Globals.GetFullUrl(this.Context);
Server.ClearError();
Response.Redirect("/ResourceNotFound.aspx?BadUrl=" + Server.UrlEncode(Resource));
return;
}
else
{
ErrorType = objHttpException.GetHttpCode().ToString();
ErrorDescription = objHttpException.Message;
}
}
else if (objException.GetType() == typeof(HttpUnhandledException) && objException.InnerException != null && objException.InnerException.GetType() == typeof(HttpException))
{
// This will occur when the code throws a HttpException (e.g. a fake 404).
HttpException objHttpException = objException.InnerException as HttpException;
if (objHttpException.GetHttpCode() == 404)
{
string Resource = Globals.GetFullUrl(this.Context);
Server.ClearError();
Response.Redirect("/ResourceNotFound.aspx?BadUrl=" + Server.UrlEncode(Resource));
return;
}
else
{
ErrorType = objHttpException.GetHttpCode().ToString();
ErrorDescription = objHttpException.Message;
}
}
else if (objException.GetType() == typeof(HttpUnhandledException))
{
// This will occur when a page throws an error.
HttpUnhandledException objHttpUnhandledException = (HttpUnhandledException) objException;
ErrorType = objHttpUnhandledException.GetHttpCode().ToString();
if (objHttpUnhandledException.InnerException != null)
ErrorDescription = objHttpUnhandledException.InnerException.Message;
else
ErrorDescription = objHttpUnhandledException.Message;
if (objHttpUnhandledException.GetHtmlErrorMessage() != null)
{
ErrorHtml = objHttpUnhandledException.GetHtmlErrorMessage();
}
}
else if (objException.GetType() == typeof(HttpRequestValidationException) && !Globals.IsTtiUser(this.Context))
{
// Do nothing. This is mostly just spider junk and we don't want to know about it.
}
else
{
// This will occur when the ASP.NET engine throws any error other than a HttpException.
ErrorType = objException.GetType().Name;
ErrorDescription = objException.Message;
}
// Send an email if there's an error to report.
if (ErrorType != "" || ErrorDescription != "")
{
Globals.SendErrorEmail(this.Context, ErrorType, ErrorDescription, ErrorHtml);
}
}
public static void SendErrorEmail (HttpContext context, string errorType, string errorDescription, string errorHtml)
{
// Build the email subject.
string Subject = "EM: " + errorType + ": " + context.Request.ServerVariables["SCRIPT_NAME"];
// Build the email body.
string Body;
StringBuilder sb = new StringBuilder("");
sb.Append("Server:\r\n");
sb.Append(Globals.Server.ToString() + "\r\n");
sb.Append("\r\n");
sb.Append("URL:\r\n");
sb.Append(Globals.GetFullUrl(context) + "\r\n");
sb.Append("\r\n");
sb.Append("Error Type" + ":\r\n");
sb.Append(errorType + "\r\n");
sb.Append("\r\n");
sb.Append("Error Description" + ":\r\n");
sb.Append(errorDescription + "\r\n");
sb.Append("\r\n");
sb.Append("Referring Page:\r\n");
sb.Append(context.Request.ServerVariables["HTTP_REFERER"] + "\r\n");
sb.Append("\r\n");
sb.Append("Date/Time:\r\n");
sb.Append(DateTime.Now.ToString() + "\r\n");
sb.Append("\r\n");
sb.Append("Remote IP:\r\n");
sb.Append(context.Request.ServerVariables["REMOTE_ADDR"] + "\r\n");
sb.Append("\r\n");
sb.Append("User Agent:\r\n");
sb.Append(context.Request.ServerVariables["HTTP_USER_AGENT"] + "\r\n");
sb.Append("\r\n");
sb.Append("Crawler:\r\n");
sb.Append(context.Request.Browser.Crawler.ToString() + "\r\n");
sb.Append("\r\n");
sb.Append("Admin User:\r\n");
sb.Append(context.User.Identity.Name + "\r\n");
sb.Append("\r\n");
sb.Append("\r\n");
Body = sb.ToString();
// If there's HTML to represent the error (usually from HttpUnhandledException),
// then stuff the body text into the HTML (if possible).
bool HtmlMessage = false;
if (errorHtml != "")
{
Regex r = new Regex("(?<thebodytext><body.*?>)", RegexOptions.IgnoreCase);
if (r.IsMatch(errorHtml))
{
Body = Body.Replace("\r\n", "<br>");
Body = r.Replace(errorHtml, "${thebodytext}" + Body, 1);
HtmlMessage = true;
}
}
// Send an email to the TTI developers.
MailMessage objMail;
objMail = new MailMessage();
objMail.From = new MailAddress("from-address");
objMail.To.Add(new MailAddress("to-address"));
objMail.CC.Add(new MailAddress("cc-address"));
objMail.CC.Add(new MailAddress("another-cc-address"));
if (HtmlMessage)
objMail.IsBodyHtml = true;
else
objMail.IsBodyHtml = false;
if (errorType == "404")
objMail.Priority = MailPriority.Low;
else
objMail.Priority = MailPriority.High;
objMail.Subject = Subject;
objMail.Body = Body;
try
{
SmtpClient objSmtpClient = new SmtpClient();
objSmtpClient.Send(objMail);
}
finally
{
// Do nothing.
}
}
}
I could see this being the target of a VERY creative attack.... You are stuffing user controlled data into your message body... At which point, crafty use of binary data COULD result in a BODY that sends the proper data during the SMTP session to get it formatted JUST RIGHT... If I may, I'd suggest either converting the body to all ASCII text, or during your string building, write a string sanitizer that only allows RFC chars in. (Filters the URL's, the REFERRER, Remote Address, and UserAgent). Those are your more likely points of attack.
A second thought might be to construct a basic email in code, and ATTACH the body that you have constructed as a text, or HTML, or PDF file.
Keep in mind, SMTP ENVELOPE data is NOT the same as message data.... If someone was crafty enough to send the correct body that caused a CRLFCRLF.CRLFCRLF to be sent during the body part, that would terminate the sending, and then if they kept sending data, they could send the whole MAIL FROM: RCPT TO:, DATA, etc... (Granted, this is an unlikely scenario...)...
I'd LOVE to see the RAW source of the email you got... (As in the hex dump of the actual SMTP transaction, not what Outlook wants you to see, or whatever).
You may also try encoding your body using QP, or B64 before sending the message.... That might solve your problem...
This is an interesting one, and I'm looking forward to the outcome of it.
Your code looks very secure, so I don't think the problem is on your side.
IMO, either someone intercepted the SMTP message while it was being sent to the mailserver and injected the extra CC: line; or the mailserver has been compromised.
If you can't find an answer I suggest contacting Microsoft directly - you may have uncovered an exploit in the .NET Framework.
As a workaround, why don't you encript the email message using asymeterical encription (for example public key encription)? That way only the intended recepient will be able to read it.
In this way even if the bad guys get a copy of your message (by whatever means) it will be of now use to them.
Lets face it, if you have a high profile website like the FBI or Google, lots of very creative people will spend lots of time and go to great lengths to comprimise it. It is extremely important to protect detailed error messages.

Resources