diff --git a/Cargo.lock b/Cargo.lock index 3e7a6038c2..3ec2c787b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1412,7 +1412,6 @@ dependencies = [ "sdp", "serde", "serde_json", - "serde_urlencoded", "sha-1", "sha2", "shadowsocks", diff --git a/Cargo.toml b/Cargo.toml index 0ff27f9b27..da51986d43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index 223ec31cbe..ed96d50193 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -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 * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index abea9ebbaf..d3ff6f42eb 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -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() diff --git a/deltachat-jsonrpc/src/api/types/login_param.rs b/deltachat-jsonrpc/src/api/types/login_param.rs index 1112e6a39b..0d650a357b 100644 --- a/deltachat-jsonrpc/src/api/types/login_param.rs +++ b/deltachat-jsonrpc/src/api/types/login_param.rs @@ -66,10 +66,6 @@ pub struct EnteredLoginParam { /// invalid hostnames. /// Default: Automatic pub certificate_checks: Option, - - /// If true, login via OAUTH2 (not recommended anymore). - /// Default: false - pub oauth2: Option, } impl From for TransportListEntry { @@ -100,7 +96,6 @@ impl From 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 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(), }) } } diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index be29cfc138..a8e07e4f69 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -319,7 +319,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu info\n\ set []\n\ get \n\ - oauth2\n\ configure\n\ connect\n\ disconnect\n\ diff --git a/deltachat-repl/src/main.rs b/deltachat-repl/src/main.rs index 6977e05285..b082a79556 100644 --- a/deltachat-repl/src/main.rs +++ b/deltachat-repl/src/main.rs @@ -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"); diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/const.py b/deltachat-rpc-client/src/deltachat_rpc_client/const.py index e300416482..581cf56a4c 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/const.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/const.py @@ -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.""" diff --git a/scripts/create-provider-data-rs.py b/scripts/create-provider-data-rs.py index 62c25b1b91..e6ee793b29 100755 --- a/scripts/create-provider-data-rs.py +++ b/scripts/create-provider-data-rs.py @@ -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 @@ def process_dir(dir): "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" diff --git a/src/config.rs b/src/config.rs index b55be91c64..922c23b375 100644 --- a/src/config.rs +++ b/src/config.rs @@ -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, diff --git a/src/configure.rs b/src/configure.rs index df1a83c0f8..5c36a32dce 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -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, ¶m.addr, ¶m.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(¶m.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, - /// 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(()), diff --git a/src/context/context_tests.rs b/src/context/context_tests.rs index dc8d031f18..1fff54e085 100644 --- a/src/context/context_tests.rs +++ b/src/context/context_tests.rs @@ -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. diff --git a/src/imap.rs b/src/imap.rs index fd455e1933..ae774803ba 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -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, @@ -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 = ¶m.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)) => { diff --git a/src/imap/client.rs b/src/imap/client.rs index cb758f46d2..ac3980599f 100644 --- a/src/imap/client.rs +++ b/src/imap/client.rs @@ -122,19 +122,6 @@ impl Client { Ok((session, capabilities)) } - pub(crate) async fn authenticate( - self, - auth_type: &str, - authenticator: impl async_imap::Authenticator, - ) -> Result>> { - 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, diff --git a/src/lib.rs b/src/lib.rs index 8146ed5734..233a4a2f64 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/login_param.rs b/src/login_param.rs index 1b80c3fe34..f6f0b12233 100644 --- a/src/login_param.rs +++ b/src/login_param.rs @@ -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::(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!( diff --git a/src/net/http.rs b/src/net/http.rs index 1922be7a3f..af86dfa419 100644 --- a/src/net/http.rs +++ b/src/net/http.rs @@ -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( - context: &Context, - url: &str, - form: &T, -) -> Result { - let parsed_url = url - .parse::() - .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::*; diff --git a/src/oauth2.rs b/src/oauth2.rs deleted file mode 100644 index 26b061fea9..0000000000 --- a/src/oauth2.rs +++ /dev/null @@ -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 - 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: - // but previous code handled its abscense. - access_token: Option, - token_type: String, - /// Duration of time the token is granted for, in seconds - expires_in: Option, - refresh_token: Option, - scope: Option, -} - -/// 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> { - 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> { - 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 - - // 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> { - 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 { - 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> { - 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 = - 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 { - 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); - } -} diff --git a/src/provider.rs b/src/provider.rs index eb3a37eacd..3e4aef3295 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -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, - /// Options with good defaults. pub opt: ProviderOptions, } diff --git a/src/provider/data.rs b/src/provider/data.rs index bc9293f901..a4126ee3b7 100644 --- a/src/provider/data.rs +++ b/src/provider/data.rs @@ -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] = [ diff --git a/src/qr.rs b/src/qr.rs index 9a07249555..4059382753 100644 --- a/src/qr.rs +++ b/src/qr.rs @@ -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) diff --git a/src/qr/dclogin_scheme.rs b/src/qr/dclogin_scheme.rs index 5e179513cd..7459c66a71 100644 --- a/src/qr/dclogin_scheme.rs +++ b/src/qr/dclogin_scheme.rs @@ -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) } diff --git a/src/smtp.rs b/src/smtp.rs index 7f1bdf1dc8..95baae4acc 100644 --- a/src/smtp.rs +++ b/src/smtp.rs @@ -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, 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, ) diff --git a/src/smtp/connect.rs b/src/smtp/connect.rs index 28215a4cab..0d36df827d 100644 --- a/src/smtp/connect.rs +++ b/src/smtp/connect.rs @@ -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( Ok(transport) } -#[expect(clippy::too_many_arguments)] pub(crate) async fn connect_and_auth( context: &Context, proxy_config: &Option, strict_tls: bool, candidate: ConnectionCandidate, - oauth2: bool, - addr: &str, user: &str, password: &str, ) -> Result>> { @@ -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 diff --git a/src/sql.rs b/src/sql.rs index 0d59281ea5..987aefb0a7 100644 --- a/src/sql.rs +++ b/src/sql.rs @@ -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> { self.get_raw_config(key) diff --git a/src/test_utils.rs b/src/test_utils.rs index f5f245c8a3..d026325414 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -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(); diff --git a/src/transport.rs b/src/transport.rs index 97d5f5b02d..82603e67f2 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -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, 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::(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 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?; diff --git a/src/transport/transport_tests.rs b/src/transport/transport_tests.rs index b06cea9185..c02f9e3655 100644 --- a/src/transport/transport_tests.rs +++ b/src/transport/transport_tests.rs @@ -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::("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, } }