cosullivan / SmtpServer

A SMTP Server component written in C#
MIT License
713 stars 164 forks source link

StartTls or SecureEnpoint do not have a timeout #234

Closed tinohager closed 1 month ago

tinohager commented 1 month ago

When switching to the secure connection, there is currently no timeout, the connection remains open forever if no data is sent.

// IO/SecurableDuplexPipe.cs
sslStream.AuthenticateAsServerAsync(certificate, false, protocols, true);

with .netstandard2.1 the method is available with a cancellationToken, a clean solution

var sslServerAuthenticationOptions = new SslServerAuthenticationOptions
{
    ServerCertificate = certificate,
    ClientCertificateRequired = false,
    EnabledSslProtocols = protocols,
    CertificateRevocationCheckMode = X509RevocationMode.Online
};

await sslStream.AuthenticateAsServerAsync(sslServerAuthenticationOptions, cancellationToken);

//TODO: it still needs to be checked whether we need a dispose

with .netstandard 2.0 we need more code

public async Task UpgradeAsync(X509Certificate certificate, SslProtocols protocols, CancellationToken cancellationToken = default)
{
    var sslStream = new SslStream(_stream, true);

    using var cancellationTokenSource = new CancellationTokenSource();
    var timeoutMilliseconds = 20000;

    Task timeoutTask = Task.Delay(timeoutMilliseconds, cancellationTokenSource.Token);

    var options = new SslServerAuthenticationOptions
    {
        ServerCertificate = certificate,
        ClientCertificateRequired = false,
        EnabledSslProtocols = protocols,
        CertificateRevocationCheckMode = X509RevocationMode.Online
    };

    Task authenticateTask = sslStream.AuthenticateAsServerAsync(certificate, false, protocols, true);

    try
    {
        Task completedTask = await Task.WhenAny(authenticateTask, timeoutTask).ConfigureAwait(false);
        if (completedTask == timeoutTask)
        {
            // Timeout occurred
            throw new TimeoutException("SSL authentication timed out.");
        }

        // Cancel the timeout task
        cancellationTokenSource.Cancel();

        // Ensure the authentication task is complete (if not already)
        await authenticateTask;
    }
    catch (Exception)
    {
        // Close the stream in case of an error or timeout
        sslStream.Close();
        sslStream.Dispose();
        throw; // Re-throw the exception
    }

    _stream = sslStream;

    Input = PipeReader.Create(_stream);
    Output = PipeWriter.Create(_stream);
}
tinohager commented 1 month ago

fixed with 11.0.0-beta1