summaryrefslogtreecommitdiff
path: root/imap/src/c-client/oauth2_aux.c
diff options
context:
space:
mode:
Diffstat (limited to 'imap/src/c-client/oauth2_aux.c')
-rw-r--r--imap/src/c-client/oauth2_aux.c314
1 files changed, 314 insertions, 0 deletions
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;
+}