melib/smtp: implement gmail XOAUTH2 authentication method

jmap-eventsource
Manos Pitsidianakis 2020-11-30 03:25:57 +02:00
parent 4914f29e20
commit 453bb0b2b2
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
2 changed files with 62 additions and 2 deletions

View File

@ -1132,7 +1132,7 @@ subsection
.El
.Ss SmtpAuth
.Bl -tag -width 36n
.It Ic type Ar "none" | "auto"
.It Ic type Ar "none" | "auto" | "xoauth2"
.El
.Pp
For type "auto":
@ -1146,6 +1146,17 @@ require authentication in every case
.Pq Em true
.El
.sp
For type "xoauth2":
.Bl -tag -width 36n
.It Ic token_command Ar String
Command to evaluate that returns an XOAUTH2 token.
.It Ic require_auth Ar bool
.Pq Em optional
require authentication in every case
.\" default value
.Pq Em true
.El
.sp
Examples:
.Bd -literal
auth = { type = "auto", username = "user", password = { type = "raw", value = "hunter2" } }
@ -1156,6 +1167,13 @@ auth = { type = "auto", username = "user", password = "hunter2" }
.Bd -literal
auth = { type = "none" }
.Ed
.sp
For Gmail (see
.Sx Gmail OAUTH2
for details on the authentication token command):
.Bd -literal
auth = { type = "xoauth2", token_command = "TOKEN=$(python3 oauth2.py --user=xxx@gmail.com --quiet --client_id=1038[...].apps.googleusercontent.com --client_secret=[..] --refresh_token=[..] && python3 oauth2.py --user=xxx@gmail.com --generate_oauth2_string --quiet --access_token=$TOKEN" }
.Ed
.Ss SmtpPassword
.Bl -tag -width 36n
.It Ic type Ar "raw" | "command_evaluation"

View File

@ -140,6 +140,12 @@ pub enum SmtpAuth {
#[serde(skip_serializing, skip_deserializing, default)]
auth_type: SmtpAuthType,
},
#[serde(alias = "xoauth2")]
XOAuth2 {
token_command: String,
#[serde(default = "true_val")]
require_auth: bool,
},
// md5, sasl, etc
}
@ -162,7 +168,7 @@ impl SmtpAuth {
use SmtpAuth::*;
match self {
None => false,
Auto { require_auth, .. } => *require_auth,
Auto { require_auth, .. } | XOAuth2 { require_auth, .. } => *require_auth,
}
}
}
@ -505,6 +511,39 @@ impl SmtpConnection {
.chain_err_kind(crate::error::ErrorKind::Authentication)?;
ret.send_command(&[b"EHLO meli.delivery"]).await?;
}
SmtpAuth::XOAuth2 { token_command, .. } => {
let password_token = {
let _token_command = token_command.clone();
let mut output = unblock(move || {
Command::new("sh")
.args(&["-c", &_token_command])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.output()
})
.await?;
if !output.status.success() {
return Err(MeliError::new(format!(
"SMTP XOAUTH2 token evaluation command `{}` returned {}: {}",
&token_command,
output.status,
String::from_utf8_lossy(&output.stderr)
)));
}
if output.stdout.ends_with(b"\n") {
output.stdout.pop();
}
output.stdout
};
// https://developers.google.com/gmail/imap/xoauth2-protocol#smtp_protocol_exchange
ret.send_command(&[b"AUTH XOAUTH2 ", &password_token])
.await?;
ret.read_lines(&mut res, Some((ReplyCode::_235, &[])))
.await
.chain_err_kind(crate::error::ErrorKind::Authentication)?;
ret.send_command(&[b"EHLO meli.delivery"]).await?;
}
}
{
let extensions_reply = ret
@ -966,6 +1005,9 @@ async fn read_lines<'r>(
}
}
}
if ret.len() < 3 {
return Err(MeliError::new(format!("Invalid SMTP reply: {}", ret)));
}
let code = ReplyCode::try_from(&ret[..3])?;
let reply = Reply::new(ret, code);
//debug!(&reply);