summaryrefslogtreecommitdiff
path: root/imap/src
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2020-06-12 02:21:59 -0600
committerEduardo Chappa <chappa@washington.edu>2020-06-12 02:21:59 -0600
commit6c120b9e3730f997af56fbbe19229915b6380b2d (patch)
tree2acef9df0ffa1991a90cc1ded54400d1e0c1b35f /imap/src
parentd600da07926e1ec3243c2a96cd988c61d6a97614 (diff)
downloadalpine-6c120b9e3730f997af56fbbe19229915b6380b2d.tar.xz
* Initial implementation of XOAUTH2 authentication support for Outlook.
Based on documentation suggested by Andrew C Aitchison.
Diffstat (limited to 'imap/src')
-rw-r--r--imap/src/c-client/auth_bea.c181
-rw-r--r--imap/src/c-client/auth_oa2.c181
-rw-r--r--imap/src/c-client/http.h10
-rw-r--r--imap/src/c-client/mail.c17
-rw-r--r--imap/src/c-client/mail.h29
-rw-r--r--imap/src/c-client/oauth2_aux.c314
-rw-r--r--imap/src/c-client/oauth2_aux.h25
-rw-r--r--imap/src/osdep/unix/Makefile6
8 files changed, 395 insertions, 368 deletions
diff --git a/imap/src/c-client/auth_bea.c b/imap/src/c-client/auth_bea.c
index 6079fb7b..bf5c9c1e 100644
--- a/imap/src/c-client/auth_bea.c
+++ b/imap/src/c-client/auth_bea.c
@@ -11,12 +11,11 @@
* ========================================================================
*/
+#include "oauth2_aux.h"
+
long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder, char *base,
char *service,NETMBX *mb,void *stream, unsigned long port,
unsigned long *trial,char *user);
-#ifndef HTTP_OAUTH2_INCLUDED
-void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
-#endif /* HTTP_OAUTH2_INCLUDED */
AUTHENTICATOR auth_bea = {
AU_HIDE | AU_SINGLE, /* hidden, single trip */
@@ -34,38 +33,6 @@ AUTHENTICATOR auth_bea = {
#define BEARER_HOST "host="
#define BEARER_PORT "port="
-#ifndef OAUTH2_GENERATE_STATE
-#define OAUTH2_GENERATE_STATE
-char *oauth2_generate_state(void);
-
-/* we generate something like a guid, but not care about
- * anything, but that it is really random.
- */
-char *oauth2_generate_state(void)
-{
- char rv[37];
- int i;
-
- rv[0] = '\0';
- for(i = 0; i < 4; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 6; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- rv[36] = '\0';
- return cpystr(rv);
-}
-#endif /* OAUTH2_GENERATE_STATE */
-
/* Client authenticator
* Accepts: challenger function
* responder function
@@ -196,147 +163,3 @@ long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder
*trial = 65535; /* don't retry if bad protocol */
return ret;
}
-
-#ifndef HTTP_OAUTH2_INCLUDED
-#define HTTP_OAUTH2_INCLUDED
-/*
- * The code above is enough to implement OAUTHBEARER, all one needs is the username
- * and access token and give it to the function above. However, normal users cannot
- * be expected to get the access token, so we ask the client to help with getting
- * the access token, refresh token and expire values, so the code below is written
- * to help with that.
- */
-
-#include "http.h"
-#include "json.h"
-
-void
-mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
- OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
-{
- int i;
- HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
- OAUTH2_SERVER_METHOD_S RefreshMethod;
- unsigned char *s = NULL;
- JSON_S *json = NULL;
- int status = 0;
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
- oauth2clientinfo_t ogci =
- (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
-
- if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
- &oauth2->param[OA2_Secret].value);
- }
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
- return;
-
- /* first check if we have a refresh token, and in that case use it */
- if(oauth2->param[OA2_RefreshToken].value){
-
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment ignore the reply text */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- return;
- }
- /*
- * else, we do not have a refresh token, nor an access token.
- * We need to start the process to get an access code. We use this
- * to get an access token and refresh token.
- */
- {
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "GET") == 0){
- unsigned char *url = http_get_param_url(RefreshMethod.urlserver, params);
- oauth2getaccesscode_t ogac =
- (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
-
- if(ogac)
- oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
- }
-
- if(oauth2->param[OA2_Code].value){
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "refresh_token");
- if(jx && jx->jtype == JString)
- oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- }
- return;
- }
-}
-#endif /* HTTP_OAUTH2_INCLUDED */
diff --git a/imap/src/c-client/auth_oa2.c b/imap/src/c-client/auth_oa2.c
index 6de4395e..69987559 100644
--- a/imap/src/c-client/auth_oa2.c
+++ b/imap/src/c-client/auth_oa2.c
@@ -15,10 +15,6 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
char *service,NETMBX *mb,void *stream, unsigned long port,
unsigned long *trial,char *user);
-#ifndef HTTP_OAUTH2_INCLUDED
-void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
-#endif /* HTTP_OAUTH2_INCLUDED */
-
AUTHENTICATOR auth_oa2 = {
AU_HIDE | AU_SINGLE, /* hidden */
OA2NAME, /* authenticator name */
@@ -31,39 +27,6 @@ AUTHENTICATOR auth_oa2 = {
#define OAUTH2_USER "user="
#define OAUTH2_BEARER "auth=Bearer "
-#ifndef OAUTH2_GENERATE_STATE
-#define OAUTH2_GENERATE_STATE
-char *oauth2_generate_state(void);
-/* we generate something like a guid, but not care about
- * anything, but that it is really random.
- */
-char *oauth2_generate_state(void)
-{
- char rv[37];
- int i;
-
- rv[0] = '\0';
- for(i = 0; i < 4; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 2; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- sprintf(rv + strlen(rv), "%c", '-');
- for(i = 0; i < 6; i++)
- sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
- rv[36] = '\0';
- return cpystr(rv);
-}
-#endif /* OAUTH2_GENERATE_STATE */
-
-
-
/* Client authenticator
* Accepts: challenger function
* responder function
@@ -213,147 +176,3 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
*trial = 65535; /* don't retry if bad protocol */
return ret;
}
-
-#ifndef HTTP_OAUTH2_INCLUDED
-#define HTTP_OAUTH2_INCLUDED
-/*
- * The code above is enough to implement XOAUTH2, all one needs is the username
- * and access token and give it to the function above. However, normal users cannot
- * be expected to get the access token, so we ask the client to help with getting
- * the access token, refresh token and expire values, so the code below is written
- * to help with that.
- */
-
-#include "http.h"
-#include "json.h"
-
-void
-mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
- OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
-{
- int i;
- HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
- OAUTH2_SERVER_METHOD_S RefreshMethod;
- char *s = NULL;
- JSON_S *json = NULL;
- int status = 0;
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
- oauth2clientinfo_t ogci =
- (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
-
- if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
- &oauth2->param[OA2_Secret].value);
- }
-
- if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
- return;
-
- /* first check if we have a refresh token, and in that case use it */
- if(oauth2->param[OA2_RefreshToken].value){
-
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, params, &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment, ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- return;
- }
- /*
- * else, we do not have a refresh token, nor an access token.
- * We need to start the process to get an access code. We use this
- * to get an access token and refresh token.
- */
- {
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "GET") == 0){
- char *url = http_get_param_url(RefreshMethod.urlserver, params);
- oauth2getaccesscode_t ogac =
- (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
-
- if(ogac)
- oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
- }
-
- if(oauth2->param[OA2_Code].value){
- RefreshMethod = oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode];
- for(i = 0; RefreshMethod.params[i] != OA2_End; i++){
- OA2_type j = RefreshMethod.params[i];
- params[i].name = oauth2->param[j].name;
- params[i].value = oauth2->param[j].value;
- }
- params[i].name = params[i].value = NULL;
-
- if(strcmp(RefreshMethod.name, "POST") == 0)
- s = http_post_param(RefreshMethod.urlserver, params, &status);
- else if(strcmp(RefreshMethod.name, "POST2") == 0)
- s = http_post_param2(RefreshMethod.urlserver, paramsm &status);
-
- if(status != 200 && s)
- fs_give((void **) &s); /* at this moment, ignore the error */
-
- if(s){
- unsigned char *u = s;
- json = json_parse(&u);
- fs_give((void **) &s);
- }
-
- if(json != NULL){
- JSON_X *jx;
-
- jx = json_body_value(json, "refresh_token");
- if(jx && jx->jtype == JString)
- oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
-
- jx = json_body_value(json, "expires_in");
- if(jx && jx->jtype == JString)
- oauth2->expiration = time(0) + atol((char *) jx->value);
-
- json_free(&json);
- }
- }
- return;
- }
-}
-#endif /* HTTP_OAUTH2_INCLUDED */
diff --git a/imap/src/c-client/http.h b/imap/src/c-client/http.h
index 84607707..621e7c48 100644
--- a/imap/src/c-client/http.h
+++ b/imap/src/c-client/http.h
@@ -71,11 +71,13 @@ typedef struct http_header_data_s {
#define HTTP_MIME_URLENCODED "application/x-www-form-urlencoded"
#define HTTP_1_1_VERSION "HTTP/1.1"
+#define HTTP_OK 200
+#define HTTP_BAD 400
-#define GET_HTTPPORT (long) 456
-#define SET_HTTPPORT (long) 457
-#define GET_SSLHTTPPORT (long) 458
-#define SET_SSLHTTPPORT (long) 459
+#define GET_HTTPPORT (long) 490
+#define SET_HTTPPORT (long) 491
+#define GET_SSLHTTPPORT (long) 492
+#define SET_SSLHTTPPORT (long) 493
typedef struct http_status_s {
char *version;
diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c
index baf636a1..a9688d4b 100644
--- a/imap/src/c-client/mail.c
+++ b/imap/src/c-client/mail.c
@@ -93,6 +93,8 @@ static appenduid_t mailappenduid = NIL;
static oauth2getaccesscode_t oauth2getaccesscode = NIL;
static oauth2clientinfo_t oauth2clientinfo = NIL;
+
+static oauth2deviceinfo_t oauth2deviceinfo = NIL;
/* free elt extra stuff callback */
static freeeltsparep_t mailfreeeltsparep = NIL;
/* free envelope extra stuff callback */
@@ -680,6 +682,11 @@ void *mail_parameters (MAILSTREAM *stream,long function,void *value)
case GET_OA2CLIENTINFO:
ret = (void *) oauth2clientinfo;
break;
+ case SET_OA2DEVICEINFO:
+ oauth2deviceinfo = (oauth2deviceinfo_t) value;
+ case GET_OA2DEVICEINFO:
+ ret = (void *) oauth2deviceinfo;
+ break;
default:
if ((r = smtp_parameters (function,value)) != NULL) ret = r;
if ((r = env_parameters (function,value)) != NULL) ret = r;
@@ -6444,3 +6451,13 @@ void free_c_client_module_globals(void)
env_end();
tcp_end();
}
+
+/* OAUTH2 support code goes here. This is necessary because
+ * 1. it helps to coordinate two different methods, such as XOAUTH2 and
+ * OAUTHBEARER, which use the same code, so it can all go in one place
+ *
+ * 2. It helps with coordinating with the client when the server requires
+ * the deviceinfo method.
+ */
+
+#include "oauth2_aux.c"
diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h
index 39c7e4f8..174fb9e3 100644
--- a/imap/src/c-client/mail.h
+++ b/imap/src/c-client/mail.h
@@ -158,6 +158,8 @@
#define SET_OA2CLIENTGETACCESSCODE (long) 170
#define GET_OA2CLIENTINFO (long) 171
#define SET_OA2CLIENTINFO (long) 172
+#define GET_OA2DEVICEINFO (long) 173
+#define SET_OA2DEVICEINFO (long) 174
/* 2xx: environment */
#define GET_USERNAME (long) 201
@@ -298,6 +300,7 @@
#define SET_IDLETIMEOUT (long) 453
#define GET_FETCHLOOKAHEADLIMIT (long) 454
#define SET_FETCHLOOKAHEADLIMIT (long) 455
+/* HTTP SUPPORT DEFINES THEIR OWN SET_ AND GET_ CONSTANTS (490..493). See http.h */
/* 5xx: local file drivers */
#define GET_MBXPROTECTION (long) 500
@@ -1927,11 +1930,12 @@ int PFLUSH (void);
typedef enum {OA2_Id = 0, OA2_Secret, OA2_Code, OA2_RefreshToken,
OA2_Scope, OA2_Redirect,
OA2_GrantTypeforAccessToken, OA2_GrantTypefromRefreshToken,
- OA2_Response, OA2_State, OA2_Prompt, OA2_End} OA2_type;
+ OA2_Response, OA2_State, OA2_Prompt, OA2_DeviceCode, OA2_End} OA2_type;
typedef enum {OA2_GetAccessCode = 0,
OA2_GetAccessTokenFromAccessCode,
OA2_GetAccessTokenFromRefreshToken,
+ OA2_GetDeviceCode,
OA2_GetEnd} OA2_function;
typedef struct OA2_param_s {
@@ -1945,15 +1949,38 @@ typedef struct OA2_serverparam_s {
OA2_type params[OAUTH2_PARAM_NUMBER];
} OAUTH2_SERVER_METHOD_S;
+typedef struct device_code_s {
+ unsigned char *device_code;
+ unsigned char *user_code;
+ char *verification_uri;
+ int expires_in;
+ int interval;
+ unsigned char *message;
+} OAUTH2_DEVICECODE_S;
+
typedef struct oauth2_s {
unsigned char *name; /* provider name */
char *host[OAUTH2_TOT_EQUIV]; /* servers for which this data applies */
OAUTH2_PARAM_S param[OA2_End]; /* parameters name and values for this server */
/* servers, methods and parameters to retrieve access code and tokens */
OAUTH2_SERVER_METHOD_S server_mthd[OA2_GetEnd];
+ OAUTH2_DEVICECODE_S devicecode;
char *access_token;
unsigned long expiration;
+ unsigned int first_time:1; /* this is the first time we get credentials for this account */
} OAUTH2_S;
+typedef struct deviceproc_s {
+ OAUTH2_S *xoauth2; /* the full OAUTH2_S structure we need to update */
+ char code_success; /* code to say we succeeded */
+ char code_failure; /* code to say we failed */
+ char code_wait; /* code to say keep waiting */
+} OAUTH2_DEVICEPROC_S;
+
+/* Supporting external functions for XOAUTH2 and OAUTHBEARER */
typedef char *(*oauth2getaccesscode_t) (unsigned char *, char *, OAUTH2_S *, int *);
typedef void (*oauth2clientinfo_t)(unsigned char *name, char **id, char **secret);
+typedef void (*oauth2deviceinfo_t)(OAUTH2_S *, char *method);
+void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
+char *oauth2_generate_state(void);
+void oauth2deviceinfo_get_accesscode(void *, void *);
diff --git a/imap/src/c-client/oauth2_aux.c b/imap/src/c-client/oauth2_aux.c
new file mode 100644
index 00000000..9ff14174
--- /dev/null
+++ b/imap/src/c-client/oauth2_aux.c
@@ -0,0 +1,314 @@
+/*
+ * ========================================================================
+ * Copyright 2013-2020 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+
+
+/* OAUTH2 support code goes here. This is necessary because
+ * 1. it helps to coordinate two different methods, such as XOAUTH2 and
+ * OAUTHBEARER, which use the same code, so it can all go in one place
+ *
+ * 2. It helps with coordinating with the client when the server requires
+ * the deviceinfo method.
+ */
+
+#include "http.h"
+#include "json.h"
+#include "oauth2_aux.h"
+
+/* we generate something like a guid, but not care about
+ * anything, but that it is really random.
+ */
+char *oauth2_generate_state(void)
+{
+ char rv[37];
+ int i;
+
+ rv[0] = '\0';
+ for(i = 0; i < 4; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 2; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ sprintf(rv + strlen(rv), "%c", '-');
+ for(i = 0; i < 6; i++)
+ sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
+ rv[36] = '\0';
+ return cpystr(rv);
+}
+
+
+JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S, OAUTH2_S *, int *);
+
+#define LOAD_HTTP_PARAMS(X, Y) { \
+ int i; \
+ for(i = 0; (X).params[i] != OA2_End; i++){ \
+ OA2_type j = (X).params[i]; \
+ (Y)[i].name = oauth2->param[j].name; \
+ (Y)[i].value = oauth2->param[j].value; \
+ } \
+ (Y)[i].name = (Y)[i].value = NULL; \
+}
+
+JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S RefreshMethod, OAUTH2_S *oauth2, int *status)
+{
+ JSON_S *json = NULL;
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+ unsigned char *s;
+
+ LOAD_HTTP_PARAMS(RefreshMethod, params);
+ *status = 0;
+ if(strcmp(RefreshMethod.name, "POST") == 0
+ && ((s = http_post_param(RefreshMethod.urlserver, params, status)) != NULL)){
+ unsigned char *u = s;
+ json = json_parse(&u);
+ fs_give((void **) &s);
+ }
+ return json;
+}
+
+
+void
+mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
+ OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
+{
+ int i, status;
+ char *s = NULL;
+ JSON_S *json = NULL;
+
+ if(oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL){
+ oauth2clientinfo_t ogci =
+ (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
+
+ if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
+ &oauth2->param[OA2_Secret].value);
+ }
+
+ if (oauth2->param[OA2_Id].value == NULL || oauth2->param[OA2_Secret].value == NULL)
+ return;
+
+ /* Do we have a method to execute? */
+ if (oauth2->first_time && oauth2->server_mthd[OA2_GetDeviceCode].name){
+ oauth2deviceinfo_t ogdi;
+
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetDeviceCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ jx = json_body_value(json, "device_code");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.device_code = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "user_code");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.user_code = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "verification_uri");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.verification_uri = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->devicecode.expires_in = atoi((char *) jx->value);
+ break;
+ case JLong : oauth2->devicecode.expires_in = *(long *) jx->value;
+ break;
+ }
+
+ if((jx = json_body_value(json, "interval")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->devicecode.interval = atoi((char *) jx->value);
+ break;
+ case JLong : oauth2->devicecode.interval = *(long *) jx->value;
+ break;
+ }
+
+ jx = json_body_value(json, "message");
+ if(jx && jx->jtype == JString)
+ oauth2->devicecode.message = cpystr((char *) jx->value);
+
+ json_free(&json);
+
+ if(oauth2->devicecode.verification_uri && oauth2->devicecode.user_code){
+ ogdi = (oauth2deviceinfo_t) mail_parameters (NIL, GET_OA2DEVICEINFO, NIL);
+ if(ogdi) (*ogdi)(oauth2, method);
+ }
+ }
+ return;
+ }
+
+ /* else check if we have a refresh token, and in that case use it */
+
+ if(oauth2->param[OA2_RefreshToken].value){
+
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ json_free(&json);
+ }
+ return;
+ }
+ /*
+ * else, we do not have a refresh token, nor an access token.
+ * We need to start the process to get an access code. We use this
+ * to get an access token and refresh token.
+ */
+ { OAUTH2_SERVER_METHOD_S RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+
+ LOAD_HTTP_PARAMS(RefreshMethod, params);
+
+ if(strcmp(RefreshMethod.name, "GET") == 0){
+ char *url = http_get_param_url(RefreshMethod.urlserver, params);
+ oauth2getaccesscode_t ogac =
+ (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
+
+ if(ogac)
+ oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
+ }
+
+ if(oauth2->param[OA2_Code].value){
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+
+ switch(status){
+ case HTTP_OK : jx = json_body_value(json, "refresh_token");
+ if(jx && jx->jtype == JString)
+ oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ jx = json_body_value(json, "expires_in");
+ if(jx && jx->jtype == JString)
+ oauth2->expiration = time(0) + atol((char *) jx->value);
+
+ break;
+
+ case HTTP_BAD : break;
+
+ default : { char tmp[100];
+ sprintf(tmp, "Oauth Client Received Code %d", status);
+ fatal (tmp);
+ }
+ }
+
+ json_free(&json);
+ }
+ }
+ return;
+ }
+
+ /* Else, does this server use the /devicecode method? */
+}
+
+void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
+{
+ OAUTH2_DEVICEPROC_S *oad = (OAUTH2_DEVICEPROC_S *) inp;
+ OAUTH2_S *oauth2 = oad->xoauth2;
+ OAUTH2_DEVICECODE_S *dcode = &oauth2->devicecode;
+ int done = 0, status, rv;
+ HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
+ JSON_S *json;
+
+ if(dcode->device_code && oauth2->param[OA2_DeviceCode].value == NULL)
+ oauth2->param[OA2_DeviceCode].value = cpystr(dcode->device_code);
+
+ rv = OA2_CODE_WAIT; /* wait by default */
+ json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
+
+ if(json != NULL){
+ JSON_X *jx;
+ char *error;
+
+ switch(status){
+ case HTTP_BAD : jx = json_body_value(json, "error");
+ if(jx && jx->jtype == JString)
+ error = cpystr((char *) jx->value);
+ else
+ break;
+
+ if(compare_cstring(error, "authorization_pending") == 0)
+ rv = OA2_CODE_WAIT;
+ else if(compare_cstring(error, "authorization_declined") == 0)
+ rv = OA2_CODE_FAIL;
+ else if(compare_cstring(error, "bad_verification_code") == 0)
+ rv = OA2_CODE_FAIL;
+ else if(compare_cstring(error, "expired_token") == 0)
+ rv = OA2_CODE_FAIL;
+ else /* keep waiting? */
+ rv = OA2_CODE_WAIT;
+
+ break;
+
+ case HTTP_OK : jx = json_body_value(json, "refresh_token");
+ if(jx && jx->jtype == JString)
+ oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
+
+ jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
+
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+
+ rv = OA2_CODE_SUCCESS;
+
+ break;
+
+ default : { char tmp[100];
+ sprintf(tmp, "Oauth device Received Code %d", status);
+ fatal (tmp);
+ }
+ }
+
+ json_free(&json);
+ }
+
+ *(int *)outp = rv;
+}
diff --git a/imap/src/c-client/oauth2_aux.h b/imap/src/c-client/oauth2_aux.h
new file mode 100644
index 00000000..b076da3c
--- /dev/null
+++ b/imap/src/c-client/oauth2_aux.h
@@ -0,0 +1,25 @@
+/*
+ * ========================================================================
+ * Copyright 2013-2020 Eduardo Chappa
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * ========================================================================
+ */
+#ifndef C_CLIENT_OAUTH2_AUX_INCLUDED
+#define C_CLIENT_OAUTH2_AUX_INCLUDED
+
+#define OA2_CODE_WAIT 1
+#define OA2_CODE_FAIL -1
+#define OA2_CODE_SUCCESS 0
+
+void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
+char *oauth2_generate_state(void);
+void oauth2deviceinfo_get_accesscode(void *, void *);
+
+#endif /* C_CLIENT_OAUTH2_AUX_INCLUDED */
+
diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile
index cdcbf315..5c9c6051 100644
--- a/imap/src/osdep/unix/Makefile
+++ b/imap/src/osdep/unix/Makefile
@@ -897,7 +897,7 @@ dummy.o: mail.h misc.h osdep.h dummy.h
fdstring.o: mail.h misc.h osdep.h fdstring.h
flstring.o: mail.h misc.h osdep.h flstring.h
imap4r1.o: mail.h misc.h osdep.h imap4r1.h rfc822.h
-mail.o: mail.h misc.h osdep.h rfc822.h linkage.h json.h
+mail.o: mail.h misc.h osdep.h rfc822.h linkage.h http.h json.h oauth2_aux.h oauth2_aux.c
mbx.o: mail.h misc.h osdep.h dummy.h
mh.o: mail.h misc.h osdep.h dummy.h
mix.o: mail.h misc.h osdep.h dummy.h
@@ -925,14 +925,14 @@ http.o: mail.h misc.h osdep.h utf8.h http.h json.h
# OS-dependent
osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \
- osdep.h env_unix.h tcp_unix.h \
+ osdep.h env_unix.h tcp_unix.h oauth2_aux.h \
osdep.c env_unix.c fs_unix.c ftl_unix.c nl_unix.c tcp_unix.c ip_unix.c\
auths.c crexcl.c flockcyg.c flocklnx.c flocksim.c fsync.c \
gethstid.c getspnam.c \
gr_wait.c gr_wait4.c gr_waitp.c \
kerb_mit.c \
auth_ext.c auth_gss.c auth_log.c auth_md5.c auth_ntl.c \
- auth_bea.c auth_oa2.c auth_pla.c \
+ auth_bea.c auth_oa2.c auth_pla.c oauth2_aux.c \
pmatch.c scandir.c setpgrp.c strerror.c truncate.c write.c \
memmove.c memmove2.c memset.c \
tz_bsd.c tz_nul.c tz_sv4.c \