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.
pull/1229/head
BlackDex 4 years ago
parent 3fed323385
commit 6faaeaae66

@ -242,9 +242,9 @@
# SMTP_HOST=smtp.domain.tld # SMTP_HOST=smtp.domain.tld
# SMTP_FROM=bitwarden-rs@domain.tld # SMTP_FROM=bitwarden-rs@domain.tld
# SMTP_FROM_NAME=Bitwarden_RS # SMTP_FROM_NAME=Bitwarden_RS
# SMTP_PORT=587 # 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. # 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. # 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_USERNAME=username
# SMTP_PASSWORD=password # SMTP_PASSWORD=password
# SMTP_TIMEOUT=15 # SMTP_TIMEOUT=15
@ -259,6 +259,22 @@
## but might need to be changed in case it trips some anti-spam filters ## but might need to be changed in case it trips some anti-spam filters
# HELO_NAME= # 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. ## 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!! ## If sending the email fails the login attempt will fail!!
# REQUIRE_DEVICE_EMAIL=false # REQUIRE_DEVICE_EMAIL=false

72
Cargo.lock generated

@ -33,12 +33,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -131,6 +125,18 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 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]] [[package]]
name = "bitwarden_rs" name = "bitwarden_rs"
version = "1.0.0" version = "1.0.0"
@ -638,6 +644,12 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "funty"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
[[package]] [[package]]
name = "futf" name = "futf"
version = "0.1.4" version = "0.1.4"
@ -1144,9 +1156,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]] [[package]]
name = "lettre" name = "lettre"
version = "0.10.0-alpha.3" version = "0.10.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e422b6c03563bc47db09bb61a8ece4f1462de131455beb96c091e2998fa316a2" checksum = "dc8c2fc7873920aca23647e5e86d44ff3f40bbc5a5efaab445c9eb0e001c9f71"
dependencies = [ dependencies = [
"base64 0.13.0", "base64 0.13.0",
"hostname", "hostname",
@ -1160,22 +1172,10 @@ dependencies = [
"rand 0.7.3", "rand 0.7.3",
"regex", "regex",
"serde", "serde",
"tracing",
"uuid", "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]] [[package]]
name = "libc" name = "libc"
version = "0.2.80" version = "0.2.80"
@ -1448,11 +1448,11 @@ checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" checksum = "4489ccc7d668957ddf64af7cd027c081728903afa6479d35da7e99bf5728f75f"
dependencies = [ dependencies = [
"lexical-core", "bitvec",
"memchr", "memchr",
"version_check 0.9.2", "version_check 0.9.2",
] ]
@ -1978,6 +1978,12 @@ dependencies = [
"scheduled-thread-pool", "scheduled-thread-pool",
] ]
[[package]]
name = "radium"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "941ba9d78d8e2f7ce474c015eea4d9c6d25b6a3327f9832ee29a4de27f91bbb8"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.4.6" version = "0.4.6"
@ -2617,12 +2623,6 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483" checksum = "3015a7d0a5fd5105c91c3710d42f9ccf0abfb287d62206484dcc67f9569a6483"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "stdweb" name = "stdweb"
version = "0.4.20" version = "0.4.20"
@ -2782,6 +2782,12 @@ dependencies = [
"time 0.1.44", "time 0.1.44",
] ]
[[package]]
name = "tap"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36474e732d1affd3a6ed582781b3683df3d0563714c59c39591e8ff707cf078e"
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.1.0" version = "3.1.0"
@ -3371,6 +3377,12 @@ dependencies = [
"winapi-build", "winapi-build",
] ]
[[package]]
name = "wyz"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]] [[package]]
name = "yansi" name = "yansi"
version = "0.5.0" version = "0.5.0"

@ -100,7 +100,7 @@ num-traits = "0.2.14"
num-derive = "0.3.3" num-derive = "0.3.3"
# Email libraries # 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" newline-converter = "0.1.0"
# Template library # Template library

@ -436,6 +436,12 @@ make_config! {
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 /// 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 /// Email 2FA Settings

@ -7,6 +7,7 @@ use percent_encoding::{percent_encode, NON_ALPHANUMERIC};
use lettre::{ use lettre::{
message::{header, Mailbox, Message, MultiPart, SinglePart}, message::{header, Mailbox, Message, MultiPart, SinglePart},
transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism}, transport::smtp::authentication::{Credentials, Mechanism as SmtpAuthMechanism},
transport::smtp::client::{Tls, TlsParameters},
transport::smtp::extension::ClientId, transport::smtp::extension::ClientId,
Address, SmtpTransport, Transport, Address, SmtpTransport, Transport,
}; };
@ -22,21 +23,30 @@ fn mailer() -> SmtpTransport {
use std::time::Duration; use std::time::Duration;
let host = CONFIG.smtp_host().unwrap(); let host = CONFIG.smtp_host().unwrap();
let smtp_client = SmtpTransport::builder_dangerous(host.as_str())
.port(CONFIG.smtp_port())
.timeout(Some(Duration::from_secs(CONFIG.smtp_timeout())));
// Determine security // Determine security
let smtp_client = if CONFIG.smtp_ssl() { 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() { if CONFIG.smtp_explicit_tls() {
SmtpTransport::relay(host.as_str()) smtp_client.tls(Tls::Wrapper(tls_parameters))
} else { } else {
SmtpTransport::starttls_relay(host.as_str()) smtp_client.tls(Tls::Required(tls_parameters))
} }
} else { } else {
Ok(SmtpTransport::builder_dangerous(host.as_str())) smtp_client
}; };
let smtp_client = smtp_client.unwrap()
.port(CONFIG.smtp_port())
.timeout(Some(Duration::from_secs(CONFIG.smtp_timeout())));
let smtp_client = match (CONFIG.smtp_username(), CONFIG.smtp_password()) { let smtp_client = match (CONFIG.smtp_username(), CONFIG.smtp_password()) {
(Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user, pass)), (Some(user), Some(pass)) => smtp_client.credentials(Credentials::new(user, pass)),
_ => smtp_client, _ => 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. // The boundary generated by Lettre it self is mostly too large based on the RFC822, so we generate one our selfs.
use uuid::Uuid; 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 alternative = MultiPart::alternative().boundary(boundary).singlepart(text).singlepart(html);
let smtp_from = &CONFIG.smtp_from();
let email = Message::builder() let email = Message::builder()
.message_id(Some(format!("<{}.{}>", unique_id, smtp_from)))
.to(Mailbox::new(None, Address::from_str(&address)?)) .to(Mailbox::new(None, Address::from_str(&address)?))
.from(Mailbox::new( .from(Mailbox::new(
Some(CONFIG.smtp_from_name()), Some(CONFIG.smtp_from_name()),
Address::from_str(&CONFIG.smtp_from())?, Address::from_str(smtp_from)?,
)) ))
.subject(subject) .subject(subject)
.multipart(alternative)?; .multipart(alternative)?;

@ -115,6 +115,16 @@ fn init_logging(level: log::LevelFilter) -> Result<(), fern::InitError> {
.level_for("rocket::fairing", log::LevelFilter::Off) .level_for("rocket::fairing", log::LevelFilter::Off)
.chain(std::io::stdout()); .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() { if CONFIG.extended_logging() {
logger = logger.format(|out, message, record| { logger = logger.format(|out, message, record| {
out.finish(format_args!( out.finish(format_args!(

Loading…
Cancel
Save