diff --git a/deltachat-core.cbp b/deltachat-core.cbp
index 875d3385..bec85594 100644
--- a/deltachat-core.cbp
+++ b/deltachat-core.cbp
@@ -65,6 +65,9 @@
+
+
+
@@ -497,6 +500,9 @@
+
+
+
diff --git a/libs/jsmn/LICENSE b/libs/jsmn/LICENSE
new file mode 100644
index 00000000..c84fb2e9
--- /dev/null
+++ b/libs/jsmn/LICENSE
@@ -0,0 +1,20 @@
+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.
+
diff --git a/libs/jsmn/README.md b/libs/jsmn/README.md
new file mode 100644
index 00000000..45436b36
--- /dev/null
+++ b/libs/jsmn/README.md
@@ -0,0 +1,168 @@
+JSMN
+====
+
+[](https://travis-ci.org/zserge/jsmn)
+
+jsmn (pronounced like 'jasmine') is a minimalistic JSON parser in C. It can be
+easily integrated into resource-limited or embedded projects.
+
+You can find more information about JSON format at [json.org][1]
+
+Library sources are available at https://github.com/zserge/jsmn
+
+The web page with some information about jsmn can be found at
+[http://zserge.com/jsmn.html][2]
+
+Philosophy
+----------
+
+Most JSON parsers offer you a bunch of functions to load JSON data, parse it
+and extract any value by its name. jsmn proves that checking the correctness of
+every JSON packet or allocating temporary objects to store parsed JSON fields
+often is an overkill.
+
+JSON format itself is extremely simple, so why should we complicate it?
+
+jsmn is designed to be **robust** (it should work fine even with erroneous
+data), **fast** (it should parse data on the fly), **portable** (no superfluous
+dependencies or non-standard C extensions). And of course, **simplicity** is a
+key feature - simple code style, simple algorithm, simple integration into
+other projects.
+
+Features
+--------
+
+* compatible with C89
+* no dependencies (even libc!)
+* highly portable (tested on x86/amd64, ARM, AVR)
+* about 200 lines of code
+* extremely small code footprint
+* API contains only 2 functions
+* no dynamic memory allocation
+* incremental single-pass parsing
+* library code is covered with unit-tests
+
+Design
+------
+
+The rudimentary jsmn object is a **token**. Let's consider a JSON string:
+
+ '{ "name" : "Jack", "age" : 27 }'
+
+It holds the following tokens:
+
+* Object: `{ "name" : "Jack", "age" : 27}` (the whole object)
+* Strings: `"name"`, `"Jack"`, `"age"` (keys and some values)
+* Number: `27`
+
+In jsmn, tokens do not hold any data, but point to token boundaries in JSON
+string instead. In the example above jsmn will create tokens like: Object
+[0..31], String [3..7], String [12..16], String [20..23], Number [27..29].
+
+Every jsmn token has a type, which indicates the type of corresponding JSON
+token. jsmn supports the following token types:
+
+* Object - a container of key-value pairs, e.g.:
+ `{ "foo":"bar", "x":0.3 }`
+* Array - a sequence of values, e.g.:
+ `[ 1, 2, 3 ]`
+* String - a quoted sequence of chars, e.g.: `"foo"`
+* Primitive - a number, a boolean (`true`, `false`) or `null`
+
+Besides start/end positions, jsmn tokens for complex types (like arrays
+or objects) also contain a number of child items, so you can easily follow
+object hierarchy.
+
+This approach provides enough information for parsing any JSON data and makes
+it possible to use zero-copy techniques.
+
+Install
+-------
+
+To clone the repository you should have Git installed. Just run:
+
+ $ git clone https://github.com/zserge/jsmn
+
+Repository layout is simple: jsmn.c and jsmn.h are library files, tests are in
+the jsmn\_test.c, you will also find README, LICENSE and Makefile files inside.
+
+To build the library, run `make`. It is also recommended to run `make test`.
+Let me know, if some tests fail.
+
+If build was successful, you should get a `libjsmn.a` library.
+The header file you should include is called `"jsmn.h"`.
+
+API
+---
+
+Token types are described by `jsmntype_t`:
+
+ typedef enum {
+ JSMN_UNDEFINED = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3,
+ JSMN_PRIMITIVE = 4
+ } jsmntype_t;
+
+**Note:** Unlike JSON data types, primitive tokens are not divided into
+numbers, booleans and null, because one can easily tell the type using the
+first character:
+
+* 't', 'f'
- boolean
+* 'n'
- null
+* '-', '0'..'9'
- number
+
+Token is an object of `jsmntok_t` type:
+
+ typedef struct {
+ jsmntype_t type; // Token type
+ int start; // Token start position
+ int end; // Token end position
+ int size; // Number of child (nested) tokens
+ } jsmntok_t;
+
+**Note:** string tokens point to the first character after
+the opening quote and the previous symbol before final quote. This was made
+to simplify string extraction from JSON data.
+
+All job is done by `jsmn_parser` object. You can initialize a new parser using:
+
+ jsmn_parser parser;
+ jsmntok_t tokens[10];
+
+ jsmn_init(&parser);
+
+ // js - pointer to JSON string
+ // tokens - an array of tokens available
+ // 10 - number of tokens available
+ jsmn_parse(&parser, js, strlen(js), tokens, 10);
+
+This will create a parser, and then it tries to parse up to 10 JSON tokens from
+the `js` string.
+
+A non-negative return value of `jsmn_parse` is the number of tokens actually
+used by the parser.
+Passing NULL instead of the tokens array would not store parsing results, but
+instead the function will return the value of tokens needed to parse the given
+string. This can be useful if you don't know yet how many tokens to allocate.
+
+If something goes wrong, you will get an error. Error will be one of these:
+
+* `JSMN_ERROR_INVAL` - bad token, JSON string is corrupted
+* `JSMN_ERROR_NOMEM` - not enough tokens, JSON string is too large
+* `JSMN_ERROR_PART` - JSON string is too short, expecting more JSON data
+
+If you get `JSMN_ERROR_NOMEM`, you can re-allocate more tokens and call
+`jsmn_parse` once more. If you read json data from the stream, you can
+periodically call `jsmn_parse` and check if return value is `JSMN_ERROR_PART`.
+You will get this error until you reach the end of JSON data.
+
+Other info
+----------
+
+This software is distributed under [MIT license](http://www.opensource.org/licenses/mit-license.php),
+ so feel free to integrate it in your commercial products.
+
+[1]: http://www.json.org/
+[2]: http://zserge.com/jsmn.html
diff --git a/libs/jsmn/jsmn.c b/libs/jsmn/jsmn.c
new file mode 100644
index 00000000..853c3f17
--- /dev/null
+++ b/libs/jsmn/jsmn.c
@@ -0,0 +1,314 @@
+#include "jsmn.h"
+
+/**
+ * Allocates a fresh unused token from the token pool.
+ */
+static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser,
+ jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *tok;
+ if (parser->toknext >= num_tokens) {
+ return NULL;
+ }
+ tok = &tokens[parser->toknext++];
+ tok->start = tok->end = -1;
+ tok->size = 0;
+#ifdef JSMN_PARENT_LINKS
+ tok->parent = -1;
+#endif
+ return tok;
+}
+
+/**
+ * Fills token type and boundaries.
+ */
+static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type,
+ int start, int end) {
+ token->type = type;
+ token->start = start;
+ token->end = end;
+ token->size = 0;
+}
+
+/**
+ * Fills next available token with JSON primitive.
+ */
+static int jsmn_parse_primitive(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+ int start;
+
+ start = parser->pos;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ switch (js[parser->pos]) {
+#ifndef JSMN_STRICT
+ /* In strict mode primitive must be followed by "," or "}" or "]" */
+ case ':':
+#endif
+ case '\t' : case '\r' : case '\n' : case ' ' :
+ case ',' : case ']' : case '}' :
+ goto found;
+ }
+ if (js[parser->pos] < 32 || js[parser->pos] >= 127) {
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#ifdef JSMN_STRICT
+ /* In strict mode primitive must be followed by a comma/object/array */
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+#endif
+
+found:
+ if (tokens == NULL) {
+ parser->pos--;
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ parser->pos--;
+ return 0;
+}
+
+/**
+ * Fills next token with JSON string.
+ */
+static int jsmn_parse_string(jsmn_parser *parser, const char *js,
+ size_t len, jsmntok_t *tokens, size_t num_tokens) {
+ jsmntok_t *token;
+
+ int start = parser->pos;
+
+ parser->pos++;
+
+ /* Skip starting quote */
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c = js[parser->pos];
+
+ /* Quote: end of string */
+ if (c == '\"') {
+ if (tokens == NULL) {
+ return 0;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL) {
+ parser->pos = start;
+ return JSMN_ERROR_NOMEM;
+ }
+ jsmn_fill_token(token, JSMN_STRING, start+1, parser->pos);
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ return 0;
+ }
+
+ /* Backslash: Quoted symbol expected */
+ if (c == '\\' && parser->pos + 1 < len) {
+ int i;
+ parser->pos++;
+ switch (js[parser->pos]) {
+ /* Allowed escaped symbols */
+ case '\"': case '/' : case '\\' : case 'b' :
+ case 'f' : case 'r' : case 'n' : case 't' :
+ break;
+ /* Allows escaped symbol \uXXXX */
+ case 'u':
+ parser->pos++;
+ for(i = 0; i < 4 && parser->pos < len && js[parser->pos] != '\0'; i++) {
+ /* If it isn't a hex character we have an error */
+ if(!((js[parser->pos] >= 48 && js[parser->pos] <= 57) || /* 0-9 */
+ (js[parser->pos] >= 65 && js[parser->pos] <= 70) || /* A-F */
+ (js[parser->pos] >= 97 && js[parser->pos] <= 102))) { /* a-f */
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ parser->pos++;
+ }
+ parser->pos--;
+ break;
+ /* Unexpected symbol */
+ default:
+ parser->pos = start;
+ return JSMN_ERROR_INVAL;
+ }
+ }
+ }
+ parser->pos = start;
+ return JSMN_ERROR_PART;
+}
+
+/**
+ * Parse JSON string and fill tokens.
+ */
+int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens) {
+ int r;
+ int i;
+ jsmntok_t *token;
+ int count = parser->toknext;
+
+ for (; parser->pos < len && js[parser->pos] != '\0'; parser->pos++) {
+ char c;
+ jsmntype_t type;
+
+ c = js[parser->pos];
+ switch (c) {
+ case '{': case '[':
+ count++;
+ if (tokens == NULL) {
+ break;
+ }
+ token = jsmn_alloc_token(parser, tokens, num_tokens);
+ if (token == NULL)
+ return JSMN_ERROR_NOMEM;
+ if (parser->toksuper != -1) {
+ tokens[parser->toksuper].size++;
+#ifdef JSMN_PARENT_LINKS
+ token->parent = parser->toksuper;
+#endif
+ }
+ token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY);
+ token->start = parser->pos;
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case '}': case ']':
+ if (tokens == NULL)
+ break;
+ type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY);
+#ifdef JSMN_PARENT_LINKS
+ if (parser->toknext < 1) {
+ return JSMN_ERROR_INVAL;
+ }
+ token = &tokens[parser->toknext - 1];
+ for (;;) {
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ token->end = parser->pos + 1;
+ parser->toksuper = token->parent;
+ break;
+ }
+ if (token->parent == -1) {
+ if(token->type != type || parser->toksuper == -1) {
+ return JSMN_ERROR_INVAL;
+ }
+ break;
+ }
+ token = &tokens[token->parent];
+ }
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ if (token->type != type) {
+ return JSMN_ERROR_INVAL;
+ }
+ parser->toksuper = -1;
+ token->end = parser->pos + 1;
+ break;
+ }
+ }
+ /* Error if unmatched closing bracket */
+ if (i == -1) return JSMN_ERROR_INVAL;
+ for (; i >= 0; i--) {
+ token = &tokens[i];
+ if (token->start != -1 && token->end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+#endif
+ break;
+ case '\"':
+ r = jsmn_parse_string(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+ case '\t' : case '\r' : case '\n' : case ' ':
+ break;
+ case ':':
+ parser->toksuper = parser->toknext - 1;
+ break;
+ case ',':
+ if (tokens != NULL && parser->toksuper != -1 &&
+ tokens[parser->toksuper].type != JSMN_ARRAY &&
+ tokens[parser->toksuper].type != JSMN_OBJECT) {
+#ifdef JSMN_PARENT_LINKS
+ parser->toksuper = tokens[parser->toksuper].parent;
+#else
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ if (tokens[i].type == JSMN_ARRAY || tokens[i].type == JSMN_OBJECT) {
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ parser->toksuper = i;
+ break;
+ }
+ }
+ }
+#endif
+ }
+ break;
+#ifdef JSMN_STRICT
+ /* In strict mode primitives are: numbers and booleans */
+ case '-': case '0': case '1' : case '2': case '3' : case '4':
+ case '5': case '6': case '7' : case '8': case '9':
+ case 't': case 'f': case 'n' :
+ /* And they must not be keys of the object */
+ if (tokens != NULL && parser->toksuper != -1) {
+ jsmntok_t *t = &tokens[parser->toksuper];
+ if (t->type == JSMN_OBJECT ||
+ (t->type == JSMN_STRING && t->size != 0)) {
+ return JSMN_ERROR_INVAL;
+ }
+ }
+#else
+ /* In non-strict mode every unquoted value is a primitive */
+ default:
+#endif
+ r = jsmn_parse_primitive(parser, js, len, tokens, num_tokens);
+ if (r < 0) return r;
+ count++;
+ if (parser->toksuper != -1 && tokens != NULL)
+ tokens[parser->toksuper].size++;
+ break;
+
+#ifdef JSMN_STRICT
+ /* Unexpected char in strict mode */
+ default:
+ return JSMN_ERROR_INVAL;
+#endif
+ }
+ }
+
+ if (tokens != NULL) {
+ for (i = parser->toknext - 1; i >= 0; i--) {
+ /* Unmatched opened object or array */
+ if (tokens[i].start != -1 && tokens[i].end == -1) {
+ return JSMN_ERROR_PART;
+ }
+ }
+ }
+
+ return count;
+}
+
+/**
+ * Creates a new parser based over a given buffer with an array of tokens
+ * available.
+ */
+void jsmn_init(jsmn_parser *parser) {
+ parser->pos = 0;
+ parser->toknext = 0;
+ parser->toksuper = -1;
+}
+
diff --git a/libs/jsmn/jsmn.h b/libs/jsmn/jsmn.h
new file mode 100644
index 00000000..5a5200ee
--- /dev/null
+++ b/libs/jsmn/jsmn.h
@@ -0,0 +1,76 @@
+#ifndef __JSMN_H_
+#define __JSMN_H_
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * JSON type identifier. Basic types are:
+ * o Object
+ * o Array
+ * o String
+ * o Other primitive: number, boolean (true/false) or null
+ */
+typedef enum {
+ JSMN_UNDEFINED = 0,
+ JSMN_OBJECT = 1,
+ JSMN_ARRAY = 2,
+ JSMN_STRING = 3,
+ JSMN_PRIMITIVE = 4
+} jsmntype_t;
+
+enum jsmnerr {
+ /* Not enough tokens were provided */
+ JSMN_ERROR_NOMEM = -1,
+ /* Invalid character inside JSON string */
+ JSMN_ERROR_INVAL = -2,
+ /* The string is not a full JSON packet, more bytes expected */
+ JSMN_ERROR_PART = -3
+};
+
+/**
+ * JSON token description.
+ * type type (object, array, string etc.)
+ * start start position in JSON data string
+ * end end position in JSON data string
+ */
+typedef struct {
+ jsmntype_t type;
+ int start;
+ int end;
+ int size;
+#ifdef JSMN_PARENT_LINKS
+ int parent;
+#endif
+} jsmntok_t;
+
+/**
+ * JSON parser. Contains an array of token blocks available. Also stores
+ * the string being parsed now and current position in that string
+ */
+typedef struct {
+ unsigned int pos; /* offset in the JSON string */
+ unsigned int toknext; /* next token to allocate */
+ int toksuper; /* superior token node, e.g parent object or array */
+} jsmn_parser;
+
+/**
+ * Create JSON parser over an array of tokens
+ */
+void jsmn_init(jsmn_parser *parser);
+
+/**
+ * Run JSON parser. It parses a JSON data string into and array of tokens, each describing
+ * a single JSON object.
+ */
+int jsmn_parse(jsmn_parser *parser, const char *js, size_t len,
+ jsmntok_t *tokens, unsigned int num_tokens);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __JSMN_H_ */
diff --git a/src/dc_context.c b/src/dc_context.c
index 7a334197..ae573d79 100644
--- a/src/dc_context.c
+++ b/src/dc_context.c
@@ -187,6 +187,7 @@ dc_context_t* dc_context_new(dc_callback_t cb, void* userdata, const char* os_na
dc_jobthread_init(&context->mvbox_thread, context, "MVBOX", "configured_mvbox_folder");
pthread_mutex_init(&context->smtpidle_condmutex, NULL);
pthread_cond_init(&context->smtpidle_cond, NULL);
+ pthread_mutex_init(&context->oauth2_critical, NULL);
context->magic = DC_CONTEXT_MAGIC;
context->userdata = userdata;
@@ -258,6 +259,7 @@ void dc_context_unref(dc_context_t* context)
dc_jobthread_exit(&context->mvbox_thread);
pthread_cond_destroy(&context->smtpidle_cond);
pthread_mutex_destroy(&context->smtpidle_condmutex);
+ pthread_mutex_destroy(&context->oauth2_critical);
free(context->os_name);
context->magic = 0;
diff --git a/src/dc_context.h b/src/dc_context.h
index 0158262f..41019e96 100644
--- a/src/dc_context.h
+++ b/src/dc_context.h
@@ -64,6 +64,8 @@ struct _dc_context
int perform_smtp_jobs_needed;
int probe_smtp_network; /**< if this flag is set, the smtp-job timeouts are bypassed and messages are sent until they fail */
+ pthread_mutex_t oauth2_critical;
+
dc_callback_t cb; /**< Internal */
char* os_name; /**< Internal, may be NULL */
diff --git a/src/dc_imap.c b/src/dc_imap.c
index 9793bf93..81ced01d 100644
--- a/src/dc_imap.c
+++ b/src/dc_imap.c
@@ -7,6 +7,7 @@
#include "dc_imap.h"
#include "dc_job.h"
#include "dc_loginparam.h"
+#include "dc_oauth2.h"
static int setup_handle_if_needed (dc_imap_t*);
@@ -774,12 +775,14 @@ static int setup_handle_if_needed(dc_imap_t* imap)
{
// for DC_LP_AUTH_OAUTH2, user_pw is assumed to be the oauth_token
dc_log_info(imap->context, 0, "IMAP-OAuth2 connect...");
- if (imap->imap_pw==NULL || imap->imap_pw[0]==0) {
- dc_log_event_seq(imap->context, DC_EVENT_ERROR_NETWORK, &imap->log_connect_errors,
- "IMAP-OAuth2 token missing for %s@%s:%i.", imap->imap_user, imap->imap_server, (int)imap->imap_port);
- goto cleanup;
+ char* access_token = dc_oauth2_get_access_token(imap->context, imap->imap_pw, 0);
+ r = mailimap_oauth2_authenticate(imap->etpan, imap->imap_user, access_token);
+ if (dc_imap_is_error(imap, r)) {
+ free(access_token);
+ access_token = dc_oauth2_get_access_token(imap->context, imap->imap_pw, DC_REGENERATE);
+ r = mailimap_oauth2_authenticate(imap->etpan, imap->imap_user, access_token);
}
- r = mailimap_oauth2_authenticate(imap->etpan, imap->imap_user, imap->imap_pw);
+ free(access_token);
}
else
{
diff --git a/src/dc_oauth2.c b/src/dc_oauth2.c
new file mode 100644
index 00000000..c7078ce3
--- /dev/null
+++ b/src/dc_oauth2.c
@@ -0,0 +1,145 @@
+#include "dc_context.h"
+#include "dc_oauth2.h"
+#include "../libs/jsmn/jsmn.h"
+
+
+static int jsoneq(const char *json, jsmntok_t *tok, const char *s) {
+ // from the jsmn parser example
+ if (tok->type == JSMN_STRING && (int) strlen(s) == tok->end - tok->start &&
+ strncmp(json + tok->start, s, tok->end - tok->start) == 0) {
+ return 0;
+ }
+ return -1;
+}
+
+
+static char* jsondup(const char *json, jsmntok_t *tok) {
+ if (tok->type == JSMN_STRING) {
+ return strndup(json+tok->start, tok->end - tok->start);
+ }
+ return strdup("");
+}
+
+
+char* dc_oauth2_get_access_token(dc_context_t* context, const char* code, int flags)
+{
+ char* access_token = NULL;
+ char* refresh_token = NULL;
+ char* auth_url = NULL;
+ char* expires_in_str = NULL;
+ char* error = NULL;
+ char* error_description = NULL;
+ char* json = NULL;
+ jsmn_parser parser;
+ jsmntok_t tok[128]; // we do not expect nor read more tokens
+ int tok_cnt = 0;
+ int locked = 0;
+
+ if (context==NULL || context->magic!=DC_CONTEXT_MAGIC
+ || code==NULL || code[0]==0) {
+ dc_log_warning(context, 0, "Internal OAuth2 error");
+ goto cleanup;
+ }
+
+ pthread_mutex_lock(&context->oauth2_critical);
+ locked = 1;
+
+ // read generated token
+ if (!(flags&DC_REGENERATE)) {
+ access_token = dc_sqlite3_get_config(context->sql, "oauth2_access_token", NULL);
+ if (access_token!=NULL) {
+ goto cleanup; // success
+ }
+ }
+
+ // generate new token: build & call auth url
+ #define CLIENT_ID "959970109878-t6pl4k9fmsdvfnobae862urapdmhfvbe.apps.googleusercontent.com"
+ #define CLIENT_SECRET "g2f_Gc1YUJ-fWjnTkdsuk4Xo"
+ #define REDIRECT_URI "urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob"
+
+ refresh_token = dc_sqlite3_get_config(context->sql, "oauth2_refresh_token", NULL);
+ if (refresh_token==NULL)
+ {
+ auth_url = dc_mprintf("https://accounts.google.com/o/oauth2/token"
+ "?client_id=%s"
+ "&client_secret=%s"
+ "&grant_type=authorization_code"
+ "&code=%s"
+ "&redirect_uri=%s",
+ CLIENT_ID, CLIENT_SECRET, code, REDIRECT_URI);
+ }
+ else
+ {
+ auth_url = dc_mprintf("https://accounts.google.com/o/oauth2/token"
+ "?client_id=%s"
+ "&client_secret=%s"
+ "&grant_type=refresh_token"
+ "&refresh_token=%s"
+ "&redirect_uri=%s",
+ CLIENT_ID, CLIENT_SECRET, refresh_token, REDIRECT_URI);
+ }
+
+ json = (char*)context->cb(context, DC_EVENT_HTTP_POST, (uintptr_t)auth_url, 0);
+ if (json==NULL) {
+ dc_log_warning(context, 0, "Error calling OAuth2 url");
+ goto cleanup;
+ }
+
+ // generate new token: parse returned json
+ jsmn_init(&parser);
+ tok_cnt = jsmn_parse(&parser, json, strlen(json), tok, sizeof(tok)/sizeof(tok[0]));
+ if (tok_cnt<2 || tok[0].type!=JSMN_OBJECT) {
+ dc_log_warning(context, 0, "Failed to parse OAuth2 json");
+ goto cleanup;
+ }
+
+ for (int i = 1; i < tok_cnt; i++) {
+ if (jsoneq(json, &tok[i], "access_token")==0) {
+ access_token = jsondup(json, &tok[i+1]);
+ }
+ else if (jsoneq(json, &tok[i], "refresh_token")==0) {
+ refresh_token = jsondup(json, &tok[i+1]);
+ }
+ else if (jsoneq(json, &tok[i], "expires_in")==0) {
+ expires_in_str = jsondup(json, &tok[i+1]);
+ }
+ else if (jsoneq(json, &tok[i], "error")==0) {
+ error = jsondup(json, &tok[i+1]);
+ }
+ else if (jsoneq(json, &tok[i], "error_description")==0) {
+ error_description = jsondup(json, &tok[i+1]);
+ }
+ }
+
+ if (error || error_description) {
+ dc_log_warning(context, 0, "OAuth error: %s: %s",
+ error? error : "unknown",
+ error_description? error_description : "no details");
+ dc_log_warning(context, 0, "FULL COMMAND WAS: %s", auth_url);
+ // continue, errors do not imply everything went wrong
+ }
+
+ if (access_token==NULL || access_token[0]==0) {
+ dc_log_warning(context, 0, "Failed to find OAuth2 access token");
+ goto cleanup;
+ }
+
+ dc_sqlite3_set_config(context->sql, "oauth2_access_token", access_token);
+
+ // update refresh_token if given,
+ // typically this is on the first round with `grant_type=authorization_code`
+ // but we update it later, too.
+ if (refresh_token && refresh_token[0]) {
+ dc_sqlite3_set_config(context->sql, "oauth2_refresh_token", refresh_token);
+ }
+
+cleanup:
+ if (locked) { pthread_mutex_unlock(&context->oauth2_critical); }
+ free(refresh_token);
+ free(auth_url);
+ free(json);
+ free(expires_in_str);
+ free(error);
+ free(error_description);
+ return access_token? access_token : dc_strdup(NULL);
+}
diff --git a/src/dc_oauth2.h b/src/dc_oauth2.h
new file mode 100644
index 00000000..3a328616
--- /dev/null
+++ b/src/dc_oauth2.h
@@ -0,0 +1,16 @@
+#ifndef __DC_OAUTH2_H__
+#define __DC_OAUTH2_H__
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#define DC_REGENERATE 0x01
+
+char* dc_oauth2_get_access_token(dc_context_t*, const char* code, int flags);
+
+
+#ifdef __cplusplus
+} /* /extern "C" */
+#endif
+#endif /* __DC_OAUTH2_H__ */
diff --git a/src/dc_smtp.c b/src/dc_smtp.c
index e14aac65..e1090d1d 100644
--- a/src/dc_smtp.c
+++ b/src/dc_smtp.c
@@ -3,6 +3,7 @@
#include "dc_context.h"
#include "dc_smtp.h"
#include "dc_job.h"
+#include "dc_oauth2.h"
#ifndef DEBUG_SMTP
@@ -180,15 +181,14 @@ int dc_smtp_connect(dc_smtp_t* smtp, const dc_loginparam_t* lp)
if (lp->server_flags&DC_LP_AUTH_OAUTH2)
{
dc_log_info(smtp->context, 0, "SMTP-OAuth2 connect...");
- if (lp->send_pw==NULL || lp->send_pw[0]==0) {
- dc_log_event_seq(smtp->context, DC_EVENT_ERROR_NETWORK, &smtp->log_connect_errors,
- "SMTP-OAuth2 token missing for %s@%s:%i.", lp->send_user, lp->send_server, (int)lp->send_port);
- goto cleanup;
- }
-
- if ((r = mailsmtp_oauth2_authenticate(smtp->etpan, lp->send_user, lp->send_pw)) != MAILSMTP_NO_ERROR) {
- r = mailsmtp_oauth2_outlook_authenticate(smtp->etpan, lp->send_user, lp->send_pw);
+ char* access_token = dc_oauth2_get_access_token(smtp->context, lp->send_pw, 0);
+ // if we want to support outlook, there is a mailsmtp_oauth2_outlook_authenticate() ...
+ if ((r = mailsmtp_oauth2_authenticate(smtp->etpan, lp->send_user, access_token)) != MAILSMTP_NO_ERROR) {
+ free(access_token);
+ access_token = dc_oauth2_get_access_token(smtp->context, lp->send_pw, DC_REGENERATE);
+ r = mailsmtp_oauth2_authenticate(smtp->etpan, lp->send_user, access_token);
}
+ free(access_token);
}
else if((r=mailsmtp_auth(smtp->etpan, lp->send_user, lp->send_pw))!=MAILSMTP_NO_ERROR)
{
diff --git a/src/meson.build b/src/meson.build
index 1dd86ca5..c378980d 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -28,6 +28,7 @@ lib_src = [
'dc_mimeparser.c',
'dc_msg.c',
'dc_openssl.c',
+ 'dc_oauth2.c',
'dc_param.c',
'dc_pgp.c',
'dc_saxparser.c',