예제 #1
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);
        }
예제 #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);
        }
예제 #3
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);
        }
예제 #4
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);
        }
예제 #5
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);
        }
예제 #6
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);
        }
예제 #7
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)
        {
            Debug.Assert(Enumerator.Peek() == new Token(TokenKind.Text, "EHLO"));

            command       = null;
            errorResponse = null;

            Enumerator.Take();
            Enumerator.Skip(TokenKind.Space);

            string domain;

            if (TryMakeDomain(out domain))
            {
                command = new EhloCommand(_options, domain);
                return(true);
            }

            string address;

            if (TryMakeAddressLiteral(out address))
            {
                command = new EhloCommand(_options, address);
                return(true);
            }

            errorResponse = SmtpResponse.SyntaxError;
            return(false);
        }
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Returns true if the command executed successfully such that the transition to the next state should occurr, false
        /// if the current state is to be maintained.</returns>
        internal override async Task <bool> ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            var response = new SmtpResponse(SmtpReplyCode.Ok, GetGreeting(context));

            await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false);

            return(true);
        }
예제 #9
0
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Returns true if the command executed successfully such that the transition to the next state should occurr, false
        /// if the current state is to be maintained.</returns>
        internal override async Task <bool> ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            var response = new SmtpResponse(SmtpReplyCode.Ok, $"Hello {DomainOrAddress}, haven't we met before?");

            await context.NetworkClient.ReplyAsync(response, cancellationToken).ReturnOnAnyThread();

            return(true);
        }
예제 #10
0
        /// <summary>
        /// Make a delegate to return a known response.
        /// </summary>
        /// <param name="errorResponse">The error response to return.</param>
        /// <returns>The delegate that will return the correct error response.</returns>
        static TryMakeDelegate MakeResponse(SmtpResponse errorResponse)
        {
            return((TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse response) =>
            {
                command = null;
                response = errorResponse;

                return false;
            });
        }
예제 #11
0
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Returns true if the command executed successfully such that the transition to the next state should occur, false
        /// if the current state is to be maintained.</returns>
        internal override async Task <bool> ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            context.Authentication = AuthenticationContext.Unauthenticated;

            switch (Method)
            {
            case AuthenticationMethod.Plain:
                if (await TryPlainAsync(context, cancellationToken).ConfigureAwait(false) == false)
                {
                    await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false);

                    return(false);
                }
                break;

            case AuthenticationMethod.Login:
                if (await TryLoginAsync(context, cancellationToken).ConfigureAwait(false) == false)
                {
                    await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationFailed, cancellationToken).ConfigureAwait(false);

                    return(false);
                }
                break;
            }

            var userAuthenticator = context.ServiceProvider.GetService <IUserAuthenticatorFactory, IUserAuthenticator>(context, UserAuthenticator.Default);

            using (var container = new DisposableContainer <IUserAuthenticator>(userAuthenticator))
            {
                if (await container.Instance.AuthenticateAsync(context, _user, _password, cancellationToken).ConfigureAwait(false) == false)
                {
                    var remaining = context.ServerOptions.MaxAuthenticationAttempts - ++context.AuthenticationAttempts;
                    var response  = new SmtpResponse(SmtpReplyCode.AuthenticationFailed, $"authentication failed, {remaining} attempt(s) remaining.");

                    await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false);

                    if (remaining <= 0)
                    {
                        throw new SmtpResponseException(SmtpResponse.ServiceClosingTransmissionChannel, true);
                    }

                    return(false);
                }
            }

            await context.Pipe.Output.WriteReplyAsync(SmtpResponse.AuthenticationSuccessful, cancellationToken).ConfigureAwait(false);

            context.Authentication = new AuthenticationContext(_user);
            context.RaiseSessionAuthenticated();

            return(true);
        }
예제 #12
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);
        }
예제 #13
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);
        }
예제 #14
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);
            }
예제 #15
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);
        }
예제 #16
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);
        }
예제 #17
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);
        }
예제 #18
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);
        }
예제 #19
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);
        }
