/// <summary> /// Remove the specified command from the state. /// </summary> /// <param name="state">The SMTP state to remove the command from.</param> /// <param name="command">The command to remove from the state.</param> void RemoveCommand(SmtpState state, string command) { if (_stateTable[state].Actions.ContainsKey(command)) { _stateTable[state].Actions.Remove(command); } }
//internal System.IO.MemoryStream DataStream { get { return this.dataStream; } set { this.dataStream = value; } } internal SmtpSession() : base(DATA_TERMINATOR, 2) { this.state = SmtpState.None; this.rcptTo = new List <string>(); //this.dataStream = new System.IO.MemoryStream(); //this.asciiEncoding = new System.Text.ASCIIEncoding(); }
/* * internal void AddData(string dataString) { * byte[] data = asciiEncoding.GetBytes(dataString); * this.AddData(data, 0, data.Length); * } */ /* * internal void AddData(byte[] buffer, int offset, int count) { * List<byte> readBytes; * * long terminatorIndex = Utils.KnuthMorrisPratt.ReadTo(DATA_TERMINATOR, buffer, offset, out readBytes); * //terminator might be split in between two packets * if (terminatorIndex == -1 && this.dataStream.Length > 0) { * int oldBytesToRead = Math.Min(DATA_TERMINATOR.Length-1, (int)dataStream.Length); * byte[] oldBufferTail = new byte[oldBytesToRead]; * this.dataStream.Seek(this.dataStream.Length - oldBytesToRead, System.IO.SeekOrigin.Begin); * int oldBytesRead = this.dataStream.Read(oldBufferTail, 0, oldBytesToRead); * byte[] tempBuffer = new byte[oldBytesRead + buffer.Length - offset]; * Array.Copy(oldBufferTail, 0, tempBuffer, 0, oldBytesRead); * Array.Copy(buffer, offset, tempBuffer, oldBytesRead, buffer.Length - offset); * long tempTerminatorIndex = Utils.KnuthMorrisPratt.ReadTo(DATA_TERMINATOR, tempBuffer, 0, out readBytes); * if (tempTerminatorIndex >= 0) { * count = (int)tempTerminatorIndex - oldBytesRead + 2; * this.state = SmtpState.Footer; * } * } * else if(terminatorIndex >= 0) { * //terminator was found * * //the final <cr><lf>.<cr><lf> will not included, but let's at least add one <cr><lf> at the end * count = (int)terminatorIndex-offset+2; //"+2" adds the <cr><lf> * //offset = 0; * this.state = SmtpState.Footer; * } * if (count > 0) { * this.dataStream.Seek(0, System.IO.SeekOrigin.End); * this.dataStream.Write(buffer, offset, count); * } * }*/ internal new void AddData(byte[] buffer, int offset, int count) { base.AddData(buffer, offset, count); if (base.TerminatorFound) { this.state = SmtpState.Footer; } }
public SmtpSession(SmtpServer server, Socket s) { _server = server; _serverName = GetServerName(s); var ascii = Encoding.ASCII; ReplyGreeting = ascii.GetBytes(string.Format("220 {0} ESMTP ready.\r\n", _serverName)); ReplyOk = ascii.GetBytes("250 OK\r\n"); ReplyEhlo = ascii.GetBytes(string.Format("250-{0}\r\n250-8BITMIME\r\n250 SIZE {0}\r\n", _serverName, MaximumMessageSize)); ReplyHelo = ascii.GetBytes(string.Format("250 {0}\r\n", _serverName)); ReplySyntaxErrorRset = ascii.GetBytes("501 RSET syntax error\r\n"); ReplySyntaxErrorEhlo = ascii.GetBytes("501 EHLO syntax error\r\n"); ReplySyntaxErrorHelo = ascii.GetBytes("501 HELO syntax error\r\n"); ReplySyntaxErrorMail = ascii.GetBytes("501 MAIL syntax error\r\n"); ReplySyntaxErrorRcpt = ascii.GetBytes("501 RCPT syntax error\r\n"); ReplySyntaxErrorData = ascii.GetBytes("501 DATA syntax error\r\n"); ReplySyntaxErrorQuit = ascii.GetBytes("501 QUIT syntax error\r\n"); ReplyCommandNotImplemented = ascii.GetBytes("502 Command not implemented\r\n"); ReplyUnknownCommand = ascii.GetBytes("500 Unknown command\r\n"); ReplyBadSequence = ascii.GetBytes("503 Bad sequence of command\r\n"); ReplyHelp = ascii.GetBytes("211 You don't need help\r\n"); ReplyStartMail = ascii.GetBytes("354 Start mail input; end with <CRLF>.<CRLF>\r\n"); ReplyBye = ascii.GetBytes("221 Bye\r\n"); ReplyNoSuchUserHere = ascii.GetBytes("550 No such user here\r\n"); ReplyMessageSizeTooBig = ascii.GetBytes("552 Message size exceeds maximum permitted\r\n"); _lock = new object(); _socket = s; _socket.ReceiveBufferSize = 8192; _socket.SendBufferSize = 8192; _utcCreationTime = DateTime.UtcNow; _state = SmtpState.WaitingForHelo; _isAborting = false; _receiveBuffer = new byte[_socket.ReceiveBufferSize + 128]; _bufferedData = new byte[_socket.ReceiveBufferSize + 128]; _bufferedDataLength = 0; ResetMailInfo(); _isAlive = true; _emptyBuffer = new ArraySegment <byte>(Bytes.Empty); Greet(); Receive(); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="context">The session context to use for making session based transitions.</param> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryAccept(SmtpSessionContext context, TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { if (_states[_current].Transitions.TryGetValue(tokenEnumerator.Peek().Text, out StateTransition transition) == false) { var response = $"expected {String.Join("/", _states[_current].Transitions.Keys)}"; command = null; errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, response); return(false); } if (transition.Delegate(tokenEnumerator, out command, out errorResponse) == false) { return(false); } _current = transition.Transition(context); return(true); }
internal void AddData(byte[] buffer, int offset, int count) { List <byte> readBytes; long terminatorIndex = Utils.KnuthMorrisPratt.ReadTo(DATA_TERMINATOR, buffer, offset, out readBytes); //terminator might be split in between two packets if (terminatorIndex == -1 && this.dataStream.Length > 0) { int oldBytesToRead = Math.Min(DATA_TERMINATOR.Length - 1, (int)dataStream.Length); byte[] oldBufferTail = new byte[oldBytesToRead]; this.dataStream.Seek(this.dataStream.Length - oldBytesToRead, System.IO.SeekOrigin.Begin); int oldBytesRead = this.dataStream.Read(oldBufferTail, 0, oldBytesToRead); byte[] tempBuffer = new byte[oldBytesRead + buffer.Length - offset]; Array.Copy(oldBufferTail, 0, tempBuffer, 0, oldBytesRead); Array.Copy(buffer, offset, tempBuffer, oldBytesRead, buffer.Length - offset); long tempTerminatorIndex = Utils.KnuthMorrisPratt.ReadTo(DATA_TERMINATOR, tempBuffer, 0, out readBytes); if (tempTerminatorIndex >= 0) { count = (int)tempTerminatorIndex - oldBytesRead + 2; this.state = SmtpState.Footer; } } else if (terminatorIndex >= 0) { //terminator was found //the final <cr><lf>.<cr><lf> will not included, but let's at least add one <cr><lf> at the end count = (int)terminatorIndex - offset + 2; //"+2" adds the <cr><lf> //offset = 0; this.state = SmtpState.Footer; } if (count > 0) { this.dataStream.Seek(0, System.IO.SeekOrigin.End); this.dataStream.Write(buffer, offset, count); } }
/// <summary> /// Sets the initial state. /// </summary> /// <param name="stateId">The ID of the initial state.</param> public void Initialize(SmtpState stateId) { _state = _states[stateId]; }
/// <summary> /// Create a new SMTP client request. /// </summary> /// <param name="actionType">type of action/command</param> /// <param name="request_Params">remainder of command line once command is removed</param> /// <param name="state">current SMTP server state</param> public SmtpRequest(SmtpActionType actionType, string request_Params, SmtpState state) { this.action = actionType; this.state = state; this.request_Params = request_Params; }
/// <summary> /// Constructor. /// </summary> /// <param name="code">response code</param> /// <param name="message">response message</param> /// <param name="next">next state of the SMTP server</param> public SmtpResponse(int code, string message, SmtpState next) { this.code = code; this.message = message; this.nextState = next; }
/// <summary> /// Constructor. /// </summary> /// <param name="code">response code</param> /// <param name="message">response message</param> /// <param name="next">next state of the SMTP server</param> public SmtpResponse(int code, string message, SmtpState next) { Code = code; Message = message; NextState = next; }
/// <summary> /// Constructor. /// </summary> /// <param name="stateId">The ID of the state.</param> public State(SmtpState stateId) { StateId = stateId; Transitions = new Dictionary <string, StateTransition>(StringComparer.OrdinalIgnoreCase); }
/// <summary> /// Returns the state with the given ID. /// </summary> /// <param name="stateId">The state ID to return.</param> /// <returns>The state with the given id.</returns> public State this[SmtpState stateId] => _states[stateId];
/// <summary> /// Remove the specified command from the state. /// </summary> /// <param name="state">The SMTP state to remove the command from.</param> /// <param name="command">The command to remove from the state.</param> void ISmtpStateMachine.RemoveCommand(SmtpState state, string command) { if (_stateTable[state].Actions.ContainsKey(command)) { _stateTable[state].Actions.Remove(command); } }
/// <summary> /// Add a state action. /// </summary> /// <param name="command">The name of the SMTP command.</param> /// <param name="tryMake">The function callback to create the command.</param> /// <param name="transitionTo">The state to transition to.</param> public void Add(string command, TryMakeDelegate tryMake, SmtpState? transitionTo = null) { Actions.Add(command, Tuple.Create(tryMake, transitionTo ?? StateId)); }
/// <summary> /// Constructor. /// </summary> /// <param name="stateId">The ID of the state.</param> public State(SmtpState stateId) { StateId = stateId; Actions = new Dictionary<string, Tuple<TryMakeDelegate, SmtpState>>(StringComparer.InvariantCultureIgnoreCase); }
/// <summary> /// Returns the state with the given ID. /// </summary> /// <param name="stateId">The state ID to return.</param> /// <returns>The state with the given id.</returns> public State this[SmtpState stateId] { get { return _states[stateId]; } }
/// <summary> /// Constructor. /// </summary> /// <param name="stateId">The ID of the state.</param> public State(SmtpState stateId) { StateId = stateId; Actions = new Dictionary <string, Tuple <TryMakeDelegate, SmtpState> >(StringComparer.OrdinalIgnoreCase); }
/// <summary> /// Sets the initial state. /// </summary> /// <param name="stateId">The ID of the initial state.</param> public void Initialize(SmtpState stateId) { _current = stateId; }
/// <summary> /// Create a new SMTP client request. /// </summary> /// <param name="actionType">type of action/command</param> /// <param name="requestParams">remainder of command line once command is removed</param> /// <param name="state">current SMTP server state</param> public SmtpRequest(SmtpActionType actionType, string requestParams, SmtpState state) { _action = actionType; _state = state; Params = requestParams; }
/// <summary> /// Accept the state and transition to the new state. /// </summary> /// <param name="context">The session context to use for accepting session based transitions.</param> public void Transition(SmtpSessionContext context) { _current = _transition.Transition(context); }
/// <summary> /// Create an SMTP request object given a line of the input stream from the client and the current internal state. /// </summary> /// <param name="s">line of input</param> /// <param name="state">current state</param> /// <returns>A populated SmtpRequest object</returns> public static SmtpRequest CreateRequest(string s, SmtpState state) { SmtpActionType action = null; string request_Params = null; if (state == SmtpState.DATA_HDR) { if (s.Equals(".")) { action = SmtpActionType.DATA_END; } else if (s.Length < 1) { action = SmtpActionType.BLANK_LINE; } else { action = SmtpActionType.UNRECOG; request_Params = s; } } else if (state == SmtpState.DATA_BODY) { if (s.Equals(".")) { action = SmtpActionType.DATA_END; } else { action = SmtpActionType.UNRECOG; request_Params = s; } } else { string su = s.ToUpper(); if (su.StartsWith("EHLO ") || su.StartsWith("HELO")) { action = SmtpActionType.EHLO; request_Params = s.Substring(5); } else if (su.StartsWith("MAIL FROM:")) { action = SmtpActionType.MAIL; request_Params = s.Substring(10); } else if (su.StartsWith("RCPT TO:")) { action = SmtpActionType.RCPT; request_Params = s.Substring(8); } else if (su.StartsWith("DATA")) { action = SmtpActionType.DATA; } else if (su.StartsWith("QUIT")) { action = SmtpActionType.QUIT; } else if (su.StartsWith("RSET")) { action = SmtpActionType.RSET; } else if (su.StartsWith("NOOP")) { action = SmtpActionType.NOOP; } else if (su.StartsWith("EXPN")) { action = SmtpActionType.EXPN; } else if (su.StartsWith("VRFY")) { action = SmtpActionType.VRFY; } else if (su.StartsWith("HELP")) { action = SmtpActionType.HELP; } else { action = SmtpActionType.UNRECOG; } } SmtpRequest req = new SmtpRequest(action, request_Params, state); return(req); }
/// <summary> /// Add a state action. /// </summary> /// <param name="command">The name of the SMTP command.</param> /// <param name="tryMake">The function callback to create the command.</param> /// <param name="transition">The state to transition to.</param> public void Add(string command, TryMakeDelegate tryMake, SmtpState transition) { Add(command, tryMake, context => transition); }
private void HandleCompleteLine(ArraySegment <byte> line) { var isHandled = false; var nextState = _state; byte[] response = null; ArraySegment <byte> cmd = _emptyBuffer; var tokens = new ArraySegment <byte>[] { }; if (_state != SmtpState.WaitingForEndOfData) { tokens = Bytes.Split(line, Whitespace); if (tokens.Length > 0) { Bytes.ToUpper(tokens[0]); cmd = tokens[0]; } } if (Bytes.IsSame(RSET, cmd)) { isHandled = true; if (tokens.Length == 1) { ResetMailInfo(); response = ReplyOk; nextState = SmtpState.WaitingForHelo; } else { response = ReplySyntaxErrorRset; } } else if (_state == SmtpState.WaitingForHelo && Bytes.IsSame(EHLO, cmd)) { isHandled = true; if (tokens.Length == 2) { response = ReplyEhlo; nextState = SmtpState.WaitingForMailFrom; } else { response = ReplySyntaxErrorEhlo; } } else if (_state == SmtpState.WaitingForHelo && Bytes.IsSame(HELO, cmd)) { isHandled = true; if (tokens.Length == 2) { response = ReplyHelo; nextState = SmtpState.WaitingForMailFrom; } else { response = ReplySyntaxErrorHelo; } } else if (_state == SmtpState.WaitingForMailFrom && Bytes.IsSame(MAIL, cmd)) { isHandled = true; // extract mail address between < > after : bool go = false; int lt = -1, gt = -1; for (var i = line.Offset; i < line.Offset + line.Count; i++) { byte ch = line.Array[i]; if (go) { if (lt == -1 && ch == LessThan) { lt = i; } if (lt != -1 && ch == GreaterThan) { gt = i; break; } } else if (ch == SemiColon) { go = true; } } if (lt != -1 && gt != -1) { // sender found, _sender = Encoding.ASCII.GetString(line.Array, lt + 1, gt - lt - 1).Trim(); // check for optional SIZE=nnnnnnn extension after the sender int sizeOffset = Bytes.OffsetOf(MessageSizeExtension, line.Array, gt + 1, line.Count - gt - 1); if (sizeOffset != -1) { int valueBeginOffset = sizeOffset + MessageSizeExtension.Length; int valueEndOffset = -1; for (var i = valueBeginOffset; i < line.Offset + line.Count; i++) { byte ch = line.Array[i]; byte digit = Bytes.Digits[ch]; if (Whitespace.Contains(ch)) { break; } else if (digit > 9 || digit < 0) { valueEndOffset = -1; break; } valueEndOffset = i; } if (valueEndOffset >= valueBeginOffset) { int size = 0, scale = 1; for (var i = valueEndOffset; i >= valueBeginOffset && size < MaximumMessageSize; i--, scale *= 10) { size += Bytes.Digits[line.Array[i]] * scale; } if (size > MaximumMessageSize) { response = ReplyMessageSizeTooBig; nextState = SmtpState.WaitingForRset; } else { _mailContentExpectedSize = size; response = ReplyOk; nextState = SmtpState.WaitingForRcptTo; } } else { response = ReplySyntaxErrorMail; } } else { // no size extension response = ReplyOk; nextState = SmtpState.WaitingForRcptTo; } } else { response = ReplySyntaxErrorMail; } } else if ((_state == SmtpState.WaitingForRcptTo || _state == SmtpState.WaitingForAdditionalRcptTo) && Bytes.IsSame(RCPT, cmd)) { // standard deviation: send only to the last given recipient even if not pretending so. isHandled = true; // extract mail address between < > after : bool go = false; int lt = -1, gt = -1; for (var i = line.Offset; i < line.Offset + line.Count; i++) { byte ch = line.Array[i]; if (go) { if (lt == -1 && ch == LessThan) { lt = i; } if (lt != -1 && ch == GreaterThan) { gt = i; break; } } else if (ch == SemiColon) { go = true; } } if (lt != -1 && gt != -1) { var recipient = Encoding.ASCII.GetString(line.Array, lt + 1, gt - lt - 1).Trim().ToLowerInvariant(); if (_server.MailDispatcher.IsMailboxActive(recipient)) { _lastRecipient = recipient; response = ReplyOk; nextState = SmtpState.WaitingForAdditionalRcptTo; } else { response = ReplyNoSuchUserHere; } } else { response = ReplySyntaxErrorRcpt; } } else if (_state == SmtpState.WaitingForAdditionalRcptTo && Bytes.IsSame(DATA, cmd)) { isHandled = true; if (tokens.Length == 1) { response = ReplyStartMail; if (_mailContent == null) { // presize stream to avoid garbage & extra reallocation if expected size makes sense. // add a bit of slack for size errors var size = (_mailContentExpectedSize > 0 && _mailContentExpectedSize <= MaximumMessageSize) ? _mailContentExpectedSize + 1024 : 4096; _mailContentId = Guid.NewGuid(); _mailContent = new FileStream(IncomingMailDTO.GetContentFileName(_mailContentId), FileMode.CreateNew, FileAccess.Write, FileShare.None, 16384); } nextState = SmtpState.WaitingForEndOfData; } else { response = ReplySyntaxErrorData; } } else if (_state == SmtpState.WaitingForEndOfData) { isHandled = true; if (line.Count == EndOfMail.Length && Bytes.StartsWith(EndOfMail, line)) { if (_mailContent.Length <= MaximumMessageSize) { response = ReplyOk; nextState = SmtpState.WaitingForMailFrom; _mailContent.Flush(); _server.MailDispatcher.Enqueue(new IncomingMailDTO(DateTime.UtcNow, _lastRecipient, _sender, (int)_mailContent.Length, _mailContentId)); _mailContent.Dispose(); _mailContent = null; } else { response = ReplyMessageSizeTooBig; nextState = SmtpState.WaitingForRset; } ResetMailInfo(); } else { // accumulate mail body & handle dot unstuffing (while msg size doesn't exceed maximum size, since it will be discarded later anyway). int offset = line.Offset; int count = line.Count; if (line.Array[offset] == Dot) { offset++; count--; } if (_mailContent != null && _mailContent.Length + count <= MaximumMessageSize) { _mailContent.Write(line.Array, offset, count); } } } else if (Bytes.IsSame(QUIT, cmd)) { isHandled = true; if (tokens.Length == 1) { ResetMailInfo(); response = ReplyBye; nextState = SmtpState.Disconnect; } else { response = ReplySyntaxErrorQuit; } } else if (Bytes.IsSame(VRFY, cmd) || Bytes.IsSame(EXPN, cmd)) { isHandled = true; response = ReplyCommandNotImplemented; } else if (Bytes.IsSame(HELP, cmd)) { isHandled = true; response = ReplyHelp; } else if (Bytes.IsSame(NOOP, cmd)) { isHandled = true; response = ReplyOk; } if (!isHandled) { var isKnownCommand = KnownCommands.Any((knownCmd) => Bytes.IsSame(knownCmd, cmd)); response = isKnownCommand ? ReplyBadSequence : ReplyUnknownCommand; } _state = nextState; if (response != null && response.Length > 0) { var e = CreateArgs(SendCompleted, response); if (!_socket.SendAsync(e)) { SendCompleted(_socket, e); } } }
/// <summary> /// Returns the state with the given ID. /// </summary> /// <param name="stateId">The state ID to return.</param> /// <returns>The state with the given id.</returns> public State this[SmtpState stateId] { get { return(_states[stateId]); } }
/// <summary> /// Constructor. /// </summary> /// <param name="stateId">The ID of the state.</param> public State(SmtpState stateId) { StateId = stateId; Actions = new Dictionary <string, Tuple <TryMakeDelegate, SmtpState> >(StringComparer.InvariantCultureIgnoreCase); }
/// <summary> /// Create an SMTP request object given a line of the input stream from the client and the current internal state. /// </summary> /// <param name="s">line of input</param> /// <param name="state">current state</param> /// <returns>A populated SmtpRequest object</returns> public static SmtpRequest CreateRequest(string s, SmtpState state) { SmtpActionType action = null; string request_Params = null; if (state == SmtpState.DATA_HDR) { if (s.Equals(".")) { action = SmtpActionType.DATA_END; } else if (s.Length < 1) { action = SmtpActionType.BLANK_LINE; } else { action = SmtpActionType.UNRECOG; request_Params = s; } } else if (state == SmtpState.DATA_BODY) { if (s.Equals(".")) { action = SmtpActionType.DATA_END; } else { action = SmtpActionType.UNRECOG; request_Params = s; } } else { string su = s.ToUpper(); if (su.StartsWith("EHLO ") || su.StartsWith("HELO")) { action = SmtpActionType.EHLO; request_Params = s.Substring(5); } else if (su.StartsWith("MAIL FROM:")) { action = SmtpActionType.MAIL; request_Params = s.Substring(10); } else if (su.StartsWith("RCPT TO:")) { action = SmtpActionType.RCPT; request_Params = s.Substring(8); } else if (su.StartsWith("DATA")) { action = SmtpActionType.DATA; } else if (su.StartsWith("QUIT")) { action = SmtpActionType.QUIT; } else if (su.StartsWith("RSET")) { action = SmtpActionType.RSET; } else if (su.StartsWith("NOOP")) { action = SmtpActionType.NOOP; } else if (su.StartsWith("EXPN")) { action = SmtpActionType.EXPN; } else if (su.StartsWith("VRFY")) { action = SmtpActionType.VRFY; } else if (su.StartsWith("HELP")) { action = SmtpActionType.HELP; } else { action = SmtpActionType.UNRECOG; } } SmtpRequest req = new SmtpRequest(action, request_Params, state); return req; }