* Additional addition of doucmentation for XOAUTH2, some fixes in the documentation,
[alpine.git] / imap / src / c-client / oauth2_aux.c
bloba23f7c287cc2b377009ae5c2ea512f0e80ae83a4
1 /*
2 * ========================================================================
3 * Copyright 2013-2020 Eduardo Chappa
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * ========================================================================
15 /* OAUTH2 support code goes here. This is necessary because
16 * 1. it helps to coordinate two different methods, such as XOAUTH2 and
17 * OAUTHBEARER, which use the same code, so it can all go in one place
19 * 2. It helps with coordinating with the client when the server requires
20 * the deviceinfo method.
23 #include "http.h"
24 #include "json.h"
25 #include "oauth2_aux.h"
27 /* we generate something like a guid, but not care about
28 * anything, but that it is really random.
30 char *oauth2_generate_state(void)
32 char rv[37];
33 int i;
35 rv[0] = '\0';
36 for(i = 0; i < 4; i++)
37 sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
38 sprintf(rv + strlen(rv), "%c", '-');
39 for(i = 0; i < 2; i++)
40 sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
41 sprintf(rv + strlen(rv), "%c", '-');
42 for(i = 0; i < 2; i++)
43 sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
44 sprintf(rv + strlen(rv), "%c", '-');
45 for(i = 0; i < 2; i++)
46 sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
47 sprintf(rv + strlen(rv), "%c", '-');
48 for(i = 0; i < 6; i++)
49 sprintf(rv + strlen(rv), "%x", (unsigned int) (random() % 256));
50 rv[36] = '\0';
51 return cpystr(rv);
55 JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S, OAUTH2_S *, int *);
57 #define LOAD_HTTP_PARAMS(X, Y) { \
58 int i; \
59 for(i = 0; (X).params[i] != OA2_End; i++){ \
60 OA2_type j = (X).params[i]; \
61 (Y)[i].name = oauth2->param[j].name; \
62 (Y)[i].value = oauth2->param[j].value; \
63 } \
64 (Y)[i].name = (Y)[i].value = NULL; \
67 JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S RefreshMethod, OAUTH2_S *oauth2, int *status)
69 JSON_S *json = NULL;
70 HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
71 unsigned char *s;
73 LOAD_HTTP_PARAMS(RefreshMethod, params);
74 *status = 0;
75 if(strcmp(RefreshMethod.name, "POST") == 0
76 && ((s = http_post_param(RefreshMethod.urlserver, params, status)) != NULL)){
77 unsigned char *u = s;
78 json = json_parse(&u);
79 fs_give((void **) &s);
81 return json;
85 void
86 mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
87 OAUTH2_S *oauth2, unsigned long trial, int *tryanother)
89 int i, status;
90 char *s = NULL;
91 JSON_S *json = NULL;
93 if(oauth2->param[OA2_Id].value == NULL
94 || (oauth2->require_secret && oauth2->param[OA2_Secret].value == NULL)){
95 oauth2clientinfo_t ogci =
96 (oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
98 if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
99 &oauth2->param[OA2_Secret].value);
102 if(oauth2->param[OA2_Id].value == NULL
103 || (oauth2->require_secret && oauth2->param[OA2_Secret].value == NULL))
104 return;
106 /* Do we have a method to execute? */
107 if (oauth2->first_time && oauth2->server_mthd[OA2_GetDeviceCode].name){
108 oauth2deviceinfo_t ogdi;
110 json = oauth2_json_reply(oauth2->server_mthd[OA2_GetDeviceCode], oauth2, &status);
112 if(json != NULL){
113 JSON_X *jx;
115 jx = json_body_value(json, "device_code");
116 if(jx && jx->jtype == JString)
117 oauth2->devicecode.device_code = cpystr((char *) jx->value);
119 jx = json_body_value(json, "user_code");
120 if(jx && jx->jtype == JString)
121 oauth2->devicecode.user_code = cpystr((char *) jx->value);
123 jx = json_body_value(json, "verification_uri");
124 if(jx && jx->jtype == JString)
125 oauth2->devicecode.verification_uri = cpystr((char *) jx->value);
127 if((jx = json_body_value(json, "expires_in")) != NULL)
128 switch(jx->jtype){
129 case JString: oauth2->devicecode.expires_in = atoi((char *) jx->value);
130 break;
131 case JLong : oauth2->devicecode.expires_in = *(long *) jx->value;
132 break;
135 if((jx = json_body_value(json, "interval")) != NULL)
136 switch(jx->jtype){
137 case JString: oauth2->devicecode.interval = atoi((char *) jx->value);
138 break;
139 case JLong : oauth2->devicecode.interval = *(long *) jx->value;
140 break;
143 jx = json_body_value(json, "message");
144 if(jx && jx->jtype == JString)
145 oauth2->devicecode.message = cpystr((char *) jx->value);
147 json_free(&json);
149 if(oauth2->devicecode.verification_uri && oauth2->devicecode.user_code){
150 ogdi = (oauth2deviceinfo_t) mail_parameters (NIL, GET_OA2DEVICEINFO, NIL);
151 if(ogdi) (*ogdi)(oauth2, method);
154 return;
157 /* else check if we have a refresh token, and in that case use it */
159 if(oauth2->param[OA2_RefreshToken].value){
161 json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken], oauth2, &status);
163 if(json != NULL){
164 JSON_X *jx;
166 jx = json_body_value(json, "access_token");
167 if(jx && jx->jtype == JString)
168 oauth2->access_token = cpystr((char *) jx->value);
170 if((jx = json_body_value(json, "expires_in")) != NULL)
171 switch(jx->jtype){
172 case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
173 break;
174 case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
175 break;
178 json_free(&json);
180 return;
183 * else, we do not have a refresh token, nor an access token.
184 * We need to start the process to get an access code. We use this
185 * to get an access token and refresh token.
187 { OAUTH2_SERVER_METHOD_S RefreshMethod = oauth2->server_mthd[OA2_GetAccessCode];
188 HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
190 LOAD_HTTP_PARAMS(RefreshMethod, params);
192 if(strcmp(RefreshMethod.name, "GET") == 0){
193 char *url = http_get_param_url(RefreshMethod.urlserver, params);
194 oauth2getaccesscode_t ogac =
195 (oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
197 if(ogac)
198 oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
201 if(oauth2->param[OA2_Code].value){
202 json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
204 if(json != NULL){
205 JSON_X *jx;
207 switch(status){
208 case HTTP_OK : jx = json_body_value(json, "refresh_token");
209 if(jx && jx->jtype == JString)
210 oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
212 jx = json_body_value(json, "access_token");
213 if(jx && jx->jtype == JString)
214 oauth2->access_token = cpystr((char *) jx->value);
216 if((jx = json_body_value(json, "expires_in")) != NULL)
217 switch(jx->jtype){
218 case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
219 break;
220 case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
221 break;
224 jx = json_body_value(json, "expires_in");
225 if(jx && jx->jtype == JString)
226 oauth2->expiration = time(0) + atol((char *) jx->value);
228 break;
230 case HTTP_BAD : break;
232 default : { char tmp[100];
233 sprintf(tmp, "Oauth Client Received Code %d", status);
234 fatal (tmp);
238 json_free(&json);
241 return;
244 /* Else, does this server use the /devicecode method? */
247 void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
249 OAUTH2_DEVICEPROC_S *oad = (OAUTH2_DEVICEPROC_S *) inp;
250 OAUTH2_S *oauth2 = oad->xoauth2;
251 OAUTH2_DEVICECODE_S *dcode = &oauth2->devicecode;
252 int done = 0, status, rv;
253 HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
254 JSON_S *json;
256 if(dcode->device_code && oauth2->param[OA2_DeviceCode].value == NULL)
257 oauth2->param[OA2_DeviceCode].value = cpystr(dcode->device_code);
259 rv = OA2_CODE_WAIT; /* wait by default */
260 json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromAccessCode], oauth2, &status);
262 if(json != NULL){
263 JSON_X *jx;
264 char *error;
266 switch(status){
267 case HTTP_BAD : jx = json_body_value(json, "error");
268 if(jx && jx->jtype == JString)
269 error = cpystr((char *) jx->value);
270 else
271 break;
273 if(compare_cstring(error, "authorization_pending") == 0)
274 rv = OA2_CODE_WAIT;
275 else if(compare_cstring(error, "authorization_declined") == 0)
276 rv = OA2_CODE_FAIL;
277 else if(compare_cstring(error, "bad_verification_code") == 0)
278 rv = OA2_CODE_FAIL;
279 else if(compare_cstring(error, "expired_token") == 0)
280 rv = OA2_CODE_FAIL;
281 else /* keep waiting? */
282 rv = OA2_CODE_WAIT;
284 break;
286 case HTTP_OK : jx = json_body_value(json, "refresh_token");
287 if(jx && jx->jtype == JString)
288 oauth2->param[OA2_RefreshToken].value = cpystr((char *) jx->value);
290 jx = json_body_value(json, "access_token");
291 if(jx && jx->jtype == JString)
292 oauth2->access_token = cpystr((char *) jx->value);
294 if((jx = json_body_value(json, "expires_in")) != NULL)
295 switch(jx->jtype){
296 case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
297 break;
298 case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
299 break;
302 rv = OA2_CODE_SUCCESS;
304 break;
306 default : { char tmp[100];
307 sprintf(tmp, "Oauth device Received Code %d", status);
308 fatal (tmp);
312 json_free(&json);
315 *(int *)outp = rv;