예제 #20
0
        internal override async Task <bool> ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            context.ProxySourceEndpoint              = SourceEndpoint;
            context.ProxyDestinationEndpoint         = DestinationEndpoint;
            context.Properties["SourceAddress"]      = SourceEndpoint.Address.ToString();
            context.Properties["DestinationAddress"] = DestinationEndpoint.Address.ToString();

            using (var container =
                       new DisposableContainer <IEndpointAuthenticator>(
                           Options.EndpointAuthenticatorFactory.CreateInstance(context)))
            {
                if (await container.Instance.AuthenticateAsync(context, SourceEndpoint.Address.ToString(),
                                                               DestinationEndpoint.Address.ToString(),
                                                               Options.ProxyAddresses, cancellationToken).ReturnOnAnyThread() == false)
                {
                    var response = new SmtpResponse(SmtpReplyCode.ClientNotPermitted,
                                                    $"PROXY not permitted from {DestinationEndpoint.Address}");
                    await context.NetworkClient.ReplyAsync(response, cancellationToken).ReturnOnAnyThread();

                    context.IsQuitRequested = true;
                    return(false);
                }

                // IEndpointAuthenticator implementations are free to authenticate the
                // session based on the IPs delivered by the proxy command obviating the
                // need for further authentication. RaiseSessionAuthenticated() can be
                // called in this case to transition the state machine.
                context.Properties.TryGetValue("Authenticated", out object authenticated);

                if (authenticated != null)
                {
                    if ((bool)authenticated)
                    {
                        context.IsAuthenticated = true;
                        context.RaiseSessionAuthenticated();
                    }
                }

                return(true);
            }
        }
예제 #21
0
        /// <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;
        }
예제 #22
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);
        }
예제 #23
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);
        }
예제 #24
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);
        }
예제 #25
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);
        }
예제 #26
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);
        }
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>Returns true if the command executed successfully such that the transition to the next state should occurr, false
        /// if the current state is to be maintained.</returns>
        internal override async Task <bool> ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            if (context.Transaction.To.Count == 0)
            {
                await context.Pipe.Output.WriteReplyAsync(SmtpResponse.NoValidRecipientsGiven, cancellationToken).ConfigureAwait(false);

                return(false);
            }

            await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.StartMailInput, "end with <CRLF>.<CRLF>"), cancellationToken).ConfigureAwait(false);

            var messageStore = context.ServiceProvider.GetService <IMessageStoreFactory, IMessageStore>(context, MessageStore.Default);

            try
            {
                using var container = new DisposableContainer <IMessageStore>(messageStore);

                SmtpResponse response = null;

                await context.Pipe.Input.ReadDotBlockAsync(
                    async buffer =>
                {
                    // ReSharper disable once AccessToDisposedClosure
                    response = await container.Instance.SaveAsync(context, context.Transaction, buffer, cancellationToken).ConfigureAwait(false);
                },
                    cancellationToken).ConfigureAwait(false);

                await context.Pipe.Output.WriteReplyAsync(response, cancellationToken).ConfigureAwait(false);
            }
            catch (Exception)
            {
                await context.Pipe.Output.WriteReplyAsync(new SmtpResponse(SmtpReplyCode.TransactionFailed), cancellationToken).ConfigureAwait(false);
            }

            return(true);
        }
예제 #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);
            }
예제 #29
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;
            }
예제 #30
0
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task which asynchronously performs the execution.</returns>
        public override Task ExecuteAsync(ISmtpSessionContext context, CancellationToken cancellationToken)
        {
            var response = new SmtpResponse(SmtpReplyCode.Ok, $"Hello {Domain}, haven't we met before?");

            return context.Text.ReplyAsync(response, cancellationToken);
        }
예제 #31
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;
        }
예제 #32
0
        /// <summary>
        /// Output the error message.
        /// </summary>
        /// <param name="errorResponse">The response that contains the error message and reply code.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task which performs the operation.</returns>
        Task OuputErrorMessageAsync(SmtpResponse errorResponse, CancellationToken cancellationToken)
        {
            var response = new SmtpResponse(errorResponse.ReplyCode, $"{errorResponse.Message}, {_retryCount} retry(ies) remaining.");

            return Context.Text.ReplyAsync(response, cancellationToken);
        }
예제 #33
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;
        }
예제 #34
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;
        }
예제 #35
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);
        }
예제 #36
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));
 }
예제 #37
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;
        }
예제 #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);
 }
예제 #39
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;
        }
예제 #40
0
        /// <summary>
        /// Execute the command.
        /// </summary>
        /// <param name="context">The execution context to operate on.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task which asynchronously performs the execution.</returns>
        internal override Task ExecuteAsync(SmtpSessionContext context, CancellationToken cancellationToken)
        {
            var response = new SmtpResponse(SmtpReplyCode.Ok, $"Hello {Domain}, haven't we met before?");

            return(context.Client.ReplyAsync(response, cancellationToken));
        }
예제 #41
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;
        }
예제 #42
0
        /// <summary>
        /// Reply to the client.
        /// </summary>
        /// <param name="stream">The text stream to perform the operation on.</param>
        /// <param name="response">The response.</param>
        /// <param name="cancellationToken">The cancellation token.</param>
        /// <returns>A task which performs the operation.</returns>
        public static async Task ReplyAsync(this ITextStream stream, SmtpResponse response, CancellationToken cancellationToken)
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            await stream.WriteLineAsync($"{(int)response.ReplyCode} {response.Message}", cancellationToken);
            await stream.FlushAsync(cancellationToken);
        }
예제 #43
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;
        }