From 392d72a88a5d2822358fcab674889e16b4065c23 Mon Sep 17 00:00:00 2001 From: Maykel Moya Date: Thu, 4 Jun 2020 01:33:20 +0200 Subject: [PATCH] Add support for G Suite domains Do a lookup based on domain's MX servers. G Suite domains are expected to have at least 'aspmx.l.google.com' listed in MXs. See https://support.google.com/a/answer/140034 fixes #1425 --- Cargo.lock | 205 ++++++++++++++++++++++++++++++++++++++------------ Cargo.toml | 1 + src/oauth2.rs | 92 ++++++++++++++++++---- 3 files changed, 234 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e083f75892..1509b354ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,9 +11,9 @@ dependencies = [ [[package]] name = "adler32" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" +checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" [[package]] name = "aead" @@ -187,7 +187,7 @@ checksum = "2a397614997df8e3a8b81ff8d882c0fdc100568d3162fadbd1b1410f95c40277" dependencies = [ "async-native-tls", "async-std", - "base64 0.12.1", + "base64 0.12.2", "byte-pool", "chrono", "futures 0.3.5", @@ -215,17 +215,17 @@ dependencies = [ [[package]] name = "async-smtp" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81598a96675097ba5df18eec536a0c1d669244aeb4170f2dfa5ef12e1b2ee4b1" +checksum = "a9720181a7d56bf3b4d0cfdcb6353df975125996bdd2958b4e639c8317fcaa68" dependencies = [ "async-native-tls", "async-std", "async-trait", - "base64 0.12.1", + "base64 0.12.2", "bufstream", "fast_chemail", - "hostname", + "hostname 0.1.5", "log", "nom 5.1.2", "pin-project", @@ -261,6 +261,18 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-std-resolver" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d113439234775ae3e43d4e7589c5cc64fa3565996ae82dfe26b4ed78c02165be" +dependencies = [ + "async-std", + "async-trait", + "futures 0.3.5", + "trust-dns-resolver", +] + [[package]] name = "async-task" version = "3.0.0" @@ -303,13 +315,14 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.48" +version = "0.3.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" +checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" dependencies = [ "addr2line", "cfg-if", "libc", + "miniz_oxide", "object", "rustc-demangle", ] @@ -337,9 +350,9 @@ checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] name = "base64" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42" +checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67" [[package]] name = "bit-set" @@ -633,7 +646,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca761767cf3fa9068cc893ec8c247a22d0fd0535848e65640c0548bd1f8bbb36" dependencies = [ "aes-gcm", - "base64 0.12.1", + "base64 0.12.2", "hkdf", "hmac", "percent-encoding", @@ -809,9 +822,10 @@ dependencies = [ "async-native-tls", "async-smtp", "async-std", + "async-std-resolver", "async-trait", "backtrace", - "base64 0.12.1", + "base64 0.12.2", "bitflags", "byteorder", "charset", @@ -1093,6 +1107,18 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" +[[package]] +name = "enum-as-inner" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "env_logger" version = "0.7.1" @@ -1389,9 +1415,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" dependencies = [ "libc", ] @@ -1432,6 +1458,17 @@ dependencies = [ "winutil", ] +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + [[package]] name = "http-client" version = "3.0.0" @@ -1570,21 +1607,24 @@ dependencies = [ ] [[package]] -name = "inflate" -version = "0.4.5" +name = "iovec" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cdb29978cc5797bd8dcc8e5bf7de604891df2a8dc576973d71a281e916db2ff" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" dependencies = [ - "adler32", + "libc", ] [[package]] -name = "iovec" -version = "0.1.4" +name = "ipconfig" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" dependencies = [ - "libc", + "socket2", + "widestring", + "winapi", + "winreg", ] [[package]] @@ -1763,6 +1803,12 @@ dependencies = [ "quoted_printable", ] +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + [[package]] name = "matches" version = "0.1.8" @@ -1978,9 +2024,9 @@ dependencies = [ [[package]] name = "object" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" [[package]] name = "once_cell" @@ -2094,7 +2140,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b" dependencies = [ - "base64 0.12.1", + "base64 0.12.2", "once_cell", "regex", ] @@ -2112,7 +2158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d3fd4e0b2b7a8195b22e196a947c9ceb0d0f722d060e38e0641f48f56e195b" dependencies = [ "aes 0.4.0", - "base64 0.12.1", + "base64 0.12.2", "bitfield", "block-modes", "block-padding 0.2.0", @@ -2155,18 +2201,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75373ff9037d112bb19bc61333a06a159eaeb217660dcfbea7d88e1db823919" +checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b4b44893d3c370407a1d6a5cfde7c41ae0478e31c516c85f67eb3adc51be6d" +checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7" dependencies = [ "proc-macro2", "quote", @@ -2205,14 +2251,14 @@ checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" [[package]] name = "png" -version = "0.16.4" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12faa637ed9ae3d3c881332e54b5ae2dba81cda9fc4bbce0faa1ba53abcead50" +checksum = "34ccdd66f6fe4b2433b07e4728e9a013e43233120427046e93ceb709c3a439bf" dependencies = [ "bitflags", "crc32fast", "deflate", - "inflate", + "miniz_oxide", ] [[package]] @@ -2261,9 +2307,9 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro-nested" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0afe1bd463b9e9ed51d0e0f0b50b6b146aec855c56fd182bb242388710a9b6de" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" [[package]] name = "proc-macro2" @@ -2479,9 +2525,9 @@ checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" [[package]] name = "remove_dir_all" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ "winapi", ] @@ -2507,6 +2553,16 @@ dependencies = [ "syn", ] +[[package]] +name = "resolv-conf" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" +dependencies = [ + "hostname 0.3.1", + "quick-error", +] + [[package]] name = "ripemd160" version = "0.9.0" @@ -2721,18 +2777,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" [[package]] name = "serde" -version = "1.0.111" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" +checksum = "736aac72d1eafe8e5962d1d1c3d99b0df526015ba40915cb3c49d042e92ec243" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.111" +version = "1.0.112" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +checksum = "bf0343ce212ac0d3d6afd9391ac8e9c9efe06b533c8d33f660f6390cc4093f57" dependencies = [ "proc-macro2", "quote", @@ -2858,9 +2914,9 @@ checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" [[package]] name = "smol" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845f5e7db6b614a8f4f59e565c3035f0fab2f71c9537ff0119eddb60d866cae3" +checksum = "afcf8beb4aa23cc616e3351e49585153b462637c8fec929163b54d88e522b3b0" dependencies = [ "async-task", "crossbeam-deque", @@ -3098,18 +3154,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" dependencies = [ "proc-macro2", "quote", @@ -3193,6 +3249,44 @@ dependencies = [ "serde", ] +[[package]] +name = "trust-dns-proto" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures 0.3.5", + "idna", + "lazy_static", + "log", + "rand 0.7.3", + "smallvec", + "thiserror", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77" +dependencies = [ + "backtrace", + "cfg-if", + "futures 0.3.5", + "ipconfig", + "lazy_static", + "log", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "trust-dns-proto", +] + [[package]] name = "try_from" version = "0.3.2" @@ -3445,6 +3539,12 @@ dependencies = [ "cc", ] +[[package]] +name = "widestring" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c" + [[package]] name = "winapi" version = "0.3.8" @@ -3476,6 +3576,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winreg" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +dependencies = [ + "winapi", +] + [[package]] name = "winutil" version = "0.1.1" diff --git a/Cargo.toml b/Cargo.toml index cdfefa6eb3..6de4d662bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ thiserror = "1.0.14" anyhow = "1.0.28" async-trait = "0.1.31" url = "2.1.1" +async-std-resolver = "0.19.5" pretty_env_logger = { version = "0.4.0", optional = true } log = {version = "0.4.8", optional = true } diff --git a/src/oauth2.rs b/src/oauth2.rs index 3dd22874fd..25e7094a54 100644 --- a/src/oauth2.rs +++ b/src/oauth2.rs @@ -1,7 +1,9 @@ //! OAuth 2 module +use regex::Regex; use std::collections::HashMap; +use async_std_resolver::{config, resolver}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use serde::Deserialize; @@ -15,6 +17,7 @@ const OAUTH2_GMAIL: Oauth2 = Oauth2 { init_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code", refresh_token: "https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&refresh_token=$REFRESH_TOKEN&grant_type=refresh_token", get_userinfo: Some("https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=$ACCESS_TOKEN"), + mx_pattern: Some(r"^aspmx\.l\.google\.com\.$"), }; const OAUTH2_YANDEX: Oauth2 = Oauth2 { @@ -24,8 +27,11 @@ const OAUTH2_YANDEX: Oauth2 = Oauth2 { 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, + mx_pattern: None, }; +const OAUTH2_PROVIDERS: [Oauth2; 1] = [OAUTH2_GMAIL]; + #[derive(Debug, Clone, PartialEq, Eq)] struct Oauth2 { client_id: &'static str, @@ -33,6 +39,7 @@ struct Oauth2 { init_token: &'static str, refresh_token: &'static str, get_userinfo: Option<&'static str>, + mx_pattern: Option<&'static str>, } /// OAuth 2 Access Token Response @@ -53,7 +60,7 @@ pub async fn dc_get_oauth2_url( addr: impl AsRef, redirect_uri: impl AsRef, ) -> Option { - if let Some(oauth2) = Oauth2::from_address(addr) { + if let Some(oauth2) = Oauth2::from_address(addr).await { if context .sql .set_raw_config( @@ -81,7 +88,7 @@ pub async fn dc_get_oauth2_access_token( code: impl AsRef, regenerate: bool, ) -> Option { - if let Some(oauth2) = Oauth2::from_address(addr) { + if let Some(oauth2) = Oauth2::from_address(addr).await { let lock = context.oauth2_mutex.lock().await; // read generated token @@ -239,7 +246,7 @@ pub async fn dc_get_oauth2_addr( addr: impl AsRef, code: impl AsRef, ) -> Option { - let oauth2 = Oauth2::from_address(addr.as_ref())?; + let oauth2 = Oauth2::from_address(addr.as_ref()).await?; oauth2.get_userinfo?; if let Some(access_token) = @@ -263,23 +270,62 @@ pub async fn dc_get_oauth2_addr( } impl Oauth2 { - fn from_address(addr: impl AsRef) -> Option { + async fn from_address(addr: impl AsRef) -> Option { let addr_normalized = normalize_addr(addr.as_ref()); if let Some(domain) = addr_normalized .find('@') .map(|index| addr_normalized.split_at(index + 1).1) { - match domain { - "gmail.com" | "googlemail.com" => Some(OAUTH2_GMAIL), - "yandex.com" | "yandex.by" | "yandex.kz" | "yandex.ru" | "yandex.ua" | "ya.ru" - | "narod.ru" => Some(OAUTH2_YANDEX), - _ => None, + if let Some(provider) = Oauth2::lookup_whitelist(domain) { + Some(provider) + } else { + Oauth2::lookup_mx(domain).await } } else { None } } + fn lookup_whitelist(domain: impl AsRef) -> Option { + let domain = domain.as_ref(); + match domain { + "gmail.com" | "googlemail.com" => Some(OAUTH2_GMAIL), + "yandex.com" | "yandex.by" | "yandex.kz" | "yandex.ru" | "yandex.ua" | "ya.ru" + | "narod.ru" => Some(OAUTH2_YANDEX), + _ => None, + } + } + + async fn lookup_mx(domain: impl AsRef) -> Option { + if let Ok(resolver) = resolver( + config::ResolverConfig::default(), + config::ResolverOpts::default(), + ) + .await + { + for provider in OAUTH2_PROVIDERS.iter() { + if let Some(pattern) = provider.mx_pattern { + let re = Regex::new(pattern).unwrap(); + + let mut fqdn: String = String::from(domain.as_ref()); + if !fqdn.ends_with('.') { + fqdn.push_str("."); + } + + if let Ok(res) = resolver.mx_lookup(fqdn).await { + for rr in res.iter() { + if re.is_match(&rr.exchange().to_lowercase().to_utf8()) { + return Some(provider.clone()); + } + } + } + } + } + } + + None + } + async fn get_addr(&self, context: &Context, access_token: impl AsRef) -> Option { let userinfo_url = self.get_userinfo.unwrap_or_else(|| ""); let userinfo_url = replace_in_uri(&userinfo_url, "$ACCESS_TOKEN", access_token); @@ -362,20 +408,34 @@ mod tests { ); } - #[test] - fn test_oauth_from_address() { - assert_eq!(Oauth2::from_address("hello@gmail.com"), Some(OAUTH2_GMAIL)); + #[async_std::test] + async fn test_oauth_from_address() { + assert_eq!( + Oauth2::from_address("hello@gmail.com").await, + Some(OAUTH2_GMAIL) + ); assert_eq!( - Oauth2::from_address("hello@googlemail.com"), + Oauth2::from_address("hello@googlemail.com").await, Some(OAUTH2_GMAIL) ); assert_eq!( - Oauth2::from_address("hello@yandex.com"), + Oauth2::from_address("hello@yandex.com").await, + Some(OAUTH2_YANDEX) + ); + assert_eq!( + Oauth2::from_address("hello@yandex.ru").await, Some(OAUTH2_YANDEX) ); - assert_eq!(Oauth2::from_address("hello@yandex.ru"), Some(OAUTH2_YANDEX)); - assert_eq!(Oauth2::from_address("hello@web.de"), None); + assert_eq!(Oauth2::from_address("hello@web.de").await, None); + } + + #[async_std::test] + async fn test_oauth_from_mx() { + assert_eq!( + Oauth2::from_address("hello@google.com").await, + Some(OAUTH2_GMAIL) + ); } #[async_std::test]