summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEduardo Chappa <chappa@washington.edu>2020-06-26 12:19:56 -0600
committerEduardo Chappa <chappa@washington.edu>2020-06-26 12:19:56 -0600
commit354674406ed866dffbcab71455f796275a63df36 (patch)
tree6cb79243d5c72f569a9a7c944558251ea4af6464
parent9a21decd8d4528c36870a86f631c754b0b63411f (diff)
downloadalpine-354674406ed866dffbcab71455f796275a63df36.tar.xz
* Expansion of the configuration screen for XOAUTH2 to include
username, and tenant. * If a user has more than one client-id for a service, Alpine tries to asks the user which client-id to use and associates that client-id to the credentials in the XOAUTH2 configuration screen.
-rw-r--r--alpine/arg.c65
-rw-r--r--alpine/confscroll.c23
-rw-r--r--alpine/conftype.h4
-rw-r--r--alpine/imap.c12
-rw-r--r--alpine/keymenu.c70
-rw-r--r--alpine/keymenu.h7
-rw-r--r--alpine/xoauth2.h8
-rw-r--r--alpine/xoauth2conf.c976
-rw-r--r--alpine/xoauth2conf.h8
-rw-r--r--imap/src/c-client/auth_bea.c21
-rw-r--r--imap/src/c-client/auth_oa2.c14
-rw-r--r--imap/src/c-client/http.h1
-rw-r--r--imap/src/c-client/mail.h14
-rw-r--r--imap/src/c-client/oauth2_aux.c132
-rw-r--r--imap/src/osdep/nt/makefile.nt2
-rw-r--r--pith/pine.hlp95
16 files changed, 1159 insertions, 293 deletions
diff --git a/alpine/arg.c b/alpine/arg.c
index 1971e802..25419b30 100644
--- a/alpine/arg.c
+++ b/alpine/arg.c
@@ -83,7 +83,9 @@ static char args_err_missing_copyabook[] = N_("missing argument for option \"-co
static char args_err_missing_server_name[] = N_("missing server name. Example: -xoauth2-server Gmail");
static char args_err_missing_client_id[] = N_("missing client-id name. Example: -xoauth2-client-id 760.someserver.com");
static char args_err_missing_client_secret[] = N_("missing client-secret name. Example: -xoauth2-client-secret V56i0fa_");
-static char args_err_missing_xoauth_option[] = N_("at least one of the arguments -xoauth2-server, or -xoauth2-client-id, or -xoauth2-client-secret is missing.");
+static char args_err_missing_tenant[] = N_("missing tenant value. Example: -xoauth2-tenant common");
+static char args_err_missing_user[] = N_("missing username value. Example: -xoauth2-user user@example.com");
+static char args_err_missing_xoauth_option[] = N_("at least one of the arguments -xoauth2-server, or -xoauth2-client-id, xoauth2-tenant, or -xoauth2-client-secret is missing.");
static char *args_pine_args[] = {
N_("Possible Starting Arguments for Alpine program:"),
@@ -217,9 +219,7 @@ pine_args(struct pine *pine_state, int argc, char **argv, ARGDATA_S *args)
char *sort = NULL;
char *pinerc_file = NULL;
char *lc = NULL;
- char *xoauth2_server = NULL;
- char *xoauth2_client_id = NULL;
- char *xoauth2_client_secret = NULL;
+ XOAUTH2_INFO_S x;
int do_help = 0;
int do_conf = 0;
int usage = 0;
@@ -232,6 +232,7 @@ pine_args(struct pine *pine_state, int argc, char **argv, ARGDATA_S *args)
av = argv;
memset(args, 0, sizeof(ARGDATA_S));
args->action = aaFolder;
+ memset((void *) &x, 0, sizeof(XOAUTH2_INFO_S));
pine_state->pine_name = (lc = last_cmpnt(argv[0])) ? lc : (lc = argv[0]);
#ifdef DOS
@@ -556,9 +557,9 @@ Loop: while(--ac > 0)
else if(strcmp(*av, "xoauth2-server") == 0){
if(--ac){
if((str = *++av) != NULL){
- if(xoauth2_server)
- fs_give((void **) &xoauth2_server);
- xoauth2_server = cpystr(str);
+ if(x.name)
+ fs_give((void **) &x.name);
+ x.name = cpystr(str);
}
}
else{
@@ -570,9 +571,9 @@ Loop: while(--ac > 0)
else if(strcmp(*av, "xoauth2-client-id") == 0){
if(--ac){
if((str = *++av) != NULL){
- if(xoauth2_client_id)
- fs_give((void **) &xoauth2_client_id);
- xoauth2_client_id = cpystr(str);
+ if(x.client_id)
+ fs_give((void **) &x.client_id);
+ x.client_id = cpystr(str);
}
}
else{
@@ -584,9 +585,9 @@ Loop: while(--ac > 0)
else if(strcmp(*av, "xoauth2-client-secret") == 0){
if(--ac){
if((str = *++av) != NULL){
- if(xoauth2_client_secret)
- fs_give((void **) &xoauth2_client_secret);
- xoauth2_client_secret = cpystr(str);
+ if(x.client_secret)
+ fs_give((void **) &x.client_secret);
+ x.client_secret = cpystr(str);
}
}
else{
@@ -595,6 +596,34 @@ Loop: while(--ac > 0)
}
goto Loop;
}
+ else if(strcmp(*av, "xoauth2-tenant") == 0){
+ if(--ac){
+ if((str = *++av) != NULL){
+ if(x.tenant)
+ fs_give((void **) &x.tenant);
+ x.tenant = cpystr(str);
+ }
+ }
+ else{
+ display_args_err(_(args_err_missing_tenant), NULL, 1);
+ ++usage;
+ }
+ goto Loop;
+ }
+ else if(strcmp(*av, "xoauth2-user") == 0){
+ if(--ac){
+ if((str = *++av) != NULL){
+ if(x.users)
+ fs_give((void **) &x.users);
+ x.users = cpystr(str);
+ }
+ }
+ else{
+ display_args_err(_(args_err_missing_user), NULL, 1);
+ ++usage;
+ }
+ goto Loop;
+ }
#ifdef _WINDOWS
else if(strcmp(*av, "install") == 0){
pine_state->install_flag = 1;
@@ -942,21 +971,15 @@ Loop: while(--ac > 0)
exit(-1);
}
- if((xoauth2_server || xoauth2_client_id || xoauth2_client_secret)
- && !(xoauth2_server && xoauth2_client_id && xoauth2_client_secret)){
- display_args_err(_(args_err_missing_xoauth_option), NULL, 1);
- ++usage;
- }
-
if(do_help || usage)
args_help();
if(usage)
exit(-1);
- if(xoauth2_server){
+ if(x.name){
char *tmp1, *tmp2;
- tmp2 = xoauth_config_line(xoauth2_server, xoauth2_client_id, xoauth2_client_secret);
+ tmp2 = xoauth_config_line(&x);
if(tmp2){
tmp1 = fs_get((strlen(ps_global->vars[V_XOAUTH2_INFO].name) + strlen(tmp2) + 2)*sizeof(char));
if(tmp1){
diff --git a/alpine/confscroll.c b/alpine/confscroll.c
index ee182b21..db7d2cf0 100644
--- a/alpine/confscroll.c
+++ b/alpine/confscroll.c
@@ -33,6 +33,7 @@ static char rcsid[] = "$Id: confscroll.c 1169 2008-08-27 06:42:06Z hubert@u.wash
#include "talk.h"
#include "setup.h"
#include "smime.h"
+#include "xoauth2conf.h"
#include "../pith/state.h"
#include "../pith/flag.h"
#include "../pith/list.h"
@@ -1318,6 +1319,28 @@ no_down:
q_status_message(SM_ORDER,0,3, _("Moved to bottom"));
break;
+ case MC_XSHELP: /* help! */
+ if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
+ km_popped = 2;
+ ps->mangled_footer = 1;
+ break;
+ }
+
+ prev_redrawer = ps_global->redrawer;
+ helper(h_xoauth2_config_screen, "XOAUTH2 CONFIGURATION SCREEN", HLPD_SIMPLE);
+ ps_global->redrawer = prev_redrawer;
+ ps->mangled_screen = 1;
+ break;
+
+ case MC_XSDELETE: /* Send caller to delete XOAUTH2 info */
+ case MC_XSADD: /* Send caller to add XOAUTH2 info */
+ if(pos){
+ *pos = get_confline_number(screen->current);
+ done++;
+ retval = cmd == MC_XSADD ? 4 : 5;
+ }
+ break;
+
case MC_REPAINT: /* redraw the display */
case MC_RESIZE:
ClearScreen();
diff --git a/alpine/conftype.h b/alpine/conftype.h
index dac07b74..9c25ad6b 100644
--- a/alpine/conftype.h
+++ b/alpine/conftype.h
@@ -100,6 +100,10 @@ typedef struct conf_line {
char *exportval;
ScreenMode *listmode;
} t;
+ struct xoauth2_conf {
+ XOAUTH2_INFO_S *pat;
+ XOAUTH2_INFO_S **selected;
+ } x;
} d;
} CONF_S;
diff --git a/alpine/imap.c b/alpine/imap.c
index 78861728..d081228e 100644
--- a/alpine/imap.c
+++ b/alpine/imap.c
@@ -145,7 +145,8 @@ OAUTH2_S alpine_oauth2_list[] =
{"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
{{"client_id", NULL},
{"client_secret", NULL},
- {"code", NULL}, /* not used */
+ {"tenant", NULL}, /* not used */
+ {"code", NULL}, /* access code from the authorization process */
{"refresh_token", NULL},
{"scope", "https://mail.google.com/"},
{"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
@@ -173,6 +174,7 @@ OAUTH2_S alpine_oauth2_list[] =
{"outlook.office365.com", "smtp.office365.com", NULL, NULL},
{{"client_id", NULL},
{"client_secret", NULL}, /* not used, but needed */
+ {"tenant", NULL}, /* used */
{"code", NULL}, /* not used, not needed */
{"refresh_token", NULL},
{"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
@@ -184,11 +186,11 @@ OAUTH2_S alpine_oauth2_list[] =
{"device_code", NULL} /* only used for frst time set up */
},
{{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
- {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/devicecode", /* first time use and get device code information */
+ {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
{OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
- {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
- {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End}},
- {"POST", "https://login.microsoftonline.com/common/oauth2/v2.0/token", /* Get access token from refresh token */
+ {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
+ {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
+ {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
{OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
},
{NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
diff --git a/alpine/keymenu.c b/alpine/keymenu.c
index 69ca92d9..8d94e878 100644
--- a/alpine/keymenu.c
+++ b/alpine/keymenu.c
@@ -1433,6 +1433,34 @@ struct key config_text_keys[] =
ENDKEY_MENU};
INST_KEY_MENU(config_text_keymenu, config_text_keys);
+struct key config_xoauth2_text_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {"^A", N_("Add Server"), {MC_XSADD,1,{ctrl('A')}}, KS_NONE},
+ {"^D", N_("Del Server"), {MC_XSDELETE,1,{ctrl('d')}}, KS_NONE},
+ NULL_MENU,
+ {"^H", N_("Conf Help"), {MC_XSHELP,1,{ctrl('h')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_xoauth2_text_keymenu, config_xoauth2_text_keys);
+
struct key config_text_to_charsets_keys[] =
{HELP_MENU,
@@ -1769,6 +1797,21 @@ struct key role_select_keys[] =
WHEREIS_MENU};
INST_KEY_MENU(role_select_km, role_select_keys);
+struct key xoauth2_id_select_keys[] =
+ {HELP_MENU,
+ NULL_MENU,
+ {"E", N_("Exit"), {MC_EXIT,1,{'e'}}, KS_EXITMODE},
+ {"S", "[" N_("Select") "]", {MC_CHOICE,3,{'s',ctrl('M'),ctrl('J')}}, KS_NONE},
+ {"P", N_("PrevID"), {MC_PREVITEM, 1, {'p'}}, KS_NONE},
+ {"N", N_("NextID"), {MC_NEXTITEM, 2, {'n', TAB}}, KS_NONE},
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU,
+ NULL_MENU,
+ WHEREIS_MENU};
+INST_KEY_MENU(xoauth2_id_select_km, xoauth2_id_select_keys);
+
struct key role_config_keys[] =
{HELP_MENU,
@@ -1829,6 +1872,33 @@ struct key config_text_wshuf_keys[] =
ENDKEY_MENU};
INST_KEY_MENU(config_text_wshuf_keymenu, config_text_wshuf_keys);
+struct key config_xoauth2_wshuf_keys[] =
+ {HELP_MENU,
+ OTHER_MENU,
+ EXIT_SETUP_MENU,
+ {"C", "[" N_("Change Val") "]", {MC_EDIT,3,{'c',ctrl('M'),ctrl('J')}}, KS_NONE},
+ PREV_MENU,
+ NEXT_MENU,
+ PREVPAGE_MENU,
+ NEXTPAGE_MENU,
+ {"A", N_("Add Value"), {MC_ADD,1,{'a'}}, KS_NONE},
+ {"D", N_("Delete Val"), {MC_DELETE,1,{'d'}}, KS_NONE},
+ PRYNTTXT_MENU,
+ WHEREIS_MENU,
+
+ HELP_MENU,
+ OTHER_MENU,
+ {"^A", N_("Add Server"), {MC_XSADD,1,{ctrl('A')}}, KS_NONE},
+ {"^D", N_("Del Server"), {MC_XSDELETE,1,{ctrl('d')}}, KS_NONE},
+ {"$", N_("Shuffle"), {MC_SHUFFLE,1,{'$'}}, KS_NONE},
+ {"^H", N_("Conf Help"), {MC_XSHELP,1,{ctrl('h')}}, KS_NONE},
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ NULL_MENU,
+ HOMEKEY_MENU,
+ ENDKEY_MENU};
+INST_KEY_MENU(config_xoauth2_wshuf_keymenu, config_xoauth2_wshuf_keys);
struct key config_text_wshufandfldr_keys[] =
{HELP_MENU,
diff --git a/alpine/keymenu.h b/alpine/keymenu.h
index 87d1151d..3cf4e2cc 100644
--- a/alpine/keymenu.h
+++ b/alpine/keymenu.h
@@ -217,7 +217,9 @@ struct key_menu {
#define MC_ADDHEADER 804
#define MC_XOAUTH2 805
#define MC_EXTERNAL 806
-
+#define MC_XSADD 807
+#define MC_XSDELETE 808
+#define MC_XSHELP 809
/* Commands for S/MIME screens */
#define MC_TRUST 900
@@ -629,6 +631,7 @@ extern struct key_menu cancel_keymenu,
role_select_km,
role_conf_km,
config_text_wshuf_keymenu,
+ config_xoauth2_wshuf_keymenu,
config_text_wshufandfldr_keymenu,
config_role_file_keymenu,
config_role_file_res_keymenu,
@@ -647,6 +650,8 @@ extern struct key_menu cancel_keymenu,
config_role_afrom_keymenu,
config_checkbox_keymenu,
config_text_keymenu,
+ xoauth2_id_select_km,
+ config_xoauth2_text_keymenu,
config_text_to_charsets_keymenu,
config_radiobutton_keymenu,
config_yesno_keymenu,
diff --git a/alpine/xoauth2.h b/alpine/xoauth2.h
index f3896e86..a50a1e8f 100644
--- a/alpine/xoauth2.h
+++ b/alpine/xoauth2.h
@@ -15,18 +15,14 @@
#ifndef ALPINE_XOAUTH2_INCLUDED
#define ALPINE_XOAUTH2_INCLUDED
-typedef struct xoauth_default_s {
- unsigned char *name;
- char *client_id;
- char *client_secret;
-} XOAUTH2_INFO_S;
-
#define GMAIL_NAME "Gmail"
#define GMAIL_ID "624395471329-0qee3goofj7kbl7hsukou3rqq0igntv1.apps.googleusercontent.com"
#define GMAIL_SECRET "vwnqVJQrJZpR6JilCfAN5nY7"
+#define GMAIL_TENANT NULL
#define OUTLOOK_NAME "Outlook"
#define OUTLOOK_ID "f21dcaf2-8020-469b-8135-343bfc35d046"
#define OUTLOOK_SECRET NULL
+#define OUTLOOK_TENANT "common"
#endif /* ALPINE_XOAUTH2_INCLUDED */
diff --git a/alpine/xoauth2conf.c b/alpine/xoauth2conf.c
index 0e898061..9a6b246a 100644
--- a/alpine/xoauth2conf.c
+++ b/alpine/xoauth2conf.c
@@ -14,96 +14,356 @@
#include "headers.h"
#include "xoauth2conf.h"
-#include "xoauth2.h"
#include "keymenu.h"
#include "status.h"
#include "confscroll.h"
+#include "init.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/list.h"
+#include "../pith/mailcmd.h"
extern OAUTH2_S alpine_oauth2_list[];
XOAUTH2_INFO_S xoauth_default[] = {
- { GMAIL_NAME, GMAIL_ID, GMAIL_SECRET},
- { OUTLOOK_NAME, OUTLOOK_ID, OUTLOOK_SECRET},
- { NULL, NULL, NULL}
+ { GMAIL_NAME, GMAIL_ID, GMAIL_SECRET, GMAIL_TENANT, NULL},
+ { OUTLOOK_NAME, OUTLOOK_ID, OUTLOOK_SECRET, OUTLOOK_TENANT, NULL},
+ { NULL, NULL, NULL, NULL, NULL}
};
+typedef enum {Xname = 0, Xid, Xsecret, Xtenant, Xuser, Xend} XTYPES;
+
+typedef struct xoauh2_info_val_s {
+ char *screen_name;
+ char *pinerc_name;
+} XOAUTH2_INFO_VAL_S;
+
+/* the order here must match the order in XTYPES above */
+XOAUTH2_INFO_VAL_S x_default[] = {
+ {NULL, "/NAME="},
+ {"Client-Id", "/ID="},
+ {"Client-Secret", "/SECRET="},
+ {"Tenant", "/TENANT="},
+ {"Username", "/USER="},
+ {NULL, NULL}
+};
+
+#define XNAME x_default[Xname].pinerc_name
+#define XID x_default[Xid].pinerc_name
+#define XSECRET x_default[Xsecret].pinerc_name
+#define XTENANT x_default[Xtenant].pinerc_name
+#define XUSER x_default[Xuser].pinerc_name
-#define NXSERVERS (sizeof(xoauth_default)/sizeof(xoauth_default[0])-1)
-#define XOAUTH2_CLIENT_ID _("Client-Id")
-#define XOAUTH2_CLIENT_SECRET _("Client-Secret")
-#define XNAME "/NAME="
-#define XID "/ID="
-#define XSECRET "/SECRET="
+#define XOAUTH2_CLIENT_ID x_default[Xid].screen_name
+#define XOAUTH2_CLIENT_SECRET x_default[Xsecret].screen_name
+#define XOAUTH2_TENANT x_default[Xtenant].screen_name
+#define XOAUTH2_USERS x_default[Xuser].screen_name
+char *list_to_array(char **);
+char **array_to_list(char *);
void write_xoauth_configuration(struct variable *, struct variable **, EditWhich);
+char **xoauth2_conf_dedup_and_merge(char ***);
+int same_xoauth2_info(XOAUTH2_INFO_S, XOAUTH2_INFO_S);
+XOAUTH2_INFO_S *xoauth_info_choice(XOAUTH2_INFO_S **, char *);
+int xoauth2_info_tool(struct pine *, int, CONF_S **, unsigned int);
+
+int
+same_xoauth2_info(XOAUTH2_INFO_S x, XOAUTH2_INFO_S y)
+{
+ int rv = 0;
+ if(x.name && y.name && !strcmp(x.name, y.name)
+ && x.client_id && y.client_id && !strcmp(x.client_id, y.client_id)
+ && ((!x.client_secret && !y.client_secret)
+ || (x.client_secret && y.client_secret && !strcmp(x.client_secret, y.client_secret)))
+ && ((!x.tenant && !y.tenant) || (x.tenant && y.tenant && !strcmp(x.tenant, y.tenant))))
+ rv = 1;
+ return rv;
+}
+
+char *
+list_to_array(char **list)
+{
+ char *rv;
+ int i;
+ size_t n;
+
+ if(list == NULL || *list == NULL) return NULL;
+
+ for(i = 0, n = 0; list[i] != NULL; i++)
+ n += strlen(list[i]) + 1;
+
+ rv = fs_get(n*sizeof(char));
+ *rv = '\0';
+ for(i = 0; list[i] != NULL; i++){
+ strcat(rv, list[i]);
+ if(list[i+1] != NULL) strcat(rv, ",");
+ }
+ return rv;
+}
+
+
+char **array_to_list(char *array)
+{
+ int i;
+ char *u;
+
+ if(array == NULL || *array == '\0') return NULL;
+
+ for(u = array, i = 0; u && *u; u++)
+ if(*u == ',') i++;
+
+ return parse_list(array, i+1, 0,NULL);
+}
char *
-xoauth_config_line(char *server, char *id, char *secret)
+xoauth_config_line(XOAUTH2_INFO_S *x)
{
size_t n;
char *rv;
+ int i;
- n = strlen(XNAME) + strlen(XID) + strlen(XSECRET)
- + strlen(server) + strlen(id) + strlen(secret) + 9;
+ if(x == NULL) return NULL;
+
+ n = strlen(XNAME) + strlen(x->name) + strlen(XID) + strlen(x->client_id)
+ + strlen(x->client_secret ? XSECRET : "") + strlen(x->client_secret ? x->client_secret : "")
+ + strlen(x->tenant ? XTENANT : "") + strlen(x->tenant ? x->tenant : "")
+ + strlen(XUSER) + strlen(x->users ? x->users : "")
+ + 2 + 3 + (x->client_secret ? 3 : 0) + (x->tenant ? 3 : 0) + 3 + 1;
rv = fs_get(n*sizeof(char));
- sprintf(rv, "%s\"%s\" %s\"%s\" %s\"%s\"", XNAME, server, XID, id,
- XSECRET, secret);
+ sprintf(rv, "%s\"%s\" %s\"%s\"", XNAME, x->name, XID, x->client_id);
+ if(x->client_secret)
+ sprintf(rv + strlen(rv), " %s\"%s\"", XSECRET, x->client_secret);
+ if(x->tenant)
+ sprintf(rv + strlen(rv), " %s\"%s\"", XTENANT, x->tenant);
+ sprintf(rv + strlen(rv), " %s\"%s\"", XUSER, x->users ? x->users : "");
return rv;
}
-/* call this function when id and secret are unknown.
- * precedence is as follows:
- * If the user has configured something, return that;
- * else if we are already using a value, return that;
- * else return default values.
- */
-void
-oauth2_get_client_info(unsigned char *name, char **id, char **secret)
+int
+xoauth2_info_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
{
- int i;
- char **lval, *name_lval, *idp, *secretp;
+ int rv = 0;
+
+ switch(cmd){
+ case MC_CHOICE:
+ *((*cl)->d.x.selected) = (*cl)->d.x.pat;
+ rv = simple_exit_cmd(flags);
+
+ case MC_EXIT:
+ rv = simple_exit_cmd(flags);
+ break;
+
+ default:
+ rv = -1;
+ }
+
+ if(rv > 0)
+ ps->mangled_body = 1;
- *id = *secret = NULL;
+ return rv;
+}
+
+XOAUTH2_INFO_S *
+xoauth_info_choice(XOAUTH2_INFO_S **xinfo, char *user)
+{
+ int i, n, rv;
+ if(!ps_global->ttyo){
+ char *s;
+ char prompt[1024];
+ char reply[1024];
+ int sel;
+ for(i = n = 0; xinfo[i] != NULL; i++)
+ n += strlen(xinfo[i]->client_id); + 5; /* number, parenthesis, space */
+ n += strlen(xinfo[0]->name) + strlen(user);
+ n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
+ s = fs_get(n*sizeof(char));
+ sprintf(s, _("Alpine cannot determine which client-id to use for the username <%s> for your %s account. "), user, xinfo[0]->name);
+ sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
+ for(i = 0; xinfo[i]; i++)
+ sprintf(s + strlen(s), " %d) %.70s\n", i+1, xinfo[i]->client_id);
+ sprintf(s + strlen(s), "%s", "\n\n");
+
+ display_init_err(s, 0);
+
+ strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
+ prompt[sizeof(prompt)-1] = '\0';
+
+ do{
+ rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
+ sel = atoi(reply) - 1;
+ rv = (sel >= 0 && sel < i) ? 0 : -1;
+ } while (rv != 0);
+ return copy_xoauth2_info(xinfo[rv]);
+ }
+ else{
+ CONF_S *ctmp = NULL, *first_line = NULL;
+ XOAUTH2_INFO_S *x_sel = NULL;
+ OPT_SCREEN_S screen;
+ char tmp[1024];
+
+ dprint((9, "xoauth2 select client-id screen"));
+ ps_global->next_screen = SCREEN_FUN_NULL;
+
+ memset(&screen, 0, sizeof(screen));
+
+ for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
+ tmp[i] = '-';
+ tmp[i] = '\0';
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(_("Select a Client-ID to use with this account"));
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ sprintf(tmp, _("Alpine cannot determine which client-id to use for the username <%s>"), user);
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ sprintf(tmp, _("for your %s account. Please select the client-id to use from the following list.\n\n"), xinfo[0]->name);
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT;
+ ctmp->value = cpystr(tmp);
+
+ new_confline(&ctmp);
+ ctmp->flags |= CF_NOSELECT | CF_B_LINE;
+
+ for(i = 0; xinfo[i] != NULL; i++){
+ new_confline(&ctmp);
+ if(!first_line)
+ first_line = ctmp;
+
+ ctmp->value = cpystr(xinfo[i]->client_id);
+ ctmp->d.x.selected = &x_sel;
+ ctmp->d.x.pat = copy_xoauth2_info(xinfo[i]);
+ ctmp->keymenu = &xoauth2_id_select_km;
+ ctmp->help = NO_HELP;
+ ctmp->help_title = NULL;
+ ctmp->tool = xoauth2_info_tool;
+ ctmp->flags = CF_STARTITEM;
+ ctmp->valoffset = 4;
+ }
+ (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT CLIENT_ID"),
+ _("xoauth2"), 0, NULL);
+ return x_sel;
+ }
+ return NULL;
+}
+
+/* Get the client-id, etc. for server "name" associated to user "user" */
+XOAUTH2_INFO_S *
+oauth2_get_client_info(unsigned char *name, char *user)
+{
+ int i, j, matches;
+ char ***alval, **lval;
+ XOAUTH2_INFO_S *x, **xinfo;
- /* first check the value configured by the user */
+ if(name == NULL || *name == '\0' || user == NULL || *user == '\0')
+ return NULL;
+
+ matches = 0;
+ /* first count how many servers */
lval = ps_global->vars[V_XOAUTH2_INFO].current_val.l;
for(i = 0; lval && lval[i]; i++){
- xoauth_parse_client_info(lval[i], &name_lval, &idp, &secretp);
- if(name_lval && !strcmp(name_lval, name)){
- *id = idp ? cpystr(idp) : NULL;
- *secret = secretp ? cpystr(secretp) : NULL;
- }
- if(name_lval) fs_give((void **) &name_lval);
- if(idp) fs_give((void **) &idp);
- if(secretp) fs_give((void **) &secretp);
- break;
+ x = xoauth_parse_client_info(lval[i]);
+ if(x && x->name && name && !strcmp(x->name, name))
+ matches++;
+ free_xoauth2_info(&x);
}
- if(*id && **id && *secret && **secret) return;
+ /* if nothing, use the default value */
+ for(i = 0; xoauth_default[i].name != NULL && strcmp(xoauth_default[i].name, name); i++);
+ if(xoauth_default[i].name) matches++;
+
+ if(matches == 0) return NULL;
+ if(matches == 1) return copy_xoauth2_info(&xoauth_default[i]);
- /* if not, now see if we already have a value set, and use that */
- for(i = 0; alpine_oauth2_list[i].name != NULL; i++){
- if(!strcmp(alpine_oauth2_list[i].name, name)){
- *id = alpine_oauth2_list[i].param[OA2_Id].value
- ? cpystr(alpine_oauth2_list[i].param[OA2_Id].value) : NULL;
- *secret = alpine_oauth2_list[i].param[OA2_Secret].value
- ? cpystr(alpine_oauth2_list[i].param[OA2_Secret].value) : NULL;
- break;
+ /* more than one match, see if it is a duplicate client-id entry */
+ xinfo = fs_get((matches + 1)*sizeof(XOAUTH2_INFO_S *));
+ memset((void *)xinfo, 0, (matches + 1)*sizeof(XOAUTH2_INFO_S *));
+ matches = 0; /* restart the recount, it might go lower! */
+ for(i = 0; lval && lval[i]; i++){
+ x = xoauth_parse_client_info(lval[i]);
+ if(x && x->name && name && !strcmp(x->name, name)){
+ for(j = 0; xinfo && xinfo[j] && !same_xoauth2_info(*x, *xinfo[j]); j++);
+ if(!xinfo[j]) xinfo[matches++] = copy_xoauth2_info(x);
}
+ free_xoauth2_info(&x);
+ }
+ for(i = 0; xoauth_default[i].name != NULL && strcmp(xoauth_default[i].name, name); i++){
+ for(j = 0; xinfo && xinfo[j] && !same_xoauth2_info(xoauth_default[i], *xinfo[j]); j++);
+ if(xoauth_default[i].name && !xinfo[j]) xinfo[matches++] = copy_xoauth2_info(&xoauth_default[i]);
}
- if(*id && **id && *secret && **secret) return;
+ /* if after removing the duplicate entries, we only have one, use it */
+ if(matches == 1){
+ x = copy_xoauth2_info(xinfo[0]);
+ free_xoauth2_info(&xinfo[0]);
+ fs_give((void **) xinfo);
+ return x;
+ }
- /* if nothing, use the default value */
- for(i = 0; xoauth_default[i].name != NULL; i++)
- if(!strcmp(xoauth_default[i].name, name)){
- *id = cpystr(xoauth_default[i].client_id);
- *secret = cpystr(xoauth_default[i].client_secret);
- break;
- }
+ /* we have more than one match, now check if any of them matches the given user */
+ matches = 0;
+ for(i = 0; xinfo && xinfo[i]; i++){
+ lval = array_to_list(xinfo[i]->users);
+ for(j = 0; lval && lval[j] && strucmp(lval[j], user); j++);
+ if(lval && lval[j]){
+ matches++;
+ free_xoauth2_info(&x);
+ x = copy_xoauth2_info(xinfo[i]);
+ }
+ if(lval) free_list_array(&lval);
+ }
+
+ /* only one server matches the username */
+ if(matches == 1){
+ for(i = 0; xinfo[i] != NULL; i++)
+ free_xoauth2_info(&xinfo[i]);
+ fs_give((void **) xinfo);
+ return x;
+ }
+
+ free_xoauth2_info(&x);
+ /* We either have no matches, or have more than one match!
+ * in either case, let the user pick what they want */
+ x = xoauth_info_choice(xinfo, user);
+ for(i = 0; xinfo[i] != NULL; i++)
+ free_xoauth2_info(&xinfo[i]);
+ fs_give((void **) xinfo);
+
+ /* Once the user chose a client-id, save it so we do not ask again */
+ if(x != NULL){
+ int n = x->users ? strlen(x->users) + 1 : 0;
+ char ***alval, **l;
+
+ fs_resize((void **) &x->users, (n + strlen(user) + 1)*sizeof(char));
+ x->users[n > 0 ? n - 1 : 0] = '\0';
+ if(n > 0) strcat(x->users, ",");
+ strcat(x->users, user);
+ alval = ALVAL(&ps_global->vars[V_XOAUTH2_INFO], Main);
+ lval = *alval;
+
+ for(n = 0; lval && lval[n]; n++);
+ fs_resize((void **) &lval, (n+2)*sizeof(char *));
+ lval[n] = xoauth_config_line(x);
+ lval[n+1] = NULL;
+ *alval = xoauth2_conf_dedup_and_merge(&lval);
+ set_current_val(&ps_global->vars[V_XOAUTH2_INFO], FALSE, FALSE);
+ write_pinerc(ps_global, Main, WRP_NONE);
+ }
+
+ return x;
}
/* write vlist to v
@@ -113,30 +373,69 @@ oauth2_get_client_info(unsigned char *name, char **id, char **secret)
void
write_xoauth_configuration(struct variable *v, struct variable **vlist, EditWhich ew)
{
- int i, j, k;
- size_t n;
- char ***alval, **lval, *p, *q, *l;
+ int i, j, k, m, n;
+ XOAUTH2_INFO_S *x = NULL, *y;
+ char ***alval, **lval, **l;
+ char *p;
+
+ for (i = 0, n = 0; vlist[i] != NULL; i++) /* count number of lines we need */
+ if(!strcmp(vlist[i]->name, XOAUTH2_USERS))
+ n++;
+ lval = fs_get((n+1)*sizeof(char *));
+ memset((void *) lval, 0, (n+1)*sizeof(char *));
alval = ALVAL(v, ew);
- for (i = 0, k = 0; vlist[i] != NULL;){
- if(PVAL(vlist[i], ew)){
- j = i/2; /* this is the location in the alpine_oauth2_list array */
- i = 2*j; /* reset i */
- p = PVAL(vlist[i], ew);
- if(p == NULL) p = vlist[i]->current_val.p;
- q = PVAL(vlist[i+1], ew);
- if(q == NULL) q = vlist[i+1]->current_val.p;
- if(k == 0) lval = fs_get((NXSERVERS +1)*sizeof(char *));
- lval[k++] = xoauth_config_line(alpine_oauth2_list[j].name, p, q);
- if(alpine_oauth2_list[j].param[OA2_Id].value)
- fs_give((void **) &alpine_oauth2_list[j].param[OA2_Id].value);
- if(alpine_oauth2_list[j].param[OA2_Secret].value)
- fs_give((void **) &alpine_oauth2_list[j].param[OA2_Secret].value);
- alpine_oauth2_list[j].param[OA2_Id].value = cpystr(p);
- alpine_oauth2_list[j].param[OA2_Secret].value = cpystr(q);
- i += 2;
+ for (i = 0, k = 0; vlist[i] != NULL; i++){
+ if(x == NULL){
+ x = new_xoauth2_info();
+ x->name = cpystr(vlist[i]->descrip); /* hack! but makes life so much easier! */
+ for(m = 0; xoauth_default[m].name != NULL
+ && strcmp(xoauth_default[m].name, x->name); m++);
+ }
+ if (x->client_id == NULL && !strcmp(vlist[i]->name, XOAUTH2_CLIENT_ID)){
+ p = PVAL(vlist[i], ew);
+ if (p == NULL) p = vlist[i]->current_val.p;
+ if(p != NULL)
+ x->client_id = cpystr(p);
+ continue;
}
- else i++;
+ if (x->client_secret == NULL
+ && m >= 0
+ && xoauth_default[m].client_secret
+ && !strcmp(vlist[i]->name, XOAUTH2_CLIENT_SECRET)){
+ p = PVAL(vlist[i], ew);
+ if (p == NULL) p = vlist[i]->current_val.p;
+ if(p != NULL)
+ x->client_secret = cpystr(p);
+ continue;
+ }
+ if (x->tenant == NULL
+ && m >= 0
+ && xoauth_default[m].tenant
+ && !strcmp(vlist[i]->name, XOAUTH2_TENANT)){
+ p = PVAL(vlist[i], ew);
+ if (p == NULL) p = vlist[i]->current_val.p;
+ if(p != NULL)
+ x->tenant = cpystr(p);
+ continue;
+ }
+ if (x->users == NULL && !strcmp(vlist[i]->name, XOAUTH2_USERS)){
+ l = LVAL(vlist[i], ew);
+ x->users = list_to_array(l);
+ }
+ /* don't let it get to here until we are done! */
+ lval[k++] = xoauth_config_line(x);
+ if(alpine_oauth2_list[j].param[OA2_Id].value)
+ fs_give((void **) &alpine_oauth2_list[j].param[OA2_Id].value);
+ if(alpine_oauth2_list[j].param[OA2_Secret].value)
+ fs_give((void **) &alpine_oauth2_list[j].param[OA2_Secret].value);
+ if(alpine_oauth2_list[j].param[OA2_Tenant].value)
+ fs_give((void **) &alpine_oauth2_list[j].param[OA2_Tenant].value);
+ alpine_oauth2_list[j].param[OA2_Id].value = cpystr(x->client_id);
+ alpine_oauth2_list[j].param[OA2_Secret].value = x->client_secret ? cpystr(x->client_secret) : NULL;
+ alpine_oauth2_list[j].param[OA2_Tenant].value = x->tenant ? cpystr(x->tenant) : NULL;
+ /* get ready for next run */
+ free_xoauth2_info(&x);
}
if(k > 0){
lval[k] = NULL;
@@ -150,25 +449,26 @@ write_xoauth_configuration(struct variable *v, struct variable **vlist, EditWhi
/* parse line of the form
- /NAME="text" /ID="text" /SECRET="text"
+ /NAME="text" /ID="text" /TENANT="text" /SECRET="text" /USER="text"
*/
-void
-xoauth_parse_client_info(char *lvalp, char **namep, char **idp, char **secretp)
+XOAUTH2_INFO_S *
+xoauth_parse_client_info(char *lvalp)
{
char *s, *t, c;
- *namep = *idp = *secretp = NULL;
+ XOAUTH2_INFO_S *x;
- if (lvalp == NULL) return;
+ if (lvalp == NULL) return NULL;
+ x = new_xoauth2_info();
if((s = strstr(lvalp, XNAME)) != NULL){
s += strlen(XNAME);
if(*s == '"') s++;
for(t = s; *t && *t != '"' && *t != ' '; t++);
c = *t;
*t = '\0';
- *namep = cpystr(s);
+ if (*s) x->name = cpystr(s);
*t = c;
- }
+ } else x->name = NULL;
if((s = strstr(lvalp, XID)) != NULL){
s += strlen(XID);
@@ -176,9 +476,19 @@ xoauth_parse_client_info(char *lvalp, char **namep, char **idp, char **secretp)
for(t = s; *t && *t != '"' && *t != ' '; t++);
c = *t;
*t = '\0';
- *idp = cpystr(s);
+ if (*s) x->client_id = cpystr(s);
*t = c;
- }
+ } else x->client_id = NULL;
+
+ if((s = strstr(lvalp, XTENANT)) != NULL){
+ s += strlen(XTENANT);
+ if(*s == '"') s++;
+ for(t = s; *t && *t != '"' && *t != ' '; t++);
+ c = *t;
+ *t = '\0';
+ if (*s) x->tenant = cpystr(s);
+ *t = c;
+ } else x->tenant = NULL;
if((s = strstr(lvalp, XSECRET)) != NULL){
s += strlen(XSECRET);
@@ -186,11 +496,247 @@ xoauth_parse_client_info(char *lvalp, char **namep, char **idp, char **secretp)
for(t = s; *t && *t != '"' && *t != ' '; t++);
c = *t;
*t = '\0';
- *secretp = cpystr(s);
+ if (*s) x->client_secret = cpystr(s);
*t = c;
- }
+ } else x->client_secret = NULL;
+
+ if((s = strstr(lvalp, XUSER)) != NULL){
+ s += strlen(XUSER);
+ if(*s == '"') s++;
+ for(t = s; *t && *t != '"' && *t != ' '; t++);
+ c = *t;
+ *t = '\0';
+ if(*s) x->users = cpystr(s);
+ *t = c;
+ } else x->users = NULL;
+
+ return x;
}
+char **
+xoauth2_conf_dedup_and_merge(char ***alval)
+{
+ int i, j, k, l, n, m;
+ char **lval, **rv;
+ XOAUTH2_INFO_S *x, *y;
+
+ if(alval == NULL || *alval == NULL || **alval == NULL)
+ return NULL;
+
+ lval = *alval;
+ for(i = 0; lval[i] != NULL; i++); /* count how many entries */
+
+ rv = fs_get((i+1)*sizeof(char *));
+ memset((void *)rv, 0, (i+1)*sizeof(char *));
+
+ for (i = 0; lval[i] != NULL; i++){
+ x = xoauth_parse_client_info(lval[i]);
+ for (j = 0; rv[j] != NULL; j++){
+ y = xoauth_parse_client_info(rv[j]);
+ /* check if this is the same data. If so, merge the users into one and discard the old data */
+ if(same_xoauth2_info(*x, *y)){
+ char **l1, **l2, **l3;
+ int k, n;
+ /* merge user1 with user2, save in x->users */
+ l1 = array_to_list(x->users);
+ l2 = array_to_list(y->users);
+
+ for(n = 0; l1 && l1[n]; n++);
+ for(m = 0; l2 && l2[m]; m++, n++);
+ l3 = fs_get((n+1)*sizeof(char*));
+ memset((void *) l3, 0, (n+1)*sizeof(char *));
+
+ for(l = 0, n = 0; l1 && l1[l]; l++){
+ for(k = 0; l1 && l1[j] && l3[k] && strucmp(l1[l], l3[k]); k++);
+ if(l3[k] == NULL && l1 && l1[l] != NULL)
+ l3[n++] = cpystr(l1[l]);
+ }
+ for(l = 0; l2 && l2[l]; l++){
+ for(k = 0; l2 && l2[l] && l3[k] && strucmp(l2[l], l3[k]); k++);
+ if(l3[k] == NULL && l2 && l2[l] != NULL)
+ l3[n++] = cpystr(l2[l]);
+ }
+ l3[n++] = NULL;
+ if(x->users) fs_give((void **) &x->users);
+ x->users = list_to_array(l3);
+ fs_give((void **) &rv[j]);
+ rv[j] = xoauth_config_line(x);
+
+ if(l1) free_list_array(&l1);
+ if(l2) free_list_array(&l2);
+ if(l3) free_list_array(&l3);
+ free_xoauth2_info(&y);
+ break;
+ }
+ free_xoauth2_info(&y);
+ }
+ if(rv[j] == NULL) rv[j] = cpystr(lval[i]);
+ }
+ return rv;
+}
+
+/*
+ * X = new value, Y = default configuration
+ */
+void
+write_xoauth_conf_entry(XOAUTH2_INFO_S *x, XOAUTH2_INFO_S *y, CONF_S **cl, CONF_S **clb, CONF_S **fline,
+ struct variable ***varlistp, int *pp, int ln, int key)
+{
+ CONF_S *ctmpb = *clb;
+ struct variable **varlist;
+ int i, p = *pp;
+ char tmp[1024], tmp2[16];
+
+ i = 2; /* client_id and users always appear */
+ if(x->client_secret) i++;
+ if(x->tenant) i++;
+
+ sprintf(tmp2, "%d", key);
+ fs_resize((void **) varlistp, (p + i + 1)*sizeof(struct variable **));
+ memset((void *) *varlistp + p*sizeof(struct variable *), 0, (i + 1)*sizeof(struct variable *));
+ varlist = *varlistp;
+
+ new_confline(cl)->var = NULL;
+ if(fline && !*fline) *fline = *cl;
+ (*cl)->flags |= CF_NOSELECT;
+ (*cl)->help = NO_HELP;
+ (*cl)->valoffset = 1;
+ (*cl)->value = cpystr(x->name);
+ (*cl)->varname = NULL;
+ (*cl)->varnamep = ctmpb = *cl;
+
+ /* Setup client-id variable */
+ varlist[p] = fs_get(sizeof(struct variable));
+ memset((void *) varlist[p], 0, sizeof(struct variable));
+ varlist[p]->name = cpystr(XOAUTH2_CLIENT_ID);
+ varlist[p]->is_used = 1;
+ varlist[p]->is_user = 1;
+ varlist[p]->main_user_val.p = x->client_id && y->client_id
+ && strcmp(x->client_id, y->client_id) ? cpystr(x->client_id) : NULL;
+ varlist[p]->global_val.p = y->client_id ? cpystr(y->client_id) : NULL;
+ varlist[p]->dname = cpystr(tmp2); /* hack, but makes life easier! */
+ varlist[p]->descrip = cpystr(x->name); /* hack, but makes life easier! */
+ set_current_val(varlist[p], FALSE, FALSE);
+
+ /* Write client-id variable */
+ new_confline(cl)->var = varlist[p];
+ utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_CLIENT_ID);
+ tmp[sizeof(tmp)-1] = '\0';
+ (*cl)->varname = cpystr(tmp);
+ (*cl)->varmem = p++;
+ (*cl)->valoffset = ln + 3 + 3;
+ (*cl)->value = pretty_value(ps_global, *cl);
+ (*cl)->keymenu = &config_xoauth2_text_keymenu;
+ (*cl)->help = h_config_xoauth2_client_id;
+ (*cl)->tool = text_tool;
+ (*cl)->varnamep = ctmpb;
+
+ /* Set up client-secret variable */
+ if(x->client_secret){
+ varlist[p] = fs_get(sizeof(struct variable));
+ memset((void *) varlist[p], 0, sizeof(struct variable));
+ varlist[p]->name = cpystr(XOAUTH2_CLIENT_SECRET);
+ varlist[p]->is_used = 1;
+ varlist[p]->is_user = 1;
+ varlist[p]->main_user_val.p = y->client_secret
+ && strcmp(x->client_secret, y->client_secret)
+ ? cpystr(x->client_secret) : NULL;
+ varlist[p]->global_val.p = y->client_secret ? cpystr(y->client_secret) : NULL;
+ varlist[p]->dname = cpystr(tmp2); /* hack, but makes life easier! */
+ varlist[p]->descrip = cpystr(x->name); /* hack, but makes life easier! */
+ set_current_val(varlist[p], FALSE, FALSE);
+
+ /* Write client-secret variable */
+ new_confline(cl)->var = varlist[p];
+ utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_CLIENT_SECRET);
+ tmp[sizeof(tmp)-1] = '\0';
+ (*cl)->varname = cpystr(tmp);
+ (*cl)->varmem = p++;
+ (*cl)->valoffset = ln + 3 + 3;
+ (*cl)->value = pretty_value(ps_global, *cl);
+ (*cl)->keymenu = &config_xoauth2_text_keymenu;
+ (*cl)->help = h_config_xoauth2_client_secret;
+ (*cl)->tool = text_tool;
+ (*cl)->varnamep = ctmpb;
+ }
+
+ /* Set up tenant variable */
+ if(x->tenant){
+ varlist[p] = fs_get(sizeof(struct variable));
+ memset((void *) varlist[p], 0, sizeof(struct variable));
+ varlist[p]->name = cpystr(XOAUTH2_TENANT);
+ varlist[p]->is_used = 1;
+ varlist[p]->is_user = 1;
+ varlist[p]->main_user_val.p = y->tenant && strcmp(x->tenant, y->tenant)
+ ? cpystr(x->tenant) : NULL;
+ varlist[p]->global_val.p = y->tenant ? cpystr(y->tenant) : NULL;
+ varlist[p]->dname = cpystr(tmp2); /* hack, but makes life easier! */
+ varlist[p]->descrip = cpystr(x->name); /* hack, but makes life easier! */
+ set_current_val(varlist[p], FALSE, FALSE);
+
+ /* Write client-secret variable */
+ new_confline(cl)->var = varlist[p];
+ utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_TENANT);
+ tmp[sizeof(tmp)-1] = '\0';
+ (*cl)->varname = cpystr(tmp);
+ (*cl)->varmem = p++;
+ (*cl)->valoffset = ln + 3 + 3;
+ (*cl)->value = pretty_value(ps_global, *cl);
+ (*cl)->keymenu = &config_xoauth2_text_keymenu;
+ (*cl)->help = h_config_xoauth2_tenant;
+ (*cl)->tool = text_tool;
+ (*cl)->varnamep = ctmpb;
+ }
+
+ /* Setup users variable */
+ varlist[p] = fs_get(sizeof(struct variable));
+ memset((void *) varlist[p], 0, sizeof(struct variable));
+ varlist[p]->name = cpystr(XOAUTH2_USERS);
+ varlist[p]->is_used = 1;
+ varlist[p]->is_user = 1;
+ varlist[p]->is_list = 1;
+ varlist[p]->main_user_val.l = x->users ? array_to_list(x->users) : NULL;
+ varlist[p]->dname = cpystr(tmp2); /* hack, but makes life easier! */
+ varlist[p]->descrip = cpystr(x->name); /* hack, but makes life easier! */
+ set_current_val(varlist[p], FALSE, FALSE);
+
+ /* Write user variable */
+ new_confline(cl)->var = varlist[p];
+ utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_USERS);
+ tmp[sizeof(tmp)-1] = '\0';
+ (*cl)->varname = cpystr(tmp);
+ (*cl)->valoffset = ln + 3 + 3;
+ (*cl)->keymenu = &config_xoauth2_wshuf_keymenu;
+ (*cl)->help = h_config_xoauth2_username;
+ if(x->users){
+ int z;
+ for(z = 0; varlist[p]->main_user_val.l[z]; z++){
+ if(z) new_confline(cl);
+ (*cl)->var = varlist[p];
+ (*cl)->varmem = z;
+ (*cl)->valoffset = ln + 3 + 3;
+ (*cl)->value = pretty_value(ps_global, *cl);
+ (*cl)->keymenu = &config_xoauth2_wshuf_keymenu;
+ (*cl)->tool = text_tool;
+ (*cl)->varnamep = ctmpb = *cl;
+ }
+ }
+ else {
+ (*cl)->varmem = 0;
+ (*cl)->value = pretty_value(ps_global, *cl);
+ (*cl)->keymenu = &config_xoauth2_wshuf_keymenu;
+ (*cl)->tool = text_tool;
+ (*cl)->varnamep = ctmpb = *cl;
+ }
+ p++;
+ *pp = p;
+ *varlistp = varlist;
+ *clb = ctmpb;
+
+ /* Separate servers with a blank line */
+ new_confline(cl);
+ (*cl)->flags |= CF_NOSELECT | CF_B_LINE;
+}
/*----------------------------------------------------------------------
Screen to add client_id and client_secret for a service
@@ -199,19 +745,20 @@ xoauth_parse_client_info(char *lvalp, char **namep, char **idp, char **secretp)
void
alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
{
- struct variable gmail_client_id_var, gmail_client_secret_var;
- struct variable outlook_client_id_var, outlook_client_secret_var;
- struct variable *varlist[2*NXSERVERS + 1];
- char tmp[MAXPATH+1], *pval, **lval;
- char *id, *secret;
- char *name_lval, *id_lval, *id_conf, *id_def, *secret_lval, *secret_conf, *secret_def;
- int i, j, k, l, ln = 0, readonly_warning = 0, pos;
- CONF_S *ctmpa = NULL, *ctmpb, *first_line = NULL;
+ struct variable **varlist = NULL;
+ char tmp[MAXPATH+1], *pval, **lval, ***alval;
+ char *s, *extraname = NULL;
+ char *name, *id, *tenant, *secret, **user;
+ char *name_lval, *id_lval, *tenant_lval, *secret_lval, *user_lval,
+ *id_def, *tenant_def, *secret_def;
+ int i, j, k, l, p, q, ln = 0, readonly_warning = 0, pos;
+ CONF_S *ctmpa = NULL, *ctmpb, *first_line;
FEATURE_S *feature;
PINERC_S *prc = NULL;
OPT_SCREEN_S screen;
int expose_hidden_config, add_hidden_vars_title = 0;
SAVED_CONFIG_S *vsave;
+ XOAUTH2_INFO_S x, *y;
dprint((3, "-- alpine_xoauth2_configuration --\n"));
@@ -239,127 +786,49 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
mailcap_free(); /* free resources we won't be using for a while */
- varlist[0] = &gmail_client_id_var;
- varlist[1] = &gmail_client_secret_var;
- varlist[2] = &outlook_client_id_var;
- varlist[3] = &outlook_client_secret_var;
- varlist[2*NXSERVERS] = NULL;
-
- for(i = 0; i < 2*NXSERVERS; i++)
- memset((void *) varlist[i], 0, sizeof(struct variable));
-
pos = -1;
+ ln = strlen(XOAUTH2_CLIENT_ID);
+ i = strlen(XOAUTH2_CLIENT_SECRET);
+ if(ln < i) ln = i;
+ i = strlen(XOAUTH2_TENANT);
+ if(ln < i) ln = i;
+ i = strlen(XOAUTH2_USERS);
+ if(ln < i) ln = i;
+
+ alval = ALVAL(&ps->vars[V_XOAUTH2_INFO], ew);
+ lval = *alval = xoauth2_conf_dedup_and_merge(alval);
+ set_current_val(&ps_global->vars[V_XOAUTH2_INFO], FALSE, FALSE);
+ write_pinerc(ps_global, ew, WRP_NONE);
+
do {
ctmpa = first_line = NULL;
- ln = strlen(XOAUTH2_CLIENT_ID);
- i = strlen(XOAUTH2_CLIENT_SECRET);
- if(ln < i) ln = i;
-
- lval = LVAL(&ps->vars[V_XOAUTH2_INFO], ew);
-
- for(i = 0, l = 0; alpine_oauth2_list[i].name != NULL; i++){
- id_conf = id_def = secret_conf = secret_def = NULL;
- name_lval = id_lval = secret_lval = NULL;
-
- id_conf = alpine_oauth2_list[i].param[OA2_Id].value;
- secret_conf = alpine_oauth2_list[i].param[OA2_Secret].value;
-
- for(j = 0; xoauth_default[j].name != NULL; j++)
- if(!strcmp(alpine_oauth2_list[i].name, xoauth_default[j].name))
- break;
-
- if(xoauth_default[j].name != NULL){
- id_def = xoauth_default[j].client_id;
- secret_def = xoauth_default[j].client_secret;
+ for(i = 0, p = 0; xoauth_default[i].name != NULL; i++){
+ /* always start with the default configuration */
+ for(k = 0, q = 0; lval && lval[k]; k++){
+ y = xoauth_parse_client_info(lval[k]);
+ if(same_xoauth2_info(xoauth_default[i], *y))
+ break;
+ free_xoauth2_info(&y);
}
-
- /* fix this: the purpose is to search for the name in lval */
- for(k = 0; lval && lval[k]; k++){
- xoauth_parse_client_info(lval[k], &name_lval, &id_lval, &secret_lval);
- if(name_lval && !strcmp(name_lval, alpine_oauth2_list[i].name))
- break;
- if (name_lval) fs_give((void **) &name_lval);
- if (id_lval) fs_give((void **) &id_lval);
- if (secret_lval) fs_give((void **) &secret_lval);
- }
-
- /* Here we have three values. The one being used by c-client in
- * id_conf, secret_conf. The default value in Alpine, obtained
- * by the programmer by registering Alpine, and the value
- * configured in Alpine by the user in id_lval, and secret_lval.
- *
- * The rules are:
- * 1. If id_lval && secret_lval are not null, we use those.
- * 2. else we use the default values.
- */
- id = id_lval ? id_lval : id_def;
- secret = secret_lval ? secret_lval : secret_def;
-
- new_confline(&ctmpa)->var = NULL;
- if(!first_line) first_line = ctmpa;
-
- /* Write Name of provider first */
- ctmpa->flags |= CF_NOSELECT;
- ctmpa->help = NO_HELP;
- ctmpa->valoffset = 1;
- ctmpa->value = cpystr(alpine_oauth2_list[i].name);
- ctmpa->varname = NULL;
- ctmpa->varnamep = ctmpb = ctmpa;
-
-
- /* Setup client-id variable */
- varlist[l]->name = cpystr(XOAUTH2_CLIENT_ID);
- varlist[l]->is_used = 1;
- varlist[l]->is_user = 1;
- varlist[l]->main_user_val.p = strcmp(id, id_def)? cpystr(id) : NULL;
- varlist[l]->global_val.p = cpystr(id_def);
- set_current_val(varlist[l], FALSE, FALSE);
-
- /* Write client-id variable */
- new_confline(&ctmpa)->var = varlist[l];
- utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_CLIENT_ID);
- tmp[sizeof(tmp)-1] = '\0';
- ctmpa->varname = cpystr(tmp);
- ctmpa->varmem = l++;
- ctmpa->valoffset = ln + 3 + 3;
- ctmpa->value = pretty_value(ps, ctmpa);
- ctmpa->keymenu = &config_text_keymenu;
- ctmpa->help = h_config_xoauth2_client_id;
- ctmpa->tool = text_tool;
- ctmpa->varnamep = ctmpb;
-
- /* Set up client-secret variable */
- varlist[l]->name = cpystr(XOAUTH2_CLIENT_SECRET);
- varlist[l]->is_used = 1;
- varlist[l]->is_user = 1;
- varlist[l]->main_user_val.p = secret && secret_def && strcmp(secret, secret_def) ? cpystr(secret) : NULL;
- varlist[l]->global_val.p = secret_def ? cpystr(secret_def) : NULL;
- set_current_val(varlist[l], FALSE, FALSE);
-
- /* Write client-secret variable */
- if(secret){
- new_confline(&ctmpa)->var = varlist[l];
- utf8_snprintf(tmp, sizeof(tmp), " %-*.100w =", ln, XOAUTH2_CLIENT_SECRET);
- tmp[sizeof(tmp)-1] = '\0';
- ctmpa->varname = cpystr(tmp);
- ctmpa->varmem = l++;
- ctmpa->valoffset = ln + 3 + 3;
- ctmpa->value = pretty_value(ps, ctmpa);
- ctmpa->keymenu = &config_text_keymenu;
- ctmpa->help = h_config_xoauth2_client_secret;
- ctmpa->tool = text_tool;
- ctmpa->varnamep = ctmpb;
+ if(lval == NULL || lval[k] == NULL)
+ write_xoauth_conf_entry(&xoauth_default[i], &xoauth_default[i], &ctmpa, &ctmpb,
+ &first_line, &varlist, &p, ln, -i-1);
+ for(k = 0, q = 0; lval && lval[k]; k++){
+ y = xoauth_parse_client_info(lval[k]);
+ if(y && (!y->name || strcmp(y->name, xoauth_default[i].name))){
+ free_xoauth2_info(&y);
+ continue;
+ }
+ if(y->client_id == NULL)
+ y->client_id = cpystr(xoauth_default[i].client_id);
+ if(y->client_secret == NULL && xoauth_default[i].client_secret != NULL)
+ y->client_secret = cpystr(xoauth_default[i].client_secret);
+ if(y->tenant == NULL && xoauth_default[i].tenant != NULL)
+ y->tenant = cpystr(xoauth_default[i].tenant);
+ write_xoauth_conf_entry(y, &xoauth_default[i], &ctmpa, &ctmpb, &first_line, &varlist, &p, ln, k);
+ free_xoauth2_info(&y);
}
-
- /* Separate servers with a blank line */
- new_confline(&ctmpa);
- ctmpa->flags |= CF_NOSELECT | CF_B_LINE;
-
- /* clean up the house */
- if(id_lval) fs_give((void **) &id_lval);
- if(secret_lval) fs_give((void **) &secret_lval);
- if(name_lval) fs_give((void **) &name_lval);
}
vsave = save_config_vars(ps, expose_hidden_config);
@@ -379,6 +848,88 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
write_pinerc(ps, ew, WRP_NONE);
break;
+ case 4: /* add a service */
+ {char service[MAILTMPLEN+1];
+ char prompt[MAILTMPLEN+1];
+ int flags = OE_DISALLOW_HELP;
+ strncpy(prompt, _("Enter service name: "), sizeof(prompt));
+ prompt[sizeof(prompt) - 1] = '\0';
+ service[0] = '\0';
+ if(optionally_enter(service,
+ -(ps_global->ttyo ? FOOTER_ROWS(ps_global) : 3),
+ 0, sizeof(service), prompt, NULL, NO_HELP, &flags) == 0){
+ for(i = 0; xoauth_default[i].name != NULL && strucmp(xoauth_default[i].name, service); i++);
+ if(xoauth_default[i].name == NULL)
+ q_status_message1(SM_ORDER, 3, 3, _("Service %s not known"), service);
+ else{
+ char **list;
+ ClearScreen();
+ ps_global->mangled_screen = 1;
+ for(j = 0; lval && lval[j]; j++);
+ list = fs_get((j+2)*sizeof(char *));
+ memset((void *)list, 0, (j+2)*sizeof(char *));
+ list[0] = xoauth_config_line(&xoauth_default[i]);
+ for(i = 0; lval && lval[i]; i++)
+ list[i+1] = cpystr(lval[i]);
+ if(lval) free_list_array(&lval);
+ *alval = lval = list;
+ for(i = 0; varlist && varlist[i]; i++){
+ free_variable_values(varlist[i]);
+ if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
+ if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
+ fs_give((void **) &varlist[i]);
+ }
+ if(varlist) fs_give((void **) varlist);
+ }
+ }
+ }
+ break;
+
+ case 5: /* delete a service */
+ { int m, key;
+ XOAUTH2_INFO_S *x;
+ char question[MAILTMPLEN];
+
+ for(i = 0, m = 1, j = 0; varlist[i] && m < pos;)
+ if(!varlist[i]->is_list){
+ i++; m++;
+ } else {
+ if(varlist[i]->current_val.l[j++]) m++;
+ else{
+ j = 0; m += 2; i++;
+ }
+ }
+ key = atoi(varlist[i]->dname); /* this hack avoids we rebuild varlist again */
+ if(key >= 0){
+ x = xoauth_parse_client_info(lval[key]);
+ snprintf(question, sizeof(question), _("Delete this configuration for %s "), x->name);
+ free_xoauth2_info(&x);
+ if(want_to(question, 'n', 'n', NO_HELP, WT_NORM) != 'y')
+ break;
+ for(i = key; lval && lval[i] && lval[i+1]; i++){
+ fs_give((void **) &lval[i]);
+ lval[i] = cpystr(lval[i+1]);
+ }
+ fs_give((void **) &lval[i]);
+ }
+ else {
+ q_status_message(SM_ORDER, 3, 3, _("Cannot delete default configuration"));
+ break;
+ }
+ if(lval && lval[0] == NULL)
+ free_list_array(&lval);
+ *alval = lval;
+ pos = 1; /* reset at the top */
+ for(i = 0; varlist && varlist[i]; i++){
+ free_variable_values(varlist[i]);
+ if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
+ if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
+ fs_give((void **) &varlist[i]);
+ }
+ if(varlist) fs_give((void **) varlist);
+ }
+ break;
+
case 10:
revert_to_saved_config(ps, vsave, expose_hidden_config);
if(prc)
@@ -392,8 +943,15 @@ alpine_xoauth2_configuration(struct pine *ps, int edit_exceptions)
}
} while (pos >= 0);
+ for(i = 0; varlist && varlist[i]; i++){
+ free_variable_values(varlist[i]);
+ if(varlist[i]->descrip) fs_give((void **) &varlist[i]->descrip);
+ if(varlist[i]->dname) fs_give((void **) &varlist[i]->dname);
+ fs_give((void **) &varlist[i]);
+ }
+ if(varlist) fs_give((void **) varlist);
+
#ifdef _WINDOWS
mswin_set_quit_confirm (F_OFF(F_QUIT_WO_CONFIRM, ps_global));
#endif
}
-
diff --git a/alpine/xoauth2conf.h b/alpine/xoauth2conf.h
index c1de4139..41a56452 100644
--- a/alpine/xoauth2conf.h
+++ b/alpine/xoauth2conf.h
@@ -16,12 +16,14 @@
#define XOAUTH2_CONFIG
#include "confscroll.h"
+#include "xoauth2.h"
#include "../pith/state.h"
/* exported prototypes */
void alpine_xoauth2_configuration(struct pine *, int);
-void xoauth_parse_client_info(char *, char **, char **, char **);
-void oauth2_get_client_info(unsigned char *, char **, char **);
-char *xoauth_config_line(char *, char *, char *);
+XOAUTH2_INFO_S *xoauth_parse_client_info(char *);
+XOAUTH2_INFO_S *oauth2_get_client_info(unsigned char *, char *);
+char *xoauth_config_line(XOAUTH2_INFO_S *);
+void write_xoauth_conf_entry(XOAUTH2_INFO_S *, XOAUTH2_INFO_S *, CONF_S **, CONF_S **, CONF_S **, struct variable ***, int *, int, int);
#endif /* XOAUTH2_CONFIG */
diff --git a/imap/src/c-client/auth_bea.c b/imap/src/c-client/auth_bea.c
index bf5c9c1e..66604a35 100644
--- a/imap/src/c-client/auth_bea.c
+++ b/imap/src/c-client/auth_bea.c
@@ -121,21 +121,9 @@ long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder
+ strlen(BEARER_HOST) + strlen(mb->orighost) + 1
+ strlen(BEARER_PORT) + strlen(ports) + 1
+ strlen(OAUTH2_BEARER) + strlen(oauth2.access_token) + 2;
- t = response = (char *) fs_get (rlen);
- for (u = BEARER_ACCOUNT; *u; *t++ = *u++);
- for (u = user; *u; *t++ = *u++);
- *t++ = ',';
- *t++ = '\001'; /* delimiting ^A */
- for (u = BEARER_HOST; *u; *t++ = *u++);
- for (u = mb->orighost; *u; *t++ = *u++);
- *t++ = '\001'; /* delimiting ^A */
- for (u = BEARER_PORT; *u; *t++ = *u++);
- for (u = ports; *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 */
+ response = (char *) fs_get (rlen+1);
+ sprintf(response, "%s%s,\001%s%s\001%s%s\001%s%s\001\001", BEARER_ACCOUNT, user,
+ BEARER_HOST, mb->orighost, BEARER_PORT, ports, OAUTH2_BEARER, oauth2.access_token);
if ((*responder) (stream,base,response,rlen)) {
if ((challenge = (*challenger) (stream,&clen)) != NULL)
fs_give ((void **) &challenge);
@@ -159,6 +147,9 @@ long auth_oauthbearer_client (authchallenge_t challenger,authrespond_t responder
fs_give ((void **) &response);
}
}
+ if(oauth2.param[OA2_Id].value) fs_give((void **) &oauth2.param[OA2_Id].value);
+ if(oauth2.param[OA2_Secret].value) fs_give((void **) &oauth2.param[OA2_Secret].value);
+ if(oauth2.param[OA2_Tenant].value) fs_give((void **) &oauth2.param[OA2_Tenant].value);
if (!ret || !oauth2.name)
*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
index 69987559..f5345bfa 100644
--- a/imap/src/c-client/auth_oa2.c
+++ b/imap/src/c-client/auth_oa2.c
@@ -140,15 +140,8 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
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 */
+ char *response = (char *) fs_get (rlen + 1);
+ sprintf(response, "%s%s\001%s%s\001\001", OAUTH2_USER, user, OAUTH2_BEARER, oauth2.access_token);
if ((*responder) (stream,base,response,rlen)) {
if ((challenge = (*challenger) (stream,&clen)) != NULL)
fs_give ((void **) &challenge);
@@ -172,6 +165,9 @@ long auth_oauth2_client (authchallenge_t challenger,authrespond_t responder, cha
fs_give ((void **) &response);
}
}
+ if(oauth2.param[OA2_Id].value) fs_give((void **) &oauth2.param[OA2_Id].value);
+ if(oauth2.param[OA2_Secret].value) fs_give((void **) &oauth2.param[OA2_Secret].value);
+ if(oauth2.param[OA2_Tenant].value) fs_give((void **) &oauth2.param[OA2_Tenant].value);
if (!ret || !oauth2.name)
*trial = 65535; /* don't retry if bad protocol */
return ret;
diff --git a/imap/src/c-client/http.h b/imap/src/c-client/http.h
index 621e7c48..41b16842 100644
--- a/imap/src/c-client/http.h
+++ b/imap/src/c-client/http.h
@@ -73,6 +73,7 @@ typedef struct http_header_data_s {
#define HTTP_1_1_VERSION "HTTP/1.1"
#define HTTP_OK 200
#define HTTP_BAD 400
+#define HTTP_UNAUTHORIZED 401
#define GET_HTTPPORT (long) 490
#define SET_HTTPPORT (long) 491
diff --git a/imap/src/c-client/mail.h b/imap/src/c-client/mail.h
index f74d889d..eb8f0139 100644
--- a/imap/src/c-client/mail.h
+++ b/imap/src/c-client/mail.h
@@ -1929,6 +1929,7 @@ int PFLUSH (void);
typedef enum {OA2_Id = 0,
OA2_Secret,
+ OA2_Tenant,
OA2_Code,
OA2_RefreshToken,
OA2_Scope,
@@ -1986,10 +1987,21 @@ typedef struct deviceproc_s {
char code_wait; /* code to say keep waiting */
} OAUTH2_DEVICEPROC_S;
+typedef struct xoauth_default_s {
+ unsigned char *name;
+ char *client_id;
+ char *client_secret;
+ char *tenant;
+ char *users;
+} XOAUTH2_INFO_S;
+
/* Supporting external functions for XOAUTH2 and OAUTHBEARER */
typedef char *(*oauth2getaccesscode_t) (unsigned char *, char *, OAUTH2_S *, int *);
-typedef void (*oauth2clientinfo_t)(unsigned char *name, char **id, char **secret);
+typedef XOAUTH2_INFO_S *(*oauth2clientinfo_t)(unsigned char *name, char *user);
typedef void (*oauth2deviceinfo_t)(OAUTH2_S *, char *method);
void mm_login_oauth2_c_client_method (NETMBX *, char *, char *, OAUTH2_S *, unsigned long, int *);
char *oauth2_generate_state(void);
void oauth2deviceinfo_get_accesscode(void *, void *);
+XOAUTH2_INFO_S *new_xoauth2_info(void);
+void free_xoauth2_info(XOAUTH2_INFO_S **);
+XOAUTH2_INFO_S *copy_xoauth2_info(XOAUTH2_INFO_S *);
diff --git a/imap/src/c-client/oauth2_aux.c b/imap/src/c-client/oauth2_aux.c
index a23f7c28..88ee52a6 100644
--- a/imap/src/c-client/oauth2_aux.c
+++ b/imap/src/c-client/oauth2_aux.c
@@ -53,6 +53,7 @@ char *oauth2_generate_state(void)
JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S, OAUTH2_S *, int *);
+char *xoauth2_server(char *, char *);
#define LOAD_HTTP_PARAMS(X, Y) { \
int i; \
@@ -64,20 +65,60 @@ JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S, OAUTH2_S *, int *);
(Y)[i].name = (Y)[i].value = NULL; \
}
+char *xoauth2_server(char *server, char *tenant)
+{
+ char *rv = NULL;
+ char *s;
+
+ if (server == NULL) return NULL;
+
+ s = cpystr(server);
+ if(tenant){
+ char *t = s, *u;
+ int i;
+ for(i = 0; t != NULL; i++){
+ t = strchr(t, '\001');
+ if(t != NULL) t++;
+ }
+ rv = fs_get((strlen(s) + i*(strlen(tenant)-1) + 1)*sizeof(char));
+ *rv = '\0';
+ for(u = t = s; t != NULL; i++){
+ t = strchr(t, '\001');
+ if (t != NULL) *t = '\0';
+ strcat(rv, u);
+ if(t != NULL){
+ strcat(rv, tenant);
+ *t++ = '\001';
+ }
+ u = t;
+ }
+
+ }
+ else
+ rv = cpystr(server);
+
+ return rv;
+}
+
JSON_S *oauth2_json_reply(OAUTH2_SERVER_METHOD_S RefreshMethod, OAUTH2_S *oauth2, int *status)
{
JSON_S *json = NULL;
HTTP_PARAM_S params[OAUTH2_PARAM_NUMBER];
unsigned char *s;
+ char *server = NULL;
LOAD_HTTP_PARAMS(RefreshMethod, params);
*status = 0;
+ server = xoauth2_server(RefreshMethod.urlserver, oauth2->param[OA2_Tenant].value);
if(strcmp(RefreshMethod.name, "POST") == 0
- && ((s = http_post_param(RefreshMethod.urlserver, params, status)) != NULL)){
+ && ((s = http_post_param(server, params, status)) != NULL)){
unsigned char *u = s;
json = json_parse(&u);
fs_give((void **) &s);
}
+ if(server)
+ fs_give((void **) &server);
+
return json;
}
@@ -92,11 +133,16 @@ mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
if(oauth2->param[OA2_Id].value == NULL
|| (oauth2->require_secret && oauth2->param[OA2_Secret].value == NULL)){
+ XOAUTH2_INFO_S *x;
oauth2clientinfo_t ogci =
(oauth2clientinfo_t) mail_parameters (NIL, GET_OA2CLIENTINFO, NIL);
- if(ogci) (*ogci)(oauth2->name, &oauth2->param[OA2_Id].value,
- &oauth2->param[OA2_Secret].value);
+ if(ogci && (x = (*ogci)(oauth2->name, user)) != NULL){
+ oauth2->param[OA2_Id].value = cpystr(x->client_id);
+ oauth2->param[OA2_Secret].value = x->client_secret ? cpystr(x->client_secret) : NULL;
+ oauth2->param[OA2_Tenant].value = x->tenant ? cpystr(x->tenant) : NULL;
+ free_xoauth2_info(&x);
+ }
}
if(oauth2->param[OA2_Id].value == NULL
@@ -157,24 +203,34 @@ mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
/* else check if we have a refresh token, and in that case use it */
if(oauth2->param[OA2_RefreshToken].value){
-
json = oauth2_json_reply(oauth2->server_mthd[OA2_GetAccessTokenFromRefreshToken], oauth2, &status);
if(json != NULL){
JSON_X *jx;
- jx = json_body_value(json, "access_token");
- if(jx && jx->jtype == JString)
- oauth2->access_token = cpystr((char *) jx->value);
+ switch(status){
+ case HTTP_UNAUTHORIZED:
+ mm_log("Client not authorized (wrong client-id?)", ERROR);
+ break;
+ case HTTP_OK: jx = json_body_value(json, "access_token");
+ if(jx && jx->jtype == JString)
+ oauth2->access_token = cpystr((char *) jx->value);
- if((jx = json_body_value(json, "expires_in")) != NULL)
- switch(jx->jtype){
- case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
- break;
- case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
- break;
- }
+ if((jx = json_body_value(json, "expires_in")) != NULL)
+ switch(jx->jtype){
+ case JString: oauth2->expiration = time(0) + atol((char *) jx->value);
+ break;
+ case JLong : oauth2->expiration = time(0) + *(long *) jx->value;
+ break;
+ }
+ break;
+ default : { char tmp[100];
+ sprintf(tmp, "Oauth2 client Received Code %d", status);
+ mm_log (tmp, ERROR);
+ }
+ break;
+ }
json_free(&json);
}
return;
@@ -190,12 +246,15 @@ mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
LOAD_HTTP_PARAMS(RefreshMethod, params);
if(strcmp(RefreshMethod.name, "GET") == 0){
- char *url = http_get_param_url(RefreshMethod.urlserver, params);
+ char *server = xoauth2_server(RefreshMethod.urlserver, oauth2->param[OA2_Tenant].value);
+ char *url = http_get_param_url(server, params);
oauth2getaccesscode_t ogac =
(oauth2getaccesscode_t) mail_parameters (NIL, GET_OA2CLIENTGETACCESSCODE, NIL);
if(ogac)
oauth2->param[OA2_Code].value = (*ogac)(url, method, oauth2, tryanother);
+
+ if(server) fs_give((void **) &server);
}
if(oauth2->param[OA2_Code].value){
@@ -230,7 +289,7 @@ mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
case HTTP_BAD : break;
default : { char tmp[100];
- sprintf(tmp, "Oauth Client Received Code %d", status);
+ sprintf(tmp, "Oauth2 Client Received Code %d", status);
fatal (tmp);
}
}
@@ -240,8 +299,6 @@ mm_login_oauth2_c_client_method (NETMBX *mb, char *user, char *method,
}
return;
}
-
- /* Else, does this server use the /devicecode method? */
}
void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
@@ -304,9 +361,9 @@ void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
break;
default : { char tmp[100];
- sprintf(tmp, "Oauth device Received Code %d", status);
- fatal (tmp);
- }
+ sprintf(tmp, "Oauth device Received Code %d", status);
+ mm_log (tmp, ERROR);
+ }
}
json_free(&json);
@@ -314,3 +371,36 @@ void oauth2deviceinfo_get_accesscode(void *inp, void *outp)
*(int *)outp = rv;
}
+
+XOAUTH2_INFO_S *new_xoauth2_info(void)
+{
+ XOAUTH2_INFO_S *rv = fs_get(sizeof(XOAUTH2_INFO_S));
+ memset((void *) rv, 0, sizeof(XOAUTH2_INFO_S));
+ return rv;
+}
+
+void free_xoauth2_info(XOAUTH2_INFO_S **xp)
+{
+ if(xp == NULL || *xp == NULL) return;
+
+ if((*xp)->name) fs_give((void **) &(*xp)->name);
+ if((*xp)->client_id) fs_give((void **) &(*xp)->client_id);
+ if((*xp)->client_secret) fs_give((void **) &(*xp)->client_secret);
+ if((*xp)->tenant) fs_give((void **) &(*xp)->tenant);
+ if((*xp)->users) fs_give((void **) &(*xp)->users);
+ fs_give((void **) xp);
+}
+
+XOAUTH2_INFO_S *copy_xoauth2_info(XOAUTH2_INFO_S *x)
+{
+ XOAUTH2_INFO_S *y;
+
+ if(x == NULL) return NULL;
+ y = new_xoauth2_info();
+ if(x->name) y->name = cpystr(x->name);
+ if(x->client_id) y->client_id = cpystr(x->client_id);
+ if(x->client_secret) y->client_secret = cpystr(x->client_secret);
+ if(x->tenant) y->tenant = cpystr(x->tenant);
+ if(x->users) y->users = cpystr(x->users);
+ return y;
+}
diff --git a/imap/src/osdep/nt/makefile.nt b/imap/src/osdep/nt/makefile.nt
index 83e74826..283d86a7 100644
--- a/imap/src/osdep/nt/makefile.nt
+++ b/imap/src/osdep/nt/makefile.nt
@@ -29,7 +29,7 @@
EXTRAAUTHENTICATORS =
EXTRADRIVERS =
EXTRACFLAGS =
-AUTHENTICATORS = ext md5 pla bea oa2 log
+AUTHENTICATORS = ext md5 pla log bea oa2
DRIVERS = imap nntp pop3 mbx mtx tenex unix
CREATEDRIVER = mbx
APPENDDRIVER = unix
diff --git a/pith/pine.hlp b/pith/pine.hlp
index 8c4ca2e3..df1d5f74 100644
--- a/pith/pine.hlp
+++ b/pith/pine.hlp
@@ -140,7 +140,7 @@ with help text for the config screen and the composer that didn't have any
reasonable place to be called from.
Dummy change to get revision in pine.hlp
============= h_revision =================
-Alpine Commit 455 2020-06-21 20:14:16
+Alpine Commit 456 2020-06-26 12:10:33
============= h_news =================
<HTML>
<HEAD>
@@ -191,6 +191,14 @@ problems you find with this release.
when new mail arrives. This is consistent with Alpine dinging the
bell when new mail arrives. Bell will not ding if it is disabled
for status messages. Suggested by Chime Hart.
+
+<LI> Expansion of the configuration screen for XOAUTH2 to include
+ username, and tenant.
+
+<LI> If a user has more than one client-id for a service, Alpine tries to
+ asks the user which client-id to use and associates that client-id to
+ the credentials in the XOAUTH2 configuration screen.
+ <A HREF="h_xoauth2_config_screen">Learn more</A>.
</UL>
<H2>New in Alpine <!--#echo var="ALPINE_VERSION"--> (<!--#echo var="ALPINE_REVISION"-->)</H2>
@@ -1720,6 +1728,44 @@ Access and Refresh Tokens for the IMAP and SMTP servers separately.
&lt;End of help&gt;
</BODY>
</HTML>
+====== h_xoauth2_config_screen ======
+<HTML>
+<HEAD>
+<TITLE>XOAUTH2 Configuration Screen</TITLE>
+</HEAD>
+<BODY>
+<H1>XOAUTH2 Configuration Screen</H1>
+
+The XOAUTH2 configuration screen helps you connect your client-id and
+other configuration information about the service you want to use with
+Alpine.
+
+<P> Alpine provides a default configuration for you. For some providers
+this configuration does not work, and requires you to get your own
+Client-ID for Alpine. Depending on the service, you might need extra information,
+such as a Client-Secret or a Tenant.
+
+<P> If the default configuration of Alpine does not work for you, you can simply
+replace the default configuration of Alpine by your new configuration. If for
+any reason you need to have a second client-id, you can use the ^A command to
+add a new configuration for one of the supported services by Alpine.
+
+<P> If you wish to delete a configuration for a service, place the cursor
+on the configuration you want to delete and press ^D.
+
+<P> When you have more than one client-id for the same service, is is convenient
+to fill up the username field. This will tell Alpine that that specific configuration
+is to be used with the username(s) specified in that variable. If Alpine cannot
+detemine which configuration to use, it will ask you to select the correct
+Client-ID for your connection. This could happen when you are trying to login to
+your account. The client-id you select will be used by Alpine for that and future
+connections to that server.
+
+
+<P>
+&lt;End of help&gt;
+</BODY>
+</HTML>
====== h_tls_failure_details ======
<HTML>
<HEAD>
@@ -16085,6 +16131,53 @@ source code of this project is located at
&lt;End of help on this topic&gt;
</BODY>
</HTML>
+======= h_config_xoauth2_tenant =======
+<HTML>
+<HEAD>
+<TITLE>Tenant Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Tenant Explained</H1>
+
+The tenant is a way in which a service can either restrict an app to access
+only certain portions of its service. For example, a service may allow
+an app to have access to your work data, or school data, and not every app
+is allowed to access this data, or the access is not allowed for all accounts,
+but only those restructed in the tenant.
+
+<P> When Alpine registers with an email service provider, it does so with the
+intention that you can use Alpine for any of your needs (work, school or
+personal), and the client-id and tenant that it uses would allow you access to
+only your work email, say, and not your personal email, because the app is
+trusted only at work, and so the client-id and tenant are good for that
+organization only.
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
+======= h_config_xoauth2_username =======
+<HTML>
+<HEAD>
+<TITLE>Username Explained</TITLE>
+</HEAD>
+<BODY>
+<H1>Username Explained</H1>
+
+This variable is a list of usernames for which the configuration for the
+given service is valid. You may have more than one username for which this
+configuration is correct.
+
+<P>
+Alpine will use the usernames in this list to associate your username with
+the correct configuration in Alpine. If Alpine cannot determine which configuration
+to use, Alpine will offer you a list of client-ids that you have configured
+for that service and ask you to pick one. Your answer will be saved in your
+.pinerc file.
+
+<P>
+&lt;End of help on this topic&gt;
+</BODY>
+</HTML>
======= h_config_role_nick =======
<HTML>
<HEAD>