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:
Friedel Ziegelmayer
2019-05-26 22:49:52 +02:00
committed by GitHub
parent 94aa314f30
commit a247e5b143
14 changed files with 491 additions and 1095 deletions

View File

@@ -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 = [

View File

@@ -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]

View File

@@ -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::*;

View File

@@ -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 {

View File

@@ -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(),

View File

@@ -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,

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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() };

View File

@@ -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() }
}

View File

@@ -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
View 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);
}
}

View File

@@ -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 {