using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using System.Net.Sockets;
using Admin.Models;
using System.Net.Mail;
namespace Admin.Services.Implementation
{
/// <summary>
/// <para>Wraps an smtp request</para>
/// </summary>
public class SmtpService : ISmtpService
{
/// <summary>
/// Get / Set the name of the SMTP mail server
/// </summary>
private enum SMTPResponse : int
{
CONNECT_SUCCESS = 220,
GENERIC_SUCCESS = 250,
DATA_SUCCESS = 354,
QUIT_SUCCESS = 221
}
private const int timeOutLimit = 1000;
bool ISmtpService.Send(MailMessage message, string SmtpServer, string returnPath)
{
IPAddress localIPAddress = IPAddress.Parse(SmtpServer);
IPEndPoint endPt = new IPEndPoint(localIPAddress, 25);
using (Socket s = new Socket(endPt.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
//set timeout
s.ReceiveTimeout = timeOutLimit;
s.SendTimeout = timeOutLimit;
// try to establish the connection
s.Connect(endPt);
if (!Check_Response(s, SMTPResponse.CONNECT_SUCCESS))
{
s.Close();
return false;
}
// say hello to the server to identify myself
SendData(s, string.Format("HELO {0}\r\n", Dns.GetHostName()));
if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
// set the mail from in the envelope
SendData(s, string.Format("MAIL From: {0}\r\n", returnPath));
if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
// set the email to in the envelope
if (message.To.Count > 0)
{
foreach (var to in message.To)
{
SendData(s, string.Format("RCPT TO: {0}\r\n", to.Address));
if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
}
}
else
return false;
// add bcc to the envelope
if (message.Bcc.Count > 0)
{
foreach (var to in message.Bcc)
{
SendData(s, string.Format("RCPT TO: {0}\r\n", to.Address));
if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
}
}
// create the header as string builder
StringBuilder Header = new StringBuilder();
Header.Append("MIME-Version: 1.0\r\n");
// email priority
Header.Append("Importance: " + message.Priority.ToString() + "\r\n");
// set the from
Header.Append("From: " + message.From.DisplayName + " <" + message.From.Address + ">\r\n");
// set the reply to
Header.Append("Reply-To: " + message.ReplyToList.FirstOrDefault() + "\r\n");
// set the email to
Header.Append("To: ");
int i = 0;
foreach (var To in message.To)
{
Header.Append(i > 0 ? "," : "");
Header.Append(To.Address);
i++;
}
Header.Append("\r\n");
// set the cc
if (message.CC.Count > 0)
{
Header.Append("Cc: ");
i = 0;
foreach (MailAddress To in message.CC)
{
Header.Append(i > 0 ? "," : "");
Header.Append(To.Address);
i++;
}
Header.Append("\r\n");
}
// set the email subject
Header.Append("Subject: " + message.Subject + "\r\n");
Header.Append("Date: ");
Header.Append(DateTime.Now.ToString("R"));
Header.Append("\r\n");
string MsgBody = message.Body;
if (!MsgBody.EndsWith("\r\n"))
MsgBody += "\r\n";
//generate a unique boundary for this email
var uniqueBoundary = EncodeTo64("uniqueboundary" + DateTime.Now.ToString("yyyy-MM-ddTHH:mm:sszzz"));
Header.Append("Content-Type: multipart/alternative; boundary=" + uniqueBoundary + "\r\n");
Header.Append("This is a multi-part message in MIME format.\r\n");
// adding message body
SendData(s, ("DATA\r\n"));
if (!Check_Response(s, SMTPResponse.DATA_SUCCESS))
{
s.Close();
return false;
}
if (message.Attachments.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach (Attachment attachment in message.Attachments)
{
byte[] binaryData;
if (attachment != null)
{
sb.Append("--" + uniqueBoundary + "\r\n");
sb.Append("Content-Type: application/octet-stream; file=" + attachment.Name + "\r\n");
sb.Append("Content-Transfer-Encoding: base64\r\n");
sb.Append("Content-Disposition: attachment; filename=" + attachment.Name + "\r\n");
sb.Append("\r\n");
var fs = attachment.ContentStream;
binaryData = new Byte[fs.Length];
long bytesRead = fs.Read(binaryData, 0, (int)fs.Length);
fs.Close();
string base64String = System.Convert.ToBase64String(binaryData, 0, binaryData.Length);
for (int j = 0; j < base64String.Length; )
{
int nextchunk = 100;
if (base64String.Length - (j + nextchunk) < 0)
nextchunk = base64String.Length - j;
sb.Append(base64String.Substring(j, nextchunk));
sb.Append("\r\n");
j += nextchunk;
}
sb.Append("\r\n");
}
}
Header.Append(sb.ToString());
}
Header.Append("--" + uniqueBoundary + "\r\n");
// is an html email?
if (message.IsBodyHtml)
{
Header.Append("Content-Type: text/html; charset=\"UTF-8\"\r\n");
Header.Append("Content-Transfer-Encoding: 7bit\n");
}
else
Header.Append("Content-Type: text/plain; charset=\"UTF-8\"\r\n");
Header.Append("\r\n");
Header.Append(MsgBody);
Header.Append("--" + uniqueBoundary + "--\r\n");
Header.Append(".\r\n");
// send the email
SendData(s, Header.ToString());
if (!Check_Response(s, SMTPResponse.GENERIC_SUCCESS))
{
s.Close();
return false;
}
// close the connection
SendData(s, "QUIT\r\n");
Check_Response(s, SMTPResponse.QUIT_SUCCESS);
s.Close();
}
return true;
}
// send a message using the socket
private static void SendData(Socket s, string msg)
{
byte[] _msg = Encoding.UTF8.GetBytes(msg);
s.Send(_msg, 0, _msg.Length, SocketFlags.None);
}
// check if the socket response watch the one expected
private static bool Check_Response(Socket s, SMTPResponse response_expected)
{
string sResponse;
int response;
byte[] bytes = new byte[1024];
s.Receive(bytes);
sResponse = Encoding.ASCII.GetString(bytes);
response = Convert.ToInt32(sResponse.Substring(0, 3));
if (response != (int)response_expected)
return false;
return true;
}
private string EncodeTo64(string toEncode)
{
byte[] toEncodeAsBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(toEncode);
string returnValue = System.Convert.ToBase64String(toEncodeAsBytes);
return returnValue;
}
}
}
Every time we send some data through the socket using the method SendData, we need to check the response from the server. If the answer is positive we proceed sending the next bunch of data.