/Arendee.Lovon.Smtp/SmtpClientProcessor.cs
C# | 356 lines | 310 code | 43 blank | 3 comment | 46 complexity | 178f0691ee52d3d9c367106efdf5242f MD5 | raw file
- //Lovon is Licensed under the MS-PL, Copyright © 2012 The Arendee Lovon Team
-
- using System;
- using System.Linq;
- using System.Collections.Generic;
- using System.Net.Sockets;
- using System.Text;
- using System.Text.RegularExpressions;
- using System.Threading.Tasks;
- using Arendee.Lovon.Smtp.Exceptions;
- using NLog;
- using System.Net.Mail;
- using System.Net;
-
- namespace Arendee.Lovon.Smtp
- {
- public class SmtpClientProcessor
- {
- private const string END_OF_LINE = "\r\n";
- private static Logger logger = LogManager.GetCurrentClassLogger();
- private static readonly Regex EMAIL_ADDRESS_REGEX = new Regex("<.+@.+>", RegexOptions.IgnoreCase);
-
- private Socket client;
- private SmtpSessionState sessionState;
- private SmtpMessage currentMessage;
- private SmtpServer server;
- private Queue<string> readQueue = new Queue<string>();
-
- public SmtpClientProcessor(Socket client, SmtpServer server)
- {
- this.client = client;
- this.server = server;
- InitializeSession();
- }
-
- private void InitializeSession()
- {
- sessionState = SmtpSessionState.Connected;
- currentMessage = new SmtpMessage();
- }
-
- public async void ProcessAsync()
- {
- WriteLine(SmtpResponseMessage.WELCOME, server.Domain);
-
- await Task.Run(() =>
- {
- var command = ReadLine();
-
- while (command != null)
- {
- if (command != String.Empty)
- ProcessCommand(command);
- command = ReadLine();
- }
-
- logger.Info("Client disconnected");
- client.Close();
- });
- }
-
- private void ProcessCommand(string command)
- {
- logger.Trace("Command received from client '{0}': {1}", client.RemoteEndPoint, command);
-
- string[] parts = command.ToLower().Split(' ');
-
- switch (parts[0].Replace(END_OF_LINE, ""))
- {
- case "helo":
- case "ehlo":
- HandleHeloCommand(parts);
- break;
- case "mail":
- HandleMailCommand(parts);
- break;
- case "rcpt":
- HandleRcptCommand(parts);
- break;
- case "data":
- HandleDataCommand();
- break;
- case "rset":
- HandleRsetCommand();
- break;
- case "noop":
- HandleNoopCommand();
- break;
- case "quit":
- HandleQuitCommand();
- break;
- default:
- WriteLine(SmtpResponseMessage.COMMAND_SYNTAX_ERROR);
- break;
- }
- }
-
- private void HandleHeloCommand(string[] parts)
- {
- if (sessionState != SmtpSessionState.Connected)
- {
- WriteLine(SmtpResponseMessage.INVALID_COMMAND_ORDER);
- }
- else if (parts.Length != 2)
- {
- WriteLine(SmtpResponseMessage.COMMAND_PARAMETERS_SYNTAX_ERROR);
- }
- else
- {
- currentMessage.ServerAddress = ((IPEndPoint)client.RemoteEndPoint).Address;
- currentMessage.ServerDomain = parts[1];
-
- ///TODO: Should perform a DNS lookup of the specified domain
- sessionState = SmtpSessionState.HeloCommandReceived;
- WriteLine(SmtpResponseMessage.HELO, server.Domain);
- }
- }
-
- private void HandleMailCommand(string[] parts)
- {
- string senderAddress = null;
-
- if (sessionState != SmtpSessionState.HeloCommandReceived && sessionState != SmtpSessionState.DataCommandReceived)
- {
- WriteLine(SmtpResponseMessage.INVALID_COMMAND_ORDER);
- }
- else if (parts.Length < 2 || !TryExtractEmailAddress(String.Join("",parts) , out senderAddress))
- {
- WriteLine(SmtpResponseMessage.COMMAND_PARAMETERS_SYNTAX_ERROR);
- }
- else if (!server.SenderFilter.SenderAccepted(senderAddress))
- {
- WriteLine(SmtpResponseMessage.SENDER_ADDRESS_REJECTED);
- }
- else
- {
- currentMessage.SenderAddress = new MailAddress(senderAddress);
- sessionState = SmtpSessionState.MailCommandReceived;
- WriteLine(SmtpResponseMessage.OK);
- }
- }
-
- private void HandleRcptCommand(string[] parts)
- {
- string recipientAddress = null;
-
- if (sessionState != SmtpSessionState.MailCommandReceived && sessionState != SmtpSessionState.RcptCommandReceived)
- {
- WriteLine(SmtpResponseMessage.INVALID_COMMAND_ORDER);
- }
- else if (parts.Length < 2 || !TryExtractEmailAddress(String.Join("",parts), out recipientAddress))
- {
- WriteLine(SmtpResponseMessage.COMMAND_PARAMETERS_SYNTAX_ERROR);
- }
- else if (!server.RecipientFilter.RecipientAccepted(recipientAddress))
- {
- WriteLine(SmtpResponseMessage.RECIPIENT_ADDRESS_REJECTED);
- }
- else
- {
- currentMessage.RecipientAddresses.Add(new MailAddress(recipientAddress));
- sessionState = SmtpSessionState.RcptCommandReceived;
- WriteLine(SmtpResponseMessage.OK);
- }
- }
-
- private void HandleDataCommand()
- {
- if (sessionState != SmtpSessionState.RcptCommandReceived)
- {
- WriteLine(SmtpResponseMessage.INVALID_COMMAND_ORDER);
- }
- else
- {
- WriteLine(SmtpResponseMessage.START_MAIL_INPUT);
- sessionState = SmtpSessionState.DataCommandReceived;
-
- var dataBuilder = new StringBuilder();
-
- var line = ReadLine();
-
- while (line != ".")
- {
- dataBuilder.AppendFormat("{0}\r\n", line);
- line = ReadLine();
- }
-
- var rawBody = dataBuilder.ToString();
- currentMessage.Headers = ExtractHeadersFromRawBody(rawBody);
-
- if (currentMessage.Headers == null)
- {
- WriteLine(SmtpResponseMessage.SERVER_ERROR, "Failed to extract the headers");
- }
- else
- {
- currentMessage.Subject = currentMessage.Headers.First(h => h.Key == "subject").Value;
- currentMessage.Body = ExtractBodyFromRawBody(rawBody);
-
- logger.Trace("Data received from client '{0}': {1}", client.RemoteEndPoint, rawBody);
- logger.Debug("Mail received from client '{0}' with subject: {1}", client.RemoteEndPoint, currentMessage.Subject);
- }
- try
- {
- server.SpoolService.Spool(currentMessage);
- logger.Trace("Message from client '{0}' spooled", client.RemoteEndPoint);
- WriteLine(SmtpResponseMessage.OK);
- }
- catch (SpoolException ex)
- {
- logger.Error("{0}: {1}", ex.Message, (ex.InnerException != null ? ex.InnerException.Message : ""));
- WriteLine(SmtpResponseMessage.SERVER_ERROR, ex.Message);
- }
- catch (Exception ex)
- {
- logger.Error("Unexpected exception while trying to spool the message: {0}", ex.Message);
- WriteLine(SmtpResponseMessage.SERVER_ERROR);
- }
- finally
- {
- sessionState = SmtpSessionState.HeloCommandReceived;
- currentMessage = new SmtpMessage()
- {
- ServerAddress = currentMessage.ServerAddress,
- ServerDomain = currentMessage.ServerDomain
- };
- }
- }
- }
-
- private void HandleQuitCommand()
- {
- WriteLine(SmtpResponseMessage.GOODBYE);
- client.Close();
- }
-
- private void HandleRsetCommand()
- {
- if (sessionState == SmtpSessionState.Connected)
- {
- WriteLine(SmtpResponseMessage.INVALID_COMMAND_ORDER);
- }
- else
- {
- InitializeSession();
- WriteLine(SmtpResponseMessage.OK);
- }
- }
-
- private void HandleNoopCommand()
- {
- WriteLine(SmtpResponseMessage.OK);
- }
-
- private string ReadLine()
- {
- if (client == null || !client.Connected)
- return null;
-
- if (readQueue.Count > 0)
- return readQueue.Dequeue();
-
- byte[] buffer = new byte[client.ReceiveBufferSize];
- var read = client.Receive(buffer);
-
- var lineBuilder = new StringBuilder();
-
- if (read > 0)
- {
- var input = ASCIIEncoding.ASCII.GetString(buffer, 0, read)
- .Split(new string[] { END_OF_LINE }, StringSplitOptions.None);
- input.Skip(1).ToList().ForEach(i => readQueue.Enqueue(i));
- return input[0];
- }
- else
- {
- return null;
- }
- }
-
- private void WriteLine(string line, params object[] args)
- {
- line = String.Concat(String.Format(line, args), END_OF_LINE);
- var data = ASCIIEncoding.ASCII.GetBytes(line);
- client.Send(data);
- logger.Trace("Response sent to client '{0}': {1}", client.RemoteEndPoint, line);
- }
-
- private bool TryExtractEmailAddress(string input, out string emailAddress)
- {
- var match = EMAIL_ADDRESS_REGEX.Match(input);
-
- if (!match.Success)
- {
- emailAddress = null;
- return false;
- }
- else
- {
- emailAddress = match.Value.Substring(1, match.Value.Length - 2);
- return true;
- }
- }
-
- //TODO: THIS NEEDS TO BE FIXED, DOES NOT GET THE HEADERS PROPERLY
- private List<KeyValuePair<string, string>> ExtractHeadersFromRawBody(string rawBody)
- {
- try
- {
- var headers = new List<KeyValuePair<string, string>>();
-
- if (rawBody.StartsWith("\r\n\r\n"))
- rawBody = rawBody.Substring(4);
-
- var bodyOffset = rawBody.IndexOf("\r\n\r\n");
- logger.Trace("Body offset: {0}", bodyOffset);
-
- rawBody.Substring(0, bodyOffset)
- .Split(new string[] { END_OF_LINE }, StringSplitOptions.RemoveEmptyEntries)
- .Where(s => !String.IsNullOrWhiteSpace(s))
- .ToList()
- .ForEach(s =>
- {
- if (!s.Contains(':'))
- {
- var lastHeader = headers.Last();
- headers[headers.Count - 1] = new KeyValuePair<string, string>(lastHeader.Key, lastHeader.Value + s);
- }
- else
- {
- logger.Trace("Header Line: {0}", s);
- var parts = s.Split(':');
- logger.Trace("Parts Length: {0}", parts.Length);
- headers.Add(new KeyValuePair<string,string>(parts[0].ToLower(), parts[1]));
- }
- });
-
- return headers;
- }
- catch (Exception ex)
- {
- logger.TraceException("Failed to extract the headers", ex);
- logger.Trace(ex.ToString());
- logger.Trace(rawBody);
- return null;
- }
- }
-
- public string ExtractBodyFromRawBody(string rawBody)
- {
- var bodyOffset = rawBody.IndexOf("\r\n\r\n");
- return rawBody.Substring(bodyOffset);
- }
-
- }
- }