Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Client::is_connected() doesn't report about connections closed by foreign host #37

Closed
tyranron opened this issue Jan 8, 2021 · 1 comment

Comments

@tyranron
Copy link

tyranron commented Jan 8, 2021

Revelead from reacherhq/check-if-email-exists#794

Preamble

Some SMTP servers (like gmx.com and ukr.net, for example) automatically close connection if some SMTP error appears.

We can check this easily via telnet:

❯ telnet mxs.ukr.net 25
Trying 212.42.75.251...
Connected to mxs.ukr.net.
Escape character is '^]'.
220 UKR.NET ESMTP Thu, 07 Jan 2021 18:48:16 +0200
HELO localhost
250 frv71.fwdcdn.com Hello localhost [87.120.35.246]
MAIL FROM: <user@example.org>   
250 OK
RCPT TO: <some@ukr.net>
550 SPF match mandatory for 87.120.35.246 user@example.org
Connection closed by foreign host.

Bug description

In such situations SmtpClient doesn't report about a connection being closed neither with Error::Client("Connection closed"), nor with SmtpClient::is_connected().

But, instead, any next command fails with the io: incomplete error.

Expected behaviour

If SMTP connection is closed automatically by the foreign server, the SmtpClient::is_connected() method should reflect that and subsequent commands should fail witn an Error::Client("Connection closed"), but not io: incomplete.

Steps to reproduce

Paste this program to examples/ directory:

use std::time::Duration;

use async_smtp::{
    smtp::{
        commands::{MailCommand, RcptCommand},
        extension::ClientId,
    },
    ClientSecurity, EmailAddress, SmtpClient,
};

fn main() {
    let result = async_std::task::block_on(async move {
        let mut mailer = SmtpClient::with_security(("mxs.ukr.net", 25), ClientSecurity::None)
            .await
            .map_err(|e| println!("Creation failed: {}", e))?
            .hello_name(ClientId::Domain("mail.example.com".to_owned()))
            .timeout(Some(Duration::new(30, 0))) // Set timeout to 30s
            .into_transport();
        if let Err(e) = mailer.connect().await {
            println!("Connection failed: {}", e);
            mailer
                .close()
                .await
                .map_err(|e| println!("Closing failed: {}", e))?;
        }

        let _ = mailer
            .command(MailCommand::new(
                Some(EmailAddress::new("test@example.com".to_owned()).unwrap()),
                vec![],
            ))
            .await
            .map_err(|e| println!("MAIL FROM failed: {}", e))?;

        let _ = mailer
            .command(RcptCommand::new(
                EmailAddress::new("some@ukr.net".to_owned()).unwrap(),
                vec![],
            ))
            .await
            .map_err(|e| println!("RCPT TO 1 failed: {}", e));

        dbg!(mailer.is_connected());

        mailer
            .command(RcptCommand::new(
                EmailAddress::new("some@ukr.net".to_owned()).unwrap(),
                vec![],
            ))
            .await
            .map_err(|e| println!("RCPT TO 2 failed: {}", e))
    });
    assert!(result.is_ok());
}

And run it:

❯ cargo run --example rcpt
    Finished dev [unoptimized + debuginfo] target(s) in 5.94s
     Running `target/debug/examples/rcpt`
RCPT TO 1 failed: permanent: SPF match mandatory for 87.120.35.246 test@example.com
[examples/rcpt.rs:43] mailer.is_connected() = true
RCPT TO 2 failed: io: incomplete
thread 'main' panicked at 'assertion failed: result.is_ok()', examples/rcpt.rs:53:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
@link2xt
Copy link
Collaborator

link2xt commented Jan 31, 2023

Since merge of #57 connection setup is not part of the library. Connection failures will result in read/write errors.

It's generally not possible to determine if TCP connection is still established without trying to use it.

@link2xt link2xt closed this as completed Jan 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants