diff --git a/docs/meli.conf.5 b/docs/meli.conf.5 index 2da732f5f..f6325c90c 100644 --- a/docs/meli.conf.5 +++ b/docs/meli.conf.5 @@ -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" diff --git a/melib/src/smtp.rs b/melib/src/smtp.rs index 6c14e63c5..5246a4402 100644 --- a/melib/src/smtp.rs +++ b/melib/src/smtp.rs @@ -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);