Example #1
0
        /// <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);
        }
Example #2
0
        /// <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);
        }
Example #3
0
        /// <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);
        }
Example #4
0
        /// <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);
        }
Example #5
0
        /// <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);
        }
Example #6
0
        /// <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);
        }
Example #7
0
        /// <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);
        }
Example #8
0
        /// <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);
        }
Example #9
0
        /// <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);
        }
Example #10
0
        /// <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);
        }
Example #11
0
        /// <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);
        }
Example #12
0
        /// <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);
        }
Example #13
0
        /// <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;
        }
Example #15
0
        /// <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);
        }
Example #16
0
        /// <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);
        }
Example #17
0
        /// <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);
        }
Example #18
0
        /// <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);
        }
Example #19
0
        /// <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);
        }
Example #20
0
        /// <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);
        }
Example #21
0
            /// <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;
        }
Example #24
0
        /// <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;
        }
Example #28
0
            /// <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;
            }
Example #29
0
 /// <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));
 }
Example #30
0
            /// <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);
            }
Example #31
0
 /// <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);
 }
Example #32
0
 /// <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));
 }
Example #33
0
 /// <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;
        }
Example #36
0
        /// <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);
        }
Example #37
0
        /// <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());
        }
Example #38
0
 /// <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));
 }