api!: remove all oauth support and drop DC_LP_AUTH flags

BREAKING CHANGE: removed oauth2 module, dc_get_oauth2_url FFI function, DC_LP_AUTH flags and configured/serverflags, and the oauth2 parameter/field from SMTP/IMAP clients, JSON-RPC interfaces, and CLI tools.

also contains regenerated provider data after dropping oauth in the update script.
This commit is contained in:
holger krekel
2026-07-19 17:15:55 +02:00
parent 1721c0316a
commit 5c1e695237
30 changed files with 20 additions and 853 deletions

1
Cargo.lock generated
View File

@@ -1412,7 +1412,6 @@ dependencies = [
"sdp",
"serde",
"serde_json",
"serde_urlencoded",
"sha-1",
"sha2",
"shadowsocks",

View File

@@ -89,7 +89,6 @@ rusqlite = { workspace = true, features = ["sqlcipher"] }
sanitize-filename = { workspace = true }
sdp = "0.17.1"
serde_json = { workspace = true }
serde_urlencoded = "0.7.1"
serde = { workspace = true, features = ["derive"] }
sha-1 = "0.10"
sha2 = "0.10"

View File

@@ -503,7 +503,6 @@ char* dc_get_blobdir (const dc_context_t* context);
* - `send_pw` = SMTP-password, guessed if left out
* - `send_port` = SMTP-port, guessed if left out
* - `send_security`= SMTP-socket, one of @ref DC_SOCKET, defaults to #DC_SOCKET_AUTO
* - `server_flags` = IMAP-/SMTP-flags as a combination of @ref DC_LP flags, guessed if left out
* - `proxy_enabled` = Proxy enabled. Disabled by default.
* - `proxy_url` = Proxy URL. May contain multiple URLs separated by newline, but only the first one is used.
* - `imap_certificate_checks` = how to check IMAP and SMTP certificates, one of the @ref DC_CERTCK flags, defaults to #DC_CERTCK_AUTO (0)
@@ -590,36 +589,7 @@ int dc_set_config_from_qr (dc_context_t* context, const char* qr);
char* dc_get_info (const dc_context_t* context);
/**
* Get URL that can be used to initiate an OAuth2 authorization.
*
* If an OAuth2 authorization is possible for a given e-mail address,
* this function returns the URL that should be opened in a browser.
*
* If the user authorizes access,
* the given redirect_uri is called by the provider.
* It's up to the UI to handle this call.
*
* The provider will attach some parameters to the URL,
* most important the parameter `code` that should be set as the `mail_pw`.
* With `server_flags` set to #DC_LP_AUTH_OAUTH2,
* dc_configure() can be called as usual afterwards.
*
* @memberof dc_context_t
* @param context The context object.
* @param addr E-mail address the user has entered.
* In case the user selects a different e-mail address during
* authorization, this is corrected in dc_configure()
* @param redirect_uri URL that will get `code` that is used as `mail_pw` then.
* Not all URLs are allowed here, however, the following should work:
* `chat.delta:/PATH`, `http://localhost:PORT/PATH`,
* `https://localhost:PORT/PATH`, `urn:ietf:wg:oauth:2.0:oob`
* (the latter just displays the code the user can copy+paste then)
* @return URL that can be opened in the browser to start OAuth2.
* Returned strings must be released using dc_str_unref().
* If OAuth2 is not possible for the given e-mail address, NULL is returned.
*/
char* dc_get_oauth2_url (dc_context_t* context, const char* addr, const char* redirect_uri);
#define DC_CONNECTIVITY_NOT_CONNECTED 1000
@@ -713,7 +683,7 @@ int dc_get_push_state (dc_context_t* context);
* to get the full configuration from well-known URLs.
*
* - If _more_ options as `mail_server`, `mail_port`, `send_server`,
* `send_port`, `send_user` or `server_flags` are specified,
* `send_port` or `send_user` are specified,
* **autoconfigure/autodiscover is skipped**.
*
* While dc_configure() returns immediately,
@@ -5736,41 +5706,6 @@ int64_t dc_lot_get_timestamp (const dc_lot_t* lot);
* @}
*/
/**
* @defgroup DC_LP DC_LP
*
* Flags for configuring IMAP and SMTP servers.
* These flags are optional
* and may be set together with the username, password etc.
* via dc_set_config() using the key "server_flags".
*
* @addtogroup DC_LP
* @{
*/
/**
* Force OAuth2 authorization. This flag does not skip automatic configuration.
* Before calling dc_configure() with DC_LP_AUTH_OAUTH2 set,
* the user has to confirm access at the URL returned by dc_get_oauth2_url().
*/
#define DC_LP_AUTH_OAUTH2 0x2
/**
* Force NORMAL authorization, this is the default.
* If this flag is set, automatic configuration is skipped.
*/
#define DC_LP_AUTH_NORMAL 0x4
/**
* @}
*/
#define DC_LP_AUTH_FLAGS (DC_LP_AUTH_OAUTH2|DC_LP_AUTH_NORMAL) // if none of these flags are set, the default is chosen
/**
* @defgroup DC_CERTCK DC_CERTCK
*

View File

@@ -401,32 +401,6 @@ pub unsafe extern "C" fn dc_get_push_state(context: *const dc_context_t) -> libc
block_on(ctx.push_state()) as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_get_oauth2_url(
context: *mut dc_context_t,
addr: *const libc::c_char,
redirect: *const libc::c_char,
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_oauth2_url()");
return ptr::null_mut(); // NULL explicitly defined as "unknown"
}
let ctx = &*context;
let addr = to_string_lossy(addr);
let redirect = to_string_lossy(redirect);
block_on(async move {
match oauth2::get_oauth2_url(ctx, &addr, &redirect)
.await
.context("dc_get_oauth2_url failed")
.log_err(ctx)
{
Ok(Some(res)) => res.strdup(),
Ok(None) | Err(_) => ptr::null_mut(),
}
})
}
fn spawn_configure(ctx: Context) {
spawn(async move {
ctx.configure()

View File

@@ -66,10 +66,6 @@ pub struct EnteredLoginParam {
/// invalid hostnames.
/// Default: Automatic
pub certificate_checks: Option<EnteredCertificateChecks>,
/// If true, login via OAUTH2 (not recommended anymore).
/// Default: false
pub oauth2: Option<bool>,
}
impl From<dc::TransportListEntry> for TransportListEntry {
@@ -100,7 +96,6 @@ impl From<dc::EnteredLoginParam> for EnteredLoginParam {
smtp_user: param.smtp.user.into_option(),
smtp_password: param.smtp.password.into_option(),
certificate_checks: certificate_checks.into_option(),
oauth2: param.oauth2.into_option(),
}
}
}
@@ -127,7 +122,6 @@ impl TryFrom<EnteredLoginParam> for dc::EnteredLoginParam {
password: param.smtp_password.unwrap_or_default(),
},
certificate_checks: param.certificate_checks.unwrap_or_default().into(),
oauth2: param.oauth2.unwrap_or_default(),
})
}
}

View File

@@ -319,7 +319,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
info\n\
set <configuration-key> [<value>]\n\
get <configuration-key>\n\
oauth2\n\
configure\n\
connect\n\
disconnect\n\

View File

@@ -11,9 +11,7 @@ use std::borrow::Cow::{self, Borrowed, Owned};
use anyhow::{bail, Error};
use deltachat::chat::ChatId;
use deltachat::config;
use deltachat::context::*;
use deltachat::oauth2::*;
use deltachat::qr_code_generator::get_securejoin_qr_svg;
use deltachat::securejoin::*;
use deltachat::EventType;
@@ -162,11 +160,10 @@ const IMEX_COMMANDS: [&str; 10] = [
"stop",
];
const DB_COMMANDS: [&str; 11] = [
const DB_COMMANDS: [&str; 10] = [
"info",
"set",
"get",
"oauth2",
"configure",
"connect",
"disconnect",
@@ -425,19 +422,6 @@ async fn handle_cmd(
"configure" => {
ctx.configure().await?;
}
"oauth2" => {
if let Some(addr) = ctx.get_config(config::Config::Addr).await? {
if let Some(oauth2_url) =
get_oauth2_url(&ctx, &addr, "chat.delta:/com.b44t.messenger").await?
{
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{oauth2_url}");
} else {
println!("OAuth2 not available for {addr}.");
}
} else {
println!("oauth2: set addr first.");
}
}
"clear" => {
println!("\n\n\n");
print!("\x1b[1;1H\x1b[2J");

View File

@@ -231,14 +231,6 @@ class KeyGenType(IntEnum):
RSA4096 = 3
# "Lp" means "login parameters"
class LpAuthFlag(IntEnum):
"""Authorization flags."""
OAUTH2 = 0x2
NORMAL = 0x4
class MediaQuality(IntEnum):
"""Media quality setting."""

View File

@@ -145,9 +145,6 @@ def process_data(data, file):
opt = process_opt(data)
config_defaults = process_config_defaults(data)
oauth2 = data.get("oauth2", "")
oauth2 = "Some(Oauth2Authorizer::" + camel(oauth2) + ")" if oauth2 != "" else "None"
provider = ""
before_login_hint = cleanstr(data.get("before_login_hint", "") or "")
after_login_hint = cleanstr(data.get("after_login_hint", ""))
@@ -165,7 +162,6 @@ def process_data(data, file):
provider += " server: &[\n" + server + " ],\n"
provider += " opt: " + opt + ",\n"
provider += " config_defaults: " + config_defaults + ",\n"
provider += " oauth2_authorizer: " + oauth2 + ",\n"
provider += "};\n\n"
else:
raise TypeError("SMTP and IMAP must be specified together or left out both")
@@ -180,7 +176,7 @@ def process_data(data, file):
out_all += "// " + file.name + ": " + comment.strip(", ") + "\n"
# also add provider with no special things to do -
# eg. _not_ supporting oauth2 is also an information and we can skip the mx-lookup in this case
# eg. we can skip the mx-lookup in this case
out_all += provider
out_domains += domains
out_ids += ids
@@ -212,7 +208,7 @@ if __name__ == "__main__":
"use crate::provider::Socket::*;\n"
"use crate::provider::UsernamePattern::*;\n"
"use crate::provider::{\n"
" Config, ConfigDefault, Oauth2Authorizer, Provider, ProviderOptions, Server, Status,\n"
" Config, ConfigDefault, Provider, ProviderOptions, Server, Status,\n"
"};\n"
"use std::collections::HashMap;\n\n"
"use std::sync::LazyLock;\n\n"

View File

@@ -118,15 +118,6 @@ pub enum Config {
/// SMTP server security (e.g. TLS, STARTTLS).
SendSecurity,
/// Deprecated(2026-04).
/// Use EnteredLoginParam and add_transport{from_qr}()/list_transports() instead.
///
/// Whether to use OAuth 2.
///
/// Historically contained other bitflags, which are now deprecated.
/// Should not be extended in the future, create new config keys instead.
ServerFlags,
/// True if proxy is enabled.
///
/// Can be used to disable proxy without erasing known URLs.
@@ -298,12 +289,6 @@ pub enum Config {
/// Configured SMTP server password.
ConfiguredSendPw,
/// Deprecated(2026-04).
/// Use ConfiguredLoginParam and add_transport{from_qr}()/list_transports() instead.
///
/// Whether OAuth 2 is used with configured provider.
ConfiguredServerFlags,
/// Configured folder for incoming messages.
ConfiguredInboxFolder,

View File

@@ -32,7 +32,6 @@ pub use crate::login_param::EnteredLoginParam;
use crate::login_param::{EnteredCertificateChecks, TransportListEntry};
use crate::message::Message;
use crate::net::proxy::ProxyConfig;
use crate::oauth2::get_oauth2_addr;
use crate::provider::{Protocol, Provider, Socket, UsernamePattern};
use crate::qr::{login_param_from_account_qr, login_param_from_login_qr};
use crate::smtp::Smtp;
@@ -451,24 +450,7 @@ async fn get_configured_param(
param.smtp.password.clone()
};
let mut addr = param.addr.clone();
if param.oauth2 {
// the used oauth2 addr may differ, check this.
// if get_oauth2_addr() is not available in the oauth2 implementation, just use the given one.
progress!(ctx, 10);
if let Some(oauth2_addr) = get_oauth2_addr(ctx, &param.addr, &param.imap.password)
.await?
.and_then(|e| e.parse().ok())
{
info!(ctx, "Authorized address is {}", oauth2_addr);
addr = oauth2_addr;
ctx.sql
.set_raw_config("addr", Some(param.addr.as_str()))
.await?;
}
progress!(ctx, 20);
}
// no oauth? - just continue it's no error
let addr = param.addr.clone();
let parsed = EmailAddress::new(&param.addr).context("Bad email-address")?;
let param_domain = parsed.domain;
@@ -618,7 +600,6 @@ async fn get_configured_param(
ConfiguredCertificateChecks::AcceptInvalidCertificates
}
},
oauth2: param.oauth2,
};
Ok(configured_login_param)
}
@@ -649,7 +630,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result<Option<&'
&proxy_config2,
&smtp_addr,
strict_tls,
configured_param.oauth2,
)
.await?;

View File

@@ -162,25 +162,6 @@ pub const DC_DESIRED_TEXT_LINE_LEN: usize = 100;
/// `char`s), not Unicode Grapheme Clusters.
pub const DC_DESIRED_TEXT_LEN: usize = DC_DESIRED_TEXT_LINE_LEN * DC_DESIRED_TEXT_LINES;
// Flags for configuring IMAP and SMTP servers.
// These flags are optional
// and may be set together with the username, password etc.
// via dc_set_config() using the key "server_flags".
/// Force OAuth2 authorization.
///
/// This flag does not skip automatic configuration.
/// Before calling configure() with DC_LP_AUTH_OAUTH2 set,
/// the user has to confirm access at the URL returned by dc_get_oauth2_url().
pub const DC_LP_AUTH_OAUTH2: i32 = 0x2;
/// Force NORMAL authorization, this is the default.
/// If this flag is set, automatic configuration is skipped.
pub const DC_LP_AUTH_NORMAL: i32 = 0x4;
/// if none of these flags are set, the default is chosen
pub const DC_LP_AUTH_FLAGS: i32 = DC_LP_AUTH_OAUTH2 | DC_LP_AUTH_NORMAL;
// max. weight of images to send w/o recoding
pub const BALANCED_IMAGE_BYTES: usize = 500_000;
pub const WORSE_IMAGE_BYTES: usize = 130_000;

View File

@@ -231,8 +231,6 @@ pub struct InnerContext {
/// This is a global mutex-like state for operations which should be modal in the
/// clients.
running_state: RwLock<RunningState>,
/// Mutex to enforce only a single running oauth2 is running.
pub(crate) oauth2_mutex: Mutex<()>,
/// Mutex to prevent a race condition when a "your pw is wrong" warning is sent, resulting in multiple messages being sent.
pub(crate) wrong_pw_warning_mutex: Mutex<()>,
/// Mutex to prevent running housekeeping from multiple threads at once.
@@ -484,7 +482,6 @@ impl Context {
blobdir,
running_state: RwLock::new(Default::default()),
sql: Sql::new(dbfile),
oauth2_mutex: Mutex::new(()),
wrong_pw_warning_mutex: Mutex::new(()),
housekeeping_mutex: Mutex::new(()),
fetch_msgs_mutex: Mutex::new(()),

View File

@@ -285,7 +285,6 @@ async fn test_get_info_completeness() {
"send_pw",
"send_port",
"send_security",
"server_flags",
"skip_start_messages",
"proxy_url", // May contain passwords, don't leak it to the logs.
"socks5_enabled", // SOCKS5 options are deprecated.

View File

@@ -34,7 +34,6 @@ use crate::message::{self, Message};
use crate::mimeparser;
use crate::net::proxy::ProxyConfig;
use crate::net::session::SessionStream;
use crate::oauth2::get_oauth2_access_token;
use crate::push::encrypt_device_token;
use crate::receive_imf::{
ReceivedMsg, from_field_to_contact_id, get_prefetch_parent_message, receive_imf_inner,
@@ -76,9 +75,6 @@ pub(crate) struct Imap {
pub(crate) idle_interrupt_receiver: Receiver<()>,
/// Email address.
pub(crate) addr: String,
/// Login parameters.
lp: Vec<ConfiguredServerLoginParam>,
@@ -90,8 +86,6 @@ pub(crate) struct Imap {
strict_tls: bool,
oauth2: bool,
/// Watched folder.
pub(crate) folder: String,
@@ -118,12 +112,6 @@ pub(crate) struct Imap {
pub(crate) resync_request_receiver: async_channel::Receiver<()>,
}
#[derive(Debug)]
struct OAuth2 {
user: String,
access_token: String,
}
#[derive(Debug, Default)]
pub(crate) struct ServerMetadata {
/// IMAP METADATA `/shared/comment` as defined in
@@ -148,17 +136,6 @@ pub(crate) struct ServerMetadata {
pub ice_servers_expiration_timestamp: i64,
}
impl async_imap::Authenticator for OAuth2 {
type Response = String;
fn process(&mut self, _data: &[u8]) -> Self::Response {
format!(
"user={}\x01auth=Bearer {}\x01\x01",
self.user, self.access_token
)
}
}
#[derive(Debug, Display, PartialEq, Eq, Clone, Copy)]
pub enum FolderMeaning {
Unknown,
@@ -250,9 +227,7 @@ impl Imap {
let lp = param.imap.clone();
let password = param.imap_password.clone();
let proxy_config = ProxyConfig::load(context).await?;
let addr = &param.addr;
let strict_tls = param.strict_tls(proxy_config.is_some());
let oauth2 = param.oauth2;
let folder = param
.imap_folder
.clone()
@@ -262,12 +237,10 @@ impl Imap {
Ok(Imap {
transport_id,
idle_interrupt_receiver,
addr: addr.to_string(),
lp,
password,
proxy_config,
strict_tls,
oauth2,
folder,
authentication_failed_once: false,
connectivity: Default::default(),
@@ -376,25 +349,8 @@ impl Imap {
let imap_user: &str = lp.user.as_ref();
let imap_pw: &str = &self.password;
let login_res = if self.oauth2 {
info!(context, "Logging into IMAP server with OAuth 2.");
let addr: &str = self.addr.as_ref();
let token = get_oauth2_access_token(context, addr, imap_pw, true)
.await?
.context("IMAP could not get OAUTH token")?;
let auth = OAuth2 {
user: imap_user.into(),
access_token: token,
};
client
.authenticate("XOAUTH2", auth)
.await
.map(|session| (session, None))
} else {
info!(context, "Logging into IMAP server with LOGIN.");
client.login(imap_user, imap_pw).await
};
info!(context, "Logging into IMAP server with LOGIN.");
let login_res = client.login(imap_user, imap_pw).await;
match login_res {
Ok((mut session, login_capabilities_opt)) => {

View File

@@ -122,19 +122,6 @@ impl Client {
Ok((session, capabilities))
}
pub(crate) async fn authenticate(
self,
auth_type: &str,
authenticator: impl async_imap::Authenticator,
) -> Result<ImapSession<Box<dyn SessionStream>>> {
let Client { inner, .. } = self;
let session = inner
.authenticate(auth_type, authenticator)
.await
.map_err(|(err, _client)| err)?;
Ok(session)
}
async fn connection_attempt(
context: Context,
host: String,

View File

@@ -76,7 +76,6 @@ pub mod login_param;
pub mod message;
mod mimefactory;
pub mod mimeparser;
pub mod oauth2;
mod param;
#[cfg(not(feature = "internals"))]
mod pgp;

View File

@@ -13,7 +13,6 @@ use num_traits::ToPrimitive as _;
use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
use crate::context::Context;
pub use crate::net::proxy::ProxyConfig;
pub use crate::provider::Socket;
@@ -142,9 +141,6 @@ pub struct EnteredLoginParam {
/// TLS options: whether to allow invalid certificates and/or
/// invalid hostnames
pub certificate_checks: EnteredCertificateChecks,
/// If true, login via OAUTH2 (not recommended anymore)
pub oauth2: bool,
}
impl EnteredLoginParam {
@@ -222,12 +218,6 @@ impl EnteredLoginParam {
.await?
.unwrap_or_default();
let server_flags = context
.get_config_parsed::<i32>(Config::ServerFlags)
.await?
.unwrap_or_default();
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
Ok(EnteredLoginParam {
addr,
imap: EnteredImapLoginParam {
@@ -246,7 +236,6 @@ impl EnteredLoginParam {
password: send_pw,
},
certificate_checks,
oauth2,
})
}
@@ -303,15 +292,6 @@ impl EnteredLoginParam {
)
.await?;
let server_flags = if self.oauth2 {
Some(DC_LP_AUTH_OAUTH2.to_string())
} else {
None
};
context
.set_config(Config::ServerFlags, server_flags.as_deref())
.await?;
Ok(())
}
}
@@ -323,7 +303,7 @@ impl fmt::Display for EnteredLoginParam {
write!(
f,
"{} imap:{}:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{}:{} cert_{}",
"{} imap:{}:{}:{}:{}:{} smtp:{}:{}:{}:{}:{} cert_{}",
unset_empty(&self.addr),
unset_empty(&self.imap.user),
if !self.imap.password.is_empty() {
@@ -334,7 +314,6 @@ impl fmt::Display for EnteredLoginParam {
unset_empty(&self.imap.server),
self.imap.port,
self.imap.security,
if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
unset_empty(&self.smtp.user),
if !self.smtp.password.is_empty() {
pw
@@ -344,7 +323,6 @@ impl fmt::Display for EnteredLoginParam {
unset_empty(&self.smtp.server),
self.smtp.port,
self.smtp.security,
if self.oauth2 { "OAUTH2" } else { "AUTH_NORMAL" },
self.certificate_checks
)
}
@@ -419,7 +397,6 @@ mod tests {
password: "".to_string(),
},
certificate_checks: Default::default(),
oauth2: false,
};
param.save_legacy(&t).await?;
assert_eq!(

View File

@@ -5,7 +5,6 @@ use bytes::Bytes;
use http_body_util::BodyExt;
use hyper_util::rt::TokioIo;
use mime::Mime;
use serde::Serialize;
use tokio::fs;
use crate::blob::BlobObject;
@@ -453,37 +452,6 @@ pub(crate) async fn post_string(context: &Context, url: &str, body: String) -> R
Ok(response.status().is_success())
}
/// Sends a POST request with x-www-form-urlencoded data.
///
/// Does not follow redirects.
pub(crate) async fn post_form<T: Serialize + ?Sized>(
context: &Context,
url: &str,
form: &T,
) -> Result<Bytes> {
let parsed_url = url
.parse::<hyper::Uri>()
.with_context(|| format!("Failed to parse URL {url:?}"))?;
let scheme = parsed_url.scheme_str().context("URL has no scheme")?;
if scheme != "https" {
bail!("POST requests to non-HTTPS URLs are not allowed");
}
let encoded_body = serde_urlencoded::to_string(form).context("Failed to encode data")?;
let mut sender = get_http_sender(context, parsed_url.clone(), true).await?;
let authority = parsed_url
.authority()
.context("URL has no authority")?
.clone();
let request = hyper::Request::post(parsed_url)
.header(hyper::header::HOST, authority.as_str())
.header("content-type", "application/x-www-form-urlencoded")
.body(encoded_body)?;
let response = sender.send_request(request).await?;
let bytes = response.collect().await?.to_bytes();
Ok(bytes)
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -1,398 +0,0 @@
//! OAuth 2 module.
use std::collections::HashMap;
use anyhow::{Context as _, Result};
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
use serde::Deserialize;
use crate::context::Context;
use crate::log::warn;
use crate::net::http::post_form;
use crate::net::read_url_blob;
use crate::provider;
use crate::provider::Oauth2Authorizer;
use crate::tools::time;
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
// see <https://tech.yandex.com/oauth/doc/dg/reference/auto-code-client-docpage/>
client_id: "c4d0b6735fc8420a816d7e1303469341",
get_code: "https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true",
init_token: "https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
refresh_token: "https://oauth.yandex.com/token?grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf",
get_userinfo: None,
};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Oauth2 {
client_id: &'static str,
get_code: &'static str,
init_token: &'static str,
refresh_token: &'static str,
get_userinfo: Option<&'static str>,
}
/// OAuth 2 Access Token Response
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Response {
// Should always be there according to: <https://www.oauth.com/oauth2-servers/access-tokens/access-token-response/>
// but previous code handled its abscense.
access_token: Option<String>,
token_type: String,
/// Duration of time the token is granted for, in seconds
expires_in: Option<u64>,
refresh_token: Option<String>,
scope: Option<String>,
}
/// Returns URL that should be opened in the browser
/// if OAuth 2 is supported for this address.
pub async fn get_oauth2_url(
context: &Context,
addr: &str,
redirect_uri: &str,
) -> Result<Option<String>> {
if let Some(oauth2) = Oauth2::from_address(addr) {
context
.sql
.set_raw_config("oauth2_pending_redirect_uri", Some(redirect_uri))
.await?;
let oauth2_url = replace_in_uri(oauth2.get_code, "$CLIENT_ID", oauth2.client_id);
let oauth2_url = replace_in_uri(&oauth2_url, "$REDIRECT_URI", redirect_uri);
Ok(Some(oauth2_url))
} else {
Ok(None)
}
}
#[expect(clippy::arithmetic_side_effects)]
pub(crate) async fn get_oauth2_access_token(
context: &Context,
addr: &str,
code: &str,
regenerate: bool,
) -> Result<Option<String>> {
if let Some(oauth2) = Oauth2::from_address(addr) {
let lock = context.oauth2_mutex.lock().await;
// read generated token
if !regenerate && !is_expired(context).await? {
let access_token = context.sql.get_raw_config("oauth2_access_token").await?;
if access_token.is_some() {
// success
return Ok(access_token);
}
}
// generate new token: build & call auth url
let refresh_token = context.sql.get_raw_config("oauth2_refresh_token").await?;
let refresh_token_for = context
.sql
.get_raw_config("oauth2_refresh_token_for")
.await?
.unwrap_or_else(|| "unset".into());
let (redirect_uri, token_url, update_redirect_uri_on_success) =
if refresh_token.is_none() || refresh_token_for != code {
info!(context, "Generate OAuth2 refresh_token and access_token...",);
(
context
.sql
.get_raw_config("oauth2_pending_redirect_uri")
.await?
.unwrap_or_else(|| "unset".into()),
oauth2.init_token,
true,
)
} else {
info!(
context,
"Regenerate OAuth2 access_token by refresh_token...",
);
(
context
.sql
.get_raw_config("oauth2_redirect_uri")
.await?
.unwrap_or_else(|| "unset".into()),
oauth2.refresh_token,
false,
)
};
// to allow easier specification of different configurations,
// token_url is in GET-method-format, sth. as <https://domain?param1=val1&param2=val2> -
// convert this to POST-format ...
let mut parts = token_url.splitn(2, '?');
let post_url = parts.next().unwrap_or_default();
let post_args = parts.next().unwrap_or_default();
let mut post_param = HashMap::new();
for key_value_pair in post_args.split('&') {
let mut parts = key_value_pair.splitn(2, '=');
let key = parts.next().unwrap_or_default();
let mut value = parts.next().unwrap_or_default();
if value == "$CLIENT_ID" {
value = oauth2.client_id;
} else if value == "$REDIRECT_URI" {
value = &redirect_uri;
} else if value == "$CODE" {
value = code;
} else if value == "$REFRESH_TOKEN"
&& let Some(refresh_token) = refresh_token.as_ref()
{
value = refresh_token;
}
post_param.insert(key, value);
}
// ... and POST
let response: Response = match post_form(context, post_url, &post_param).await {
Ok(resp) => match serde_json::from_slice(&resp) {
Ok(response) => response,
Err(err) => {
warn!(
context,
"Failed to parse OAuth2 JSON response from {token_url}: {err:#}."
);
return Ok(None);
}
},
Err(err) => {
warn!(context, "Error calling OAuth2 at {token_url}: {err:#}.");
return Ok(None);
}
};
// update refresh_token if given, typically on the first round, but we update it later as well.
if let Some(ref token) = response.refresh_token {
context
.sql
.set_raw_config("oauth2_refresh_token", Some(token))
.await?;
context
.sql
.set_raw_config("oauth2_refresh_token_for", Some(code))
.await?;
}
// after that, save the access token.
// if it's unset, we may get it in the next round as we have the refresh_token now.
if let Some(ref token) = response.access_token {
context
.sql
.set_raw_config("oauth2_access_token", Some(token))
.await?;
let expires_in = response
.expires_in
// refresh a bit before
.map(|t| time() + t as i64 - 5)
.unwrap_or_else(|| 0);
context
.sql
.set_raw_config_int64("oauth2_timestamp_expires", expires_in)
.await?;
if update_redirect_uri_on_success {
context
.sql
.set_raw_config("oauth2_redirect_uri", Some(redirect_uri.as_ref()))
.await?;
}
} else {
warn!(context, "Failed to find OAuth2 access token");
}
drop(lock);
Ok(response.access_token)
} else {
warn!(context, "Internal OAuth2 error: 2");
Ok(None)
}
}
pub(crate) async fn get_oauth2_addr(
context: &Context,
addr: &str,
code: &str,
) -> Result<Option<String>> {
let oauth2 = match Oauth2::from_address(addr) {
Some(o) => o,
None => return Ok(None),
};
if oauth2.get_userinfo.is_none() {
return Ok(None);
}
if let Some(access_token) = get_oauth2_access_token(context, addr, code, false).await? {
let addr_out = match oauth2.get_addr(context, &access_token).await {
Ok(addr) => addr,
Err(err) => {
warn!(context, "Error getting addr: {err:#}.");
None
}
};
if addr_out.is_none() {
// regenerate
if let Some(access_token) = get_oauth2_access_token(context, addr, code, true).await? {
Ok(oauth2
.get_addr(context, &access_token)
.await
.unwrap_or_default())
} else {
Ok(None)
}
} else {
Ok(addr_out)
}
} else {
Ok(None)
}
}
impl Oauth2 {
#[expect(clippy::arithmetic_side_effects)]
fn from_address(addr: &str) -> Option<Self> {
let addr_normalized = normalize_addr(addr);
if let Some(domain) = addr_normalized
.find('@')
.map(|index| addr_normalized.split_at(index + 1).1)
&& let Some(oauth2_authorizer) = provider::get_provider_info(domain)
.and_then(|provider| provider.oauth2_authorizer.as_ref())
{
return Some(match oauth2_authorizer {
Oauth2Authorizer::Yandex => OAUTH2_YANDEX,
});
}
None
}
async fn get_addr(&self, context: &Context, access_token: &str) -> Result<Option<String>> {
let userinfo_url = self.get_userinfo.unwrap_or("");
let userinfo_url = replace_in_uri(userinfo_url, "$ACCESS_TOKEN", access_token);
// should returns sth. as
// {
// "id": "100000000831024152393",
// "email": "NAME@gmail.com",
// "verified_email": true,
// "picture": "https://lh4.googleusercontent.com/-Gj5jh_9R0BY/AAAAAAAAAAI/AAAAAAAAAAA/IAjtjfjtjNA/photo.jpg"
// }
let response = read_url_blob(context, &userinfo_url).await?;
let parsed: HashMap<String, serde_json::Value> =
serde_json::from_slice(&response.blob).context("Error getting userinfo")?;
// CAVE: serde_json::Value.as_str() removes the quotes of json-strings
// but serde_json::Value.to_string() does not!
if let Some(addr) = parsed.get("email") {
if let Some(s) = addr.as_str() {
Ok(Some(s.to_string()))
} else {
warn!(context, "E-mail in userinfo is not a string: {}", addr);
Ok(None)
}
} else {
warn!(context, "E-mail missing in userinfo.");
Ok(None)
}
}
}
async fn is_expired(context: &Context) -> Result<bool> {
let expire_timestamp = context
.sql
.get_raw_config_int64("oauth2_timestamp_expires")
.await?
.unwrap_or_default();
if expire_timestamp <= 0 {
return Ok(false);
}
if expire_timestamp > time() {
return Ok(false);
}
Ok(true)
}
fn replace_in_uri(uri: &str, key: &str, value: &str) -> String {
let value_urlencoded = utf8_percent_encode(value, NON_ALPHANUMERIC).to_string();
uri.replace(key, &value_urlencoded)
}
fn normalize_addr(addr: &str) -> &str {
let normalized = addr.trim();
normalized.trim_start_matches("mailto:")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::TestContext;
#[test]
fn test_normalize_addr() {
assert_eq!(normalize_addr(" hello@mail.de "), "hello@mail.de");
assert_eq!(normalize_addr("mailto:hello@mail.de "), "hello@mail.de");
}
#[test]
fn test_replace_in_uri() {
assert_eq!(
replace_in_uri("helloworld", "world", "a-b c"),
"helloa%2Db%20c"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_oauth_from_address() {
// Delta Chat does not have working Gmail client ID anymore.
assert_eq!(Oauth2::from_address("hello@gmail.com"), None);
assert_eq!(Oauth2::from_address("hello@googlemail.com"), None);
assert_eq!(
Oauth2::from_address("hello@yandex.com"),
Some(OAUTH2_YANDEX)
);
assert_eq!(Oauth2::from_address("hello@yandex.ru"), Some(OAUTH2_YANDEX));
assert_eq!(Oauth2::from_address("hello@web.de"), None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_oauth2_addr() {
let ctx = TestContext::new().await;
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = get_oauth2_addr(&ctx.ctx, addr, code).await.unwrap();
// this should fail as it is an invalid password
assert_eq!(res, None);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_oauth2_url() {
let ctx = TestContext::new().await;
let addr = "example@yandex.com";
let redirect_uri = "chat.delta:/com.b44t.messenger";
let res = get_oauth2_url(&ctx.ctx, addr, redirect_uri).await.unwrap();
assert_eq!(res, Some("https://oauth.yandex.com/authorize?client_id=c4d0b6735fc8420a816d7e1303469341&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true".into()));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_get_oauth2_token() {
let ctx = TestContext::new().await;
let addr = "dignifiedquire@gmail.com";
let code = "fail";
let res = get_oauth2_access_token(&ctx.ctx, addr, code, false)
.await
.unwrap();
// this should fail as it is an invalid password
assert_eq!(res, None);
}
}

View File

@@ -76,13 +76,6 @@ pub enum UsernamePattern {
Emaillocalpart = 2,
}
/// Type of OAuth 2 authorization.
#[derive(Debug, PartialEq, Eq)]
pub enum Oauth2Authorizer {
/// Yandex.
Yandex,
}
/// Email server endpoint.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Server {
@@ -136,9 +129,6 @@ pub struct Provider {
/// Default configuration values to set when provider is configured.
pub config_defaults: Option<&'static [ConfigDefault]>,
/// Type of OAuth 2 authorization if provider supports it.
pub oauth2_authorizer: Option<Oauth2Authorizer>,
/// Options with good defaults.
pub opt: ProviderOptions,
}

View File

@@ -3,9 +3,7 @@
use crate::provider::Protocol::*;
use crate::provider::Socket::*;
use crate::provider::UsernamePattern::*;
use crate::provider::{
Config, ConfigDefault, Oauth2Authorizer, Provider, ProviderOptions, Server, Status,
};
use crate::provider::{Config, ConfigDefault, Provider, ProviderOptions, Server, Status};
use std::collections::HashMap;
use std::sync::LazyLock;
@@ -35,7 +33,6 @@ static P_163: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// aktivix.org.md: aktivix.org
@@ -63,7 +60,6 @@ static P_AKTIVIX_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// aliyun.md: aliyun.com
@@ -91,7 +87,6 @@ static P_ALIYUN: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// aol.md: aol.com
@@ -119,7 +114,6 @@ static P_AOL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// arcor.de.md: arcor.de
@@ -147,7 +141,6 @@ static P_ARCOR_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// autistici.org.md: autistici.org
@@ -175,7 +168,6 @@ static P_AUTISTICI_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// blindzeln.org.md: delta.blinzeln.de, delta.blindzeln.org
@@ -203,7 +195,6 @@ static P_BLINDZELN_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// bluewin.ch.md: bluewin.ch
@@ -231,7 +222,6 @@ static P_BLUEWIN_CH: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// chello.at.md: chello.at
@@ -259,7 +249,6 @@ static P_CHELLO_AT: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// comcast.md: xfinity.com, comcast.net
@@ -272,7 +261,6 @@ static P_COMCAST: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// dismail.de.md: dismail.de
@@ -285,7 +273,6 @@ static P_DISMAIL_DE: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// disroot.md: disroot.org
@@ -313,7 +300,6 @@ static P_DISROOT: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// e.email.md: e.email
@@ -341,7 +327,6 @@ static P_E_EMAIL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// espiv.net.md: espiv.net
@@ -354,7 +339,6 @@ static P_ESPIV_NET: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// example.com.md: example.com, example.org, example.net
@@ -382,7 +366,6 @@ static P_EXAMPLE_COM: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// fastmail.md: 123mail.org, 150mail.com, 150ml.com, 16mail.com, 2-mail.com, 4email.net, 50mail.com, airpost.net, allmail.net, bestmail.us, cluemail.com, elitemail.org, emailcorner.net, emailengine.net, emailengine.org, emailgroups.net, emailplus.org, emailuser.net, eml.cc, f-m.fm, fast-email.com, fast-mail.org, fastem.com, fastemail.us, fastemailer.com, fastest.cc, fastimap.com, fastmail.cn, fastmail.co.uk, fastmail.com, fastmail.com.au, fastmail.de, fastmail.es, fastmail.fm, fastmail.fr, fastmail.im, fastmail.in, fastmail.jp, fastmail.mx, fastmail.net, fastmail.nl, fastmail.org, fastmail.se, fastmail.to, fastmail.tw, fastmail.uk, fastmail.us, fastmailbox.net, fastmessaging.com, fea.st, fmail.co.uk, fmailbox.com, fmgirl.com, fmguy.com, ftml.net, h-mail.us, hailmail.net, imap-mail.com, imap.cc, imapmail.org, inoutbox.com, internet-e-mail.com, internet-mail.org, internetemails.net, internetmailing.net, jetemail.net, justemail.net, letterboxes.org, mail-central.com, mail-page.com, mailandftp.com, mailas.com, mailbolt.com, mailc.net, mailcan.com, mailforce.net, mailftp.com, mailhaven.com, mailingaddress.org, mailite.com, mailmight.com, mailnew.com, mailsent.net, mailservice.ms, mailup.net, mailworks.org, ml1.net, mm.st, myfastmail.com, mymacmail.com, nospammail.net, ownmail.net, petml.com, postinbox.com, postpro.net, proinbox.com, promessage.com, realemail.net, reallyfast.biz, reallyfast.info, rushpost.com, sent.as, sent.at, sent.com, speedpost.net, speedymail.org, ssl-mail.com, swift-mail.com, the-fastest.net, the-quickest.com, theinternetemail.com, veryfast.biz, veryspeedy.net, warpmail.net, xsmail.com, yepmail.net, your-mail.com
@@ -410,7 +393,6 @@ static P_FASTMAIL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// firemail.de.md: firemail.at, firemail.de
@@ -423,7 +405,6 @@ static P_FIREMAIL_DE: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// freenet.de.md: freenet.de
@@ -465,7 +446,6 @@ static P_FREENET_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// gmail.md: gmail.com, googlemail.com, google.com
@@ -493,7 +473,6 @@ static P_GMAIL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// gmx.net.md: gmx.net, gmx.de, gmx.at, gmx.ch, gmx.org, gmx.eu, gmx.info, gmx.biz, gmx.com
@@ -528,7 +507,6 @@ static P_GMX_NET: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// hermes.radio.md: *.hermes.radio, *.aco-connexion.org
@@ -547,7 +525,6 @@ static P_HERMES_RADIO: Provider = Provider {
key: Config::MdnsEnabled,
value: "0",
}]),
oauth2_authorizer: None,
};
// hey.com.md: hey.com
@@ -560,7 +537,6 @@ static P_HEY_COM: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// i.ua.md: i.ua
@@ -573,7 +549,6 @@ static P_I_UA: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// i3.net.md: i3.net
@@ -586,7 +561,6 @@ static P_I3_NET: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// icloud.md: icloud.com, me.com, mac.com
@@ -614,7 +588,6 @@ static P_ICLOUD: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// infomaniak.com.md: ik.me
@@ -645,7 +618,6 @@ static P_INFOMANIAK_COM: Provider = Provider {
..ProviderOptions::new()
},
config_defaults: None,
oauth2_authorizer: None,
};
// kolst.com.md: kolst.com
@@ -658,7 +630,6 @@ static P_KOLST_COM: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// kontent.com.md: kontent.com
@@ -671,7 +642,6 @@ static P_KONTENT_COM: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mail.com.md: email.com, groupmail.com, post.com, homemail.com, housemail.com, writeme.com, mail.com, mail-me.com, workmail.com, accountant.com, activist.com, adexec.com, allergist.com, alumni.com, alumnidirector.com, archaeologist.com, auctioneer.net, bartender.net, brew-master.com, chef.net, chemist.com, collector.org, columnist.com, comic.com, consultant.com, contractor.net, counsellor.com, deliveryman.com, diplomats.com, dr.com, engineer.com, financier.com, fireman.net, gardener.com, geologist.com, graphic-designer.com, graduate.org, hairdresser.net, instructor.net, insurer.com, journalist.com, legislator.com, lobbyist.com, minister.com, musician.org, optician.com, orthodontist.net, pediatrician.com, photographer.net, physicist.net, politician.com, presidency.com, priest.com, programmer.net, publicist.com, radiologist.net, realtyagent.com, registerednurses.com, repairman.com, representative.com, salesperson.net, secretary.net, socialworker.net, sociologist.com, songwriter.net, teachers.org, techie.com, technologist.com, therapist.net, umpire.com, worker.com, artlover.com, bikerider.com, birdlover.com, blader.com, kittymail.com, lovecat.com, marchmail.com, boardermail.com, catlover.com, clubmember.org, nonpartisan.com, petlover.com, doglover.com, greenmail.net, hackermail.com, theplate.com, bsdmail.com, computer4u.com, coolsite.net, cyberdude.com, cybergal.com, cyberservices.com, cyber-wizard.com, linuxmail.org, null.net, solution4u.com, tech-center.com, webname.com, acdcfan.com, angelic.com, discofan.com, elvisfan.com, hiphopfan.com, kissfans.com, madonnafan.com, metalfan.com, ninfan.com, ravemail.com, reggaefan.com, snakebite.com, bellair.net, californiamail.com, dallasmail.com, nycmail.com, pacific-ocean.com, pacificwest.com, sanfranmail.com, usa.com, africamail.com, asia-mail.com, australiamail.com, berlin.com, brazilmail.com, chinamail.com, dublin.com, dutchmail.com, englandmail.com, europe.com, arcticmail.com, europemail.com, germanymail.com, irelandmail.com, israelmail.com, italymail.com, koreamail.com, mexicomail.com, moscowmail.com, munich.com, asia.com, polandmail.com, safrica.com, samerica.com, scotlandmail.com, spainmail.com, swedenmail.com, swissmail.com, torontomail.com, aircraftmail.com, cash4u.com, disposable.com, execs.com, fastservice.com, instruction.com, job4u.com, net-shopping.com, planetmail.com, planetmail.net, qualityservice.com, rescueteam.com, surgical.net, atheist.com, disciples.com, muslim.com, protestant.com, reborn.com, reincarnate.com, religious.com, saintly.com, brew-meister.com, cutey.com, dbzmail.com, doramail.com, galaxyhit.com, hilarious.com, humanoid.net, hot-shot.com, inorbit.com, iname.com, innocent.com, keromail.com, myself.com, rocketship.com, toothfairy.com, toke.com, tvstar.com, uymail.com, 2trom.com
@@ -684,7 +654,6 @@ static P_MAIL_COM: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mail.de.md: mail.de
@@ -712,7 +681,6 @@ static P_MAIL_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mail.ru.md: mail.ru, inbox.ru, internet.ru, bk.ru, list.ru
@@ -740,7 +708,6 @@ static P_MAIL_RU: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mail2tor.md: mail2tor.com
@@ -768,7 +735,6 @@ static P_MAIL2TOR: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mailbox.org.md: mailbox.org, secure.mailbox.org
@@ -796,7 +762,6 @@ static P_MAILBOX_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// mailo.com.md: mailo.com
@@ -824,7 +789,6 @@ static P_MAILO_COM: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// migadu.md: migadu.com
@@ -859,7 +823,6 @@ static P_MIGADU: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// nauta.cu.md: nauta.cu
@@ -894,7 +857,6 @@ static P_NAUTA_CU: Provider = Provider {
key: Config::MediaQuality,
value: "1",
}]),
oauth2_authorizer: None,
};
// naver.md: naver.com
@@ -922,7 +884,6 @@ static P_NAVER: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// nine.testrun.org.md: nine.testrun.org
@@ -978,7 +939,6 @@ static P_NINE_TESTRUN_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// nubo.coop.md: nubo.coop
@@ -1006,7 +966,6 @@ static P_NUBO_COOP: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// outlook.com.md: hotmail.com, outlook.com, office365.com, outlook.com.tr, live.com, outlook.de
@@ -1034,7 +993,6 @@ static P_OUTLOOK_COM: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// ouvaton.coop.md: ouvaton.org
@@ -1062,7 +1020,6 @@ static P_OUVATON_COOP: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// posteo.md: posteo.de, posteo.af, posteo.at, posteo.be, posteo.ca, posteo.ch, posteo.cl, posteo.co, posteo.co.uk, posteo.com, posteo.com.br, posteo.cr, posteo.cz, posteo.dk, posteo.ee, posteo.es, posteo.eu, posteo.fi, posteo.gl, posteo.gr, posteo.hn, posteo.hr, posteo.hu, posteo.ie, posteo.in, posteo.is, posteo.it, posteo.jp, posteo.la, posteo.li, posteo.lt, posteo.lu, posteo.me, posteo.mx, posteo.my, posteo.net, posteo.nl, posteo.no, posteo.nz, posteo.org, posteo.pe, posteo.pl, posteo.pm, posteo.pt, posteo.ro, posteo.ru, posteo.se, posteo.sg, posteo.si, posteo.tn, posteo.uk, posteo.us
@@ -1104,7 +1061,6 @@ static P_POSTEO: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// protonmail.md: protonmail.com, protonmail.ch, pm.me
@@ -1117,7 +1073,6 @@ static P_PROTONMAIL: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// purelymail.com.md: purelymail.com, cheapermail.com, placeq.com, rethinkmail.com, worldofmail.com
@@ -1145,7 +1100,6 @@ static P_PURELYMAIL_COM: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// qq.md: qq.com, foxmail.com
@@ -1173,7 +1127,6 @@ static P_QQ: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// rambler.ru.md: rambler.ru, autorambler.ru, myrambler.ru, rambler.ua, lenta.ru, ro.ru, r0.ru
@@ -1208,7 +1161,6 @@ static P_RAMBLER_RU: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// riseup.net.md: riseup.net
@@ -1236,7 +1188,6 @@ static P_RISEUP_NET: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// rogers.com.md: rogers.com
@@ -1249,7 +1200,6 @@ static P_ROGERS_COM: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// sonic.md: sonic.net
@@ -1262,7 +1212,6 @@ static P_SONIC: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// stinpriza.net.md: stinpriza.net, stinpriza.eu, el-hoyo.net
@@ -1293,7 +1242,6 @@ static P_STINPRIZA_NET: Provider = Provider {
..ProviderOptions::new()
},
config_defaults: None,
oauth2_authorizer: None,
};
// systemausfall.org.md: systemausfall.org, solidaris.me
@@ -1321,7 +1269,6 @@ static P_SYSTEMAUSFALL_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// systemli.org.md: systemli.org
@@ -1349,7 +1296,6 @@ static P_SYSTEMLI_ORG: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// t-online.md: t-online.de, magenta.de
@@ -1377,7 +1323,6 @@ static P_T_ONLINE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// tiscali.it.md: tiscali.it
@@ -1405,7 +1350,6 @@ static P_TISCALI_IT: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// tutanota.md: tutanota.com, tutanota.de, tutamail.com, tuta.io, keemail.me
@@ -1418,7 +1362,6 @@ static P_TUTANOTA: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// ukr.net.md: ukr.net
@@ -1446,7 +1389,6 @@ static P_UKR_NET: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// undernet.uy.md: undernet.uy
@@ -1474,7 +1416,6 @@ static P_UNDERNET_UY: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// vfemail.md: vfemail.net
@@ -1487,7 +1428,6 @@ static P_VFEMAIL: Provider = Provider {
server: &[],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// vivaldi.md: vivaldi.net
@@ -1515,7 +1455,6 @@ static P_VIVALDI: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// vk.com.md: vk.com
@@ -1543,7 +1482,6 @@ static P_VK_COM: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// vodafone.de.md: vodafone.de, vodafonemail.de
@@ -1571,7 +1509,6 @@ static P_VODAFONE_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// web.de.md: web.de, email.de, flirt.ms, hallo.ms, kuss.ms, love.ms, magic.ms, singles.ms, cool.ms, kanzler.ms, okay.ms, party.ms, pop.ms, stars.ms, techno.ms, clever.ms, deutschland.ms, genial.ms, ich.ms, online.ms, smart.ms, wichtig.ms, action.ms, fussball.ms, joker.ms, planet.ms, power.ms
@@ -1606,7 +1543,6 @@ static P_WEB_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// wkpb.de.md: wkpb.de
@@ -1634,7 +1570,6 @@ static P_WKPB_DE: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// yahoo.md: yahoo.com, yahoo.de, yahoo.it, yahoo.fr, yahoo.es, yahoo.se, yahoo.co.uk, yahoo.co.nz, yahoo.com.au, yahoo.com.ar, yahoo.com.br, yahoo.com.mx, myyahoo.com, ymail.com, rocketmail.com, yahoodns.net
@@ -1662,7 +1597,6 @@ static P_YAHOO: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// yandex.ru.md: yandex.com, yandex.by, yandex.kz, yandex.ru, yandex.ua, ya.ru, narod.ru
@@ -1690,7 +1624,6 @@ static P_YANDEX_RU: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: Some(Oauth2Authorizer::Yandex),
};
// yggmail.md: yggmail
@@ -1718,7 +1651,6 @@ static P_YGGMAIL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// ziggo.nl.md: ziggo.nl
@@ -1746,7 +1678,6 @@ static P_ZIGGO_NL: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
// zoho.md: zohomail.eu, zohomail.com, zoho.com
@@ -1774,7 +1705,6 @@ static P_ZOHO: Provider = Provider {
],
opt: ProviderOptions::new(),
config_defaults: None,
oauth2_authorizer: None,
};
pub(crate) static PROVIDER_DATA: [(&str, &Provider); 521] = [

View File

@@ -841,7 +841,6 @@ pub(crate) async fn login_param_from_account_qr(
},
smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false,
};
return Ok(param);
}
@@ -861,7 +860,6 @@ pub(crate) async fn login_param_from_account_qr(
},
smtp: Default::default(),
certificate_checks: EnteredCertificateChecks::Strict,
oauth2: false,
};
Ok(param)

View File

@@ -196,7 +196,6 @@ pub(crate) fn login_param_from_login_qr(
password: smtp_password.unwrap_or_default(),
},
certificate_checks: certificate_checks.unwrap_or_default(),
oauth2: false,
};
Ok(param)
}

View File

@@ -100,13 +100,11 @@ impl Smtp {
&proxy_config,
&lp.addr,
lp.strict_tls(proxy_config.is_some()),
lp.oauth2,
)
.await
}
/// Connect using the provided login params.
#[expect(clippy::too_many_arguments)]
pub async fn connect(
&mut self,
context: &Context,
@@ -115,7 +113,6 @@ impl Smtp {
proxy_config: &Option<ProxyConfig>,
addr: &str,
strict_tls: bool,
oauth2: bool,
) -> Result<()> {
if self.is_connected() {
warn!(context, "SMTP already connected.");
@@ -136,8 +133,6 @@ impl Smtp {
proxy_config,
strict_tls,
lp.connection.clone(),
oauth2,
addr,
&lp.user,
password,
)

View File

@@ -15,7 +15,6 @@ use crate::net::tls::{SpkiHashStore, TlsSessionStore, wrap_tls};
use crate::net::{
connect_tcp_inner, connect_tls_inner, run_connection_attempts, update_connection_history,
};
use crate::oauth2::get_oauth2_access_token;
use crate::sql::Sql;
use crate::tools::time;
use crate::transport::ConnectionCandidate;
@@ -48,14 +47,11 @@ async fn new_smtp_transport<S: AsyncBufRead + AsyncWrite + Unpin>(
Ok(transport)
}
#[expect(clippy::too_many_arguments)]
pub(crate) async fn connect_and_auth(
context: &Context,
proxy_config: &Option<ProxyConfig>,
strict_tls: bool,
candidate: ConnectionCandidate,
oauth2: bool,
addr: &str,
user: &str,
password: &str,
) -> Result<SmtpTransport<Box<dyn SessionBufStream>>> {
@@ -65,31 +61,13 @@ pub(crate) async fn connect_and_auth(
let mut transport = new_smtp_transport(session_stream).await?;
// Authenticate.
let (creds, mechanism) = if oauth2 {
// oauth2
let access_token = get_oauth2_access_token(context, addr, password, false)
.await
.context("SMTP failed to get OAUTH2 access token")?;
if access_token.is_none() {
bail!("SMTP OAuth 2 error {addr}");
}
(
async_smtp::authentication::Credentials::new(
user.to_string(),
access_token.unwrap_or_default(),
),
vec![async_smtp::authentication::Mechanism::Xoauth2],
)
} else {
// plain
(
async_smtp::authentication::Credentials::new(user.to_string(), password.to_string()),
vec![
async_smtp::authentication::Mechanism::Plain,
async_smtp::authentication::Mechanism::Login,
],
)
};
let (creds, mechanism) = (
async_smtp::authentication::Credentials::new(user.to_string(), password.to_string()),
vec![
async_smtp::authentication::Mechanism::Plain,
async_smtp::authentication::Mechanism::Login,
],
);
transport
.try_login(&creds, &mechanism)
.await

View File

@@ -645,11 +645,6 @@ impl Sql {
self.set_raw_config(key, value).await
}
/// Sets configuration for the given key to 64-bit signed integer value.
pub async fn set_raw_config_int64(&self, key: &str, value: i64) -> Result<()> {
self.set_raw_config(key, Some(&format!("{value}"))).await
}
/// Returns 64-bit signed integer configuration value for the given key.
pub async fn get_raw_config_int64(&self, key: &str) -> Result<Option<i64>> {
self.get_raw_config(key)

View File

@@ -226,7 +226,7 @@ impl TestContextManager {
(
new_addr,
serde_json::to_string(&EnteredLoginParam{addr: new_addr.to_string(), ..Default::default()}).unwrap(),
format!(r#"{{"addr":"{new_addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
format!(r#"{{"addr":"{new_addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic"}}"#)
),
).await.unwrap();

View File

@@ -17,7 +17,6 @@ use serde::{Deserialize, Serialize};
use crate::config::Config;
use crate::configure::server_params::{ServerParams, expand_param_vector};
use crate::constants::{DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2};
use crate::context::Context;
use crate::ensure_and_debug_assert;
use crate::events::EventType;
@@ -199,9 +198,6 @@ pub(crate) struct ConfiguredLoginParam {
/// TLS options: whether to allow invalid certificates and/or
/// invalid hostnames
pub certificate_checks: ConfiguredCertificateChecks,
/// If true, login via OAUTH2 (not recommended anymore)
pub oauth2: bool,
}
/// JSON representation of ConfiguredLoginParam
@@ -224,7 +220,6 @@ pub(crate) struct ConfiguredLoginParamJson {
pub smtp_password: String,
pub provider_id: Option<String>,
pub certificate_checks: ConfiguredCertificateChecks,
pub oauth2: bool,
}
impl fmt::Display for ConfiguredLoginParam {
@@ -350,12 +345,6 @@ impl ConfiguredLoginParam {
.await?
.context("IMAP password is not configured")?;
let server_flags = context
.get_config_parsed::<i32>(Config::ConfiguredServerFlags)
.await?
.unwrap_or_default();
let oauth2 = matches!(server_flags & DC_LP_AUTH_FLAGS, DC_LP_AUTH_OAUTH2);
let provider = context
.get_config(Config::ConfiguredProvider)
.await?
@@ -583,7 +572,6 @@ impl ConfiguredLoginParam {
smtp_password: send_pw,
certificate_checks,
provider,
oauth2,
}))
}
@@ -627,7 +615,6 @@ impl ConfiguredLoginParam {
smtp_password: json.smtp_password,
provider,
certificate_checks: json.certificate_checks,
oauth2: json.oauth2,
})
}
@@ -663,7 +650,6 @@ impl From<ConfiguredLoginParam> for ConfiguredLoginParamJson {
smtp_password: configured_login_param.smtp_password,
provider_id: configured_login_param.provider.map(|p| p.id.to_string()),
certificate_checks: configured_login_param.certificate_checks,
oauth2: configured_login_param.oauth2,
}
}
}
@@ -857,7 +843,7 @@ pub(crate) async fn add_pseudo_transport(context: &Context, addr: &str) -> Resul
(
addr,
serde_json::to_string(&EnteredLoginParam{addr: addr.to_string(), ..Default::default()})?,
format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic","oauth2":false}}"#)
format!(r#"{{"addr":"{addr}","imap":[],"imap_user":"","imap_password":"","smtp":[],"smtp_user":"","smtp_password":"","certificate_checks":"Automatic"}}"#)
),
)
.await?;

View File

@@ -49,14 +49,13 @@ async fn test_save_load_login_param() -> Result<()> {
smtp_password: "bar".to_string(),
provider: None,
certificate_checks: ConfiguredCertificateChecks::Strict,
oauth2: false,
};
param
.clone()
.save_to_transports_table(&t, &EnteredLoginParam::default(), time())
.await?;
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_folder":"Folder","imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict","oauth2":false}"#;
let expected_param = r#"{"addr":"alice@example.org","imap":[{"connection":{"host":"imap.example.com","port":123,"security":"Starttls"},"user":"alice"}],"imap_folder":"Folder","imap_user":"","imap_password":"foo","smtp":[{"connection":{"host":"smtp.example.com","port":456,"security":"Tls"},"user":"alice@example.org"}],"smtp_user":"","smtp_password":"bar","provider_id":null,"certificate_checks":"Strict"}"#;
assert_eq!(
t.sql
.query_get_value::<String>("SELECT configured_param FROM transports", ())
@@ -126,8 +125,6 @@ async fn test_posteo_alias() -> Result<()> {
t.set_config(Config::ConfiguredSendUser, Some(user)).await?;
t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
.await?;
t.set_config(Config::ConfiguredServerFlags, Some("0"))
.await?;
let param = ConfiguredLoginParam {
addr: "alice@posteo.at".to_string(),
@@ -174,7 +171,6 @@ async fn test_posteo_alias() -> Result<()> {
smtp_password: "foobarbaz".to_string(),
provider: get_provider_by_id("posteo"),
certificate_checks: ConfiguredCertificateChecks::Strict,
oauth2: false,
};
let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
@@ -213,8 +209,6 @@ async fn test_empty_server_list_legacy() -> Result<()> {
.await?; // Strict
t.set_config(Config::ConfiguredSendPw, Some("foobarbaz"))
.await?;
t.set_config(Config::ConfiguredServerFlags, Some("0"))
.await?;
let loaded = ConfiguredLoginParam::load_legacy(&t).await?.unwrap();
assert_eq!(loaded.provider, Some(*provider));
@@ -293,7 +287,6 @@ fn dummy_configured_login_param(
smtp_password: "foobarbaz".to_string(),
provider,
certificate_checks: ConfiguredCertificateChecks::Automatic,
oauth2: false,
}
}