From c024a78dbaa9b42db7f18b0fea1894c41e2b0d67 Mon Sep 17 00:00:00 2001 From: Eduardo Chappa Date: Sat, 4 May 2019 12:41:11 -0600 Subject: * Initial release of XOAUTH2 authentication support in Alpine for Gmail. --- imap/src/c-client/auth_log.c | 10 +- imap/src/c-client/auth_md5.c | 10 +- imap/src/c-client/auth_ntl.c | 13 +- imap/src/c-client/auth_oa2.c | 359 ++++++++++++ imap/src/c-client/auth_pla.c | 10 +- imap/src/c-client/http.c | 1215 ++++++++++++++++++++++++++++++++++++++++ imap/src/c-client/http.h | 116 ++++ imap/src/c-client/imap4r1.c | 30 +- imap/src/c-client/json.c | 497 ++++++++++++++++ imap/src/c-client/json.h | 32 ++ imap/src/c-client/mail.c | 18 +- imap/src/c-client/mail.h | 57 +- imap/src/c-client/nntp.c | 10 +- imap/src/c-client/pop3.c | 10 +- imap/src/c-client/rfc822.c | 2 +- imap/src/c-client/sslio.h | 2 + imap/src/c-client/tcp.h | 1 + imap/src/dmail/dmail.c | 6 +- imap/src/imapd/imapd.c | 9 +- imap/src/ipopd/ipop2d.c | 11 +- imap/src/ipopd/ipop3d.c | 12 +- imap/src/mailutil/mailutil.c | 36 +- imap/src/mtest/mtest.c | 11 +- imap/src/osdep/unix/Makefile | 12 +- imap/src/osdep/unix/ssl_unix.c | 26 +- imap/src/osdep/unix/tcp_unix.c | 22 + imap/src/tmail/tmail.c | 6 +- 27 files changed, 2480 insertions(+), 63 deletions(-) create mode 100644 imap/src/c-client/auth_oa2.c create mode 100644 imap/src/c-client/http.c create mode 100644 imap/src/c-client/http.h create mode 100644 imap/src/c-client/json.c create mode 100644 imap/src/c-client/json.h (limited to 'imap/src') diff --git a/imap/src/c-client/auth_log.c b/imap/src/c-client/auth_log.c index 6615744f..a54d6365 100644 --- a/imap/src/c-client/auth_log.c +++ b/imap/src/c-client/auth_log.c @@ -58,16 +58,15 @@ long auth_login_client (authchallenge_t challenger,authrespond_t responder, char *service,NETMBX *mb,void *stream, unsigned long *trial,char *user) { - char pwd[MAILTMPLEN]; + char *pwd = NIL; void *challenge; unsigned long clen; long ret = NIL; /* get user name prompt */ if ((challenge = (*challenger) (stream,&clen)) != NULL) { fs_give ((void **) &challenge); - pwd[0] = NIL; /* prompt user */ - mm_login (mb,user,pwd,*trial); - if (!pwd[0]) { /* user requested abort */ + mm_login (mb,user, &pwd,*trial); + if (!pwd) { /* user requested abort */ (*responder) (stream,NIL,0); *trial = 0; /* cancel subsequent attempts */ ret = LONGT; /* will get a BAD response back */ @@ -85,9 +84,10 @@ long auth_login_client (authchallenge_t challenger,authrespond_t responder, ret = LONGT; /* check the authentication */ } } + fs_give((void **) &pwd); } } - memset (pwd,0,MAILTMPLEN); /* erase password */ + if(pwd) fs_give((void **) &pwd); if (!ret) *trial = 65535; /* don't retry if bad protocol */ return ret; } diff --git a/imap/src/c-client/auth_md5.c b/imap/src/c-client/auth_md5.c index 8c989769..d4e7024b 100644 --- a/imap/src/c-client/auth_md5.c +++ b/imap/src/c-client/auth_md5.c @@ -99,15 +99,14 @@ long auth_md5_client (authchallenge_t challenger,authrespond_t responder, char *service,NETMBX *mb,void *stream, unsigned long *trial,char *user) { - char pwd[MAILTMPLEN],hshbuf[2*MD5DIGLEN + 1]; + char *pwd = NIL,hshbuf[2*MD5DIGLEN + 1]; void *challenge; unsigned long clen; long ret = NIL; /* get challenge */ if ((challenge = (*challenger) (stream,&clen)) != NULL) { - pwd[0] = NIL; /* prompt user */ - mm_login (mb,user,pwd,*trial); - if (!pwd[0]) { /* user requested abort */ + mm_login (mb,user, &pwd,*trial); + if (!pwd) { /* user requested abort */ fs_give ((void **) &challenge); (*responder) (stream,NIL,0); *trial = 0; /* cancel subsequent attempts */ @@ -126,9 +125,10 @@ long auth_md5_client (authchallenge_t challenger,authrespond_t responder, ret = LONGT; /* check the authentication */ } } + fs_give((void **) &pwd); } } - memset (pwd,0,MAILTMPLEN); /* erase password in case not overwritten */ + if(pwd) fs_give((void **) &pwd); if (!ret) *trial = 65535; /* don't retry if bad protocol */ return ret; } diff --git a/imap/src/c-client/auth_ntl.c b/imap/src/c-client/auth_ntl.c index 0afe99fc..52ae9048 100644 --- a/imap/src/c-client/auth_ntl.c +++ b/imap/src/c-client/auth_ntl.c @@ -1,6 +1,7 @@ /* ======================================================================== - * Copyright 1988-2008 University of Washington + * Copyright 2018 Eduardo Chappa * Copyright 2015 Imagination Technologies + * Copyright 1988-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +57,7 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder, tSmbNtlmAuthRequest request; char tbuf[MAILTMPLEN]; char ubuf[MAILTMPLEN]; - char pass[MAILTMPLEN]; + char *pass = NIL; unsigned long clen; unsigned long ulen; unsigned long dlen; @@ -66,9 +67,8 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder, /* get initial (empty) challenge */ if (challenge = (*challenger) (stream, &clen)) { fs_give ((void **) &challenge); - pass[0] = NIL; /* prompt user */ - mm_login (mb, user, pass, *trial); - if (!pass[0]) { /* user requested abort */ + mm_login (mb, user, &pass, *trial); + if (!pass) { /* user requested abort */ (*responder) (stream, NIL, 0); *trial = 0; /* cancel subsequent attempts */ ret = LONGT; /* will get a BAD response back */ @@ -104,9 +104,10 @@ long auth_ntlm_client (authchallenge_t challenger, authrespond_t responder, } } } + if(pass) fs_give((void **) &pass); } } - memset (pass,0,MAILTMPLEN); /* erase password */ + if(pass) fs_give((void **) &pass); if (!ret) *trial = 65535; /* don't retry if bad protocol */ return ret; } diff --git a/imap/src/c-client/auth_oa2.c b/imap/src/c-client/auth_oa2.c new file mode 100644 index 00000000..d77b0102 --- /dev/null +++ b/imap/src/c-client/auth_oa2.c @@ -0,0 +1,359 @@ +/* ======================================================================== + * Copyright 2018 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 + * + * + * ======================================================================== + */ + +long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user); + +void mm_login_oauth2_c_client_method (NETMBX *, char *, OAUTH2_S *, unsigned long, int *); + +char *oauth2_generate_state(void); + +AUTHENTICATOR auth_oa2 = { + AU_HIDE, /* hidden */ + OA2NAME, /* authenticator name */ + NIL, /* always valid */ + auth_oauth2_client, /* client method */ + NIL, /* server method */ + NIL /* next authenticator */ +}; + +#define OAUTH2_USER "user=" +#define OAUTH2_BEARER "auth=Bearer " + +/* we generate something like a guid, but not care about + * anything, but that it is really random. + */ +char *oauth2_generate_state(void) +{ + char rv[36]; + int i; + + rv[0] = '\0'; + for(i = 0; i < 4; i++) + sprintf(rv + strlen(rv), "%x", random() % 256); + sprintf(rv + strlen(rv), "%c", '-'); + for(i = 0; i < 2; i++) + sprintf(rv + strlen(rv), "%x", random() % 256); + sprintf(rv + strlen(rv), "%c", '-'); + for(i = 0; i < 2; i++) + sprintf(rv + strlen(rv), "%x", random() % 256); + sprintf(rv + strlen(rv), "%c", '-'); + for(i = 0; i < 2; i++) + sprintf(rv + strlen(rv), "%x", random() % 256); + sprintf(rv + strlen(rv), "%c", '-'); + for(i = 0; i < 6; i++) + sprintf(rv + strlen(rv), "%x", random() % 256); + rv[36] = '\0'; + return cpystr(rv); +} + + +/* Client authenticator + * Accepts: challenger function + * responder function + * SASL service name + * parsed network mailbox structure + * stream argument for functions + * pointer to current trial count + * returned user name + * Returns: T if success, NIL otherwise, number of trials incremented if retry + */ + +long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, + char *service,NETMBX *mb,void *stream, + unsigned long *trial,char *user) +{ + char *u; + void *challenge; + unsigned long clen; + long ret = NIL; + OAUTH2_S oauth2; + int tryanother = 0; /* try another authentication method */ + + memset((void *) &oauth2, 0, sizeof(OAUTH2_S)); + /* snarl if not SSL/TLS session */ + if (!mb->sslflag && !mb->tlsflag) + mm_log ("SECURITY PROBLEM: insecure server advertised AUTH=XOAUTH2",WARN); + + /* get initial (empty) challenge */ + if ((challenge = (*challenger) (stream,&clen)) != NULL) { + fs_give ((void **) &challenge); + if (clen) { /* abort if challenge non-empty */ + mm_log ("Server bug: non-empty initial XOAUTH2 challenge",WARN); + (*responder) (stream,NIL,0); + ret = LONGT; /* will get a BAD response back */ + } + + /* + * the call to mm_login_method is supposed to return the username + * and access token. If this is not known by the application, then + * we call our internal functions to get a refresh token, access token + * and expiration time. + * + * Programmers note: We always call mm_login_method at least once. + * The first call is done with empty parameters and it indicates + * we are asking the application to load it the best it can. Then + * the application returns the loaded value. If we get it fully loaded + * we use the value, but if we don't get it fully loaded, we call + * our internal functions to try to fully load it. + * + * If in the internal call we get it loaded, then we use these values + * to log in. At this time we call the app to send back the loaded values + * so it can save them for the next time we call. This is done in a + * second call to mm_login_method. If we do not get oauth2 back with + * fully loaded values we cancel authentication completely. If the + * user cannot load this variable, then the user, through the client, + * should disable XOAUTH2 as an authentication method and try a new one. + * + * If we make our internal mm_login_oauth2_c_client_method call, + * we might still need to call the client to get the access token, + * this is done through a callback declared by the client. If we need + * that information, but the callback is not declared, this process + * will fail, so we will check if that call is declared as soon as we + * know we should start it, and we will only start it if this callback + * is declared. + * + * We start this process by calling the client and loading oauth2 + * with the required information as best as we can. + */ + + mm_login_method (mb, user, (void *) &oauth2, *trial, OA2NAME); + + if(oauth2.param[OA2_State].value) + fs_give((void **) &oauth2.param[OA2_State].value); + + oauth2.param[OA2_State].value = oauth2_generate_state(); + + /* + * If we did not get an access token, try to get one through + * our internal functions + */ + if(oauth2.name && oauth2.access_token == NIL){ + char *RefreshToken = NIL; + + if(oauth2.param[OA2_RefreshToken].value) + RefreshToken = cpystr(oauth2.param[OA2_RefreshToken].value); + + mm_login_oauth2_c_client_method (mb, user, &oauth2, *trial, &tryanother); + + /* + * if we got an access token from the c_client_method call, + * or somehow there was a change in the refresh token, return + * it to the client so that it will save it. + */ + + if(!tryanother + && (oauth2.access_token + || (!RefreshToken && oauth2.param[OA2_RefreshToken].value) + || (RefreshToken && oauth2.param[OA2_RefreshToken].value + && strcmp(RefreshToken, oauth2.param[OA2_RefreshToken].value)))) + mm_login_method (mb, user, (void *) &oauth2, *trial, OA2NAME); + } + + /* empty challenge or user requested abort or client does not have info */ + if(!oauth2.access_token) { + (*responder) (stream,NIL,0); + *trial = 0; /* cancel subsequent attempts */ + ret = LONGT; /* will get a BAD response back */ + } + else { + unsigned long rlen = strlen(OAUTH2_USER) + strlen(user) + + strlen(OAUTH2_BEARER) + strlen(oauth2.access_token) + 1 + 2; + char *response = (char *) fs_get (rlen); + char *t = response; /* copy authorization id */ + for (u = OAUTH2_USER; *u; *t++ = *u++); + for (u = user; *u; *t++ = *u++); + *t++ = '\001'; /* delimiting ^A */ + for (u = OAUTH2_BEARER; *u; *t++ = *u++); + for (u = oauth2.access_token; *u; *t++ = *u++); + *t++ = '\001'; /* delimiting ^A */ + *t++ = '\001'; /* delimiting ^A */ + if ((*responder) (stream,response,rlen)) { + if ((challenge = (*challenger) (stream,&clen)) != NULL) + fs_give ((void **) &challenge); + else { + ++*trial; /* can try again if necessary */ + ret = *trial < 3 ? LONGT : NIL; /* check the authentication */ + /* When the Access Token expires we fail once, but after we get + * a new one, we should succeed at the second attempt. If the + * Refresh Token has expired somehow, we invalidate it if we + * reach *trial to 3. This forces the process to restart later on. + */ + if(*trial == 3){ + if(oauth2.param[OA2_State].value) + fs_give((void **) &oauth2.param[OA2_State].value); + fs_give((void **) &oauth2.param[OA2_RefreshToken].value); + fs_give((void **) &oauth2.access_token); + oauth2.expiration = 0L; + } + } + } + fs_give ((void **) &response); + } + } + if (!ret || !oauth2.name || tryanother) + *trial = 65535; /* don't retry if bad protocol */ + return ret; +} + +/* + * 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, + 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; + + if(oauth2->param[OA2_Id].value == NULL + || oauth2->param[OA2_Secret].value == NULL){ + /* + * We need to implement client-side entering client_id and + * client_secret, and other parameters. In the mean time, bail out. + */ + 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); + else if(strcmp(RefreshMethod.name, "POST2") == 0) + s = http_post_param2(RefreshMethod.urlserver, params); + + if(s){ + unsigned char *t, *u; + if((t = strstr(s, "\r\n\r\n")) && (u = strchr(t, '{'))) + 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){ + if(jx->jtype == JString){ + unsigned long *l = fs_get(sizeof(unsigned long)); + *l = atol((char *) jx->value); + fs_give(&jx->value); + jx->value = (void *) l; + jx->jtype = JLong; + } + if(jx->jtype == JLong) + oauth2->expiration = time(0) + *(unsigned long *) 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, 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); + else if(strcmp(RefreshMethod.name, "POST2") == 0) + s = http_post_param2(RefreshMethod.urlserver, params); + + if(s){ + unsigned char *t, *u; + if((t = strstr(s, "\r\n\r\n")) && (u = strchr(t, '{'))) + 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){ + if(jx->jtype == JString){ + unsigned long *l = fs_get(sizeof(unsigned long)); + *l = atol((char *) jx->value); + fs_give(&jx->value); + jx->value = (void *) l; + jx->jtype = JLong; + } + if(jx->jtype == JLong) + oauth2->expiration = time(0) + *(unsigned long *) jx->value; + } + json_free(&json); + } + } + return; + } +} diff --git a/imap/src/c-client/auth_pla.c b/imap/src/c-client/auth_pla.c index ef991f1d..6cc26c71 100644 --- a/imap/src/c-client/auth_pla.c +++ b/imap/src/c-client/auth_pla.c @@ -55,7 +55,7 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder, char *service,NETMBX *mb,void *stream, unsigned long *trial,char *user) { - char *u,pwd[MAILTMPLEN]; + char *u, *pwd = NIL; void *challenge; unsigned long clen; long ret = NIL; @@ -70,9 +70,8 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder, (*responder) (stream,NIL,0); ret = LONGT; /* will get a BAD response back */ } - pwd[0] = NIL; /* prompt user if empty challenge */ - mm_login (mb,user,pwd,*trial); - if (!pwd[0]) { /* empty challenge or user requested abort */ + mm_login (mb,user, &pwd,*trial); + if (!pwd) { /* empty challenge or user requested abort */ (*responder) (stream,NIL,0); *trial = 0; /* cancel subsequent attempts */ ret = LONGT; /* will get a BAD response back */ @@ -100,9 +99,10 @@ long auth_plain_client (authchallenge_t challenger,authrespond_t responder, } memset (response,0,rlen); /* erase credentials */ fs_give ((void **) &response); + fs_give ((void **) &pwd); } } - memset (pwd,0,MAILTMPLEN); /* erase password */ + if(pwd) fs_give ((void **) &pwd); if (!ret) *trial = 65535; /* don't retry if bad protocol */ return ret; } diff --git a/imap/src/c-client/http.c b/imap/src/c-client/http.c new file mode 100644 index 00000000..1e580c6a --- /dev/null +++ b/imap/src/c-client/http.c @@ -0,0 +1,1215 @@ +/* + * Copyright 2018 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 + * + */ +#include +#include +#include +#include "c-client.h" +#include "flstring.h" +#include "netmsg.h" +#include "http.h" + +//char t[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!#$%&'*+-.^_`|~"; +static char http_notok[] = "\1\2\3\4\5\6\7\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\40\42\50\51\54\57\72\73\74\75\76\77\100\133\134\135\173\175\177\200\201\202\203\204\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367\370\371\372\373\374\375\376\377"; +static char http_noparam_val[] = "\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\42\134\177"; + +#define HTTPTCPPORT (long) 80 /* assigned TCP contact port */ +#define HTTPSSLPORT (long) 443 /* assigned SSL TCP contact port */ + +typedef struct http_request_s { + char *request; + char *header; + char *body; +} HTTP_REQUEST_S; + +#define HTP_NOVAL 0x001 /* the header accepts parameters without value */ + +#define HTP_UNLIMITED (-1) /* parse and infinite list */ + +#if 0 +typedef struct http_val_param_s { + char *value; + PARAMETER *plist; +} HTTP_VAL_PARAM_S; + +typedef struct http_param_list_s { + HTTP_VAL_PARAM_S *vp; + struct http_param_list_s *next; +} HTTP_PARAM_LIST_S; + +typedef struct http_header_value_s { + char *data; + HTTP_PARAM_LIST_S *p; +} HTTP_HEADER_S; + +typedef struct http_header_data_s { + HTTP_HEADER_S *accept, /* RFC 7231, Section 5.3.2 */ + *accept_charset, /* RFC 7231, Section 5.3.3 */ + *accept_encoding, /* RFC 7231, Section 5.3.4 */ + *accept_language, /* RFC 7231, Section 5.3.5 */ + *accept_ranges, /* RFC 7233, Section 2.3 */ + *age, /* RFC 7234, Section 5.1 */ + *allow, /* RFC 7231, Section 7.4.1 */ + *cache_control, /* RFC 7234, Section 5.2 */ + *connection, /* RFC 7230, Section 6.1 */ + *content_encoding, /* RFC 7231, Section 3.1.2.2 */ + *content_disposition, /* RFC 6266 */ + *content_language, /* RFC 7231, Section 3.1.3.2 */ + *content_length, /* RFC 7230, Section 3.3.2 */ + *content_location, /* RFC 7231, Section 3.1.4.2 */ + *content_type, /* RFC 7231, Section 3.1.1.5 */ + *date, /* RFC 7231, Section 7.1.1.2 */ + *etag, /* RFC 7232, Section 2.3 */ + *expect, /* RFC 7231, Section 5.1.1 */ + *expires, /* RFC 7234, Section 5.3 */ + *from, /* RFC 7231, Section 5.5.1 */ + *host, /* RFC 7230, Section 5.4 */ + *last_modified, /* RFC 7232, Section 2.2 */ + *location, /* RFC 7231, Section 7.1.2 */ + *max_forwards, /* RFC 7231, Section 5.1.2 */ + *mime_version, /* RFC 7231, Appendix A.1 */ + *pragma, /* RFC 7234, Section 5.4 */ + *proxy_authenticate, /* RFC 7235, Section 4.3 */ + *referer, /* RFC 7231, Section 5.5.2 */ + *retry_after, /* RFC 7231, Section 7.1.3 */ + *server, /* RFC 7231, Section 7.4.2 */ + *te, /* RFC 7230, Section 4.3 */ + *trailer, /* RFC 7230, Section 4.4 */ + *transfer_encoding, /* RFC 7230, Section 3.3.1 */ + *upgrade, /* RFC 7230, Section 6.7 */ + *user_agent, /* RFC 7231, Section 5.5.3 */ + *via, /* RFC 7230, Section 5.7.1 */ + *vary, /* RFC 7231, Section 7.1.4 */ + *warning, /* RFC 7234, Section 5.5 */ + *www_authenticate; /* RFC 7235, Section 4.1 */ +} HTTP_HEADER_DATA_S; +#endif + +/* helper functions */ +HTTP_STATUS_S *http_status_line_get(char *); +void http_status_line_free(HTTP_STATUS_S **); +HTTP_REQUEST_S *http_request_get(void); +void http_request_free(HTTP_REQUEST_S **); +char *http_request_line(char *, char *, char *); +void http_add_header(HTTP_REQUEST_S **, char *, char *); +void http_add_body(HTTP_REQUEST_S **, char *); +void buffer_add(char **, char *); +char *hex_escape_url_part(char *, char *); +char *encode_url_body_part(char *, char *); + + +/* HTTP function prototypes */ +int http_valid_net_parse (char *, NETMBX *); +void *http_parameters (long function,void *value); + +long http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *); +long http_reply (HTTPSTREAM *stream); +long http_fake (HTTPSTREAM *stream, char *text); + +void http_skipows(char **); +void http_remove_trailing_ows(char *); + +int valid_dquote_text(char *); +#define valid_token_name(X) (strpbrk((X), http_notok) ? 0 : 1) +#define valid_parameter_value(X) \ + ((valid_token_name((X)) || valid_dquote_text((X))) ? 1 : 0) + +/* HTTP HEADER FUNCTIONS */ +void http_add_header_data(HTTPSTREAM *, char *); +void http_add_data_to_header(HTTP_HEADER_S **, char *); + +HTTP_PARAM_LIST_S *http_parse_token_parameter(char *, int); +HTTP_PARAM_LIST_S *http_parse_token_list(char *, int); +PARAMETER *http_parse_parameter(char *, int); + +void http_parse_headers(HTTPSTREAM *); + +void +http_parse_headers(HTTPSTREAM *stream) +{ + HTTP_HEADER_DATA_S *hd; + HTTP_HEADER_S *h; + + if(!stream || !stream->header) return; + + hd = stream->header; + + if(((h = hd->accept)) && h->data){ /* RFC 7231, Section 5.3.2 */ + h->p = http_parse_token_parameter(h->data, HTP_NOVAL); + fs_give((void **) &h->data); + } + + if(((h = hd->accept_charset)) && h->data){ /* RFC 7231, Section 5.3.3 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->accept_encoding)) && h->data){ /* RFC 7231, Section 5.3.4 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->accept_language)) && h->data){ /* RFC 7231, Section 5.3.5 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->accept_ranges)) && h->data){ /* RFC 7233, Section 2.3 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->age)) && h->data){ /* RFC 7234, Section 5.1 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->allow)) && h->data){ /* RFC 7231, Section 7.4.1 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->cache_control)) && h->data){ /* RFC 7234, Section 5.2 */ + h->p = http_parse_token_parameter(h->data, HTP_NOVAL); + fs_give((void **) &h->data); + } + + if(((h = hd->connection)) && h->data){ /* RFC 7230, Section 6.1 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->content_encoding)) && h->data){ /* RFC 7231, Section 3.1.2.2 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->content_disposition)) && h->data){ /* RFC 6266 */ + h->p = http_parse_token_parameter(h->data, HTP_NOVAL); + fs_give((void **) &h->data); + } + + if(((h = hd->content_language)) && h->data){ /* RFC 7231, Section 3.1.3.2 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->content_length)) && h->data){ /* RFC 7230, Section 3.3.2 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->content_location)) && h->data){ /* RFC 7231, Section 3.1.4.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->content_type)) && h->data){ /* RFC 7231, Section 3.1.1.5 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->date)) && h->data){ /* RFC 7231, Section 7.1.1.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->etag)) && h->data){ /* Rewrite this. RFC 7232, Section 2.3 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->expect)) && h->data){ /* Rewrite this. RFC 7231, Section 5.1.1 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->expires)) && h->data){ /* Rewrite this. RFC 7234, Section 5.3 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->from)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.1 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->host)) && h->data){ /* Rewrite this. RFC 7230, Section 5.4 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->last_modified)) && h->data){ /* Rewrite this. RFC 7232, Section 2.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->location)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->max_forwards)) && h->data){ /* RFC 7231, Section 5.1.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->mime_version)) && h->data){ /* Rewrite this. RFC 7231, Appendix A.1 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->pragma)) && h->data){ /* RFC 7234, Section 5.4 */ + h->p = http_parse_token_parameter(h->data, HTP_NOVAL); + fs_give((void **) &h->data); + } + + if(((h = hd->proxy_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.3 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->referer)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->retry_after)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.3 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->server)) && h->data){ /* Rewrite this. RFC 7231, Section 7.4.2 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->te)) && h->data){ /* Rewrite this. RFC 7230, Section 4.3 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->trailer)) && h->data){ /* RFC 7230, Section 4.4 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->transfer_encoding)) && h->data){ /* Rewrite this. RFC 7230, Section 3.3.1 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } + + if(((h = hd->upgrade)) && h->data){ /* Rewrite this. RFC 7230, Section 6.7 */ + h->p = http_parse_token_list(h->data, 1); + fs_give((void **) &h->data); + } + + if(((h = hd->user_agent)) && h->data){ /* Rewrite this. RFC 7231, Section 5.5.3 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->via)) && h->data){ /* Rewrite this. RFC 7230, Section 5.7.1 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->vary)) && h->data){ /* Rewrite this. RFC 7231, Section 7.1.4 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->warning)) && h->data){ /* Rewrite this. RFC 7234, Section 5.5 */ + h->p = http_parse_token_list(h->data, HTP_UNLIMITED); + fs_give((void **) &h->data); + } + + if(((h = hd->www_authenticate)) && h->data){ /* Rewrite this. RFC 7235, Section 4.1 */ + h->p = http_parse_token_parameter(h->data, 0); + fs_give((void **) &h->data); + } +} + + +void +http_add_data_to_header(HTTP_HEADER_S **headerp, char *data) +{ + HTTP_HEADER_S *h = *headerp; + + if(!h){ + h = fs_get(sizeof(HTTP_HEADER_S)); + memset((void *)h, 0, sizeof(HTTP_HEADER_S)); + } + + if(h->data) buffer_add(&h->data, ", "); + buffer_add(&h->data, data); + *headerp = h; +} + +void +http_add_header_data(HTTPSTREAM *stream, char *hdata) +{ + char *hname, *h; + size_t hlen; + int found = 1; + + if(!stream || !hdata || !*hdata) return; + + if(!stream->header){ + stream->header = fs_get(sizeof(HTTP_HEADER_DATA_S)); + memset((void *) stream->header, 0, sizeof(HTTP_HEADER_DATA_S)); + } + + + /* extract header name first */ + if((h = strchr(hdata, ':'))){ + *h = '\0'; + hname = fs_get(h-hdata+1); + strncpy(hname, hdata, h-hdata); + hname[h-hdata] = '\0'; + if(!valid_token_name(hname)) + return; + hname[h-hdata] = ':'; + hname[h-hdata+1] = '\0'; + *h++ = ':'; + } + else return; + + switch(*hname){ + case 'A': + case 'a': if(!compare_cstring(hname+1, "ccept:")) /* RFC 7231, Section 5.3.2 */ + http_add_data_to_header(&stream->header->accept, h); + else if(!compare_cstring(hname+1, "ccept-charset:")) /* RFC 7231, Section 5.3.3 */ + http_add_data_to_header(&stream->header->accept_charset, h); + else if(!compare_cstring(hname+1, "ccept-encoding:")) /* RFC 7231, Section 5.3.4 */ + http_add_data_to_header(&stream->header->accept_encoding, h); + else if(!compare_cstring(hname+1, "ccept-language:")) /* RFC 7231, Section 5.3.5 */ + http_add_data_to_header(&stream->header->accept_language, h); + else if(!compare_cstring(hname+1, "ccept-ranges:")) /* RFC 7233, Section 2.3 */ + http_add_data_to_header(&stream->header->accept_ranges, h); + else if(!compare_cstring(hname+1, "ge:")) /* RFC 7234, Section 5.1 */ + http_add_data_to_header(&stream->header->age, h); + else if(!compare_cstring(hname+1, "llow:")) /* RFC 7231, Section 7.4.1 */ + http_add_data_to_header(&stream->header->allow, h); + else found = 0; + break; + + case 'C': + case 'c': if(!compare_cstring(hname+1, "ache-control:")) /* RFC 7234, Section 5.2 */ + http_add_data_to_header(&stream->header->cache_control, h); + else if(!compare_cstring(hname+1, "onnection:")) /* RFC 7230, Section 6.1 */ + http_add_data_to_header(&stream->header->connection, h); + else if(!compare_cstring(hname+1, "ontent-disposition:")) /* RFC 6266 */ + http_add_data_to_header(&stream->header->content_disposition, h); + else if(!compare_cstring(hname+1, "ontent-encoding:")) /* RFC 7231, Section 3.1.2.2 */ + http_add_data_to_header(&stream->header->content_encoding, h); + else if(!compare_cstring(hname+1, "ontent-language:")) /* RFC 7231, Section 3.1.3.2 */ +/* rewrite this */ http_add_data_to_header(&stream->header->content_language, h); + else if(!compare_cstring(hname+1, "ontent-length:")) /* RFC 7230, Section 3.3.2 */ + http_add_data_to_header(&stream->header->content_length, h); + else if(!compare_cstring(hname+1, "ontent-location:")) /* RFC 7231, Section 3.1.4.2 */ +/* rewrite this */ http_add_data_to_header(&stream->header->content_location, h); + else if(!compare_cstring(hname+1, "ontent-type:")) /* RFC 7231, Section 3.1.1.5 */ + http_add_data_to_header(&stream->header->content_type, h); + else found = 0; + break; + + case 'D': + case 'd': if(!compare_cstring(hname+1, "ate:")) /* RFC 7231, Section 7.1.1.2 */ +/* revise this */ http_add_data_to_header(&stream->header->date, h); + else found = 0; + break; + + case 'E': + case 'e': if(!compare_cstring(hname+1, "tag:")) /* RFC 7232, Section 2.3 */ +/* rewrite this */ http_add_data_to_header(&stream->header->etag, h); + else if(!compare_cstring(hname+1, "xpect:")) /* RFC 7231, Section 5.1.1 */ +/* rewrite this */ http_add_data_to_header(&stream->header->expect, h); + else if(!compare_cstring(hname+1, "xpires:")) /* RFC 7234, Section 5.3 */ +/* rewrite this */ http_add_data_to_header(&stream->header->expires, h); + else found = 0; + break; + + case 'F': + case 'f': if(!compare_cstring(hname+1, "rom:")) /* RFC 7231, Section 5.5.1 */ +/* rewrite this */ http_add_data_to_header(&stream->header->from, h); + else found = 0; + break; + + case 'H': + case 'h': if(!compare_cstring(hname+1, "ost:")) /* RFC 7230, Section 5.4 */ + http_add_data_to_header(&stream->header->host, h); + else found = 0; + break; + + case 'L': + case 'l': if(!compare_cstring(hname+1, "ast-modified:")) /* RFC 7232, Section 2.2 */ + http_add_data_to_header(&stream->header->last_modified, h); + else if(!compare_cstring(hname+1, "ocation:")) /* RFC 7231, Section 7.1.2 */ + http_add_data_to_header(&stream->header->location, h); + else found = 0; + break; + + case 'M': + case 'm': if(!compare_cstring(hname+1, "ax-forwards:")) /* RFC 7231, Section 5.1.2 */ + http_add_data_to_header(&stream->header->max_forwards, h); + else if(!compare_cstring(hname+1, "ime-version:")) /* RFC 7231, Appendix A.1 */ + http_add_data_to_header(&stream->header->mime_version, h); + else found = 0; + break; + + case 'P': + case 'p': if(!compare_cstring(hname+1, "ragma:")) /* RFC 7234, Section 5.4 */ + http_add_data_to_header(&stream->header->pragma, h); + else if(!compare_cstring(hname+1, "roxy-authenticate:")) /* RFC 7235, Section 4.3 */ + http_add_data_to_header(&stream->header->proxy_authenticate, h); + else found = 0; + break; + + case 'R': + case 'r': if(!compare_cstring(hname+1, "eferer:")) /* RFC 7231, Section 5.5.2 */ + http_add_data_to_header(&stream->header->referer, h); + else if(!compare_cstring(hname+1, "etry-after:")) /* RFC 7231, Section 7.1.3 */ + http_add_data_to_header(&stream->header->retry_after, h); + else found = 0; + break; + + case 'S': + case 's': if(!compare_cstring(hname+1, "erver:")) /* RFC 7231, Section 7.4.2 */ + http_add_data_to_header(&stream->header->server, h); + else found = 0; + break; + + case 'T': + case 't': if(!compare_cstring(hname+1, "e:")) /* RFC 7230, Section 4.3 */ + http_add_data_to_header(&stream->header->te, h); + else if(!compare_cstring(hname+1, "railer:")) /* RFC 7230, Section 4.4 */ + http_add_data_to_header(&stream->header->trailer, h); + else if(!compare_cstring(hname+1, "ransfer-encoding:")) /* RFC 7230, Section 3.3.1 */ + http_add_data_to_header(&stream->header->transfer_encoding, h); + else found = 0; + break; + break; + + case 'U': + case 'u': if(!compare_cstring(hname+1, "pgrade:")) /* RFC 7230, Section 6.7 */ + http_add_data_to_header(&stream->header->upgrade, h); + else if(!compare_cstring(hname+1, "ser-agent:")) /* RFC 7231, Section 5.5.3 */ + http_add_data_to_header(&stream->header->user_agent, h); + else found = 0; + break; + + case 'V': + case 'v': if(!compare_cstring(hname+1, "ia:")) /* RFC 7230, Section 5.7.1 */ + http_add_data_to_header(&stream->header->via, h); + else if(!compare_cstring(hname+1, "ary:")) /* RFC 7231, Section 7.1.4 */ + http_add_data_to_header(&stream->header->vary, h); + else found = 0; + break; + + case 'W': + case 'w': if(!compare_cstring(hname+1, "arning:")) /* RFC 7234, Section 5.5 */ + http_add_data_to_header(&stream->header->warning, h); + else if(!compare_cstring(hname+1, "ww-authenticate:")) /* RFC 7235, Section 4.1 */ + http_add_data_to_header(&stream->header->www_authenticate, h); + else found = 0; + break; + + default: break; + } +} + + +/* parse a list of tokens. If num is positive, parse at most + * num members in the list. Set num to HTP_UNLIMITED for a list + * without bounds + */ +HTTP_PARAM_LIST_S * +http_parse_token_list(char *h, int num) +{ + char *s = h, *t, *u, c, d; + HTTP_PARAM_LIST_S *rv = NIL; + + if(!s || !*s || num == 0) return NIL; + http_skipows(&s); + if(!*s) return NIL; + for(t = s; *t != '\0' && *t != ','; t++); + c = *t; *t = '\0'; + http_remove_trailing_ows(s); + + if(!valid_token_name(s)) + return c == ',' ? http_parse_token_list(t+1, num) : NIL; + + if(num > 0) num--; /* this one counts! */ + rv = fs_get(sizeof(HTTP_PARAM_LIST_S)); + memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S)); + rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S)); + memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S)); + rv->vp->value = cpystr(s); + *t = c; + if(c == ',') + rv->next = http_parse_token_list(t+1, num); + + return rv; +} + + +/* + * parse a list of tokens with optional parameters + * into a HEADER_DATA structure. Do not parse into + * it anything invalid. + */ +HTTP_PARAM_LIST_S * +http_parse_token_parameter(char *h, int flag) +{ + char *s = h, *t, *u, c, d; + HTTP_PARAM_LIST_S *rv = NIL; + + /* + * Step 1: + * isolate first list element from list and remove + * leading and trailing white space. + */ + if(!s) return NIL; + http_skipows(&s); + if(!*s) return NIL; + for(t = s; *t != '\0' && *t != ','; t++); + c = *t; *t = '\0'; + http_remove_trailing_ows(s); + + /* + * Step 2: + * isolate token name from its parameters. Remove + * any trailing spaces. If not valid token, move + * to the next entry in the list. + */ + for(u = s; *u != '\0' && *u != ';'; u++); + d = *u; *u = '\0'; + http_remove_trailing_ows(s); + if(!valid_token_name(s)) + return c == ',' ? http_parse_token_parameter(t+1, flag) : NIL; + + /* + * Step 3: + * If we make it this far, create a non-null reply + * and parse the token and parameters into a + * HTTP_HEADER_DATA_S structure + */ + rv = fs_get(sizeof(HTTP_PARAM_LIST_S)); + memset((void *) rv, 0, sizeof(HTTP_PARAM_LIST_S)); + rv->vp = fs_get(sizeof(HTTP_VAL_PARAM_S)); + memset((void *) rv->vp, 0, sizeof(HTTP_VAL_PARAM_S)); + rv->vp->value = cpystr(s); + if(d == ';') + rv->vp->plist = http_parse_parameter(u+1, flag); + *u = d; + *t = c; + if(c == ',') + rv->next = http_parse_token_parameter(t+1, flag); + + return rv; +} + +int +valid_dquote_text(char *s) +{ + char *t; + + if(!s || *s != '\"') return 0; + + t = strchr(s+1, '\"'); + return (t && !t[1]) ? 1 : 0; +} + + +void +http_skipows(char **sp) +{ + char *s = *sp; + for(; *s == ' ' || *s == '\t'; s++); + *sp = s; +} + +void +http_remove_trailing_ows(char *s) +{ + char *t; + for(t = s; strlen(t) > 0 ;) + if(t[strlen(t)-1] == ' ' || t[strlen(t)-1] == '\t') + t[strlen(t)-1] = '\0'; + else + break; +} + +PARAMETER * +http_parse_parameter(char *s, int flag) +{ + PARAMETER *p; + char *t, *u, c, d; + + /* Step 1: + * separate the parameters into a list separated by ";" + */ + if(!s || !*s) return NIL; + http_skipows(&s); + if(!*s) return NIL; + for(t = s; *t != '\0' && *t != ';'; t++); + c = *t; *t = '\0'; + + /* Now we look for separation of attribute and value */ + u = strchr(s, '='); + + if(u){ + *u = '\0'; + http_remove_trailing_ows(s); http_remove_trailing_ows(u+1); + if(!valid_token_name(s) || !valid_parameter_value(u+1)) + return c == ';' ? http_parse_parameter(t+1, flag) : NIL; + p = mail_newbody_parameter(); + p->attribute = cpystr(s); + p->value = cpystr(u+1); + p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL; + *u = '='; + } + else if(flag & HTP_NOVAL){ + /* this is a parameter with attribute but no value. RFC 7231 + * section 5.3.2 allows this. + */ + http_remove_trailing_ows(s); + if(!valid_token_name(s)) + return c == ';' ? http_parse_parameter(t+1, flag) : NIL; + p = mail_newbody_parameter(); + p->attribute = cpystr(s); + p->next = c == ';' ? http_parse_parameter(t+1, flag) : NIL; + } else + p = c == ';' ? http_parse_parameter(t+1, flag) : NIL; + + return p; +} + +char * +http_get_param_url(char *url, HTTP_PARAM_S *param) +{ + int i; + char *rv = NULL; + HTTP_PARAM_S enc_param; + + buffer_add(&rv, url); + for(i = 0; param[i].name != NULL; i++){ + enc_param.name = hex_escape_url_part(param[i].name, NULL); + enc_param.value = hex_escape_url_part(param[i].value, NULL); + buffer_add(&rv, i == 0 ? "?" : "&"); + buffer_add(&rv, enc_param.name); + buffer_add(&rv, "="); + buffer_add(&rv, enc_param.value); + fs_give((void **) &enc_param.name); + fs_give((void **) &enc_param.value); + } + + return rv; +} + +HTTP_REQUEST_S * +http_request_get(void) +{ + HTTP_REQUEST_S *rv = fs_get(sizeof(HTTP_REQUEST_S)); + memset((void *) rv, 0, sizeof(HTTP_REQUEST_S)); + + return rv; +} + +void +http_request_free(HTTP_REQUEST_S **hr) +{ + if(!hr) return; + + if((*hr)->request) fs_give((void **) &(*hr)->request); + if((*hr)->header) fs_give((void **) &(*hr)->header); + if((*hr)->body) fs_give((void **) &(*hr)->body); + fs_give((void **) hr); +} + +char * +http_request_line(char *method, char *target, char *version) +{ + int len = strlen(method) + strlen(target) + strlen(version) + 2 + 1; + char *line = fs_get(len); + + sprintf(line, "%s %s %s", method, target, version); + return line; +} + +void +http_add_header(HTTP_REQUEST_S **reqp, char *name, char *value) +{ + int len, hlen; + + if(!reqp) return; + + if(!*reqp) *reqp = http_request_get(); + + len = strlen(name) + 2 + strlen(value) + 2 + 1; + hlen = (*reqp)->header ? strlen((*reqp)->header) : 0; + len += hlen; + fs_resize((void **) &(*reqp)->header, len); + sprintf((*reqp)->header + hlen, "%s: %s\015\012", name, value); +} + +void +buffer_add(char **bufp, char *text) +{ + int len; + + if(!bufp || !text || !*text) return; + + len = *bufp ? strlen(*bufp) : 0; + fs_resize((void **) bufp, len + strlen(text) + 1); + (*bufp)[len] = '\0'; + strcat(*bufp, text); +} + +void +http_add_body(HTTP_REQUEST_S **reqp, char *text) +{ + if(!reqp) return; + + if(!*reqp) *reqp = http_request_get(); + + buffer_add(&(*reqp)->body, text); +} + + +/* NULL terminated list of HTTP_PARAM_S objects. + * If caller needs "x" parameters, call this function + * with argument "x+1". + */ +HTTP_PARAM_S * +http_param_get(int len) +{ + HTTP_PARAM_S *http_params; + + http_params = fs_get(len*sizeof(HTTP_PARAM_S)); + memset((void *) http_params, 0, len*sizeof(HTTP_PARAM_S)); + return http_params; +} + +void +http_param_free(HTTP_PARAM_S **param) +{ + int i; + + if(param == NULL) return; + + for(i = 0; (*param)[i].name != NULL; i++) + fs_give((void **) &(*param)[i].name); + + for(i = 0; (*param)[i].value != NULL; i++) + fs_give((void **) &(*param)[i].value); + + fs_give((void **) param); +} + + +/* This encodes for a GET request */ +char * +hex_escape_url_part(char *text, char *addsafe) +{ + char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-"; + char *s = fs_get(3*strlen(text) + 1), *t; + + *s = '\0'; + for(t = text; *t != '\0'; t++) + if(strchr(safechars, *t) != NULL + || (addsafe != NULL && strchr(addsafe, *t) != NULL)) + sprintf(s + strlen(s), "%c", *t); + else + sprintf(s + strlen(s), "%%%X", *t); + fs_resize((void **) &s, strlen(s)+1); + return s; +} + +/* this encodes for a POST request */ +char * +encode_url_body_part(char *text, char *addsafe) +{ + char *safechars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-"; + char *s = fs_get(3*strlen(text) + 1), *t; + + *s = '\0'; + for(t = text; *t != '\0'; t++) + if(*t == ' ') /* ASCII 32 is never safe, must always be encoded */ + sprintf(s + strlen(s), "%c", '+'); + else if(strchr(safechars, *t) != NULL + || (addsafe != NULL && strchr(addsafe, *t) != NULL)) + sprintf(s + strlen(s), "%c", *t); + else + sprintf(s + strlen(s), "%%%X", *t); + fs_resize((void **) &s, strlen(s)+1); + return s; +} + +int +http_valid_net_parse (char *url, NETMBX *mb) +{ + int i, len; + char *s, *p; + + if((url == NIL) + || (url[0] != 'h' && url[0] != 'H') + || (url[1] == 't' && url[1] == 'T') + || (url[2] == 't' && url[2] == 'T') + || (url[3] == 'p' && url[3] == 'P')) + return 0; + + if(url[i = 4] == 's' || url[i] == 'S') + mb->sslflag = mb->notlsflag = T; + else i = 3; + + if(url[++i] != ':' || url[++i] != '/' || url[++i] != '/') + return 0; + + strcpy(mb->service, "http"); + s = strchr(url+i+1, '/'); + len = s ? s - url - i - 1 : strlen(url+i+1); + strncpy(mb->orighost, url+i+1, len); + mb->orighost[len] = '\0'; + if((p = strchr(mb->orighost, ':')) != NULL){ + *p++ = '\0'; + mb->port = strtoul(p, &p, 10); + if(mb->port == 0L || *p != '\0') + return NIL; + } + strcpy(mb->host, mb->orighost); + return T; +} + +HTTPSTREAM * +http_open (char *url) +{ + HTTPSTREAM *stream; + NETMBX mb; + char *s; + + memset((void *) &mb, 0, sizeof(NETMBX)); + if(http_valid_net_parse (url,&mb) == 0) + return NIL; + + stream = fs_get(sizeof(HTTPSTREAM)); + memset((void *) stream, 0, sizeof(HTTPSTREAM)); + + s = strchr(url + 7 + (mb.trysslflag ? 1 : 0) + 1, '/'); /* 7 = strlen("http://") + 1 */ + stream->url = cpystr(url); + stream->urlhost = cpystr(mb.orighost); + stream->urltail = cpystr(s ? s : "/"); + stream->netstream = net_open (&mb, NIL, mb.port ? mb.port : HTTPTCPPORT, + (NETDRIVER *) mail_parameters (NIL,GET_SSLDRIVER,NIL), + "*https", mb.port ? mb.port : HTTPSSLPORT); + if(!stream->netstream) http_close(stream); + return stream; +} + +char * +http_post_param(char *url, HTTP_PARAM_S *param) +{ + HTTPSTREAM *stream; + HTTP_PARAM_S enc_param; + HTTP_REQUEST_S *http_request; + char *reply; + int i; + + if(url == NULL || param == NULL || (stream = http_open(url)) == NULL) + return NULL; + + http_request = http_request_get(); + http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION); + http_add_header(&http_request, "Host", stream->urlhost); + http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED); + + for(i = 0; param[i].name != NULL; i++){ + enc_param.name = encode_url_body_part(param[i].name, NULL); + enc_param.value = encode_url_body_part(param[i].value, NULL); + if(i > 0) + http_add_body(&http_request, "&"); + http_add_body(&http_request, enc_param.name); + http_add_body(&http_request, "="); + http_add_body(&http_request, enc_param.value); + fs_give((void **) &enc_param.name); + fs_give((void **) &enc_param.value); + } + + if(http_send(stream, http_request)){ + reply = cpystr(stream->reply ? stream->reply : ""); + http_close(stream); + } + + http_request_free(&http_request); + + return reply; +} + +char * +http_post_param2(char *url, HTTP_PARAM_S *param) +{ + HTTPSTREAM *stream; + HTTP_PARAM_S enc_param; + HTTP_REQUEST_S *http_request; + char *reply; + int i; + + if(url == NULL || param == NULL || (stream = http_open(url)) == NULL) + return NULL; + + http_request = http_request_get(); + http_request->request = http_request_line("POST", stream->urltail, HTTP_1_1_VERSION); + http_add_header(&http_request, "Host", stream->urlhost); + http_add_header(&http_request, "User-Agent", "Alpine"); + http_add_header(&http_request, "Content-Type", HTTP_MIME_URLENCODED); + + for(i = 0; param[i].name != NULL; i++){ + enc_param.name = encode_url_body_part(param[i].name, NULL); + enc_param.value = encode_url_body_part(param[i].value, NULL); + if(i > 0) + http_add_body(&http_request, "&"); + http_add_body(&http_request, enc_param.name); + http_add_body(&http_request, "="); + http_add_body(&http_request, enc_param.value); + fs_give((void **) &enc_param.name); + fs_give((void **) &enc_param.value); + } + + if(http_send(stream, http_request)){ + reply = cpystr(stream->reply ? stream->reply : ""); + http_close(stream); + } + + http_request_free(&http_request); + + return reply; +} + +char * +http_get_param(char *base_url, HTTP_PARAM_S *param) +{ + HTTPSTREAM *stream; + char *url, *reply; + + url = http_get_param_url(base_url, param); + if(url){ + reply = http_get(url); + fs_give((void **) &url); + } + return reply; +} + +char * +http_get(char *url) +{ + HTTP_REQUEST_S *http_request; + char *reply; + HTTPSTREAM *stream; + + if(!url) return NIL; + stream = http_open(url); + if(!stream){ + fs_give((void **) &url); + return NIL; + } + + http_request = http_request_get(); + http_request->request = http_request_line("GET", stream->urltail, HTTP_1_1_VERSION); + http_add_header(&http_request, "Host", stream->urlhost); + + if(http_send(stream, http_request)){ + reply = cpystr(stream->reply ? stream->reply : ""); + http_close(stream); + } + + http_request_free(&http_request); + + return reply; +} + +void +http_close (HTTPSTREAM *stream) +{ + if(stream){ + if (stream->netstream) net_close (stream->netstream); + stream->netstream = NIL; + if (stream->url) fs_give ((void **) &stream->url); + if (stream->urlhost) fs_give ((void **) &stream->urlhost); + if (stream->urltail) fs_give ((void **) &stream->urltail); + if (stream->response) fs_give ((void **) &stream->response); + if (stream->reply) fs_give ((void **) &stream->reply); + fs_give((void **) &stream); + } +} + +long +http_send (HTTPSTREAM *stream, HTTP_REQUEST_S *req) +{ + long ret; + int len; + char *s = NULL; + + if (!stream->netstream) + ret = http_fake (stream,"http connection lost"); + else { + if(req->body){ + char length[20]; + + sprintf(length, "%lu", strlen(req->body)); + http_add_header(&req, "Content-Length", length); + } + + buffer_add(&s, req->request); buffer_add(&s, "\015\012"); + buffer_add(&s, req->header); buffer_add(&s, "\015\012"); + buffer_add(&s, req->body); buffer_add(&s, "\015\012"); + mm_log(s, TCPDEBUG); + ret = net_soutr (stream->netstream,s) + ? http_reply (stream) + : http_fake (stream,"http connection broken in command"); + fs_give ((void **) &s); + } + return ret; +} + +HTTP_STATUS_S * +http_status_line_get(char *status_line) +{ + HTTP_STATUS_S *rv = NULL; + char *version, *s; + int status; + int len; + + if(!status_line) return NIL; + + if((s = strchr(status_line, ' ')) != NIL){ + *s = '\0'; + version = cpystr(status_line); + *s++ = ' '; + status = strtoul(s, &s, 10); + if(s && *s == ' ' && status >= 100 && status < 600){ + rv = fs_get(sizeof(HTTP_STATUS_S)); + rv->version = version; + rv->status = status; + rv->text = cpystr(++s); + } + else + fs_give((void **) &version); + } + return rv; +} + +void +http_status_line_free(HTTP_STATUS_S **status) +{ + if(status == NULL) return; + + if((*status)->version) fs_give((void **) &(*status)->version); + if((*status)->text) fs_give((void **) &(*status)->text); + fs_give((void **) status); +} + + +long +http_reply (HTTPSTREAM *stream) +{ + int in_header = 1; + HTTP_STATUS_S reply_status; + unsigned long size; + + if (stream->response) fs_give ((void **) &stream->response); + stream->response = net_getline(stream->netstream); + + if(stream->response){ + buffer_add(&stream->reply, stream->response); + buffer_add(&stream->reply, "\015\012"); + } + + if(stream->status) http_status_line_free(&stream->status); + stream->status = http_status_line_get(stream->response); + + if(!stream->status){ + http_fake(stream, "Invalid status line received. Closing connection"); + return NIL; + } + + while (in_header > 0){ + if (stream->response) fs_give ((void **) &stream->response); + stream->response = net_getline (stream->netstream); + if(stream->response){ + buffer_add(&stream->reply, stream->response); + http_add_header_data(stream, stream->response); + } + buffer_add(&stream->reply, "\015\012"); +// save_header(stream->headers, stream->response); + if(!stream->response || *stream->response == '\0') + in_header--; + } + + http_parse_headers(stream); + if(stream->header->content_length){ + size = atol(stream->header->content_length->p->vp->value); + if (stream->response) fs_give ((void **) &stream->response); + stream->response = net_getsize (stream->netstream, size); + if(stream->response) buffer_add(&stream->reply, stream->response); + } + else if (stream->header->transfer_encoding){ + HTTP_PARAM_LIST_S *p = stream->header->transfer_encoding->p; + for(; p ; p = p->next){ + if(!compare_cstring(p->vp->value, "chunked")) + break; + } + if(p && p->vp->value){ /* chunked transfer */ + int done = 0; + size = 0L; + while(!done){ + if (stream->response) fs_give ((void **) &stream->response); + stream->response = net_getline (stream->netstream); + if(stream->response){ + buffer_add(&stream->reply, stream->response); + buffer_add(&stream->reply, "\015\012"); + size = strtol(stream->response, NIL, 16); + fs_give ((void **) &stream->response); + stream->response = net_getsize (stream->netstream, size); + buffer_add(&stream->reply, stream->response); + } + if(size == 0L) done++; + } + } + } + +#if 0 + while(in_header == 0){ + if (stream->response) fs_give ((void **) &stream->response); + stream->response = net_getline (stream->netstream); + if(stream->response){ + buffer_add(&stream->reply, stream->response); + buffer_add(&stream->reply, stream->response); + } + buffer_add(&stream->reply, "\015\012"); + if(!stream->response || *stream->response == '\0') + in_header--; + } +#endif + + if(!stream->netstream) + http_fake(stream, "Connection to HTTP server closed"); + return stream->netstream ? T : NIL; +} + +long +http_fake (HTTPSTREAM *stream, char *text) +{ + if (stream->netstream) net_close (stream->netstream); + stream->netstream = NIL; + if (stream->response) fs_give ((void **) &stream->response); + /* add *text to the log, to pass this to the client */ + return NIL; +} diff --git a/imap/src/c-client/http.h b/imap/src/c-client/http.h new file mode 100644 index 00000000..59b7e1d0 --- /dev/null +++ b/imap/src/c-client/http.h @@ -0,0 +1,116 @@ +/* + * Copyright 2018 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 + * + * Last Edited: July 23, 2018 Eduardo Chappa + * + */ + +typedef struct http_val_param_s { + char *value; + PARAMETER *plist; +} HTTP_VAL_PARAM_S; + +typedef struct http_param_list_s { + HTTP_VAL_PARAM_S *vp; + struct http_param_list_s *next; +} HTTP_PARAM_LIST_S; + +typedef struct http_header_value_s { + char *data; + HTTP_PARAM_LIST_S *p; +} HTTP_HEADER_S; + +typedef struct http_header_data_s { + HTTP_HEADER_S *accept, /* RFC 7231, Section 5.3.2 */ + *accept_charset, /* RFC 7231, Section 5.3.3 */ + *accept_encoding, /* RFC 7231, Section 5.3.4 */ + *accept_language, /* RFC 7231, Section 5.3.5 */ + *accept_ranges, /* RFC 7233, Section 2.3 */ + *age, /* RFC 7234, Section 5.1 */ + *allow, /* RFC 7231, Section 7.4.1 */ + *cache_control, /* RFC 7234, Section 5.2 */ + *connection, /* RFC 7230, Section 6.1 */ + *content_disposition, /* RFC 6266 */ + *content_encoding, /* RFC 7231, Section 3.1.2.2 */ + *content_language, /* RFC 7231, Section 3.1.3.2 */ + *content_length, /* RFC 7230, Section 3.3.2 */ + *content_location, /* RFC 7231, Section 3.1.4.2 */ + *content_type, /* RFC 7231, Section 3.1.1.5 */ + *date, /* RFC 7231, Section 7.1.1.2 */ + *etag, /* RFC 7232, Section 2.3 */ + *expect, /* RFC 7231, Section 5.1.1 */ + *expires, /* RFC 7234, Section 5.3 */ + *from, /* RFC 7231, Section 5.5.1 */ + *host, /* RFC 7230, Section 5.4 */ + *last_modified, /* RFC 7232, Section 2.2 */ + *location, /* RFC 7231, Section 7.1.2 */ + *max_forwards, /* RFC 7231, Section 5.1.2 */ + *mime_version, /* RFC 7231, Appendix A.1 */ + *pragma, /* RFC 7234, Section 5.4 */ + *proxy_authenticate, /* RFC 7235, Section 4.3 */ + *referer, /* RFC 7231, Section 5.5.2 */ + *retry_after, /* RFC 7231, Section 7.1.3 */ + *server, /* RFC 7231, Section 7.4.2 */ + *user_agent, /* RFC 7231, Section 5.5.3 */ + *te, /* RFC 7230, Section 4.3 */ + *trailer, /* RFC 7230, Section 4.4 */ + *transfer_encoding, /* RFC 7230, Section 3.3.1 */ + *upgrade, /* RFC 7230, Section 6.7 */ + *via, /* RFC 7230, Section 5.7.1 */ + *vary, /* RFC 7231, Section 7.1.4 */ + *warning, /* RFC 7234, Section 5.5 */ + *www_authenticate; /* RFC 7235, Section 4.1 */ +} HTTP_HEADER_DATA_S; + +#define HTTP_MIME_URLENCODED "application/x-www-form-urlencoded" + +#define HTTP_1_1_VERSION "HTTP/1.1" + +#define GET_HTTPPORT (long) 456 +#define SET_HTTPPORT (long) 457 +#define GET_SSLHTTPPORT (long) 458 +#define SET_SSLHTTPPORT (long) 459 + +typedef struct http_status_s { + char *version; + int status; + char *text; +} HTTP_STATUS_S; + + +typedef struct http_stream { + NETSTREAM *netstream; + HTTP_HEADER_DATA_S *header; /* headers sent by the server */ + char *url; /* original url */ + char *urlhost; /* get original host */ + char *urltail; /* the part of the URL after the original host */ + HTTP_STATUS_S *status;/* parsed status line from server */ + char *response; /* last reply line from server */ + char *reply; /* the full reply from the server */ +} HTTPSTREAM; + +/* parameters for a get or post call */ +typedef struct http_param_s { + char *name; + char *value; +} HTTP_PARAM_S; + +/* exported prototypes */ +HTTPSTREAM *http_open (char *); +char *http_post_param(char *, HTTP_PARAM_S *); +char *http_post_param2(char *, HTTP_PARAM_S *); +char *http_get_param(char *, HTTP_PARAM_S *); +char *http_get(char *); +void http_close (HTTPSTREAM *stream); + +HTTP_PARAM_S *http_param_get(int); +void http_param_free(HTTP_PARAM_S **); + +/* Ugghh.... just construct the URL for a get request */ +char *http_get_param_url(char *, HTTP_PARAM_S *); diff --git a/imap/src/c-client/imap4r1.c b/imap/src/c-client/imap4r1.c index b7423056..aaaa66e4 100644 --- a/imap/src/c-client/imap4r1.c +++ b/imap/src/c-client/imap4r1.c @@ -1142,14 +1142,22 @@ long imap_anon (MAILSTREAM *stream,char *tmp) long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) { - unsigned long trial,ua; + unsigned long trial,ua,uasaved; int ok; char tag[16]; char *lsterr = NIL; - AUTHENTICATOR *at; + AUTHENTICATOR *at, *atsaved; IMAPPARSEDREPLY *reply; for (ua = LOCAL->cap.auth, LOCAL->saslcancel = NIL; LOCAL->netstream && ua && (at = mail_lookup_auth (find_rightmost_bit (&ua) + 1));) { + if(mb && *mb->auth){ + if(!compare_cstring(at->name, mb->auth)) + atsaved = at; + else{ + uasaved = ua; + continue; + } + } if (lsterr) { /* previous authenticator failed? */ sprintf (tmp,"Retrying using %s authentication after %.80s", at->name,lsterr); @@ -1201,6 +1209,11 @@ long imap_auth (MAILSTREAM *stream,NETMBX *mb,char *tmp,char *usr) } fs_give ((void **) &lsterr); } + if(mb && *mb->auth){ + if(!uasaved) sprintf (tmp,"Client does not support AUTH=%.80s authenticator",mb->auth); + else if (!atsaved) sprintf (tmp,"IMAP server does not support AUTH=%.80s authenticator",mb->auth); + if (!uasaved || !atsaved) mm_log (tmp,ERROR); + } return NIL; /* ran out of authenticators */ } @@ -1219,6 +1232,7 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) IMAPARG *args[3]; IMAPARG ausr,apwd; long ret = NIL; + char *app_pwd = NIL; if (stream->secure) /* never do LOGIN if want security */ mm_log ("Can't do secure authentication with this server",ERROR); /* never do LOGIN if server disabled it */ @@ -1232,8 +1246,13 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) apwd.text = (void *) pwd; args[0] = &ausr; args[1] = &apwd; args[2] = NIL; do { - pwd[0] = 0; /* prompt user for password */ - mm_login (mb,usr,pwd,trial++); + if(app_pwd) fs_give((void **) &app_pwd); + pwd[0] = '\0'; + mm_login (mb,usr, &app_pwd,trial++); + if(app_pwd){ + strncpy(pwd, app_pwd, MAILTMPLEN); + pwd[MAILTMPLEN-1] = '\0'; + } if (pwd[0]) { /* send login command if have password */ LOCAL->sensitive = T; /* hide this command */ /* send "LOGIN usr pwd" */ @@ -1251,7 +1270,8 @@ long imap_login (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) } while (!ret && pwd[0] && (trial < imap_maxlogintrials) && LOCAL->netstream && !LOCAL->byeseen && !LOCAL->referral); } - memset (pwd,0,MAILTMPLEN); /* erase password */ + if(app_pwd) fs_give((void **) &app_pwd); + memset((void *) pwd, 0, MAILTMPLEN); return ret; } diff --git a/imap/src/c-client/json.c b/imap/src/c-client/json.c new file mode 100644 index 00000000..f6d05736 --- /dev/null +++ b/imap/src/c-client/json.c @@ -0,0 +1,497 @@ +/* + * Copyright 2018 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 + * + */ +#ifdef STANDALONE +#include "headers.h" +#include "mem.h" +#include "readfile.h" +#include "json.h" +#else +#include +#include +#include +#include "c-client.h" +#include "json.h" +#endif /* STANDALONE */ + +#define NIL 0 + +#define json_ws(X) (((X) == ' ' || (X) == '\t' || (X) == '\n' || (X) == '\r') ? 1 : 0) + +#define json_skipws(X) for(; json_ws(*(X)); (X)++) + +#define json_skipchar(X) { \ + (X)++; \ + json_skipws((X)); \ +} + +#define json_last_char(X) (((X) == ',' || (X) == ']' || (X) == '}') ? 1 : 0) + +JObjType json_number_type(unsigned char *); +unsigned char *json_strchr(unsigned char *, unsigned char); +JSON_S *json_parse_work(unsigned char **); +JSON_S *json_parse_pairs(unsigned char **); + +JSON_X *json_value_parse(unsigned char **); +void json_value_free(JSON_X **); + +/* An array is a JSON object where the name is null */ +JSON_S *json_array_parse_work(unsigned char **); +JSON_S *json_array_parse(unsigned char **); +void json_array_free(JSON_S **); + +#ifdef STANDALONE +int compare_cstring (unsigned char *,unsigned char *); +int compare_uchar (unsigned char,unsigned char); +int compare_ulong (unsigned long,unsigned long); + +int compare_ulong (unsigned long l1,unsigned long l2) +{ + if (l1 < l2) return -1; + if (l1 > l2) return 1; + return 0; +} + +int compare_uchar (unsigned char c1,unsigned char c2) +{ + return compare_ulong (((c1 >= 'a') && (c1 <= 'z')) ? c1 - ('a' - 'A') : c1, + ((c2 >= 'a') && (c2 <= 'z')) ? c2 - ('a' - 'A') : c2); +} + +int compare_cstring (unsigned char *s1,unsigned char *s2) +{ + int i; + if (!s1) return s2 ? -1 : 0; /* empty string cases */ + else if (!s2) return 1; + for (; *s1 && *s2; s1++,s2++) if ((i = (compare_uchar (*s1,*s2))) != 0) return i; + if (*s1) return 1; /* first string is longer */ + return *s2 ? -1 : 0; /* second string longer : strings identical */ +} +#endif /* STANDALONE */ + +/* we are parsing from the text of the json object, so it is + * never possible to have a null character, unless something + * is corrupt, in whose case we return JNumberError. + */ +JObjType json_number_type(unsigned char *s) +{ + unsigned char *w = s, *u; + int PossibleDecimal, PossibleExponential; + + PossibleDecimal = PossibleExponential = 0; + + if(*w == '+' || *w == '-') + w++; + + if (*w == '0'){ + u = w + 1; + if(json_ws(*u)) json_skipws(u); + if(json_last_char(*u)) return JLong; + else if(*(w+1) == '.'){ + for(u = w+2; *u >= '0' && *u <= '9'; u++); + if(json_ws(*u)) json_skipws(u); + if(json_last_char(*u)) return JDecimal; + else return JNumberError; + } + else return JNumberError; + } + + if(*w < '1' || *w > '9') + return JNumberError; + + u = w + 1; + if(json_ws(*u)) json_skipws(u); + if(json_last_char(*u)) return JLong; + else if (*(w+1) == 'e' || *(w+1) == 'E'){ + PossibleExponential++; + w += 2; + } + else if(*(w+1) == '.'){ + PossibleDecimal++; + PossibleExponential++; + w += 2; + } else { + for(; *w >= '0' && *w <= '9'; w++); + if(*w == '.'){ + PossibleDecimal++; + w++; + } + else { + if(json_ws(*w)) json_skipws(w); + if(json_last_char(*w)) return JLong; + else return JNumberError; + } + } + + if(PossibleDecimal){ + for(; *w >= '0' && *w <= '9'; w++); + u = w; + if(json_ws(*u)) json_skipws(u); + if(json_last_char(*u)) return JDecimal; + else if (*w == 'e' || *w == 'E'){ + PossibleExponential++; + w++; + } + else return JNumberError; + } + + if(PossibleExponential){ + if(*w == '+' || *w == '-') + w++; + if(*w == '0'){ + u = w + 1; + if(json_ws(*u)) json_skipws(u); + if(json_last_char(*u)) return JExponential; + else return JNumberError; + } + if(*w < '1' || *w > '9') + return JNumberError; + for(; *w >= '0' && *w <= '9'; w++); + if(json_ws(*w)) json_skipws(w); + if(json_last_char(*w)) return JExponential; + else return JNumberError; + } + return JNumberError; /* never reached */ +} + + +JSON_X * +json_body_value(JSON_S *j, unsigned char *s) +{ + JSON_S *js; + + if(!j || !j->value|| j->value->jtype != JObject) return NIL; + + for(js = (JSON_S *) j->value->value; js ; js = js->next) + if(js->name && !compare_cstring(js->name, s)) + break; + + return js ? js->value : NIL; +} + +unsigned char * +json_strchr(unsigned char *s, unsigned char c) +{ + unsigned char *t, d; + int escaped, extended; + + if(c == '\0'){ + t = s + strlen((char *) s); + return t; + } + + escaped = extended = 0; + for(t = s; (d = *t) != '\0';){ + if(escaped){ + if (d == '\"' || d == '\\' || d == '/' || d == 'b' + || d == 'f' || d == 'n' || d == 'r' || d == 't'){ + escaped = 0; + t++; + continue; + } + else{ + if(d == 'u'){ + escaped = 0; + extended = 1; + t++; + } + else + return NIL; + } + } + else { + if(d == '\\'){ + escaped = 1; + t++; + continue; + } + else if(extended){ + int i; + static char HEX[] = "abcdefABCDEF0123456789"; + + if(strlen((char *) t) < 4) + return NIL; + + for(i = 0; i < 4; i++){ + if(!strchr(HEX, *t)) + return NIL; + else + t++; + } + extended = 0; + } + else if (d == c) + break; + else t++; + } + } + return (*t == '\0') ? NULL : t; +} + +JSON_X * +json_value_parse(unsigned char **s) +{ + JSON_X *jx; + unsigned char *u, *w; + unsigned long *l; + + w = *s; + json_skipws(w); + jx = fs_get(sizeof(JSON_X)); + memset((void **) jx, 0, sizeof(JSON_X)); + jx->jtype = JEnd; + switch(*w){ + case '\"': jx->jtype = JString; break; + case '{' : jx->jtype = JObject; break; + case '[' : jx->jtype = JArray; break; + case 'f' : if(strlen((char *) w) > 5 + && !strncmp((char *) w, "false", 5)){ + u = w+5; + json_skipws(u); + if(json_last_char(*u)) + jx->jtype = JBoolean; + } + break; + case 'n' : if(strlen((char *) w) > 4 + && !strncmp((char *) w, "null", 4)){ + u = w+4; + json_skipws(u); + if(json_last_char(*u)) + jx->jtype = JNull; + } + break; + case 't' : if(strlen((char *) w) > 4 + && !strncmp((char *) w, "true", 4)){ + u = w+4; + json_skipws(u); + if(json_last_char(*u)) + jx->jtype = JBoolean; + } + break; + default: jx->jtype = json_number_type(w); + break; + } + + switch(jx->jtype){ + case JString: u = json_strchr(w+1, '\"'); + if(u != NULL){ + *u = '\0'; + jx->value = (void *) cpystr((char *) w+1); + *u = '\"'; + json_skipchar(u); + w = u; + } + break; + + case JObject: jx->value = (void *) json_parse(&w); + break; + + case JLong : l = fs_get(sizeof(unsigned long)); + *l = strtoul((char *) w, (char **) &w, 10); + jx->value = (void *) l; + json_skipws(w); + break; + + case JDecimal : + case JExponential: l = fs_get(sizeof(double)); + *l = strtod((char *) w, (char **) &w); + jx->value = (void *) l; + json_skipws(w); + break; + + case JBoolean: if(*w == 't'){ + jx->value = (void *) cpystr("true"); + w += 4; + } + else{ + jx->value = (void *) cpystr("false"); + w += 5; + } + json_skipws(w); + break; + + case JNull: jx->value = (void *) cpystr("null"); + w += 4; + json_skipws(w); + break; + + case JArray: jx->value = json_array_parse(&w); + json_skipchar(w); + break; + + default: break; /* let caller handle this */ + } + *s = w; + return jx; +} + +JSON_S * +json_array_parse(unsigned char **s) +{ + JSON_S *j; + unsigned char *w = *s; + + json_skipws(w); + if(*w == '['){ + json_skipchar(w); + j = json_array_parse_work(&w); + } + *s = w; + return j; +} + +JSON_S * +json_array_parse_work(unsigned char **s) +{ + unsigned char *w = *s; + JSON_S *j; + + json_skipws(w); + j = fs_get(sizeof(JSON_S)); + memset((void *) j, 0, sizeof(JSON_S)); + j->value = json_value_parse(&w); + json_skipws(w); + switch(*w){ + case ',' : json_skipchar(w); + j->next = json_array_parse_work(&w); + break; + + case ']' : break; + default : json_free(&j); + } + *s = w; + return j; +} + +void +json_array_free(JSON_S **j) +{ + json_free(j); +} + +JSON_S * +json_parse_pairs(unsigned char **s) +{ + JSON_S *j; + unsigned char *u, *w = *s; + + json_skipws(w); + if(*w++ != '\"') + return NIL; + + u = json_strchr(w, '\"'); + if(!u) + return NIL; + + *u = '\0'; + j = fs_get(sizeof(JSON_S)); + memset((void *) j, 0, sizeof(JSON_S)); + j->name = (unsigned char *) cpystr((char *) w); + + *u = '\"'; + json_skipchar(u); + if(*u != ':') + return j; + json_skipchar(u); + + j->value = json_value_parse(&u); + json_skipws(u); + if (*u == ','){ + json_skipchar(u); + j->next = json_parse_pairs(&u); + } + *s = u; + return j; +} + +void +json_value_free(JSON_X **jxp) +{ + if(!jxp || !*jxp) + return; + + switch((*jxp)->jtype){ + case JString: + case JLong : + case JDecimal: + case JExponential: + case JBoolean: + case JNull: fs_give((void **) &(*jxp)->value); + break; + + case JArray: json_array_free((JSON_S **) &(*jxp)->value); + break; + + case JObject: json_free((JSON_S **) &(*jxp)->value); + break; + + default: printf("Unhandled case in json_value_free"); + exit(1); + } +} + +void +json_free(JSON_S **jp) +{ + + if(jp == NULL || *jp == NULL) return; + + if((*jp)->name) fs_give((void **) &(*jp)->name); + if((*jp)->value) json_value_free(&(*jp)->value); + if((*jp)->next) json_free(&(*jp)->next); + fs_give((void **) jp); +} + +JSON_S * +json_parse(unsigned char **s) +{ + JSON_S *j = NULL; + JSON_X *jx; + unsigned char *w = *s; + + json_skipws(w); + if(*w == '{'){ + json_skipchar(w); + jx = fs_get(sizeof(JSON_X)); + memset((void *) jx, 0, sizeof(JSON_X)); + jx->jtype = JObject; + jx->value = (void *) json_parse_pairs(&w); + j = fs_get(sizeof(JSON_S)); + memset((void *) j, 0, sizeof(JSON_S)); + j->value = jx; + json_skipws(w); + if(*w == '}'){ + json_skipchar(w); + } + else + json_free(&j); + } + *s = w; + return j; +} + +#ifdef STANDALONE +int main (int argc, char *argv[]) +{ + unsigned char *s, *t; + JSON_S *json; + JSON_X *jx; + + t = NULL; + readfile(argv[1], (char **) &t, NULL); + s = t; + json = json_parse(&s); + if(!*s) printf("Success!\n"); +// jx = json_body_value(json, (unsigned char *) "subject"); +// if(jx) printf("subject = %s\n", (char *) jx->value); + if(t) fs_give((void **)&t); + exit(0); +} +#endif /* STANDALONE */ diff --git a/imap/src/c-client/json.h b/imap/src/c-client/json.h new file mode 100644 index 00000000..88585170 --- /dev/null +++ b/imap/src/c-client/json.h @@ -0,0 +1,32 @@ +/* + * Copyright 2018 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 JSON_H_INCLUDED +#define JSON_H_INCLUDED + +typedef enum {JValue, JString, JLong, JDecimal, JExponential, JNumberError, + JObject, JArray, JBoolean, JNull, JEnd} JObjType; + +typedef struct json_x { + JObjType jtype; + void *value; +} JSON_X; + +typedef struct json_s { + unsigned char *name; + JSON_X *value; + struct json_s *next; +} JSON_S; + +JSON_S *json_parse(unsigned char **); +JSON_X *json_body_value(JSON_S *, unsigned char *); +void json_free(JSON_S **); + +#endif /* JSON_H_INCLUDED */ diff --git a/imap/src/c-client/mail.c b/imap/src/c-client/mail.c index 8f0373ed..49444ebe 100644 --- a/imap/src/c-client/mail.c +++ b/imap/src/c-client/mail.c @@ -89,6 +89,8 @@ static threadresults_t mailthreadresults = NIL; static copyuid_t mailcopyuid = NIL; /* APPEND UID results */ static appenduid_t mailappenduid = NIL; + +static oauth2getaccesscode_t oauth2getaccesscode = NIL; /* free elt extra stuff callback */ static freeeltsparep_t mailfreeeltsparep = NIL; /* free envelope extra stuff callback */ @@ -665,6 +667,12 @@ void *mail_parameters (MAILSTREAM *stream,long function,void *value) idapp = (IDLIST *) value; case GET_IDPARAMS: ret = (void *) idapp; + break; + case SET_OA2CLIENTGETACCESSCODE: + oauth2getaccesscode = (oauth2getaccesscode_t) value; + case GET_OA2CLIENTGETACCESSCODE: + ret = (void *) oauth2getaccesscode; + break; default: if ((r = smtp_parameters (function,value)) != NULL) ret = r; if ((r = env_parameters (function,value)) != NULL) ret = r; @@ -6211,7 +6219,8 @@ static NETDRIVER tcpdriver = { tcp_host, /* return host name */ tcp_remotehost, /* return remote host name */ tcp_port, /* return port number */ - tcp_localhost /* return local host name */ + tcp_localhost, /* return local host name */ + tcp_getsize /* read a specific number of bytes */ }; @@ -6325,6 +6334,13 @@ char *net_getline (NETSTREAM *stream) } +char *net_getsize (NETSTREAM *stream, unsigned long size) +{ + return (*stream->dtb->getsize) (stream->stream, size); +} + + + /* Network receive buffer * Accepts: Network stream (must be void * for use as readfn_t) * size in bytes diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h index 58d2979c..b5cd65d2 100644 --- a/imap/src/c-client/mail.h +++ b/imap/src/c-client/mail.h @@ -1,7 +1,7 @@ /* - * Copyright 2016 Eduardo Chappa + * Copyright 2016-2018 Eduardo Chappa * - * Last Edited: February 6, 2015 Eduardo Chappa + * Last Edited: July 21, 2018 Eduardo Chappa * */ /* ======================================================================== @@ -154,6 +154,8 @@ #define SET_IDPARAMS (long) 166 #define GET_IDSTREAM (long) 167 #define SET_IDSTREAM (long) 168 +#define GET_OA2CLIENTGETACCESSCODE (long) 169 +#define SET_OA2CLIENTGETACCESSCODE (long) 170 /* 2xx: environment */ #define GET_USERNAME (long) 201 @@ -396,7 +398,7 @@ #define DR_HALFOPEN (long) 0x10000 #define DR_DIRFMT (long) 0x20000/* driver is a directory-format */ #define DR_MODSEQ (long) 0x40000/* driver supports modseqs */ - +#define DR_SHORTLIVED (long) 0x80000/* assume server disconnects after sending command */ /* Cache management function codes */ @@ -769,7 +771,8 @@ typedef struct mail_envelope { #define TYPEIMAGE 5 /* static image */ #define TYPEVIDEO 6 /* video */ #define TYPEMODEL 7 /* model */ -#define TYPEOTHER 8 /* unknown */ +#define TYPEALL 8 /* type "*" */ +#define TYPEOTHER 9 /* unknown */ #define TYPEMAX 15 /* maximum type code */ @@ -1253,6 +1256,7 @@ NETDRIVER { char *(*remotehost) (void *stream); unsigned long (*port) (void *stream); char *(*localhost) (void *stream); + char *(*getsize) (void *stream, unsigned long size); }; @@ -1662,7 +1666,8 @@ void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes); void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status); void mm_log (char *string,long errflg); void mm_dlog (char *string); -void mm_login (NETMBX *mb,char *user,char *pwd,long trial); +void mm_login (NETMBX *mb,char *user,char **pwd,long trial); +void mm_login_method (NETMBX *mb,char *user, void *info,long trial, char *method); void mm_critical (MAILSTREAM *stream); void mm_nocritical (MAILSTREAM *stream); long mm_diskerror (MAILSTREAM *stream,long errcode,long serious); @@ -1882,6 +1887,7 @@ char *net_host (NETSTREAM *stream); char *net_remotehost (NETSTREAM *stream); unsigned long net_port (NETSTREAM *stream); char *net_localhost (NETSTREAM *stream); +char *net_getsize(NETSTREAM *stream, unsigned long size); long sm_subscribe (char *mailbox); long sm_unsubscribe (char *mailbox); @@ -1902,3 +1908,44 @@ long INWAIT (long seconds); int PSOUT (char *s); int PSOUTR (SIZEDTEXT *s); int PFLUSH (void); + +/* XOAUTH Client-Side Support */ + +#define OA2NAME "XOAUTH2" + +#define OAUTH2_MAX_EQUIV (2) +#define OAUTH2_TOT_EQUIV (OAUTH2_MAX_EQUIV + 2) +#define OAUTH2_PARAM_NUMBER (7) + +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; + +typedef enum {OA2_GetAccessCode = 0, + OA2_GetAccessTokenFromAccessCode, + OA2_GetAccessTokenFromRefreshToken, + OA2_GetEnd} OA2_function; + +typedef struct OA2_param_s { + char *name; + char *value; +} OAUTH2_PARAM_S; + +typedef struct OA2_serverparam_s { + char *name; /* method name: GET or POST */ + char *urlserver; + OA2_type params[OAUTH2_PARAM_NUMBER]; +} OAUTH2_SERVER_METHOD_S; + +typedef struct oauth2_s { + 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]; + char *access_token; + unsigned long expiration; +} OAUTH2_S; + +typedef char *(*oauth2getaccesscode_t) (char *url, OAUTH2_S *, int *); diff --git a/imap/src/c-client/nntp.c b/imap/src/c-client/nntp.c index b1d08024..53b51454 100644 --- a/imap/src/c-client/nntp.c +++ b/imap/src/c-client/nntp.c @@ -2039,7 +2039,7 @@ long nntp_send_auth (SENDSTREAM *stream,long flags) long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) { unsigned long trial,auths; - char tmp[MAILTMPLEN],usr[MAILTMPLEN]; + char tmp[MAILTMPLEN],usr[MAILTMPLEN], *pwd2 = NIL; AUTHENTICATOR *at; char *lsterr = NIL; long ret = NIL; @@ -2096,10 +2096,10 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) else for (trial = 0, pwd[0] = 'x'; !ret && pwd[0] && (trial < nntp_maxlogintrials) && stream->netstream; ) { - pwd[0] = NIL; /* get user name and password */ - mm_login (mb,usr,pwd,trial++); + mm_login (mb,usr, &pwd2,trial++); + pwd[0] = pwd2 ? pwd2[0] : '\0'; /* do the authentication */ - if (pwd[0]) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) { + if (pwd2 && *pwd2) switch ((int) nntp_send_work (stream,"AUTHINFO USER",usr)) { case NNTPBADCMD: /* give up if unrecognized command */ mm_log (NNTP.ext.authuser ? stream->reply : "Can't do AUTHINFO USER to this server",ERROR); @@ -2110,7 +2110,7 @@ long nntp_send_auth_work (SENDSTREAM *stream,NETMBX *mb,char *pwd,long flags) break; case NNTPWANTPASS: /* wants password */ stream->sensitive = T; /* hide this command */ - if (nntp_send_work (stream,"AUTHINFO PASS",pwd) == NNTPAUTHED) + if (nntp_send_work (stream,"AUTHINFO PASS",pwd2) == NNTPAUTHED) ret = LONGT; /* password OK */ stream->sensitive = NIL; /* unhide */ if (ret) break; /* OK if successful */ diff --git a/imap/src/c-client/pop3.c b/imap/src/c-client/pop3.c index dfc4f925..7ca07da8 100644 --- a/imap/src/c-client/pop3.c +++ b/imap/src/c-client/pop3.c @@ -554,7 +554,7 @@ long pop3_capa (MAILSTREAM *stream,long flags) long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) { unsigned long i,trial,auths = 0, authsaved; - char *t; + char *t, *app_pwd = NIL; AUTHENTICATOR *at, *atsaved; long ret = NIL; long flags = (stream->secure ? AU_SECURE : NIL) | @@ -675,7 +675,12 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) trial = 0; /* initial trial count */ do { pwd[0] = 0; /* prompt user for password */ - mm_login (mb,usr,pwd,trial++); + if(app_pwd) fs_give((void **) &app_pwd); + mm_login (mb,usr, &app_pwd,trial++); + if(app_pwd){ + strncpy(pwd, app_pwd, MAILTMPLEN); + pwd[MAILTMPLEN-1] = '\0'; + } if (pwd[0]) { /* send login sequence if have password */ if (pop3_send (stream,"USER",usr)) { LOCAL->sensitive = T; /* hide this command */ @@ -693,6 +698,7 @@ long pop3_auth (MAILSTREAM *stream,NETMBX *mb,char *pwd,char *usr) } while (!ret && pwd[0] && (trial < pop3_maxlogintrials) && LOCAL->netstream); } + if(app_pwd) fs_give((void **) &app_pwd); memset (pwd,0,MAILTMPLEN); /* erase password */ /* get capabilities if logged in */ if (ret && capaok) pop3_capa (stream,flags); diff --git a/imap/src/c-client/rfc822.c b/imap/src/c-client/rfc822.c index b4c72a36..a0195b97 100644 --- a/imap/src/c-client/rfc822.c +++ b/imap/src/c-client/rfc822.c @@ -65,7 +65,7 @@ static const char *errhst = ERRHOST; char *body_types[TYPEMAX+1] = { "TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", - "MODEL", "X-UNKNOWN" + "MODEL", "*", "X-UNKNOWN" }; diff --git a/imap/src/c-client/sslio.h b/imap/src/c-client/sslio.h index 8063f3fc..ab163d6c 100644 --- a/imap/src/c-client/sslio.h +++ b/imap/src/c-client/sslio.h @@ -40,6 +40,7 @@ struct ssl_driver { /* must parallel NETDRIVER in mail.h */ char *(*remotehost) (SSLSTREAM *stream); unsigned long (*port) (SSLSTREAM *stream); char *(*localhost) (SSLSTREAM *stream); + char *(*getsize) (SSLSTREAM *stream, unsigned long size); }; @@ -58,6 +59,7 @@ typedef struct ssl_stdiostream { SSLSTREAM *ssl_open (char *host,char *service,unsigned long port); SSLSTREAM *ssl_aopen (NETMBX *mb,char *service,char *usrbuf); char *ssl_getline (SSLSTREAM *stream); +char *ssl_getsize (SSLSTREAM *stream, unsigned long size); long ssl_getbuffer (SSLSTREAM *stream,unsigned long size,char *buffer); long ssl_getdata (SSLSTREAM *stream); long ssl_soutr (SSLSTREAM *stream,char *string); diff --git a/imap/src/c-client/tcp.h b/imap/src/c-client/tcp.h index ecf39bef..48883e2a 100644 --- a/imap/src/c-client/tcp.h +++ b/imap/src/c-client/tcp.h @@ -40,6 +40,7 @@ void *tcp_parameters (long function,void *value); TCPSTREAM *tcp_open (char *host,char *service,unsigned long port); TCPSTREAM *tcp_aopen (NETMBX *mb,char *service,char *usrbuf); char *tcp_getline (TCPSTREAM *stream); +char *tcp_getsize (TCPSTREAM *stream, unsigned long size); long tcp_getbuffer (TCPSTREAM *stream,unsigned long size,char *buffer); long tcp_getdata (TCPSTREAM *stream); long tcp_soutr (TCPSTREAM *stream,char *string); diff --git a/imap/src/dmail/dmail.c b/imap/src/dmail/dmail.c index cf9bb732..31c71355 100644 --- a/imap/src/dmail/dmail.c +++ b/imap/src/dmail/dmail.c @@ -612,11 +612,15 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { fatal ("mm_login() call"); } +void mm_login_method(NETMBX *mb, char *user, void *login, long trial, char *method) +{ + fatal ("mm_login_method() call"); +} /* About to enter critical code * Accepts: stream diff --git a/imap/src/imapd/imapd.c b/imap/src/imapd/imapd.c index 1c1d4011..f53dca82 100644 --- a/imap/src/imapd/imapd.c +++ b/imap/src/imapd/imapd.c @@ -4642,11 +4642,16 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { /* set user name */ strncpy (username,*mb->user ? mb->user : (char *) user,NETMAXUSER); - strncpy (password,pass,256); /* and password */ + *password = cpystr(pass); /* and password */ +} + +void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method) +{ + password = NULL; } diff --git a/imap/src/ipopd/ipop2d.c b/imap/src/ipopd/ipop2d.c index 58d4d2d5..48844b3c 100644 --- a/imap/src/ipopd/ipop2d.c +++ b/imap/src/ipopd/ipop2d.c @@ -646,12 +646,17 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { /* set user name */ strncpy (username,*mb->user ? mb->user : user,NETMAXUSER-1); - strncpy (password,pass,255); /* and password */ - username[NETMAXUSER] = password[255] = '\0'; + *password = cpystr(pass); /* and password */ + username[NETMAXUSER] = (*password)[255] = '\0'; +} + +void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method) +{ + password = NULL; } /* About to enter critical code diff --git a/imap/src/ipopd/ipop3d.c b/imap/src/ipopd/ipop3d.c index 9cb8bffc..2be17afb 100644 --- a/imap/src/ipopd/ipop3d.c +++ b/imap/src/ipopd/ipop3d.c @@ -1013,18 +1013,22 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { /* set user name */ strncpy (username,*mb->user ? mb->user : user,NETMAXUSER-1); if (pass) { - strncpy (password,pass,255);/* and password */ + *password = cpystr(pass);/* and password */ + (*password)[255] = '\0'; fs_give ((void **) &pass); } - else memset (password,0,256); /* no password to send, abort login */ - username[NETMAXUSER] = password[255] = '\0'; + username[NETMAXUSER] = '\0'; } +void mm_login_method (NETMBX *mb,char *username,void *password,long trial, char *method) +{ + password = NULL; +} /* About to enter critical code * Accepts: stream */ diff --git a/imap/src/mailutil/mailutil.c b/imap/src/mailutil/mailutil.c index d8faefa4..979416bd 100644 --- a/imap/src/mailutil/mailutil.c +++ b/imap/src/mailutil/mailutil.c @@ -1,5 +1,5 @@ /* - * Copyright 2016 Eduardo Chappa + * Copyright 2016-2018 Eduardo Chappa */ /* ======================================================================== @@ -1035,7 +1035,7 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { char *s,tmp[MAILTMPLEN]; sprintf (s = tmp,"{%s/%s",mb->host,mb->service); @@ -1050,7 +1050,37 @@ void mm_login (NETMBX *mb,char *username,char *password,long trial) if ((s = strchr (username,'\n')) != NULL) *s = '\0'; s = "password: "; } - if(strlen (s = getpass (s)) < MAILTMPLEN) strcpy (password,s); + if(strlen (s = getpass (s)) < MAILTMPLEN) *password = cpystr(s); +} + +void mm_login_method (NETMBX *mb,char *username, void *login,long trial, char *method) +{ + if(method == NULL) return; + if(strcmp(method, "XOAUTH2") == 0){ + OAUTH2_S *muinfo = NULL; /* mail util info */ + char *s,tmp[MAILTMPLEN]; + sprintf (s = tmp,"{%s/%s",mb->host,mb->service); + if (*mb->user) sprintf (tmp+strlen (tmp),"/user=%s", + strcpy (username,mb->user)); + if (*mb->user) strcat (s = tmp,"} access token: "); + else { + printf ("%s} username: ",tmp); + fgets (username,NETMAXUSER-1,stdin); + username[NETMAXUSER-1] = '\0'; + if ((s = strchr (username,'\n')) != NULL) *s = '\0'; + printf ("%s} access token: ", tmp); + } + fgets (s,MAILTMPLEN/2-1,stdin); + tmp[MAILTMPLEN/2-1] = '\0'; + if ((s = strchr (tmp,'\n')) != NULL) *s = '\0'; + if(tmp[0]){ + muinfo = fs_get(sizeof(OAUTH2_S)); + memset((void *) muinfo, 0, sizeof(OAUTH2_S)); + muinfo->access_token = cpystr(tmp); + /* STILL MISSING: get expires in info */ + } + login = (void *) muinfo; + } } diff --git a/imap/src/mtest/mtest.c b/imap/src/mtest/mtest.c index 018d1bcd..e433ba8d 100644 --- a/imap/src/mtest/mtest.c +++ b/imap/src/mtest/mtest.c @@ -705,7 +705,7 @@ void mm_dlog (char *string) } -void mm_login (NETMBX *mb,char *user,char *pwd,long trial) +void mm_login (NETMBX *mb,char *user,char **pwd,long trial) { char *s,tmp[MAILTMPLEN]; if (curhst) fs_give ((void **) &curhst); @@ -724,9 +724,16 @@ void mm_login (NETMBX *mb,char *user,char *pwd,long trial) } if (curusr) fs_give ((void **) &curusr); curusr = cpystr (user); - strcpy (pwd,getpass (s)); + if(pwd) *pwd = cpystr(getpass (s)); } +/* IMPLEMENT THIS LATER */ +void mm_login_method (NETMBX *mb,char *user,void *pwd,long trial, char *method) +{ + mm_login(mb, user, (char **) pwd, trial); +} + + void mm_critical (MAILSTREAM *stream) { diff --git a/imap/src/osdep/unix/Makefile b/imap/src/osdep/unix/Makefile index 8d740bb4..a94040c8 100644 --- a/imap/src/osdep/unix/Makefile +++ b/imap/src/osdep/unix/Makefile @@ -140,7 +140,7 @@ RANLIB=ranlib # Standard distribution build parameters -DEFAULTAUTHENTICATORS=ext md5 pla log +DEFAULTAUTHENTICATORS=ext md5 pla oa2 log # # mh needs to be after any other directory format drivers (such as mx or mix) # since otherwise mh will seize any directory that is under the mh path. @@ -155,7 +155,7 @@ CHUNKSIZE=65536 ARCHIVE=c-client.a BINARIES=osdep.o mail.o misc.o newsrc.o smanager.o utf8.o utf8aux.o siglocal.o \ dummy.o pseudo.o netmsg.o flstring.o fdstring.o \ - rfc822.o nntp.o smtp.o imap4r1.o pop3.o \ + rfc822.o nntp.o smtp.o imap4r1.o http.o json.o pop3.o \ unix.o mbx.o mmdf.o tenex.o mtx.o news.o phile.o mh.o mx.o mix.o CFLAGS=-g @@ -889,7 +889,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 +mail.o: mail.h misc.h osdep.h rfc822.h linkage.h json.h 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 @@ -911,7 +911,8 @@ tenex.o: mail.h misc.h osdep.h dummy.h unix.o: mail.h misc.h osdep.h unix.h pseudo.h dummy.h utf8.o: mail.h misc.h osdep.h utf8.h tmap.c widths.c utf8aux.o: mail.h misc.h osdep.h utf8.h - +json.o: mail.h misc.h osdep.h utf8.h json.h +http.o: mail.h misc.h osdep.h utf8.h http.h json.h # OS-dependent @@ -922,7 +923,8 @@ osdep.o:mail.h misc.h env.h fs.h ftl.h nl.h tcp.h \ 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_pla.c \ + auth_ext.c auth_gss.c auth_log.c auth_md5.c auth_ntl.c \ + auth_oa2.c auth_pla.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 \ diff --git a/imap/src/osdep/unix/ssl_unix.c b/imap/src/osdep/unix/ssl_unix.c index 53c5d05a..21bf55ee 100644 --- a/imap/src/osdep/unix/ssl_unix.c +++ b/imap/src/osdep/unix/ssl_unix.c @@ -107,7 +107,8 @@ static struct ssl_driver ssldriver = { ssl_host, /* return host name */ ssl_remotehost, /* return remote host name */ ssl_port, /* return port number */ - ssl_localhost /* return local host name */ + ssl_localhost, /* return local host name */ + ssl_getsize /* return needed number of bytes */ }; /* non-NIL if doing SSL primary I/O */ static SSLSTDIOSTREAM *sslstdio = NIL; @@ -546,7 +547,28 @@ char *ssl_getline (SSLSTREAM *stream) } return ret; } - + +char *ssl_getsize (SSLSTREAM *stream, unsigned long size) +{ + char *ret = NIL; + unsigned long got = 0L, need = size, n; + int done = 0; + + while(!done){ + if(!ssl_getdata (stream)) return ret; /* return what we have */ + n = stream->ictr < need ? stream->ictr : need; + fs_resize((void **) &ret, got + n + 1); + memcpy(ret + got, stream->iptr, n); + ret[got+n] = '\0'; + got += n; + need -= n; + stream->iptr += n; + stream->ictr -= n; + if(need == 0L) done++; + } + + return ret; +} /* SSL receive line or partial line * Accepts: SSL stream * pointer to return size diff --git a/imap/src/osdep/unix/tcp_unix.c b/imap/src/osdep/unix/tcp_unix.c index b076ce44..73791f60 100644 --- a/imap/src/osdep/unix/tcp_unix.c +++ b/imap/src/osdep/unix/tcp_unix.c @@ -1063,6 +1063,28 @@ long tcp_isclienthost (char *host) return ret; } +char *tcp_getsize (TCPSTREAM *stream, unsigned long size) +{ + char *ret = NIL; + unsigned long got = 0L, need = size, n; + int done = 0; + + while(!done){ + if(!tcp_getdata (stream)) return ret; /* return what we have */ + n = stream->ictr < need ? stream->ictr : need; + fs_resize((void **) &ret, got + n + 1); + memcpy(ret + got, stream->iptr, n); + ret[got+n] = '\0'; + got += n; + need -= n; + stream->iptr += n; + stream->ictr -= n; + if(need == 0L) done++; + } + + return ret; +} + /* Following statement must be at end of this module */ #undef fork /* undo any use of vfork() */ diff --git a/imap/src/tmail/tmail.c b/imap/src/tmail/tmail.c index 273db40a..1e4a4e47 100644 --- a/imap/src/tmail/tmail.c +++ b/imap/src/tmail/tmail.c @@ -752,11 +752,15 @@ void mm_dlog (char *string) * trial count */ -void mm_login (NETMBX *mb,char *username,char *password,long trial) +void mm_login (NETMBX *mb,char *username,char **password,long trial) { fatal ("mm_login() call"); } +void mm_login_method(NETMBX *mb, char *user, void *login, long trial, char *method) +{ + fatal ("mm_login_method() call"); +} /* About to enter critical code * Accepts: stream -- cgit v1.2.3-70-g09d2