/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="command">The RCTP command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enumerator.Take() != Tokens.Text.To || Enumerator.Take() != Tokens.Colon) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return(false); } // according to the spec, whitespace isnt allowed here anyway Enumerator.Skip(TokenKind.Space); if (TryMakePath(out IMailbox mailbox) == false) { _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); errorResponse = SmtpResponse.SyntaxError; return(false); } // TODO: support optional service extension parameters here command = new RcptCommand(_options, mailbox); return(true); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="command">The HELO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out var domain)) { command = new HeloCommand(_options, domain); return(true); } // according to RFC5321 the HELO command should only accept the Domain // and not the address literal, however some mail clients will send the // address literal and there is no harm in accepting it if (TryMakeAddressLiteral(out var address)) { command = new HeloCommand(_options, address); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The EHLO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "EHLO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain)) { command = new EhloCommand(domain, _options); return(true); } string address; if (_parser.TryMakeAddressLiteral(enumerator, out address)) { command = new EhloCommand(address, _options); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make an AUTH command from the given enumerator. /// </summary> /// <param name="command">The AUTH command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeAuth(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enum.TryParse(Enumerator.Take().Text, true, out AuthenticationMethod method) == false) { _options.Logger.LogVerbose("AUTH command requires a valid method (PLAIN or LOGIN)"); errorResponse = SmtpResponse.SyntaxError; return(false); } Enumerator.Take(); string parameter = null; if (TryMake(TryMakeEnd) == false && TryMakeBase64(out parameter) == false) { _options.Logger.LogVerbose("AUTH parameter must be a Base64 encoded string"); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new AuthCommand(_options, method, parameter); return(true); }
/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RCTP command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RCPT")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "TO") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return(false); } // according to the spec, whitespace isnt allowed here anyway enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return(false); } // TODO: support optional service extension parameters here command = new RcptCommand(mailbox, _options.MailboxFilterFactory); return(true); }
/// <summary> /// Make an 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 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); }
/// <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); }
/// <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; }); }
/// <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); }
/// <summary> /// Make a QUIT command. /// </summary> /// <param name="command">The QUIT command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeQuit(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.WriteTrace("QUIT command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new QuitCommand(_options); return(true); }
/// <summary> /// Make an NOOP command from the given enumerator. /// </summary> /// <param name="command">The NOOP command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeNoop(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("NOOP command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new NoopCommand(_options); return(true); }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="context">The session context to use for making session based transitions.</param> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryMake(SmtpSessionContext context, TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { if (_states[_current].Transitions.TryGetValue(tokenEnumerator.Peek().Text, out _transition) == false) { var response = $"expected {String.Join("/", _states[_current].Transitions.Keys)}"; command = null; errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, response); return(false); } if (_transition.Delegate(tokenEnumerator, out command, out errorResponse) == false) { return(false); } return(true); }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="command">The STARTTLS command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("STARTTLS command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new StartTlsCommand(_options); return(true); }
/// <summary> /// Make a DATA command from the given enumerator. /// </summary> /// <param name="command">The DATA command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeData(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeEnd() == false) { _options.Logger.WriteTrace("DATA command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new DataCommand(_options); return(true); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="command">The HELO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out string domain) == false) { _options.Logger.LogVerbose("Could not match the domain name (Text={0}).", CompleteTokenizedText()); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new HeloCommand(_options, domain); return(true); }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="command">The MAIL command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(Enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (Enumerator.Take() != new Token(TokenKind.Text, "FROM") || Enumerator.Take() != new Token(TokenKind.Other, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return(false); } // according to the spec, whitespace isnt allowed here but most servers send it Enumerator.Skip(TokenKind.Space); IMailbox mailbox; if (TryMakeReversePath(out mailbox) == false) { _options.Logger.LogVerbose("Syntax Error (Text={0})", CompleteTokenizedText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return(false); } Enumerator.Skip(TokenKind.Space); // match the optional (ESMTP) parameters IReadOnlyDictionary <string, string> parameters; if (TryMakeMailParameters(out parameters) == false) { parameters = new Dictionary <string, string>(); } command = new MailCommand(_options, mailbox, parameters); return(true); }
/// <summary> /// Make an RSET command from the given enumerator. /// </summary> /// <param name="command">The RSET command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeRset(out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(Enumerator.Peek() == new Token(TokenKind.Text, "RSET")); command = null; errorResponse = null; Enumerator.Take(); if (TryMakeEnd() == false) { _options.Logger.LogVerbose("RSET command can not have parameters."); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new RsetCommand(_options); return(true); }
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); } }
/// <summary> /// Make an NOOP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The NOOP command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeNoop(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "NOOP")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("NOOP command can not have parameters (found {0} parameters).", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return false; } command = NoopCommand.Instance; return true; }
/// <summary> /// Make a DATA command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The DATA command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeData(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "DATA")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("DATA command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new DataCommand(_options.MessageStoreFactory); return(true); }
/// <summary> /// Make an RSET command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RSET command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeRset(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RSET")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("RSET command can not have parameters (found {0} parameters).", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = RsetCommand.Instance; return(true); }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The MAIL command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "FROM") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return(false); } // according to the spec, whitespace isnt allowed here but most servers send it enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakeReversePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return(false); } // match the optional (ESMTP) parameters IDictionary <string, string> parameters; if (_parser.TryMakeMailParameters(enumerator, out parameters) == false) { parameters = new Dictionary <string, string>(); } command = new MailCommand(mailbox, parameters, _options.MailboxFilterFactory); return(true); }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="command">The EHLO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(out SmtpCommand command, out SmtpResponse errorResponse) { command = null; errorResponse = null; Enumerator.Take(); Enumerator.Skip(TokenKind.Space); if (TryMakeDomain(out var domain)) { command = new EhloCommand(_options, domain); return(true); } if (TryMakeAddressLiteral(out var address)) { command = new EhloCommand(_options, address); return(true); } errorResponse = SmtpResponse.SyntaxError; return(false); }
/// <summary> /// Make a HELO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The HELO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeHelo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "HELO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain) == false) { _logger.LogVerbose("Could not match the domain name (Text={0}).", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new HeloCommand(domain); return(true); }
/// <summary> /// 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); }
/// <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> /// 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> /// 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); }
/// <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> /// 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); }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The STARTTLS command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "STARTTLS")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("STARTTLS command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return false; } command = new StartTlsCommand(_options.ServerCertificate); return true; }
/// <summary> /// Make a RCTP command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The RCTP command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeRcpt(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "RCPT")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "TO") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the TO:"); return false; } // according to the spec, whitespace isnt allowed here anyway enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = SmtpResponse.SyntaxError; return false; } // TODO: support optional service extension parameters here command = new RcptCommand(mailbox, _options.MailboxFilterFactory); return true; }
/// <summary> /// Make an STARTTLS command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The STARTTLS command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeStartTls(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "STARTTLS")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Count > 1) { _logger.LogVerbose("STARTTLS command can not have parameters (Tokens={0})", enumerator.Count); errorResponse = SmtpResponse.SyntaxError; return(false); } command = new StartTlsCommand(_options.ServerCertificate); return(true); }
/// <summary> /// Try to make a DATA command. /// </summary> /// <param name="tokenEnumerator">The token enumerator to use when matching the command.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that was returned if a command could not be matched.</param> /// <returns>true if a DATA command was found, false if not.</returns> bool TryMakeData(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return(new SmtpParser(_context.ServerOptions, tokenEnumerator).TryMakeData(out command, out errorResponse)); }
/// <summary> /// Make a MAIL command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The MAIL command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeMail(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "MAIL")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); if (enumerator.Take() != new Token(TokenKind.Text, "FROM") || enumerator.Take() != new Token(TokenKind.Punctuation, ":")) { errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError, "missing the FROM:"); return false; } // according to the spec, whitespace isnt allowed here but most servers send it enumerator.TakeWhile(TokenKind.Space); IMailbox mailbox; if (_parser.TryMakeReversePath(enumerator, out mailbox) == false) { _logger.LogVerbose("Syntax Error (Text={0})", enumerator.AsText()); errorResponse = new SmtpResponse(SmtpReplyCode.SyntaxError); return false; } // match the optional (ESMTP) parameters IDictionary<string, string> parameters; if (_parser.TryMakeMailParameters(enumerator, out parameters) == false) { parameters = new Dictionary<string, string>(); } command = new MailCommand(mailbox, parameters, _options.MailboxFilterFactory); return true; }
/// <summary> /// Advances the enumerator to the next command in the stream. /// </summary> /// <param name="tokenEnumerator">The token enumerator to accept the command from.</param> /// <param name="command">The command that was found.</param> /// <param name="errorResponse">The error response that indicates why a command could not be accepted.</param> /// <returns>true if a valid command was found, false if not.</returns> public bool TryAccept(TokenEnumerator tokenEnumerator, out SmtpCommand command, out SmtpResponse errorResponse) { return _stateTable.TryAccept(tokenEnumerator, out command, out errorResponse); }
/// <summary> /// Make an EHLO command from the given enumerator. /// </summary> /// <param name="enumerator">The enumerator to create the command from.</param> /// <param name="command">The EHLO command that is defined within the token enumerator.</param> /// <param name="errorResponse">The error that indicates why the command could not be made.</param> /// <returns>Returns true if a command could be made, false if not.</returns> public bool TryMakeEhlo(TokenEnumerator enumerator, out SmtpCommand command, out SmtpResponse errorResponse) { Debug.Assert(enumerator.Peek() == new Token(TokenKind.Text, "EHLO")); command = null; errorResponse = null; enumerator.Take(); enumerator.TakeWhile(TokenKind.Space); string domain; if (_parser.TryMakeDomain(enumerator, out domain)) { command = new EhloCommand(domain, _options); return true; } string address; if (_parser.TryMakeAddressLiteral(enumerator, out address)) { command = new EhloCommand(address, _options); return true; } errorResponse = SmtpResponse.SyntaxError; return false; }
/// <summary> /// 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)); }
/// <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> /// 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); }
/// <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; }