From 6faaeaae6649ddfa9a1b4b424a27e6792b1f90b3 Mon Sep 17 00:00:00 2001 From: BlackDex Date: Wed, 18 Nov 2020 12:07:08 +0100 Subject: [PATCH] Updated email processing. - Added an option to enable smtp debugging via SMTP_DEBUG. This will trigger a trace of the smtp commands sent/received to/from the mail server. Useful when troubleshooting. - Added two options to ignore invalid certificates which either do not match at all, or only doesn't match the hostname. - Updated lettre to the latest alpha.4 version. --- .env.template | 22 +++++++++++++--- Cargo.lock | 72 ++++++++++++++++++++++++++++++--------------------- Cargo.toml | 2 +- src/config.rs | 30 ++++++++++++--------- src/mail.rs | 41 +++++++++++++++++++---------- src/main.rs | 10 +++++++ 6 files changed, 117 insertions(+), 60 deletions(-) diff --git a/.env.template b/.env.template index 4d987c4e..9c30ca61 100644 --- a/.env.template +++ b/.env.template @@ -242,9 +242,9 @@ # SMTP_HOST=smtp.domain.tld # SMTP_FROM=bitwarden-rs@domain.tld # SMTP_FROM_NAME=Bitwarden_RS -# SMTP_PORT=587 -# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. -# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. +# SMTP_PORT=587 # Ports 587 (submission) and 25 (smtp) are standard without encryption and with encryption via STARTTLS (Explicit TLS). Port 465 is outdated and used with Implicit TLS. +# SMTP_SSL=true # (Explicit) - This variable by default configures Explicit STARTTLS, it will upgrade an insecure connection to a secure one. Unless SMTP_EXPLICIT_TLS is set to true. Either port 587 or 25 are default. +# SMTP_EXPLICIT_TLS=true # (Implicit) - N.B. This variable configures Implicit TLS. It's currently mislabelled (see bug #851) - SMTP_SSL Needs to be set to true for this option to work. Usually port 465 is used here. # SMTP_USERNAME=username # SMTP_PASSWORD=password # SMTP_TIMEOUT=15 @@ -259,6 +259,22 @@ ## but might need to be changed in case it trips some anti-spam filters # HELO_NAME= +## SMTP debugging +## When set to true this will output very detailed SMTP messages. +## WARNING: This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting! +# SMTP_DEBUG=false + +## Accept Invalid Hostnames +## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! +## Only use this as a last resort if you are not able to use a valid certificate. +# SMTP_ACCEPT_INVALID_HOSTNAMES=false + +## Accept Invalid Certificates +## DANGEROUS: This option introduces significant vulnerabilities to man-in-the-middle attacks! +## Only use this as a last resort if you are not able to use a valid certificate. +## If the Certificate is valid but the hostname doesn't match, please use SMTP_ACCEPT_INVALID_HOSTNAMES instead. +# SMTP_ACCEPT_INVALID_CERTS=false + ## Require new device emails. When a user logs in an email is required to be sent. ## If sending the email fails the login attempt will fail!! # REQUIRE_DEVICE_EMAIL=false diff --git a/Cargo.lock b/Cargo.lock index e07eadcd..c5405e15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -33,12 +33,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "arrayvec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - [[package]] name = "atty" version = "0.2.14" @@ -131,6 +125,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "bitvec" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ba35e9565969edb811639dbebfe34edc0368e472c5018474c8eb2543397f81" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bitwarden_rs" version = "1.0.0" @@ -638,6 +644,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +[[package]] +name = "funty" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8" + [[package]] name = "futf" version = "0.1.4" @@ -1144,9 +1156,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lettre" -version = "0.10.0-alpha.3" +version = "0.10.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e422b6c03563bc47db09bb61a8ece4f1462de131455beb96c091e2998fa316a2" +checksum = "dc8c2fc7873920aca23647e5e86d44ff3f40bbc5a5efaab445c9eb0e001c9f71" dependencies = [ "base64 0.13.0", "hostname", @@ -1160,22 +1172,10 @@ dependencies = [ "rand 0.7.3", "regex", "serde", + "tracing", "uuid", ] -[[package]] -name = "lexical-core" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" -dependencies = [ - "arrayvec", - "bitflags", - "cfg-if 0.1.10", - "ryu", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.80" @@ -1448,11 +1448,11 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" [[package]] name = "nom" -version = "5.1.2" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +checksum = "4489ccc7d668957ddf64af7cd027c081728903afa6479d35da7e99bf5728f75f" dependencies = [ - "lexical-core", + "bitvec", "memchr", "version_check 0.9.2", ] @@ -1978,6 +1978,12 @@ dependencies = [ "scheduled-thread-pool", ] +[[package]] +name = "radium" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8" + [[package]] name = "rand" version = "0.4.6" @@ -2617,12 +2623,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - [[package]] name = "stdweb" version = "0.4.20" @@ -2782,6 +2782,12 @@ dependencies = [ "time 0.1.44", ] +[[package]] +name = "tap" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e" + [[package]] name = "tempfile" version = "3.1.0" @@ -3371,6 +3377,12 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "wyz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" + [[package]] name = "yansi" version = "0.5.0" diff --git a/Cargo.toml b/Cargo.toml index 1745d435..97d84f65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,7 +100,7 @@ num-traits = "0.2.14" num-derive = "0.3.3" # Email libraries -lettre = { version = "0.10.0-alpha.3", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname"], default-features = false } +lettre = { version = "0.10.0-alpha.4", features = ["smtp-transport", "builder", "serde", "native-tls", "hostname", "tracing"], default-features = false } newline-converter = "0.1.0" # Template library diff --git a/src/config.rs b/src/config.rs index b23dd674..60dc317c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -413,29 +413,35 @@ make_config! { /// SMTP Email Settings smtp: _enable_smtp { /// Enabled - _enable_smtp: bool, true, def, true; + _enable_smtp: bool, true, def, true; /// Host - smtp_host: String, true, option; + smtp_host: String, true, option; /// Enable Secure SMTP |> (Explicit) - Enabling this by default would use STARTTLS (Standard ports 587 or 25) - smtp_ssl: bool, true, def, true; + smtp_ssl: bool, true, def, true; /// Force TLS |> (Implicit) - Enabling this would force the use of an SSL/TLS connection, instead of upgrading an insecure one with STARTTLS (Standard port 465) - smtp_explicit_tls: bool, true, def, false; + smtp_explicit_tls: bool, true, def, false; /// Port - smtp_port: u16, true, auto, |c| if c.smtp_explicit_tls {465} else if c.smtp_ssl {587} else {25}; + smtp_port: u16, true, auto, |c| if c.smtp_explicit_tls {465} else if c.smtp_ssl {587} else {25}; /// From Address - smtp_from: String, true, def, String::new(); + smtp_from: String, true, def, String::new(); /// From Name - smtp_from_name: String, true, def, "Bitwarden_RS".to_string(); + smtp_from_name: String, true, def, "Bitwarden_RS".to_string(); /// Username - smtp_username: String, true, option; + smtp_username: String, true, option; /// Password - smtp_password: Pass, true, option; + smtp_password: Pass, true, option; /// SMTP Auth mechanism |> Defaults for SSL is "Plain" and "Login" and nothing for Non-SSL connections. Possible values: ["Plain", "Login", "Xoauth2"]. Multiple options need to be separated by a comma ','. - smtp_auth_mechanism: String, true, option; + smtp_auth_mechanism: String, true, option; /// SMTP connection timeout |> Number of seconds when to stop trying to connect to the SMTP server - smtp_timeout: u64, true, def, 15; + smtp_timeout: u64, true, def, 15; /// Server name sent during HELO |> By default this value should be is on the machine's hostname, but might need to be changed in case it trips some anti-spam filters - helo_name: String, true, option; + helo_name: String, true, option; + /// Enable SMTP debugging (Know the risks!) |> DANGEROUS: Enabling this will output very detailed SMTP messages. This could contain sensitive information like passwords and usernames! Only enable this during troubleshooting! + smtp_debug: bool, true, def, false; + /// Accept Invalid Certs (Know the risks!) |> DANGEROUS: Allow invalid certificates. This option introduces significant vulnerabilities to man-in-the-middle attacks! + smtp_accept_invalid_certs: bool, true, def, false; + /// Accept Invalid Hostnames (Know the risks!) |> DANGEROUS: Allow invalid hostnames. This option introduces significant vulnerabilities to man-in-the-middle attacks! + smtp_accept_invalid_hostnames: bool, true, def, false; }, /// Email 2FA Settings diff --git a/src/mail.rs b/src/mail.rs index bfde2529..7419169a 100644 --- a/src/mail.rs +++ b/src/mail.rs @@ -7,6 +7,7 @@ use percent_encoding::{percent_encode, NON_ALPHANUMERIC}; use lettre::{ message::{header, Mailbox, Message, MultiPart, SinglePart}, transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism}, + transport::smtp::client::{Tls, TlsParameters}, transport::smtp::extension::ClientId, Address, SmtpTransport, Transport, }; @@ -22,21 +23,30 @@ fn mailer() -> SmtpTransport { use std::time::Duration; let host = CONFIG.smtp_host().unwrap(); - // Determine security - let smtp_client = if CONFIG.smtp_ssl() { - if CONFIG.smtp_explicit_tls() { - SmtpTransport::relay(host.as_str()) - } else { - SmtpTransport::starttls_relay(host.as_str()) - } - } else { - Ok(SmtpTransport::builder_dangerous(host.as_str())) - }; - - let smtp_client = smtp_client.unwrap() + let smtp_client = SmtpTransport::builder_dangerous(host.as_str()) .port(CONFIG.smtp_port()) .timeout(Some(Duration::from_secs(CONFIG.smtp_timeout()))); + // Determine security + let smtp_client = if CONFIG.smtp_ssl() { + let mut tls_parameters = TlsParameters::builder(host); + if CONFIG.smtp_accept_invalid_hostnames() { + tls_parameters.dangerous_accept_invalid_hostnames(true); + } + if CONFIG.smtp_accept_invalid_certs() { + tls_parameters.dangerous_accept_invalid_certs(true); + } + let tls_parameters = tls_parameters.build().unwrap(); + + if CONFIG.smtp_explicit_tls() { + smtp_client.tls(Tls::Wrapper(tls_parameters)) + } else { + smtp_client.tls(Tls::Required(tls_parameters)) + } + } else { + smtp_client + }; + let smtp_client = match (CONFIG.smtp_username(), CONFIG.smtp_password()) { (Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user, pass)), _ => smtp_client, @@ -318,14 +328,17 @@ fn send_email(address: &str, subject: &str, body_html: &str, body_text: &str) -> // The boundary generated by Lettre it self is mostly too large based on the RFC822, so we generate one our selfs. use uuid::Uuid; - let boundary = format!("_Part_{}_", Uuid::new_v4().to_simple()); + let unique_id = Uuid::new_v4().to_simple(); + let boundary = format!("_Part_{}_", unique_id); let alternative = MultiPart::alternative().boundary(boundary).singlepart(text).singlepart(html); + let smtp_from = &CONFIG.smtp_from(); let email = Message::builder() + .message_id(Some(format!("<{}.{}>", unique_id, smtp_from))) .to(Mailbox::new(None, Address::from_str(&address)?)) .from(Mailbox::new( Some(CONFIG.smtp_from_name()), - Address::from_str(&CONFIG.smtp_from())?, + Address::from_str(smtp_from)?, )) .subject(subject) .multipart(alternative)?; diff --git a/src/main.rs b/src/main.rs index 5f929a8b..1da0d88e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,6 +115,16 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> { .level_for("rocket::fairing", log::LevelFilter::Off) .chain(std::io::stdout()); + // Enable smtp debug logging only specifically for smtp when need. + // This can contain sensitive information we do not want in the default debug/trace logging. + if CONFIG.smtp_debug() { + println!("[WARNING] SMTP Debugging is enabled (SMTP_DEBUG=true). Sensitive information could be disclosed via logs!"); + println!("[WARNING] Only enable SMTP_DEBUG during troubleshooting!\n"); + logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Debug) + } else { + logger = logger.level_for("lettre::transport::smtp", log::LevelFilter::Off) + } + if CONFIG.extended_logging() { logger = logger.format(|out, message, record| { out.finish(format_args!(