/// <summary> /// Make an AUTH command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The AUTH 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeAuth(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "AUTH")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); AuthenticationMethod method; if (Enum.TryParse(enumerator.Peek().Text, true, out method) == false) { _logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); errorResponse = SmtpResponse.SyntaxError; return(false); } enumerator.Take(); string parameter = null; if (enumerator.Count > 0 && _parser.TryMakeBase64(enumerator, out parameter) == false) { _logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new AuthCommand(_options.UserAuthenticator, method, parameter); return(true); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="command">The HELO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out var domain)) { command = new HeloCommand(_options, domain); return(true); } // according to RFC5321 the HELO command should only accept the Domain // and not the address literal, however some mail clients will send the // address literal and there is no harm in accepting it if (TryMakeAddressLiteral(out var address)) { command = new HeloCommand(_options, address); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RCTP 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RCPT")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "TO") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return(false); } // according to the spec, whitespace isnt allowed here anyway enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return(false); } // TODO: support optional service extension parameters here command = new RcptCommand(mailbox, _options.MailboxFilterFactory); return(true); }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The EHLO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "EHLO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain)) { command = new EhloCommand(domain, _options); return(true); } string address; if (_parser.TryMakeAddressLiteral(enumerator, out address)) { command = new EhloCommand(address, _options); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make an AUTH command from the given enumerator. /// </summary> /// <param name="command">The AUTH 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeAuth(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enum.TryParse(Enumerator.Take().Text, true, out AuthenticationMethod method) == false) { _options.Logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); errorResponse = SmtpResponse.SyntaxError; return(false); } Enumerator.Take(); string parameter = null; if (TryMake(TryMakeEnd) == false && TryMakeBase64(out parameter) == false) { _options.Logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new AuthCommand(_options, method, parameter); return(true); }
/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="command">The RCTP 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enumerator.Take() != Tokens.Text.To || Enumerator.Take() != Tokens.Colon) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return(false); } // according to the spec, whitespace isnt allowed here anyway Enumerator.Skip(TokenKind.Space); if (TryMakePath(out IMailbox mailbox) == false) { _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); errorResponse = SmtpResponse.SyntaxError; return(false); } // TODO: support optional service extension parameters here command = new RcptCommand(_options, mailbox); return(true); }
/// <summary> /// Make a QUIT command. /// </summary> /// <param name="command">The QUIT 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeQuit(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.WriteTrace("QUIT command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new QuitCommand(_options); return(true); }
/// <summary> /// Make an NOOP command from the given enumerator. /// </summary> /// <param name="command">The NOOP 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeNoop(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("NOOP command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new NoopCommand(_options); return(true); }
/// <summary> /// Make a DATA command from the given enumerator. /// </summary> /// <param name="command">The DATA 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeData(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeEnd() == false) { _options.Logger.WriteTrace("DATA command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new DataCommand(_options); return(true); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="command">The HELO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out string domain) == false) { _options.Logger.LogVerbose("Could not match the domain name (Text={0}).", CompleteTokenizedText()); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new HeloCommand(_options, domain); return(true); }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="command">The STARTTLS 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("STARTTLS command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new StartTlsCommand(_options); return(true); }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="command">The MAIL 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(Enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enumerator.Take() != new Token(TokenKind.Text, "FROM") || Enumerator.Take() != new Token(TokenKind.Other, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return(false); } // according to the spec, whitespace isnt allowed here but most servers send it Enumerator.Skip(TokenKind.Space); IMailbox mailbox; if (TryMakeReversePath(out mailbox) == false) { _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return(false); } Enumerator.Skip(TokenKind.Space); // match the optional (ESMTP) parameters IReadOnlyDictionary <string, string> parameters; if (TryMakeMailParameters(out parameters) == false) { parameters = new Dictionary <string, string>(); } command = new MailCommand(_options, mailbox, parameters); return(true); }
/// <summary> /// Make an RSET command from the given enumerator. /// </summary> /// <param name="command">The RSET 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeRset(out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(Enumerator.Peek() == new Token(TokenKind.Text, "RSET")); command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("RSET command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new RsetCommand(_options); return(true); }
/// <summary> /// Make an NOOP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The NOOP 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeNoop(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "NOOP")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("NOOP command can not have parameters (found {0} parameters).", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return false; } command = NoopCommand.Instance; return true; }
/// <summary> /// Make a DATA command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The DATA 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeData(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "DATA")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("DATA command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new DataCommand(_options.MessageStoreFactory); return(true); }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The STARTTLS 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "STARTTLS")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("STARTTLS command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new StartTlsCommand(_options.ServerCertificate); return(true); }
/// <summary> /// Make an RSET command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RSET 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeRset(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RSET")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("RSET command can not have parameters (found {0} parameters).", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = RsetCommand.Instance; return(true); }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The MAIL 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "FROM") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return(false); } // according to the spec, whitespace isnt allowed here but most servers send it enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakeReversePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return(false); } // match the optional (ESMTP) parameters IDictionary <string, string> parameters; if (_parser.TryMakeMailParameters(enumerator, out parameters) == false) { parameters = new Dictionary <string, string>(); } command = new MailCommand(mailbox, parameters, _options.MailboxFilterFactory); return(true); }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="command">The EHLO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out var domain)) { command = new EhloCommand(_options, domain); return(true); } if (TryMakeAddressLiteral(out var address)) { command = new EhloCommand(_options, address); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The HELO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "HELO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain) == false) { _logger.LogVerbose("Could not match the domain name (Text={0}).", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new HeloCommand(domain); return(true); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <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(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { // lookup the correct action Tuple <State.TryMakeDelegate, SmtpState> action; if (_state.Actions.TryGetValue(tokenEnumerator.Peek().Text, out action) == false) { var response = $"expected {String.Join("/", _state.Actions.Keys)}"; command = null; errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, response); return(false); } if (action.Item1(tokenEnumerator, out command, out errorResponse) == false) { return(false); } // transition to the next state _state = _states[action.Item2]; return(true); }
/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RCTP 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RCPT")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "TO") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return false; } // according to the spec, whitespace isnt allowed here anyway enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return false; } // TODO: support optional service extension parameters here command = new RcptCommand(mailbox, _options.MailboxFilterFactory); return true; }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The MAIL 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "FROM") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return false; } // according to the spec, whitespace isnt allowed here but most servers send it enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakeReversePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return false; } // match the optional (ESMTP) parameters IDictionary<string, string> parameters; if (_parser.TryMakeMailParameters(enumerator, out parameters) == false) { parameters = new Dictionary<string, string>(); } command = new MailCommand(mailbox, parameters, _options.MailboxFilterFactory); return true; }
/// <summary> /// Visit the command. /// </summary> /// <param name="command"></param> public void Visit(SmtpCommand command) { if (command is AuthCommand authCommand) { Visit(authCommand); return; } if (command is DataCommand dataCommand) { Visit(dataCommand); return; } if (command is HeloCommand heloCommand) { Visit(heloCommand); return; } if (command is EhloCommand ehloCommand) { Visit(ehloCommand); return; } if (command is MailCommand mailCommand) { Visit(mailCommand); return; } if (command is NoopCommand noopCommand) { Visit(noopCommand); return; } if (command is ProxyCommand proxyCommand) { Visit(proxyCommand); return; } if (command is QuitCommand quitCommand) { Visit(quitCommand); return; } if (command is RcptCommand rcptCommand) { Visit(rcptCommand); return; } if (command is RsetCommand rsetCommand) { Visit(rsetCommand); return; } if (command is StartTlsCommand tlsCommand) { Visit(tlsCommand); return; } throw new NotSupportedException(command.ToString()); }
/// <summary> /// Make a DATA command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The DATA 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeData(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "DATA")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("DATA command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return false; } command = new DataCommand(_options.MessageStoreFactory); return true; }
/// <summary> /// Make an AUTH command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The AUTH 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeAuth(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "AUTH")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); AuthenticationMethod method; if (Enum.TryParse(enumerator.Peek().Text, true, out method) == false) { _logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); errorResponse = SmtpResponse.SyntaxError; return false; } enumerator.Take(); string parameter = null; if (enumerator.Count > 0 && _parser.TryMakeBase64(enumerator, out parameter) == false) { _logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); errorResponse = SmtpResponse.SyntaxError; return false; } command = new AuthCommand(_options.UserAuthenticator, method, parameter); return true; }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The STARTTLS 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "STARTTLS")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("STARTTLS command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return false; } command = new StartTlsCommand(_options.ServerCertificate); return true; }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <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(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { // lookup the correct action Tuple<State.TryMakeDelegate, SmtpState> action; if (_state.Actions.TryGetValue(tokenEnumerator.Peek().Text, out action) == false) { var response = $"expected {String.Join("/", _state.Actions.Keys)}"; command = null; errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, response); return false; } if (action.Item1(tokenEnumerator, out command, out errorResponse) == false) { return false; } // transition to the next state _state = _states[action.Item2]; return true; }
/// <summary> /// Try to make a RCPT command. /// </summary> /// <param name="tokenEnumerator">The token enumerator to use when matching the command.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that was returned if a command could not be matched.</param> /// <returns>true if a RCPT command was found, false if not.</returns> bool TryMakeRcpt(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return(new SmtpParser(_options, tokenEnumerator).TryMakeRcpt(out command, out errorResponse)); }
/// <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 TryMake(SmtpSessionContext context, TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { if (_states[_current].Transitions.TryGetValue(tokenEnumerator.Peek().Text, out _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); } return(true); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that indicates why a command could not be accepted.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryAccept(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return _stateTable.TryAccept(tokenEnumerator, out command, out errorResponse); }
/// <summary> /// Try to make a DATA command. /// </summary> /// <param name="tokenEnumerator">The token enumerator to use when matching the command.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that was returned if a command could not be matched.</param> /// <returns>true if a DATA command was found, false if not.</returns> bool TryMakeData(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return(new SmtpParser(_context.ServerOptions, tokenEnumerator).TryMakeData(out command, out errorResponse)); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="context">The SMTP session context to allow for session based state transitions.</param> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that indicates why a command could not be accepted.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryMake(SmtpSessionContext context, TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return(_stateTable.TryMake(context, tokenEnumerator, out command, out errorResponse)); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The HELO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "HELO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain) == false) { _logger.LogVerbose("Could not match the domain name (Text={0}).", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return false; } command = new HeloCommand(domain); return true; }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The EHLO 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>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "EHLO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain)) { command = new EhloCommand(domain, _options); return true; } string address; if (_parser.TryMakeAddressLiteral(enumerator, out address)) { command = new EhloCommand(address, _options); return true; } errorResponse = SmtpResponse.SyntaxError; return false; }
/// <summary> /// Support proxy protocol version 1 header for use with HAProxy. /// Documented at http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt /// </summary> /// <param name="command"></param> /// <param name="errorResponse"></param> /// <returns></returns> public bool TryMakeProxy(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMake(TryMakeProxyProtoTypeString, out string inetProto) == false) { return(false); } if (inetProto == "UNKNOWN") { // IF INET PROTO IS UNKNOWN REST OF THIS LINE SHOULD BE IGNORED. command = new ProxyCommand(_options, null, null); return(true); } Enumerator.Skip(TokenKind.Space); IPAddress sourceIp; IPAddress destinationIp; // read addresses if IPv4 if (inetProto == "TCP4") { if (TryMakeProxyFromAndToAddresses(TryMakeIpv4AddressLiteral, out sourceIp, out destinationIp) == false) { return(false); } } else if (inetProto == "TCP6") { if (TryMakeProxyFromAndToAddresses(TryMakeIpv6AddressLiteral, out sourceIp, out destinationIp) == false) { return(false); } } else { // Unexpected / Invalid protocol in proxy protocol format. return(false); } Enumerator.Skip(TokenKind.Space); // Read ports if (TryMakeShortNum(out var sourcePort) == false) { return(false); } Enumerator.Skip(TokenKind.Space); if (TryMakeShortNum(out var destinationPort) == false) { return(false); } command = new ProxyCommand(_options, new IPEndPoint(sourceIp, sourcePort), new IPEndPoint(destinationIp, destinationPort)); return(true); }
/// <summary> /// Visit the command. /// </summary> /// <param name="command"></param> public void Visit(SmtpCommand command) { if (command is AuthCommand) { Visit((AuthCommand)command); return; } if (command is DataCommand) { Visit((DataCommand)command); return; } if (command is DbugCommand) { Visit((DbugCommand)command); return; } if (command is HeloCommand) { Visit((HeloCommand)command); return; } if (command is EhloCommand) { Visit((EhloCommand)command); return; } if (command is MailCommand) { Visit((MailCommand)command); return; } if (command is NoopCommand) { Visit((NoopCommand)command); return; } if (command is QuitCommand) { Visit((QuitCommand)command); return; } if (command is RcptCommand) { Visit((RcptCommand)command); return; } if (command is RsetCommand) { Visit((RsetCommand)command); return; } if (command is StartTlsCommand) { Visit((StartTlsCommand)command); return; } throw new NotSupportedException(command.ToString()); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that indicates why a command could not be accepted.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryAccept(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return(_stateTable.TryAccept(tokenEnumerator, out command, out errorResponse)); }