mirror of
https://github.com/chatmail/core.git
synced 2026-04-26 09:56:35 +03:00
refactor(oauth): safe oauth2 and remove custom json parser (#80)
* refactor(oauth): safe oauth2 and remove custom json parser Closes #46,#53
This commit is contained in:
committed by
GitHub
parent
94aa314f30
commit
a247e5b143
@@ -23,11 +23,14 @@ native-tls = "0.2.3"
|
||||
lettre = "0.9.0"
|
||||
imap = "1.0.1"
|
||||
mmime = { git = "https://github.com/dignifiedquire/mmime" }
|
||||
base64 = "0.10.1"
|
||||
charset = "0.1.2"
|
||||
base64 = "0.10"
|
||||
charset = "0.1"
|
||||
percent-encoding = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.0.7"
|
||||
tempfile = "3.0"
|
||||
|
||||
[workspace]
|
||||
members = [
|
||||
|
||||
@@ -123,9 +123,14 @@ pub unsafe extern "C" fn dc_get_oauth2_url(
|
||||
redirect: *mut libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
assert!(!context.is_null());
|
||||
let context = &*context;
|
||||
|
||||
dc_oauth2::dc_get_oauth2_url(context, addr, redirect)
|
||||
let context = &*context;
|
||||
let addr = dc_tools::to_string(addr);
|
||||
let redirect = dc_tools::to_string(redirect);
|
||||
match oauth2::dc_get_oauth2_url(context, addr, redirect) {
|
||||
Some(res) => libc::strdup(dc_tools::to_cstring(res).as_ptr()),
|
||||
None => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
|
||||
@@ -16,7 +16,6 @@ use deltachat::dc_imap::*;
|
||||
use deltachat::dc_imex::*;
|
||||
use deltachat::dc_job::*;
|
||||
use deltachat::dc_jobthread::*;
|
||||
use deltachat::dc_jsmn::*;
|
||||
use deltachat::dc_key::*;
|
||||
use deltachat::dc_keyhistory::*;
|
||||
use deltachat::dc_keyring::*;
|
||||
@@ -28,7 +27,6 @@ use deltachat::dc_mimefactory::*;
|
||||
use deltachat::dc_mimeparser::*;
|
||||
use deltachat::dc_move::*;
|
||||
use deltachat::dc_msg::*;
|
||||
use deltachat::dc_oauth2::*;
|
||||
use deltachat::dc_param::*;
|
||||
use deltachat::dc_pgp::*;
|
||||
use deltachat::dc_qr::*;
|
||||
@@ -43,6 +41,7 @@ use deltachat::dc_strbuilder::*;
|
||||
use deltachat::dc_strencode::*;
|
||||
use deltachat::dc_token::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
|
||||
@@ -35,7 +35,6 @@ use deltachat::dc_imap::*;
|
||||
use deltachat::dc_imex::*;
|
||||
use deltachat::dc_job::*;
|
||||
use deltachat::dc_jobthread::*;
|
||||
use deltachat::dc_jsmn::*;
|
||||
use deltachat::dc_key::*;
|
||||
use deltachat::dc_keyhistory::*;
|
||||
use deltachat::dc_keyring::*;
|
||||
@@ -47,7 +46,6 @@ use deltachat::dc_mimefactory::*;
|
||||
use deltachat::dc_mimeparser::*;
|
||||
use deltachat::dc_move::*;
|
||||
use deltachat::dc_msg::*;
|
||||
use deltachat::dc_oauth2::*;
|
||||
use deltachat::dc_param::*;
|
||||
use deltachat::dc_pgp::*;
|
||||
use deltachat::dc_qr::*;
|
||||
@@ -62,6 +60,7 @@ use deltachat::dc_strbuilder::*;
|
||||
use deltachat::dc_strencode::*;
|
||||
use deltachat::dc_token::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::peerstate::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
@@ -427,22 +426,19 @@ unsafe fn main_0(argc: libc::c_int, argv: *mut *mut libc::c_char) -> libc::c_int
|
||||
if addr.is_null() || *addr.offset(0isize) as libc::c_int == 0i32 {
|
||||
printf(b"oauth2: set addr first.\n\x00" as *const u8 as *const libc::c_char);
|
||||
} else {
|
||||
let oauth2_url: *mut libc::c_char = dc_get_oauth2_url(
|
||||
let oauth2_url = dc_get_oauth2_url(
|
||||
&ctx.read().unwrap(),
|
||||
addr,
|
||||
b"chat.delta:/com.b44t.messenger\x00" as *const u8 as *const libc::c_char,
|
||||
to_str(addr),
|
||||
"chat.delta:/com.b44t.messenger",
|
||||
);
|
||||
if oauth2_url.is_null() {
|
||||
if oauth2_url.is_none() {
|
||||
printf(
|
||||
b"OAuth2 not available for %s.\n\x00" as *const u8 as *const libc::c_char,
|
||||
addr,
|
||||
);
|
||||
} else {
|
||||
printf(b"Open the following url, set mail_pw to the generated token and server_flags to 2:\n%s\n\x00"
|
||||
as *const u8 as *const libc::c_char,
|
||||
oauth2_url);
|
||||
println!("Open the following url, set mail_pw to the generated token and server_flags to 2:\n{}", oauth2_url.unwrap());
|
||||
}
|
||||
free(oauth2_url as *mut libc::c_void);
|
||||
}
|
||||
free(addr as *mut libc::c_void);
|
||||
} else if strcmp(cmd, b"clear\x00" as *const u8 as *const libc::c_char) == 0i32 {
|
||||
|
||||
@@ -5,11 +5,11 @@ use crate::dc_imap::*;
|
||||
use crate::dc_job::*;
|
||||
use crate::dc_log::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_oauth2::*;
|
||||
use crate::dc_saxparser::*;
|
||||
use crate::dc_sqlite3::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::oauth2::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
@@ -205,11 +205,14 @@ pub unsafe fn dc_job_do_DC_JOB_CONFIGURE_IMAP(context: &dc_context_t, _job: *mut
|
||||
}) as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
);
|
||||
let oauth2_addr: *mut libc::c_char =
|
||||
dc_get_oauth2_addr(context, (*param).addr, (*param).mail_pw);
|
||||
if !oauth2_addr.is_null() {
|
||||
let oauth2_addr = dc_get_oauth2_addr(
|
||||
context,
|
||||
to_str((*param).addr),
|
||||
to_str((*param).mail_pw),
|
||||
);
|
||||
if oauth2_addr.is_some() {
|
||||
free((*param).addr as *mut libc::c_void);
|
||||
(*param).addr = oauth2_addr;
|
||||
(*param).addr = strdup(to_cstring(oauth2_addr.unwrap()).as_ptr());
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
|
||||
@@ -839,7 +839,7 @@ pub unsafe fn dc_get_contact_encrinfo(
|
||||
.map(|k| k.formatted_fingerprint_c())
|
||||
.unwrap_or(std::ptr::null_mut());
|
||||
if peerstate.addr.is_some()
|
||||
&& to_str((*loginparam).addr) < peerstate.addr.as_ref().unwrap()
|
||||
&& to_str((*loginparam).addr) < peerstate.addr.as_ref().unwrap().as_str()
|
||||
{
|
||||
cat_fingerprint(
|
||||
&mut ret,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CString;
|
||||
use std::net;
|
||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -7,9 +7,9 @@ use crate::constants::*;
|
||||
use crate::dc_context::dc_context_t;
|
||||
use crate::dc_log::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_oauth2::dc_get_oauth2_access_token;
|
||||
use crate::dc_sqlite3::*;
|
||||
use crate::dc_tools::{to_str, to_string};
|
||||
use crate::oauth2::dc_get_oauth2_access_token;
|
||||
use crate::types::*;
|
||||
|
||||
pub const DC_IMAP_SEEN: usize = 0x0001;
|
||||
@@ -413,22 +413,17 @@ impl Imap {
|
||||
if (server_flags & DC_LP_AUTH_OAUTH2) != 0 {
|
||||
let addr: &str = config.addr.as_ref();
|
||||
|
||||
let access_token = unsafe {
|
||||
CStr::from_ptr(dc_get_oauth2_access_token(
|
||||
context,
|
||||
CString::new(addr).unwrap().as_ptr(),
|
||||
CString::new(imap_pw).unwrap().as_ptr(),
|
||||
DC_REGENERATE as libc::c_int,
|
||||
))
|
||||
.to_str()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let auth = OAuth2 {
|
||||
user: imap_user.into(),
|
||||
access_token: access_token.into(),
|
||||
};
|
||||
client.authenticate("XOAUTH2", &auth)
|
||||
if let Some(token) =
|
||||
dc_get_oauth2_access_token(context, addr, imap_pw, DC_REGENERATE as usize)
|
||||
{
|
||||
let auth = OAuth2 {
|
||||
user: imap_user.into(),
|
||||
access_token: token,
|
||||
};
|
||||
client.authenticate("XOAUTH2", &auth)
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
client.login(imap_user, imap_pw)
|
||||
}
|
||||
|
||||
405
src/dc_jsmn.rs
405
src/dc_jsmn.rs
@@ -1,405 +0,0 @@
|
||||
use crate::types::*;
|
||||
|
||||
/*
|
||||
Copyright (c) 2010 Serge A. Zaitsev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
/* *
|
||||
* JSON type identifier. Basic types are:
|
||||
* o Object
|
||||
* o Array
|
||||
* o String
|
||||
* o Other primitive: number, boolean (true/false) or null
|
||||
*/
|
||||
pub type jsmntype_t = libc::c_uint;
|
||||
pub const JSMN_PRIMITIVE: jsmntype_t = 4;
|
||||
pub const JSMN_STRING: jsmntype_t = 3;
|
||||
pub const JSMN_ARRAY: jsmntype_t = 2;
|
||||
pub const JSMN_OBJECT: jsmntype_t = 1;
|
||||
pub const JSMN_UNDEFINED: jsmntype_t = 0;
|
||||
pub type jsmnerr = libc::c_int;
|
||||
/* The string is not a full JSON packet, more bytes expected */
|
||||
pub const JSMN_ERROR_PART: jsmnerr = -3;
|
||||
/* Invalid character inside JSON string */
|
||||
pub const JSMN_ERROR_INVAL: jsmnerr = -2;
|
||||
/* Not enough tokens were provided */
|
||||
pub const JSMN_ERROR_NOMEM: jsmnerr = -1;
|
||||
/* *
|
||||
* JSON token description.
|
||||
* type type (object, array, string etc.)
|
||||
* start start position in JSON data string
|
||||
* end end position in JSON data string
|
||||
*/
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct jsmntok_t {
|
||||
pub type_0: jsmntype_t,
|
||||
pub start: libc::c_int,
|
||||
pub end: libc::c_int,
|
||||
pub size: libc::c_int,
|
||||
}
|
||||
/* *
|
||||
* JSON parser. Contains an array of token blocks available. Also stores
|
||||
* the string being parsed now and current position in that string
|
||||
*/
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct jsmn_parser {
|
||||
pub pos: libc::c_uint,
|
||||
pub toknext: libc::c_uint,
|
||||
pub toksuper: libc::c_int,
|
||||
}
|
||||
|
||||
/* *
|
||||
* Create JSON parser over an array of tokens
|
||||
*/
|
||||
pub unsafe fn jsmn_init(mut parser: *mut jsmn_parser) {
|
||||
(*parser).pos = 0i32 as libc::c_uint;
|
||||
(*parser).toknext = 0i32 as libc::c_uint;
|
||||
(*parser).toksuper = -1i32;
|
||||
}
|
||||
|
||||
/* *
|
||||
* Run JSON parser. It parses a JSON data string into and array of tokens, each describing
|
||||
* a single JSON object.
|
||||
*/
|
||||
pub unsafe fn jsmn_parse(
|
||||
mut parser: *mut jsmn_parser,
|
||||
js: *const libc::c_char,
|
||||
len: size_t,
|
||||
tokens: *mut jsmntok_t,
|
||||
num_tokens: libc::c_uint,
|
||||
) -> libc::c_int {
|
||||
let mut r: libc::c_int;
|
||||
let mut i: libc::c_int;
|
||||
let mut token: *mut jsmntok_t;
|
||||
let mut count: libc::c_int = (*parser).toknext as libc::c_int;
|
||||
while (*parser).pos < len as libc::c_uint
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
||||
{
|
||||
let c: libc::c_char;
|
||||
let type_0: jsmntype_t;
|
||||
c = *js.offset((*parser).pos as isize);
|
||||
match c as libc::c_int {
|
||||
123 | 91 => {
|
||||
count += 1;
|
||||
if !tokens.is_null() {
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens as size_t);
|
||||
if token.is_null() {
|
||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
||||
}
|
||||
if (*parser).toksuper != -1i32 {
|
||||
let ref mut fresh0 = (*tokens.offset((*parser).toksuper as isize)).size;
|
||||
*fresh0 += 1
|
||||
}
|
||||
(*token).type_0 = (if c as libc::c_int == '{' as i32 {
|
||||
JSMN_OBJECT as libc::c_int
|
||||
} else {
|
||||
JSMN_ARRAY as libc::c_int
|
||||
}) as jsmntype_t;
|
||||
(*token).start = (*parser).pos as libc::c_int;
|
||||
(*parser).toksuper =
|
||||
(*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int
|
||||
}
|
||||
}
|
||||
125 | 93 => {
|
||||
if !tokens.is_null() {
|
||||
type_0 = (if c as libc::c_int == '}' as i32 {
|
||||
JSMN_OBJECT as libc::c_int
|
||||
} else {
|
||||
JSMN_ARRAY as libc::c_int
|
||||
}) as jsmntype_t;
|
||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
||||
while i >= 0i32 {
|
||||
token = &mut *tokens.offset(i as isize) as *mut jsmntok_t;
|
||||
if (*token).start != -1i32 && (*token).end == -1i32 {
|
||||
if (*token).type_0 as libc::c_uint != type_0 as libc::c_uint {
|
||||
return JSMN_ERROR_INVAL as libc::c_int;
|
||||
}
|
||||
(*parser).toksuper = -1i32;
|
||||
(*token).end =
|
||||
(*parser).pos.wrapping_add(1i32 as libc::c_uint) as libc::c_int;
|
||||
break;
|
||||
} else {
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
if i == -1i32 {
|
||||
return JSMN_ERROR_INVAL as libc::c_int;
|
||||
}
|
||||
while i >= 0i32 {
|
||||
token = &mut *tokens.offset(i as isize) as *mut jsmntok_t;
|
||||
if (*token).start != -1i32 && (*token).end == -1i32 {
|
||||
(*parser).toksuper = i;
|
||||
break;
|
||||
} else {
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34 => {
|
||||
r = jsmn_parse_string(parser, js, len, tokens, num_tokens as size_t);
|
||||
if r < 0i32 {
|
||||
return r;
|
||||
}
|
||||
count += 1;
|
||||
if (*parser).toksuper != -1i32 && !tokens.is_null() {
|
||||
let ref mut fresh1 = (*tokens.offset((*parser).toksuper as isize)).size;
|
||||
*fresh1 += 1
|
||||
}
|
||||
}
|
||||
9 | 13 | 10 | 32 => {}
|
||||
58 => {
|
||||
(*parser).toksuper =
|
||||
(*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int
|
||||
}
|
||||
44 => {
|
||||
if !tokens.is_null()
|
||||
&& (*parser).toksuper != -1i32
|
||||
&& (*tokens.offset((*parser).toksuper as isize)).type_0 as libc::c_uint
|
||||
!= JSMN_ARRAY as libc::c_int as libc::c_uint
|
||||
&& (*tokens.offset((*parser).toksuper as isize)).type_0 as libc::c_uint
|
||||
!= JSMN_OBJECT as libc::c_int as libc::c_uint
|
||||
{
|
||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
||||
while i >= 0i32 {
|
||||
if (*tokens.offset(i as isize)).type_0 as libc::c_uint
|
||||
== JSMN_ARRAY as libc::c_int as libc::c_uint
|
||||
|| (*tokens.offset(i as isize)).type_0 as libc::c_uint
|
||||
== JSMN_OBJECT as libc::c_int as libc::c_uint
|
||||
{
|
||||
if (*tokens.offset(i as isize)).start != -1i32
|
||||
&& (*tokens.offset(i as isize)).end == -1i32
|
||||
{
|
||||
(*parser).toksuper = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens as size_t);
|
||||
if r < 0i32 {
|
||||
return r;
|
||||
}
|
||||
count += 1;
|
||||
if (*parser).toksuper != -1i32 && !tokens.is_null() {
|
||||
let ref mut fresh2 = (*tokens.offset((*parser).toksuper as isize)).size;
|
||||
*fresh2 += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
||||
}
|
||||
if !tokens.is_null() {
|
||||
i = (*parser).toknext.wrapping_sub(1i32 as libc::c_uint) as libc::c_int;
|
||||
while i >= 0i32 {
|
||||
if (*tokens.offset(i as isize)).start != -1i32
|
||||
&& (*tokens.offset(i as isize)).end == -1i32
|
||||
{
|
||||
return JSMN_ERROR_PART as libc::c_int;
|
||||
}
|
||||
i -= 1
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
/* *
|
||||
* Fills next available token with JSON primitive.
|
||||
*/
|
||||
unsafe fn jsmn_parse_primitive(
|
||||
mut parser: *mut jsmn_parser,
|
||||
js: *const libc::c_char,
|
||||
len: size_t,
|
||||
tokens: *mut jsmntok_t,
|
||||
num_tokens: size_t,
|
||||
) -> libc::c_int {
|
||||
let token: *mut jsmntok_t;
|
||||
let start: libc::c_int;
|
||||
start = (*parser).pos as libc::c_int;
|
||||
while (*parser).pos < len as libc::c_uint
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
||||
{
|
||||
match *js.offset((*parser).pos as isize) as libc::c_int {
|
||||
58 | 9 | 13 | 10 | 32 | 44 | 93 | 125 => {
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if (*js.offset((*parser).pos as isize) as libc::c_int) < 32i32
|
||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 127i32
|
||||
{
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
return JSMN_ERROR_INVAL as libc::c_int;
|
||||
}
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
||||
}
|
||||
if tokens.is_null() {
|
||||
(*parser).pos = (*parser).pos.wrapping_sub(1);
|
||||
return 0i32;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if token.is_null() {
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
||||
}
|
||||
jsmn_fill_token(token, JSMN_PRIMITIVE, start, (*parser).pos as libc::c_int);
|
||||
(*parser).pos = (*parser).pos.wrapping_sub(1);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
/* *
|
||||
* Fills token type and boundaries.
|
||||
*/
|
||||
unsafe fn jsmn_fill_token(
|
||||
mut token: *mut jsmntok_t,
|
||||
type_0: jsmntype_t,
|
||||
start: libc::c_int,
|
||||
end: libc::c_int,
|
||||
) {
|
||||
(*token).type_0 = type_0;
|
||||
(*token).start = start;
|
||||
(*token).end = end;
|
||||
(*token).size = 0i32;
|
||||
}
|
||||
|
||||
/*
|
||||
Copyright (c) 2010 Serge A. Zaitsev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* *
|
||||
* Allocates a fresh unused token from the token pool.
|
||||
*/
|
||||
unsafe fn jsmn_alloc_token(
|
||||
mut parser: *mut jsmn_parser,
|
||||
tokens: *mut jsmntok_t,
|
||||
num_tokens: size_t,
|
||||
) -> *mut jsmntok_t {
|
||||
let mut tok: *mut jsmntok_t;
|
||||
if (*parser).toknext as size_t >= num_tokens {
|
||||
return 0 as *mut jsmntok_t;
|
||||
}
|
||||
let fresh3 = (*parser).toknext;
|
||||
(*parser).toknext = (*parser).toknext.wrapping_add(1);
|
||||
tok = &mut *tokens.offset(fresh3 as isize) as *mut jsmntok_t;
|
||||
(*tok).end = -1i32;
|
||||
(*tok).start = (*tok).end;
|
||||
(*tok).size = 0i32;
|
||||
|
||||
tok
|
||||
}
|
||||
|
||||
/* *
|
||||
* Fills next token with JSON string.
|
||||
*/
|
||||
unsafe fn jsmn_parse_string(
|
||||
mut parser: *mut jsmn_parser,
|
||||
js: *const libc::c_char,
|
||||
len: size_t,
|
||||
tokens: *mut jsmntok_t,
|
||||
num_tokens: size_t,
|
||||
) -> libc::c_int {
|
||||
let token: *mut jsmntok_t;
|
||||
let start: libc::c_int = (*parser).pos as libc::c_int;
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
||||
while ((*parser).pos as size_t) < len
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
||||
{
|
||||
let c: libc::c_char = *js.offset((*parser).pos as isize);
|
||||
if c as libc::c_int == '\"' as i32 {
|
||||
if tokens.is_null() {
|
||||
return 0i32;
|
||||
}
|
||||
token = jsmn_alloc_token(parser, tokens, num_tokens);
|
||||
if token.is_null() {
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
return JSMN_ERROR_NOMEM as libc::c_int;
|
||||
}
|
||||
jsmn_fill_token(
|
||||
token,
|
||||
JSMN_STRING,
|
||||
start + 1i32,
|
||||
(*parser).pos as libc::c_int,
|
||||
);
|
||||
return 0i32;
|
||||
}
|
||||
if c as libc::c_int == '\\' as i32 && ((*parser).pos.wrapping_add(1) as size_t) < len {
|
||||
let mut i: libc::c_int;
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
||||
match *js.offset((*parser).pos as isize) as libc::c_int {
|
||||
34 | 47 | 92 | 98 | 102 | 114 | 110 | 116 => {}
|
||||
117 => {
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
||||
i = 0i32;
|
||||
while i < 4i32
|
||||
&& ((*parser).pos as size_t) < len
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int != '\u{0}' as i32
|
||||
{
|
||||
if !(*js.offset((*parser).pos as isize) as libc::c_int >= 48i32
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 57i32
|
||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 65i32
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 70i32
|
||||
|| *js.offset((*parser).pos as isize) as libc::c_int >= 97i32
|
||||
&& *js.offset((*parser).pos as isize) as libc::c_int <= 102i32)
|
||||
{
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
return JSMN_ERROR_INVAL as libc::c_int;
|
||||
}
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1);
|
||||
i += 1
|
||||
}
|
||||
(*parser).pos = (*parser).pos.wrapping_sub(1)
|
||||
}
|
||||
_ => {
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
return JSMN_ERROR_INVAL as libc::c_int;
|
||||
}
|
||||
}
|
||||
}
|
||||
(*parser).pos = (*parser).pos.wrapping_add(1)
|
||||
}
|
||||
(*parser).pos = start as libc::c_uint;
|
||||
|
||||
JSMN_ERROR_PART as libc::c_int
|
||||
}
|
||||
621
src/dc_oauth2.rs
621
src/dc_oauth2.rs
@@ -1,621 +0,0 @@
|
||||
use crate::constants::Event;
|
||||
use crate::dc_contact::*;
|
||||
use crate::dc_context::dc_context_t;
|
||||
use crate::dc_jsmn::*;
|
||||
use crate::dc_log::*;
|
||||
use crate::dc_sqlite3::*;
|
||||
use crate::dc_strencode::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(C)]
|
||||
pub struct oauth2_t {
|
||||
pub client_id: *mut libc::c_char,
|
||||
pub get_code: *mut libc::c_char,
|
||||
pub init_token: *mut libc::c_char,
|
||||
pub refresh_token: *mut libc::c_char,
|
||||
pub get_userinfo: *mut libc::c_char,
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_oauth2_url(
|
||||
context: &dc_context_t,
|
||||
addr: *const libc::c_char,
|
||||
redirect_uri: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
||||
let mut oauth2_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
if !(redirect_uri.is_null() || *redirect_uri.offset(0isize) as libc::c_int == 0i32) {
|
||||
oauth2 = get_info(addr);
|
||||
if !oauth2.is_null() {
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_pending_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
||||
redirect_uri,
|
||||
);
|
||||
oauth2_url = dc_strdup((*oauth2).get_code);
|
||||
replace_in_uri(
|
||||
&mut oauth2_url,
|
||||
b"$CLIENT_ID\x00" as *const u8 as *const libc::c_char,
|
||||
(*oauth2).client_id,
|
||||
);
|
||||
replace_in_uri(
|
||||
&mut oauth2_url,
|
||||
b"$REDIRECT_URI\x00" as *const u8 as *const libc::c_char,
|
||||
redirect_uri,
|
||||
);
|
||||
}
|
||||
}
|
||||
free(oauth2 as *mut libc::c_void);
|
||||
|
||||
oauth2_url
|
||||
}
|
||||
|
||||
unsafe fn replace_in_uri(
|
||||
uri: *mut *mut libc::c_char,
|
||||
key: *const libc::c_char,
|
||||
value: *const libc::c_char,
|
||||
) {
|
||||
if !uri.is_null() && !key.is_null() && !value.is_null() {
|
||||
let value_urlencoded: *mut libc::c_char = dc_urlencode(value);
|
||||
dc_str_replace(uri, key, value_urlencoded);
|
||||
free(value_urlencoded as *mut libc::c_void);
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn get_info(addr: *const libc::c_char) -> *mut oauth2_t {
|
||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
||||
let addr_normalized: *mut libc::c_char;
|
||||
let mut domain: *const libc::c_char;
|
||||
addr_normalized = dc_addr_normalize(addr);
|
||||
domain = strchr(addr_normalized, '@' as i32);
|
||||
if !(domain.is_null() || *domain.offset(0isize) as libc::c_int == 0i32) {
|
||||
domain = domain.offset(1isize);
|
||||
if strcasecmp(domain, b"gmail.com\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcasecmp(
|
||||
domain,
|
||||
b"googlemail.com\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
oauth2 = calloc(1, ::std::mem::size_of::<oauth2_t>()) as *mut oauth2_t;
|
||||
(*oauth2).client_id =
|
||||
b"959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).get_code =
|
||||
b"https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).init_token =
|
||||
b"https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&code=$CODE&grant_type=authorization_code\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).refresh_token =
|
||||
b"https://accounts.google.com/o/oauth2/token?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&refresh_token=$REFRESH_TOKEN&grant_type=refresh_token\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).get_userinfo =
|
||||
b"https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=$ACCESS_TOKEN\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char
|
||||
} else if strcasecmp(
|
||||
domain,
|
||||
b"yandex.com\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
|| strcasecmp(domain, b"yandex.ru\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
|| strcasecmp(domain, b"yandex.ua\x00" as *const u8 as *const libc::c_char) == 0i32
|
||||
{
|
||||
oauth2 = calloc(1, ::std::mem::size_of::<oauth2_t>()) as *mut oauth2_t;
|
||||
(*oauth2).client_id = b"c4d0b6735fc8420a816d7e1303469341\x00" as *const u8
|
||||
as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).get_code =
|
||||
b"https://oauth.yandex.com/authorize?client_id=$CLIENT_ID&response_type=code&scope=mail%3Aimap_full%20mail%3Asmtp&force_confirm=true\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).init_token =
|
||||
b"https://oauth.yandex.com/token?grant_type=authorization_code&code=$CODE&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char;
|
||||
(*oauth2).refresh_token =
|
||||
b"https://oauth.yandex.com/token?grant_type=refresh_token&refresh_token=$REFRESH_TOKEN&client_id=$CLIENT_ID&client_secret=58b8c6e94cf44fbe952da8511955dacf\x00"
|
||||
as *const u8 as *const libc::c_char as *mut libc::c_char
|
||||
}
|
||||
}
|
||||
free(addr_normalized as *mut libc::c_void);
|
||||
|
||||
oauth2
|
||||
}
|
||||
|
||||
// the following function may block due http-requests;
|
||||
// must not be called from the main thread or by the ui!
|
||||
pub unsafe fn dc_get_oauth2_access_token(
|
||||
context: &dc_context_t,
|
||||
addr: *const libc::c_char,
|
||||
code: *const libc::c_char,
|
||||
flags: libc::c_int,
|
||||
) -> *mut libc::c_char {
|
||||
let current_block: u64;
|
||||
let mut oauth2: *mut oauth2_t = 0 as *mut oauth2_t;
|
||||
let mut access_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut refresh_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut refresh_token_for: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut redirect_uri: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut update_redirect_uri_on_success: libc::c_int = 0i32;
|
||||
let mut token_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut expires_in: time_t = 0i32 as time_t;
|
||||
let mut error: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut error_description: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut json: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut parser: jsmn_parser = jsmn_parser {
|
||||
pos: 0,
|
||||
toknext: 0,
|
||||
toksuper: 0,
|
||||
};
|
||||
// we do not expect nor read more tokens
|
||||
let mut tok: [jsmntok_t; 128] = [jsmntok_t {
|
||||
type_0: JSMN_UNDEFINED,
|
||||
start: 0,
|
||||
end: 0,
|
||||
size: 0,
|
||||
}; 128];
|
||||
let tok_cnt: libc::c_int;
|
||||
if code.is_null() || *code.offset(0isize) as libc::c_int == 0i32 {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Internal OAuth2 error\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
} else {
|
||||
oauth2 = get_info(addr);
|
||||
if oauth2.is_null() {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Internal OAuth2 error: 2\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
} else {
|
||||
let lock = context.oauth2_critical.clone();
|
||||
|
||||
let l = lock.lock().unwrap();
|
||||
// read generated token
|
||||
if 0 == flags & 0x1i32 && !is_expired(context) {
|
||||
access_token = dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_access_token\x00" as *const u8 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
if !access_token.is_null() {
|
||||
// success
|
||||
current_block = 16914036240511706173;
|
||||
} else {
|
||||
current_block = 2838571290723028321;
|
||||
}
|
||||
} else {
|
||||
current_block = 2838571290723028321;
|
||||
}
|
||||
match current_block {
|
||||
16914036240511706173 => {}
|
||||
_ => {
|
||||
refresh_token = dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_refresh_token\x00" as *const u8 as *const libc::c_char,
|
||||
0 as *const libc::c_char,
|
||||
);
|
||||
refresh_token_for = dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_refresh_token_for\x00" as *const u8 as *const libc::c_char,
|
||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
if refresh_token.is_null() || strcmp(refresh_token_for, code) != 0i32 {
|
||||
dc_log_info(
|
||||
context,
|
||||
0i32,
|
||||
b"Generate OAuth2 refresh_token and access_token...\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
redirect_uri = dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_pending_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
update_redirect_uri_on_success = 1i32;
|
||||
token_url = dc_strdup((*oauth2).init_token)
|
||||
} else {
|
||||
dc_log_info(
|
||||
context,
|
||||
0i32,
|
||||
b"Regenerate OAuth2 access_token by refresh_token...\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
redirect_uri = dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_redirect_uri\x00" as *const u8 as *const libc::c_char,
|
||||
b"unset\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
token_url = dc_strdup((*oauth2).refresh_token)
|
||||
}
|
||||
replace_in_uri(
|
||||
&mut token_url,
|
||||
b"$CLIENT_ID\x00" as *const u8 as *const libc::c_char,
|
||||
(*oauth2).client_id,
|
||||
);
|
||||
replace_in_uri(
|
||||
&mut token_url,
|
||||
b"$REDIRECT_URI\x00" as *const u8 as *const libc::c_char,
|
||||
redirect_uri,
|
||||
);
|
||||
replace_in_uri(
|
||||
&mut token_url,
|
||||
b"$CODE\x00" as *const u8 as *const libc::c_char,
|
||||
code,
|
||||
);
|
||||
replace_in_uri(
|
||||
&mut token_url,
|
||||
b"$REFRESH_TOKEN\x00" as *const u8 as *const libc::c_char,
|
||||
refresh_token,
|
||||
);
|
||||
json = (context.cb)(
|
||||
context,
|
||||
Event::HTTP_POST,
|
||||
token_url as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
) as *mut libc::c_char;
|
||||
if json.is_null() {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Error calling OAuth2 at %s\x00" as *const u8 as *const libc::c_char,
|
||||
token_url,
|
||||
);
|
||||
} else {
|
||||
jsmn_init(&mut parser);
|
||||
tok_cnt = jsmn_parse(
|
||||
&mut parser,
|
||||
json,
|
||||
strlen(json),
|
||||
tok.as_mut_ptr(),
|
||||
(::std::mem::size_of::<[jsmntok_t; 128]>() as libc::c_ulong)
|
||||
.wrapping_div(::std::mem::size_of::<jsmntok_t>() as libc::c_ulong)
|
||||
as libc::c_uint,
|
||||
);
|
||||
if tok_cnt < 2i32
|
||||
|| tok[0usize].type_0 as libc::c_uint
|
||||
!= JSMN_OBJECT as libc::c_int as libc::c_uint
|
||||
{
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Failed to parse OAuth2 json from %s\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
token_url,
|
||||
);
|
||||
} else {
|
||||
let mut i: libc::c_int = 1i32;
|
||||
while i < tok_cnt {
|
||||
if access_token.is_null()
|
||||
&& jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"access_token\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
access_token = jsondup(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
||||
)
|
||||
} else if refresh_token.is_null()
|
||||
&& jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"refresh_token\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
refresh_token = jsondup(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
||||
)
|
||||
} else if jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"expires_in\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
let expires_in_str: *mut libc::c_char = jsondup(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
||||
);
|
||||
if !expires_in_str.is_null() {
|
||||
let val: time_t = atol(expires_in_str);
|
||||
if val > 20 && val < (60 * 60 * 24 * 365 * 5) {
|
||||
expires_in = val
|
||||
}
|
||||
free(expires_in_str as *mut libc::c_void);
|
||||
}
|
||||
} else if error.is_null()
|
||||
&& jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"error\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
error = jsondup(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
||||
)
|
||||
} else if error_description.is_null()
|
||||
&& jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"error_description\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
error_description = jsondup(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset((i + 1i32) as isize),
|
||||
)
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
if !error.is_null() || !error_description.is_null() {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"OAuth error: %s: %s\x00" as *const u8 as *const libc::c_char,
|
||||
if !error.is_null() {
|
||||
error
|
||||
} else {
|
||||
b"unknown\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
if !error_description.is_null() {
|
||||
error_description
|
||||
} else {
|
||||
b"no details\x00" as *const u8 as *const libc::c_char
|
||||
},
|
||||
);
|
||||
}
|
||||
if !refresh_token.is_null()
|
||||
&& 0 != *refresh_token.offset(0isize) as libc::c_int
|
||||
{
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_refresh_token\x00" as *const u8 as *const libc::c_char,
|
||||
refresh_token,
|
||||
);
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_refresh_token_for\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
code,
|
||||
);
|
||||
}
|
||||
// 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 access_token.is_null()
|
||||
|| *access_token.offset(0isize) as libc::c_int == 0i32
|
||||
{
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Failed to find OAuth2 access token\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
);
|
||||
} else {
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_access_token\x00" as *const u8 as *const libc::c_char,
|
||||
access_token,
|
||||
);
|
||||
dc_sqlite3_set_config_int64(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_timestamp_expires\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
(if 0 != expires_in {
|
||||
time(0 as *mut time_t) + expires_in - 5
|
||||
} else {
|
||||
0
|
||||
}) as int64_t,
|
||||
);
|
||||
if 0 != update_redirect_uri_on_success {
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_redirect_uri\x00" as *const u8
|
||||
as *const libc::c_char,
|
||||
redirect_uri,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(l);
|
||||
}
|
||||
}
|
||||
free(refresh_token as *mut libc::c_void);
|
||||
free(refresh_token_for as *mut libc::c_void);
|
||||
free(redirect_uri as *mut libc::c_void);
|
||||
free(token_url as *mut libc::c_void);
|
||||
free(json as *mut libc::c_void);
|
||||
free(error as *mut libc::c_void);
|
||||
free(error_description as *mut libc::c_void);
|
||||
free(oauth2 as *mut libc::c_void);
|
||||
return if !access_token.is_null() {
|
||||
access_token
|
||||
} else {
|
||||
dc_strdup(0 as *const libc::c_char)
|
||||
};
|
||||
}
|
||||
|
||||
unsafe fn jsondup(json: *const libc::c_char, tok: *mut jsmntok_t) -> *mut libc::c_char {
|
||||
if (*tok).type_0 as libc::c_uint == JSMN_STRING as libc::c_int as libc::c_uint
|
||||
|| (*tok).type_0 as libc::c_uint == JSMN_PRIMITIVE as libc::c_int as libc::c_uint
|
||||
{
|
||||
return strndup(
|
||||
json.offset((*tok).start as isize),
|
||||
((*tok).end - (*tok).start) as libc::c_ulong,
|
||||
);
|
||||
}
|
||||
|
||||
strdup(b"\x00" as *const u8 as *const libc::c_char)
|
||||
}
|
||||
|
||||
unsafe fn jsoneq(
|
||||
json: *const libc::c_char,
|
||||
tok: *mut jsmntok_t,
|
||||
s: *const libc::c_char,
|
||||
) -> libc::c_int {
|
||||
if (*tok).type_0 as libc::c_uint == JSMN_STRING as libc::c_int as libc::c_uint
|
||||
&& strlen(s) as libc::c_int == (*tok).end - (*tok).start
|
||||
&& strncmp(
|
||||
json.offset((*tok).start as isize),
|
||||
s,
|
||||
((*tok).end - (*tok).start) as usize,
|
||||
) == 0i32
|
||||
{
|
||||
return 0i32;
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
|
||||
unsafe fn is_expired(context: &dc_context_t) -> bool {
|
||||
let expire_timestamp: time_t = dc_sqlite3_get_config_int64(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_timestamp_expires\x00" as *const u8 as *const libc::c_char,
|
||||
0i32 as int64_t,
|
||||
) as time_t;
|
||||
if expire_timestamp <= 0 {
|
||||
return false;
|
||||
}
|
||||
if expire_timestamp > time(0 as *mut time_t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub unsafe fn dc_get_oauth2_addr(
|
||||
context: &dc_context_t,
|
||||
addr: *const libc::c_char,
|
||||
code: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut access_token: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut addr_out: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let oauth2: *mut oauth2_t;
|
||||
if !({
|
||||
oauth2 = get_info(addr);
|
||||
oauth2.is_null()
|
||||
} || (*oauth2).get_userinfo.is_null())
|
||||
{
|
||||
access_token = dc_get_oauth2_access_token(context, addr, code, 0i32);
|
||||
addr_out = get_oauth2_addr(context, oauth2, access_token);
|
||||
if addr_out.is_null() {
|
||||
free(access_token as *mut libc::c_void);
|
||||
access_token = dc_get_oauth2_access_token(context, addr, code, 0x1i32);
|
||||
addr_out = get_oauth2_addr(context, oauth2, access_token)
|
||||
}
|
||||
}
|
||||
free(access_token as *mut libc::c_void);
|
||||
free(oauth2 as *mut libc::c_void);
|
||||
|
||||
addr_out
|
||||
}
|
||||
|
||||
unsafe fn get_oauth2_addr(
|
||||
context: &dc_context_t,
|
||||
oauth2: *const oauth2_t,
|
||||
access_token: *const libc::c_char,
|
||||
) -> *mut libc::c_char {
|
||||
let mut addr_out: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut userinfo_url: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut json: *mut libc::c_char = 0 as *mut libc::c_char;
|
||||
let mut parser: jsmn_parser = jsmn_parser {
|
||||
pos: 0,
|
||||
toknext: 0,
|
||||
toksuper: 0,
|
||||
};
|
||||
// we do not expect nor read more tokens
|
||||
let mut tok: [jsmntok_t; 128] = [jsmntok_t {
|
||||
type_0: JSMN_UNDEFINED,
|
||||
start: 0,
|
||||
end: 0,
|
||||
size: 0,
|
||||
}; 128];
|
||||
let tok_cnt: libc::c_int;
|
||||
if !(access_token.is_null()
|
||||
|| *access_token.offset(0isize) as libc::c_int == 0i32
|
||||
|| oauth2.is_null())
|
||||
{
|
||||
userinfo_url = dc_strdup((*oauth2).get_userinfo);
|
||||
replace_in_uri(
|
||||
&mut userinfo_url,
|
||||
b"$ACCESS_TOKEN\x00" as *const u8 as *const libc::c_char,
|
||||
access_token,
|
||||
);
|
||||
json = (context.cb)(
|
||||
context,
|
||||
Event::HTTP_GET,
|
||||
userinfo_url as uintptr_t,
|
||||
0i32 as uintptr_t,
|
||||
) as *mut libc::c_char;
|
||||
if json.is_null() {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Error getting userinfo.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
} else {
|
||||
jsmn_init(&mut parser);
|
||||
tok_cnt = jsmn_parse(
|
||||
&mut parser,
|
||||
json,
|
||||
strlen(json),
|
||||
tok.as_mut_ptr(),
|
||||
(::std::mem::size_of::<[jsmntok_t; 128]>() as libc::c_ulong)
|
||||
.wrapping_div(::std::mem::size_of::<jsmntok_t>() as libc::c_ulong)
|
||||
as libc::c_uint,
|
||||
);
|
||||
if tok_cnt < 2i32
|
||||
|| tok[0usize].type_0 as libc::c_uint != JSMN_OBJECT as libc::c_int as libc::c_uint
|
||||
{
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"Failed to parse userinfo.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
} else {
|
||||
let mut i: libc::c_int = 1i32;
|
||||
while i < tok_cnt {
|
||||
if addr_out.is_null()
|
||||
&& jsoneq(
|
||||
json,
|
||||
&mut *tok.as_mut_ptr().offset(i as isize),
|
||||
b"email\x00" as *const u8 as *const libc::c_char,
|
||||
) == 0i32
|
||||
{
|
||||
addr_out = jsondup(json, &mut *tok.as_mut_ptr().offset((i + 1i32) as isize))
|
||||
}
|
||||
i += 1
|
||||
}
|
||||
if addr_out.is_null() {
|
||||
dc_log_warning(
|
||||
context,
|
||||
0i32,
|
||||
b"E-mail missing in userinfo.\x00" as *const u8 as *const libc::c_char,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(userinfo_url as *mut libc::c_void);
|
||||
free(json as *mut libc::c_void);
|
||||
|
||||
addr_out
|
||||
}
|
||||
@@ -8,7 +8,8 @@ use crate::constants::*;
|
||||
use crate::dc_context::dc_context_t;
|
||||
use crate::dc_log::*;
|
||||
use crate::dc_loginparam::*;
|
||||
use crate::dc_oauth2::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::oauth2::*;
|
||||
use crate::x::*;
|
||||
|
||||
pub struct Smtp {
|
||||
@@ -106,18 +107,15 @@ impl Smtp {
|
||||
|
||||
let creds = if 0 != lp.server_flags & (DC_LP_AUTH_OAUTH2 as i32) {
|
||||
// oauth2
|
||||
|
||||
let access_token =
|
||||
unsafe { dc_get_oauth2_access_token(context, lp.addr, lp.send_pw, 0i32) };
|
||||
if access_token.is_null() {
|
||||
let addr = to_str(lp.addr);
|
||||
let send_pw = to_str(lp.send_pw);
|
||||
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, 0);
|
||||
if access_token.is_none() {
|
||||
return 0;
|
||||
}
|
||||
let user = to_str(lp.send_user);
|
||||
|
||||
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
|
||||
let token = unsafe { CStr::from_ptr(access_token).to_str().unwrap().to_string() };
|
||||
unsafe { free(access_token as *mut libc::c_void) };
|
||||
|
||||
lettre::smtp::authentication::Credentials::new(user, token)
|
||||
lettre::smtp::authentication::Credentials::new(user.into(), access_token.unwrap())
|
||||
} else {
|
||||
// plain
|
||||
let user = unsafe { CStr::from_ptr(lp.send_user).to_str().unwrap().to_string() };
|
||||
|
||||
@@ -1645,6 +1645,22 @@ pub unsafe fn dc_make_rel_and_copy(
|
||||
success
|
||||
}
|
||||
|
||||
pub fn to_cstring<S: AsRef<str>>(s: S) -> std::ffi::CString {
|
||||
std::ffi::CString::new(s.as_ref()).unwrap()
|
||||
}
|
||||
|
||||
pub fn to_string(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap().to_string() }
|
||||
}
|
||||
|
||||
pub fn to_str<'a>(s: *const libc::c_char) -> &'a str {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -1952,19 +1968,3 @@ mod tests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_cstring<S: AsRef<str>>(s: S) -> std::ffi::CString {
|
||||
std::ffi::CString::new(s.as_ref()).unwrap()
|
||||
}
|
||||
|
||||
pub fn to_string(s: *const libc::c_char) -> String {
|
||||
if s.is_null() {
|
||||
return "".into();
|
||||
}
|
||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap().to_string() }
|
||||
}
|
||||
|
||||
pub fn to_str<'a>(s: *const libc::c_char) -> &'a str {
|
||||
assert!(!s.is_null(), "cannot be used on null pointers");
|
||||
unsafe { std::ffi::CStr::from_ptr(s).to_str().unwrap() }
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ extern crate smallvec;
|
||||
#[macro_use]
|
||||
pub mod dc_log;
|
||||
|
||||
pub mod oauth2;
|
||||
pub mod peerstate;
|
||||
pub mod types;
|
||||
pub mod x;
|
||||
@@ -42,7 +43,6 @@ pub mod dc_imap;
|
||||
pub mod dc_imex;
|
||||
pub mod dc_job;
|
||||
pub mod dc_jobthread;
|
||||
pub mod dc_jsmn;
|
||||
pub mod dc_key;
|
||||
pub mod dc_keyhistory;
|
||||
pub mod dc_keyring;
|
||||
@@ -53,7 +53,6 @@ pub mod dc_mimefactory;
|
||||
pub mod dc_mimeparser;
|
||||
pub mod dc_move;
|
||||
pub mod dc_msg;
|
||||
pub mod dc_oauth2;
|
||||
pub mod dc_param;
|
||||
pub mod dc_pgp;
|
||||
pub mod dc_qr;
|
||||
|
||||
393
src/oauth2.rs
Normal file
393
src/oauth2.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::CString;
|
||||
|
||||
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::dc_context::dc_context_t;
|
||||
use crate::dc_log::*;
|
||||
use crate::dc_sqlite3::*;
|
||||
use crate::dc_tools::*;
|
||||
use crate::types::*;
|
||||
use crate::x::*;
|
||||
|
||||
const OAUTH2_GMAIL: Oauth2 = Oauth2 {
|
||||
client_id: "959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com",
|
||||
get_code: "https://accounts.google.com/o/oauth2/auth?client_id=$CLIENT_ID&redirect_uri=$REDIRECT_URI&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline",
|
||||
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"),
|
||||
};
|
||||
|
||||
const OAUTH2_YANDEX: Oauth2 = Oauth2 {
|
||||
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)]
|
||||
pub struct Oauth2 {
|
||||
client_id: &'static str,
|
||||
get_code: &'static str,
|
||||
init_token: &'static str,
|
||||
refresh_token: &'static str,
|
||||
get_userinfo: Option<&'static str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
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,
|
||||
expires_in: Option<u64>,
|
||||
refresh_token: Option<String>,
|
||||
scope: Option<String>,
|
||||
}
|
||||
|
||||
pub fn dc_get_oauth2_url(
|
||||
context: &dc_context_t,
|
||||
addr: impl AsRef<str>,
|
||||
redirect_uri: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
set_config(
|
||||
context,
|
||||
"oauth2_pending_redirect_uri",
|
||||
redirect_uri.as_ref(),
|
||||
);
|
||||
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.as_ref());
|
||||
|
||||
Some(oauth2_url)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
// The following function may block due http-requests;
|
||||
// must not be called from the main thread or by the ui!
|
||||
pub fn dc_get_oauth2_access_token(
|
||||
context: &dc_context_t,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
flags: usize,
|
||||
) -> Option<String> {
|
||||
if let Some(oauth2) = Oauth2::from_address(addr) {
|
||||
let lock = context.oauth2_critical.clone();
|
||||
let _l = lock.lock().unwrap();
|
||||
|
||||
// read generated token
|
||||
if 0 == flags & 0x1 && !is_expired(context) {
|
||||
let access_token = get_config(context, "oauth2_access_token");
|
||||
if access_token.is_some() {
|
||||
// success
|
||||
return access_token;
|
||||
}
|
||||
}
|
||||
|
||||
let refresh_token = get_config(context, "oauth2_refresh_token");
|
||||
let refresh_token_for =
|
||||
get_config(context, "oauth2_refresh_token_for").unwrap_or_else(|| "unset".into());
|
||||
|
||||
let (redirect_uri, token_url, update_redirect_uri_on_success) =
|
||||
if refresh_token.is_none() || refresh_token_for != code.as_ref() {
|
||||
info!(
|
||||
context,
|
||||
0, "Generate OAuth2 refresh_token and access_token...",
|
||||
);
|
||||
(
|
||||
get_config(context, "oauth2_pending_redirect_uri")
|
||||
.unwrap_or_else(|| "unset".into()),
|
||||
oauth2.init_token,
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
info!(
|
||||
context,
|
||||
0, "Regenerate OAuth2 access_token by refresh_token...",
|
||||
);
|
||||
(
|
||||
get_config(context, "oauth2_redirect_uri").unwrap_or_else(|| "unset".into()),
|
||||
oauth2.refresh_token,
|
||||
false,
|
||||
)
|
||||
};
|
||||
let mut token_url = replace_in_uri(&token_url, "$CLIENT_ID", oauth2.client_id);
|
||||
token_url = replace_in_uri(&token_url, "$REDIRECT_URI", &redirect_uri);
|
||||
token_url = replace_in_uri(&token_url, "$CODE", code.as_ref());
|
||||
if let Some(ref token) = refresh_token {
|
||||
token_url = replace_in_uri(&token_url, "$REFRESH_TOKEN", token);
|
||||
}
|
||||
|
||||
let response = reqwest::Client::new().post(&token_url).send();
|
||||
if response.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!("Error calling OAuth2 at {}: {:?}", token_url, response)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let mut response = response.unwrap();
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!(
|
||||
"Error calling OAuth2 at {}: {:?}",
|
||||
token_url,
|
||||
response.status()
|
||||
)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let parsed: reqwest::Result<Response> = response.json();
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!(
|
||||
"Failed to parse OAuth2 JSON response from {}: error: {:?}",
|
||||
token_url, parsed
|
||||
)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
println!("response: {:?}", &parsed);
|
||||
let response = parsed.unwrap();
|
||||
if let Some(ref token) = response.refresh_token {
|
||||
set_config(context, "oauth2_refresh_token", token);
|
||||
set_config(context, "oauth2_refresh_token_for", code.as_ref());
|
||||
}
|
||||
|
||||
// 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 {
|
||||
set_config(context, "oauth2_access_token", token);
|
||||
let expires_in = response
|
||||
.expires_in
|
||||
// refresh a bet before
|
||||
.map(|t| unsafe { time(0 as *mut time_t) as u64 } + t - 5)
|
||||
.unwrap_or_else(|| 0);
|
||||
set_config_int64(context, "oauth2_timestamp_expires", expires_in as i64);
|
||||
|
||||
if update_redirect_uri_on_success {
|
||||
set_config(context, "oauth2_redirect_uri", redirect_uri.as_ref());
|
||||
}
|
||||
} else {
|
||||
warn!(context, 0, "Failed to find OAuth2 access token");
|
||||
}
|
||||
|
||||
response.access_token
|
||||
} else {
|
||||
warn!(context, 0, "Internal OAuth2 error: 2");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dc_get_oauth2_addr(
|
||||
context: &dc_context_t,
|
||||
addr: impl AsRef<str>,
|
||||
code: impl AsRef<str>,
|
||||
) -> Option<String> {
|
||||
let oauth2 = Oauth2::from_address(addr.as_ref());
|
||||
if oauth2.is_none() {
|
||||
return None;
|
||||
}
|
||||
let oauth2 = oauth2.unwrap();
|
||||
if oauth2.get_userinfo.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr.as_ref(), code.as_ref(), 0)
|
||||
{
|
||||
let addr_out = oauth2.get_addr(context, access_token);
|
||||
if addr_out.is_none() {
|
||||
// regenerate
|
||||
if let Some(access_token) = dc_get_oauth2_access_token(context, addr, code, 0x1) {
|
||||
oauth2.get_addr(context, access_token)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
addr_out
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl Oauth2 {
|
||||
fn from_address(addr: impl AsRef<str>) -> Option<Self> {
|
||||
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.ru" | "yandex.ua" => Some(OAUTH2_YANDEX),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_addr(&self, context: &dc_context_t, access_token: impl AsRef<str>) -> Option<String> {
|
||||
let userinfo_url = self.get_userinfo.unwrap_or_else(|| "");
|
||||
let userinfo_url = replace_in_uri(&userinfo_url, "$ACCESS_TOKEN", access_token);
|
||||
|
||||
let response = reqwest::Client::new().post(&userinfo_url).send();
|
||||
if response.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!("Error getting userinfo: {:?}", response)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let mut response = response.unwrap();
|
||||
if !response.status().is_success() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!("Error getting userinfo: {:?}", response.status())
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let parsed: reqwest::Result<HashMap<String, String>> = response.json();
|
||||
if parsed.is_err() {
|
||||
warn!(
|
||||
context,
|
||||
0,
|
||||
format!("Failed to parse userinfo JSON response: {:?}", parsed)
|
||||
);
|
||||
return None;
|
||||
}
|
||||
if let Ok(response) = parsed {
|
||||
let addr = response.get("email");
|
||||
if addr.is_none() {
|
||||
warn!(context, 0, "E-mail missing in userinfo.");
|
||||
}
|
||||
|
||||
addr.map(|addr| addr.to_string())
|
||||
} else {
|
||||
warn!(context, 0, "Failed to parse userinfo.");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config(context: &dc_context_t, key: &str) -> Option<String> {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
let res = unsafe {
|
||||
dc_sqlite3_get_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
key_c.as_ptr(),
|
||||
std::ptr::null(),
|
||||
)
|
||||
};
|
||||
if res.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(to_string(res))
|
||||
}
|
||||
|
||||
fn set_config(context: &dc_context_t, key: &str, value: &str) {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
let value_c = CString::new(value).unwrap();
|
||||
unsafe {
|
||||
dc_sqlite3_set_config(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
key_c.as_ptr(),
|
||||
value_c.as_ptr(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
fn set_config_int64(context: &dc_context_t, key: &str, value: i64) {
|
||||
let key_c = CString::new(key).unwrap();
|
||||
unsafe {
|
||||
dc_sqlite3_set_config_int64(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
key_c.as_ptr(),
|
||||
value,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
fn is_expired(context: &dc_context_t) -> bool {
|
||||
let expire_timestamp = unsafe {
|
||||
dc_sqlite3_get_config_int64(
|
||||
context,
|
||||
&context.sql.clone().read().unwrap(),
|
||||
b"oauth2_timestamp_expires\x00" as *const u8 as *const libc::c_char,
|
||||
0i32 as int64_t,
|
||||
)
|
||||
} as time_t;
|
||||
|
||||
if expire_timestamp <= 0 {
|
||||
return false;
|
||||
}
|
||||
if expire_timestamp > unsafe { time(0 as *mut time_t) } {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn replace_in_uri(uri: impl AsRef<str>, key: impl AsRef<str>, value: impl AsRef<str>) -> String {
|
||||
let value_urlencoded = utf8_percent_encode(value.as_ref(), DEFAULT_ENCODE_SET).to_string();
|
||||
uri.as_ref().replace(key.as_ref(), &value_urlencoded)
|
||||
}
|
||||
|
||||
fn normalize_addr(addr: &str) -> &str {
|
||||
let normalized = addr.trim();
|
||||
normalized.trim_start_matches("mailto:")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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-b%20c"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_oauth_from_address() {
|
||||
assert_eq!(Oauth2::from_address("hello@gmail.com"), Some(OAUTH2_GMAIL));
|
||||
assert_eq!(
|
||||
Oauth2::from_address("hello@googlemail.com"),
|
||||
Some(OAUTH2_GMAIL)
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ use deltachat::dc_securejoin::*;
|
||||
use deltachat::dc_strbuilder::*;
|
||||
use deltachat::dc_strencode::*;
|
||||
use deltachat::dc_tools::*;
|
||||
use deltachat::oauth2::*;
|
||||
use deltachat::types::*;
|
||||
use deltachat::x::*;
|
||||
use libc;
|
||||
@@ -2519,6 +2520,36 @@ fn test_dc_mimeparser_with_context() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_url() {
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let redirect_uri = "chat.delta:/com.b44t.messenger";
|
||||
let res = dc_get_oauth2_url(&ctx.ctx, addr, redirect_uri);
|
||||
|
||||
assert_eq!(res, Some("https://accounts.google.com/o/oauth2/auth?client_id=959970109878-4mvtgf6feshskf7695nfln6002mom908.apps.googleusercontent.com&redirect_uri=chat.delta:/com.b44t.messenger&response_type=code&scope=https%3A%2F%2Fmail.google.com%2F%20email&access_type=offline".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_addr() {
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_addr(&ctx.ctx, addr, code);
|
||||
// this should fail as it is an invalid password
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dc_get_oauth2_token() {
|
||||
let ctx = unsafe { create_test_context() };
|
||||
let addr = "dignifiedquire@gmail.com";
|
||||
let code = "fail";
|
||||
let res = dc_get_oauth2_access_token(&ctx.ctx, addr, code, 0);
|
||||
// this should fail as it is an invalid password
|
||||
assert_eq!(res, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stress_tests() {
|
||||
unsafe {
|
||||
|
||||
Reference in New Issue
Block a user