melib/smtp: impl AUTH LOGIN

AUTH LOGIN is deprecated but predictably still around.
jmap-eventsource
Manos Pitsidianakis 2020-11-15 16:28:42 +02:00
parent c0e3e78940
commit d404910a0f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
1 changed files with 71 additions and 22 deletions

View File

@ -137,10 +137,18 @@ pub enum SmtpAuth {
password: Password, password: Password,
#[serde(default = "true_val")] #[serde(default = "true_val")]
require_auth: bool, require_auth: bool,
#[serde(skip_serializing, skip_deserializing, default)]
auth_type: SmtpAuthType,
}, },
// md5, sasl, etc // md5, sasl, etc
} }
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, Default)]
pub struct SmtpAuthType {
plain: bool,
login: bool,
}
fn true_val() -> bool { fn true_val() -> bool {
true true
} }
@ -396,29 +404,44 @@ impl SmtpConnection {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"SMTP Server doesn't advertise Authentication support. Server response was: {:?}", "SMTP Server doesn't advertise Authentication support. Server response was: {:?}",
pre_auth_extensions_reply pre_auth_extensions_reply
))); )).set_kind(crate::error::ErrorKind::Authentication));
} }
no_auth_needed = no_auth_needed =
ret.server_conf.auth == SmtpAuth::None || !ret.server_conf.auth.require_auth(); ret.server_conf.auth == SmtpAuth::None || !ret.server_conf.auth.require_auth();
if no_auth_needed { if no_auth_needed {
ret.set_extension_support(pre_auth_extensions_reply); ret.set_extension_support(pre_auth_extensions_reply);
} else if let SmtpAuth::Auto {
ref mut auth_type, ..
} = ret.server_conf.auth
{
for l in pre_auth_extensions_reply
.lines
.iter()
.filter(|l| l.starts_with("AUTH"))
{
let l = l["AUTH ".len()..].trim();
for _type in l.split_whitespace() {
if _type == "PLAIN" {
auth_type.plain = true;
} else if _type == "LOGIN" {
auth_type.login = true;
}
}
break;
}
} }
} }
if !no_auth_needed { if !no_auth_needed {
match &ret.server_conf.auth { match &ret.server_conf.auth {
SmtpAuth::None => {} SmtpAuth::None => {}
SmtpAuth::Auto { SmtpAuth::Auto {
username, password, .. username,
password,
auth_type,
..
} => { } => {
// # RFC 4616 The PLAIN SASL Mechanism let password = match password {
// # https://www.ietf.org/rfc/rfc4616.txt Password::Raw(p) => p.as_bytes().to_vec(),
// message = [authzid] UTF8NUL authcid UTF8NUL passwd
// authcid = 1*SAFE ; MUST accept up to 255 octets
// authzid = 1*SAFE ; MUST accept up to 255 octets
// passwd = 1*SAFE ; MUST accept up to 255 octets
// UTF8NUL = %x00 ; UTF-8 encoded NUL character
let username_password = match password {
Password::Raw(p) => base64::encode(format!("\0{}\0{}", username, p)),
Password::CommandEval(command) => { Password::CommandEval(command) => {
let _command = command.clone(); let _command = command.clone();
@ -439,22 +462,44 @@ impl SmtpConnection {
String::from_utf8_lossy(&output.stderr) String::from_utf8_lossy(&output.stderr)
))); )));
} }
let mut buf =
Vec::with_capacity(2 + username.len() + output.stdout.len());
buf.push(b'\0');
buf.extend(username.as_bytes().to_vec());
buf.push(b'\0');
if output.stdout.ends_with(b"\n") { if output.stdout.ends_with(b"\n") {
output.stdout.pop(); output.stdout.pop();
} }
buf.extend(output.stdout); output.stdout
base64::encode(buf)
} }
}; };
let mut auth_command: SmallVec<[&[u8]; 16]> = SmallVec::new(); if auth_type.login {
auth_command.push(b"AUTH PLAIN "); let username = username.to_string();
auth_command.push(username_password.as_bytes()); ret.send_command(&[b"AUTH LOGIN"]).await?;
ret.send_command(&auth_command).await?; ret.read_lines(&mut res, Some((ReplyCode::_334, &[])))
.await
.chain_err_kind(crate::error::ErrorKind::Authentication)?;
let buf = base64::encode(&username);
ret.send_command(&[buf.as_bytes()]).await?;
ret.read_lines(&mut res, Some((ReplyCode::_334, &[])))
.await
.chain_err_kind(crate::error::ErrorKind::Authentication)?;
let buf = base64::encode(&password);
ret.send_command(&[buf.as_bytes()]).await?;
} else {
// # RFC 4616 The PLAIN SASL Mechanism
// # https://www.ietf.org/rfc/rfc4616.txt
// message = [authzid] UTF8NUL authcid UTF8NUL passwd
// authcid = 1*SAFE ; MUST accept up to 255 octets
// authzid = 1*SAFE ; MUST accept up to 255 octets
// passwd = 1*SAFE ; MUST accept up to 255 octets
// UTF8NUL = %x00 ; UTF-8 encoded NUL character
let username_password = {
let mut buf = Vec::with_capacity(2 + username.len() + password.len());
buf.push(b'\0');
buf.extend(username.as_bytes().to_vec());
buf.push(b'\0');
buf.extend(password);
base64::encode(buf)
};
ret.send_command(&[b"AUTH PLAIN ", username_password.as_bytes()])
.await?;
}
ret.read_lines(&mut res, Some((ReplyCode::_235, &[]))) ret.read_lines(&mut res, Some((ReplyCode::_235, &[])))
.await .await
.chain_err_kind(crate::error::ErrorKind::Authentication)?; .chain_err_kind(crate::error::ErrorKind::Authentication)?;
@ -711,6 +756,8 @@ pub enum ReplyCode {
_251, _251,
///Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3) ///Cannot VRFY user, but will accept message and attempt delivery (See Section 3.5.3)
_252, _252,
///rfc4954 AUTH continuation request
_334,
///PRDR specific, eg "content analysis has started| ///PRDR specific, eg "content analysis has started|
_353, _353,
///Start mail input; end with <CRLF>.<CRLF> ///Start mail input; end with <CRLF>.<CRLF>
@ -765,6 +812,7 @@ impl ReplyCode {
_235 => "Authentication successful", _235 => "Authentication successful",
_251 => "User not local; will forward", _251 => "User not local; will forward",
_252 => "Cannot VRFY user, but will accept message and attempt delivery", _252 => "Cannot VRFY user, but will accept message and attempt delivery",
_334 => "Intermediate response to the AUTH command",
_353 => "PRDR specific notice", _353 => "PRDR specific notice",
_354 => "Start mail input; end with <CRLF>.<CRLF>", _354 => "Start mail input; end with <CRLF>.<CRLF>",
_421 => "Service not available, closing transmission channel", _421 => "Service not available, closing transmission channel",
@ -815,6 +863,7 @@ impl TryFrom<&'_ str> for ReplyCode {
"250" => Ok(_250), "250" => Ok(_250),
"251" => Ok(_251), "251" => Ok(_251),
"252" => Ok(_252), "252" => Ok(_252),
"334" => Ok(_334),
"354" => Ok(_354), "354" => Ok(_354),
"421" => Ok(_421), "421" => Ok(_421),
"450" => Ok(_450), "450" => Ok(_450),