summaryrefslogtreecommitdiff
path: root/imap/src
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
committerEduardo Chappa <chappa@washington.edu>2019-05-04 12:41:11 -0600
commitc024a78dbaa9b42db7f18b0fea1894c41e2b0d67 (patch)
tree441e7308e4577ac8766c44edda682704aa432262 /imap/src
parent19cde66486e27063a9af8cfd79c6eb7f106b9111 (diff)
downloadalpine-c024a78dbaa9b42db7f18b0fea1894c41e2b0d67.tar.xz
* Initial release of XOAUTH2 authentication support in Alpine for
Gmail.
Diffstat (limited to 'imap/src')
-rw-r--r--imap/src/c-client/auth_log.c10
-rw-r--r--imap/src/c-client/auth_md5.c10
-rw-r--r--imap/src/c-client/auth_ntl.c13
-rw-r--r--imap/src/c-client/auth_oa2.c359
-rw-r--r--imap/src/c-client/auth_pla.c10
-rw-r--r--imap/src/c-client/http.c1215
-rw-r--r--imap/src/c-client/http.h116
-rw-r--r--imap/src/c-client/imap4r1.c30
-rw-r--r--imap/src/c-client/json.c497
-rw-r--r--imap/src/c-client/json.h32
-rw-r--r--imap/src/c-client/mail.c18
-rw-r--r--imap/src/c-client/mail.h57
-rw-r--r--imap/src/c-client/nntp.c10
-rw-r--r--imap/src/c-client/pop3.c10
-rw-r--r--imap/src/c-client/rfc822.c2
-rw-r--r--imap/src/c-client/sslio.h2
-rw-r--r--imap/src/c-client/tcp.h1
-rw-r--r--imap/src/dmail/dmail.c6
-rw-r--r--imap/src/imapd/imapd.c9
-rw-r--r--imap/src/ipopd/ipop2d.c11
-rw-r--r--imap/src/ipopd/ipop3d.c12
-rw-r--r--imap/src/mailutil/mailutil.c36
-rw-r--r--imap/src/mtest/mtest.c11
-rw-r--r--imap/src/osdep/unix/Makefile12
-rw-r--r--imap/src/osdep/unix/ssl_unix.c26
-rw-r--r--imap/src/osdep/unix/tcp_unix.c22
-rw-r--r--imap/src/tmail/tmail.c6
27 files changed, 2480 insertions, 63 deletions
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 <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#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 <chappa@washington.edu>
+ *
+ */
+
+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 <ctype.h>
+#include <stdio.h>
+#include <time.h>
+#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 <chappa@gmx.com>
+ * Last Edited: July 21, 2018 Eduardo Chappa <chappa@washington.edu>
*
*/
/* ========================================================================
@@ -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