melib/smtp: impl AUTH LOGIN
AUTH LOGIN is deprecated but predictably still around.jmap-eventsource
parent
c0e3e78940
commit
d404910a0f
|
@ -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),
|
||||||
|
|
Loading…
Reference in New Issue