/*
 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
 * Copyright (C) 2021-2023 the Claws Mail team
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#include "claws-features.h"
#endif

#ifdef USE_OAUTH2

#include <glib.h>
#ifdef ENABLE_NLS
#include <glib/gi18n.h>
#else
#define _(a) (a)
#define N_(a) (a)
#endif
#include <stdio.h>
#include <string.h>
#include <errno.h>

#include "imap.h"
#include "oauth2.h"
#include "md5.h"
#include "utils.h"
#include "log.h"
#include "time.h"
#include "common/passcrypt.h"
#include "prefs_common.h"
#define GNUTLS_PRIORITY "NORMAL:!VERS-SSL3.0:!VERS-TLS1.0:!VERS-TLS1.1"
//Yahoo requires token requests to send POST header Authorization: Basic
//where the password is Base64 encoding of client_id:client_secret

static gchar *OAUTH2info[5][17]={
  {"accounts.google.com",
   "",
   ".",
   "http://127.0.0.1:8888",
   "/o/oauth2/auth",
   "/o/oauth2/token",
   "/o/oauth2/token",
   "code",
   "https://mail.google.com",
   "authorization_code",
   "refresh_token",
   "",
   "",
   "",
   "",
   "",
   ""},
  {"login.microsoftonline.com",
   "",
   "",
   "http://127.0.0.1:8888",
   "/common/oauth2/v2.0/authorize",
   "/common/oauth2/v2.0/token",
   "/common/oauth2/v2.0/token",
   "code",
   "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
   "authorization_code",
   "refresh_token",
   "common",
   "",
   "offline",
   "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
   "query",
   ""},
  {"login.microsoftonline.com",
   "",
   "",
   "http://127.0.0.1:8888",
   "/common/oauth2/v2.0/authorize",
   "/common/oauth2/v2.0/token",
   "/common/oauth2/v2.0/token",
   "code",
   "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
   "authorization_code",
   "refresh_token",
   "common",
   "",
   "offline",
   "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send",
   "query",
   ""},
   {"login.microsoftonline.us",
   "",
   "",
   "http://127.0.0.1:8888",
   "/common/oauth2/v2.0/authorize",
   "/common/oauth2/v2.0/token",
   "/common/oauth2/v2.0/token",
   "code",
   "offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send",
   "authorization_code",
   "refresh_token",
   "common",
   "",
   "offline",
   "offline_access https://outlook.office365.us/IMAP.AccessAsUser.All https://outlook.office365.us/POP.AccessAsUser.All https://outlook.office365.us/SMTP.Send",
   "query",
   ""},
 {"api.login.yahoo.com",
   "",
   ".",
   "oob",
   "/oauth2/request_auth",
   "/oauth2/get_token",
   "/oauth2/get_token",
   "code",
   "",
   "authorization_code",
   "refresh_token",
   "",
   "",
   "",
   "",
   "",
   "1"}
};

static gchar *OAUTH2CodeMarker[6][2] = {
    {"",""},
    {"code=","&scope="},
    {"code="," HTTP"},
    {"code=","&session_state="},
    {"code=","&session_state="},
    {"yahoo_begin_mark","yahoo_end_mark"} /* Not used since token avalable to user to copy in browser window */
};

static gint oauth2_post_request (gchar *buf, gchar *host, gchar *resource, gchar *header, gchar *body);
static gint oauth2_filter_refresh (gchar *json, gchar *refresh_token);
static gint oauth2_filter_access (gchar *json, gchar *access_token, gint *expiry);


static gint oauth2_post_request (gchar *buf, gchar *host, gchar *resource, gchar *header, gchar *body)
{
       gint len;
  
       debug_print("Complete body: %s\n", body);
       len = strlen(body);
       if (header[0])
	 return snprintf(buf, OAUTH2BUFSIZE, "POST %s HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: text/html,application/json\r\nContent-Length: %i\r\nHost: %s\r\nConnection: close\r\nUser-Agent: ClawsMail\r\n%s\r\n\r\n%s", resource, len, host, header, body);
       else
	 return snprintf(buf, OAUTH2BUFSIZE, "POST %s HTTP/1.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept: text/html,application/json\r\nContent-Length: %i\r\nHost: %s\r\nConnection: close\r\nUser-Agent: ClawsMail\r\n\r\n%s", resource, len, host, body);
}

static gint oauth2_filter_access (gchar *json, gchar *access_token, gint *expiry)
{
       GMatchInfo *matchInfo;
       GRegex *regex;
       
       regex = g_regex_new ("\"access_token\": ?\"(.*?)\",?", G_REGEX_RAW, 0, NULL);
       g_regex_match (regex, json, 0, &matchInfo);
       if (g_match_info_matches (matchInfo)) 
	 g_stpcpy (access_token,g_match_info_fetch (matchInfo, 1));
       else{  
	 g_match_info_free (matchInfo);
	 return (-1);
       }
       
       g_match_info_free (matchInfo);
       
       regex = g_regex_new ("\"expires_in\": ?([0-9]*),?", G_REGEX_RAW, 0, NULL);
       g_regex_match (regex, json, 0, &matchInfo);
       if (g_match_info_matches (matchInfo)){
	 // Reduce available token life to avoid attempting connections with (near) expired tokens
	 *expiry = (g_get_real_time () / G_USEC_PER_SEC) + atoi(g_match_info_fetch (matchInfo, 1)) - 120; 
       }else{
	 g_match_info_free (matchInfo);
	 return (-2);
       }
       
       g_match_info_free (matchInfo);
       
       return(0);
}

static gint oauth2_filter_refresh (gchar *json, gchar *refresh_token)
{
       GMatchInfo *matchInfo;
       GRegex *regex;
       
       regex = g_regex_new ("\"refresh_token\": ?\"(.*?)\",?", G_REGEX_RAW, 0, NULL);
       g_regex_match (regex, json, 0, &matchInfo);
       if (g_match_info_matches (matchInfo)) 
	 g_stpcpy (refresh_token,g_match_info_fetch (matchInfo, 1));
       else{  
	 g_match_info_free (matchInfo);
	 return (-1);
       }
       
       g_match_info_free (matchInfo);
       
       return(0);
}

static gchar* oauth2_get_token_from_response(Oauth2Service provider, const gchar* response) {
	gchar* token = NULL;
	
        debug_print("Auth response: %s\n", response);
        if (provider == OAUTH2AUTH_YAHOO) {
                /* Providers which display auth token in browser for users to copy */
                token = g_strdup(response);
        } else {
                gchar* start = g_strstr_len(response, strlen(response), OAUTH2CodeMarker[provider][0]);
                if (start == NULL)
                        return NULL;
                start += strlen(OAUTH2CodeMarker[provider][0]);
                gchar* stop = g_strstr_len(response, strlen(response), OAUTH2CodeMarker[provider][1]);
                if (stop == NULL)
                        return NULL;
                token = g_strndup(start, stop - start);
        }
	
	return token;
}

static gchar *oauth2_contact_server(SockInfo *sock, const gchar *request)
{
	gboolean got_some_error, timeout;
	gint ret;
	char buf[1024];
	GString *response = g_string_sized_new(sizeof(buf));
	time_t end_time = time(NULL);

	end_time += prefs_common_get_prefs()->io_timeout_secs;

	if (!response)
		return NULL;

	if (sock_write(sock, request, strlen(request)) < 0) {
		log_message(LOG_PROTOCOL, _("OAuth2 socket write error\n"));
		g_string_free(response, TRUE);
		return NULL;
	}

	do {
		ret = sock_read(sock, buf, sizeof(buf) - 1);
		got_some_error = ret < 0;
		timeout = time(NULL) > end_time;

		if (timeout)
			break;

		if (ret < 0 && errno == EAGAIN)
			continue;

		if (got_some_error)
			break;

		if (ret) {
			buf[ret] = '\0';
			g_string_append_len(response, buf, ret);
		}
	} while (ret);

	if (timeout)
		log_message(LOG_PROTOCOL, _("OAuth2 socket timeout error\n"));

	return g_string_free(response, got_some_error || timeout);
}

int oauth2_obtain_tokens (Oauth2Service provider, OAUTH2Data *OAUTH2Data, const gchar *authcode)
{
	gchar *request;
	gchar *response;
	gchar *body;
	gchar *uri;
	gchar *header;
	gchar *tmp_hd, *tmp_hd_encoded;
	gchar *access_token;
	gchar *refresh_token;
	gint expiry = 0;
	gint ret;
	SockInfo *sock;
	gchar *client_id;
	gchar *client_secret;
        gchar *token = NULL;
        gchar *tmp;
	gint i;

	i = (int)provider - 1;
	if (i < 0 || i > (OAUTH2AUTH_LAST-1))
	  return (1);
    
        token = oauth2_get_token_from_response(provider, authcode);
        debug_print("Auth token: %s\n", token);
        if (token == NULL) {
                log_message(LOG_PROTOCOL, _("OAuth2 missing authorization code\n"));
                return (1);
        }
        debug_print("Connect: %s:443\n", OAUTH2info[i][OA2_BASE_URL]);
	sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443);
	if (sock == NULL) {
                log_message(LOG_PROTOCOL, _("OAuth2 connection error\n"));
                g_free(token);
                return (1);
        }
        sock->ssl_cert_auto_accept = TRUE;
	sock->use_tls_sni = TRUE;
	sock_set_nonblocking_mode(sock, FALSE);
	gint timeout_secs = prefs_common_get_prefs()->io_timeout_secs;
	debug_print("Socket timeout: %i sec(s)\n", timeout_secs);
	sock_set_io_timeout(timeout_secs);
	sock->gnutls_priority = GNUTLS_PRIORITY;
        if (ssl_init_socket(sock) == FALSE) {
                log_message(LOG_PROTOCOL, _("OAuth2 TLS connection error\n"));
                g_free(token);
                return (1);
        }

	refresh_token = g_malloc(OAUTH2BUFSIZE+1);	
	access_token = g_malloc(OAUTH2BUFSIZE+1);
	request = g_malloc(OAUTH2BUFSIZE+1);

	if(OAUTH2Data->custom_client_id)
	  client_id = g_strdup(OAUTH2Data->custom_client_id);
	else
	  client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);

        body = g_strconcat ("client_id=", client_id, "&code=", token, NULL);
        debug_print("Body: %s\n", body);
        g_free(token);

	if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){
	  //Only allow custom client secret if the service provider would usually expect a client secret
	  if(OAUTH2Data->custom_client_secret)
	    client_secret = g_strdup(OAUTH2Data->custom_client_secret);
	  else
	    client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]);
	  uri = g_uri_escape_string (client_secret, NULL, FALSE);
	  tmp = g_strconcat (body, "&client_secret=", uri, NULL);
	  g_free(body);
          g_free(uri);
	  body = tmp;
	}else{
	  client_secret = g_strconcat ("", NULL);
	}

	if(OAUTH2info[i][OA2_REDIRECT_URI][0]) {
          tmp = g_strconcat(body, "&redirect_uri=", OAUTH2info[i][OA2_REDIRECT_URI], NULL);
	  g_free(body);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_GRANT_TYPE_ACCESS][0]) {
          tmp = g_strconcat(body, "&grant_type=", OAUTH2info[i][OA2_GRANT_TYPE_ACCESS], NULL);
	  g_free(body);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_TENANT][0]) {
          tmp = g_strconcat(body, "&tenant=", OAUTH2info[i][OA2_TENANT], NULL);
	  g_free(body);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) {
          tmp = g_strconcat(body, "&scope=", OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL);
	  g_free(body);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_STATE][0]) {
          tmp = g_strconcat(body, "&state=", OAUTH2info[i][OA2_STATE], NULL);
	  g_free(body);
	  body = tmp;
	}

	if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){
	  tmp_hd = g_strconcat(client_id, ":", client_secret, NULL);
	  tmp_hd_encoded = g_base64_encode (tmp_hd, strlen(tmp_hd));
	  header = g_strconcat ("Authorization: Basic ", tmp_hd_encoded, NULL);
	  g_free(tmp_hd_encoded);
	  g_free(tmp_hd);
	}else{
	  header = g_strconcat ("", NULL);
	}

	oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_ACCESS_RESOURCE], header, body);
	response = oauth2_contact_server (sock, request);

	if(response && oauth2_filter_access (response, access_token, &expiry) == 0){
	  OAUTH2Data->access_token = g_strdup(access_token);
	  OAUTH2Data->expiry = expiry;
	  OAUTH2Data->expiry_str = g_strdup_printf ("%i", expiry);
	  ret = 0;
	  log_message(LOG_PROTOCOL, _("OAuth2 access token obtained\n"));
	}else{
	  log_message(LOG_PROTOCOL, _("OAuth2 access token not obtained\n"));
	  debug_print("OAuth2 - request: %s\n Response: %s", request, response);
	  ret = 1;
	}

	if(response && oauth2_filter_refresh (response, refresh_token) == 0){
	  OAUTH2Data->refresh_token = g_strdup(refresh_token);
	  log_message(LOG_PROTOCOL, _("OAuth2 refresh token obtained\n"));
	}else{
	  log_message(LOG_PROTOCOL, _("OAuth2 refresh token not obtained\n"));
	}

	sock_close(sock, TRUE);
	g_free(body);
	g_free(header);
	g_free(request);
	g_free(response);
	g_free(client_id);
	g_free(client_secret);
	g_free(access_token);
	g_free(refresh_token);

	return (ret);
}

gint oauth2_use_refresh_token (Oauth2Service provider, OAUTH2Data *OAUTH2Data)
{

	gchar *request;
	gchar *response;
	gchar *body;
	gchar *uri;
	gchar *header;
	gchar *tmp_hd, *tmp_hd_encoded;
	gchar *access_token;
	gchar *refresh_token;
	gint expiry = 0;
	gint ret;
	SockInfo *sock;
	gchar *client_id;
	gchar *client_secret;
	gchar *tmp;
	gint i;

	i = (int)provider - 1;
	if (i < 0 || i > (OAUTH2AUTH_LAST-1))
	  return (1);

	sock = sock_connect(OAUTH2info[i][OA2_BASE_URL], 443);
	if (sock == NULL) {
                log_message(LOG_PROTOCOL, _("OAuth2 connection error\n"));
                return (1);
        }
        sock->ssl_cert_auto_accept = TRUE;
	sock->use_tls_sni = TRUE;
	sock_set_nonblocking_mode(sock, FALSE);
	gint timeout_secs = prefs_common_get_prefs()->io_timeout_secs;
	debug_print("Socket timeout: %i sec(s)\n", timeout_secs);
	sock_set_io_timeout(timeout_secs);
	sock->gnutls_priority = GNUTLS_PRIORITY;
        if (ssl_init_socket(sock) == FALSE) {
                log_message(LOG_PROTOCOL, _("OAuth2 TLS connection error\n"));
                return (1);
        }

	access_token = g_malloc(OAUTH2BUFSIZE+1);
	refresh_token = g_malloc(OAUTH2BUFSIZE+1);
	request = g_malloc(OAUTH2BUFSIZE+1);

	if(OAUTH2Data->custom_client_id)
	  client_id = g_strdup(OAUTH2Data->custom_client_id);
	else
	  client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);

	uri = g_uri_escape_string (client_id, NULL, FALSE);
	body = g_strconcat ("client_id=", uri, "&refresh_token=", OAUTH2Data->refresh_token, NULL); 
	g_free(uri);

	if(OAUTH2info[i][OA2_CLIENT_SECRET][0]){
	  //Only allow custom client secret if the service provider would usually expect a client secret
	  if(OAUTH2Data->custom_client_secret)
	    client_secret = g_strdup(OAUTH2Data->custom_client_secret);
	  else
	    client_secret = oauth2_decode(OAUTH2info[i][OA2_CLIENT_SECRET]);
	  uri = g_uri_escape_string (client_secret, NULL, FALSE);
	  tmp = g_strconcat (body, "&client_secret=", uri, NULL);
	  g_free(body);
	  g_free(uri);
	  body = tmp;
	}else{
	  client_secret = g_strconcat ("", NULL);
	}

	if(OAUTH2info[i][OA2_GRANT_TYPE_REFRESH][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_GRANT_TYPE_REFRESH], NULL, FALSE);
	  tmp = g_strconcat (body, "&grant_type=", uri, NULL);	
	  g_free(body);
	  g_free(uri);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_SCOPE_FOR_ACCESS][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_ACCESS], NULL, FALSE);
	  tmp = g_strconcat (body, "&scope=", uri, NULL);
	  g_free(body);
	  g_free(uri);
	  body = tmp;
	}
	if(OAUTH2info[i][OA2_STATE][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE);
	  tmp = g_strconcat (body, "&state=", uri, NULL);
	  g_free(body);
	  g_free(uri);
	  body = tmp;
	}

	if(OAUTH2info[i][OA2_HEADER_AUTH_BASIC][0]){
	  tmp_hd = g_strconcat(client_id, ":", client_secret, NULL);
	  tmp_hd_encoded = g_base64_encode (tmp_hd, strlen(tmp_hd));
	  header = g_strconcat ("Authorization: Basic ", tmp_hd_encoded, NULL);
	  g_free(tmp_hd_encoded);
	  g_free(tmp_hd);
	}else{
	  header = g_strconcat ("", NULL);
	}

	oauth2_post_request (request, OAUTH2info[i][OA2_BASE_URL], OAUTH2info[i][OA2_REFRESH_RESOURCE], header, body);
	response = oauth2_contact_server (sock, request);

	if(response && oauth2_filter_access (response, access_token, &expiry) == 0){
	  OAUTH2Data->access_token = g_strdup(access_token);
	  OAUTH2Data->expiry = expiry;
	  OAUTH2Data->expiry_str = g_strdup_printf ("%i", expiry);
	  ret = 0;
	  log_message(LOG_PROTOCOL, _("OAuth2 access token obtained\n"));
	}else{
	  log_message(LOG_PROTOCOL, _("OAuth2 access token not obtained\n"));
	  debug_print("OAuth2 - request: %s\n Response: %s", request, response);
	  ret = 1;
	}

	if (response && oauth2_filter_refresh (response, refresh_token) == 0) {
		OAUTH2Data->refresh_token = g_strdup(refresh_token);
		log_message(LOG_PROTOCOL, _("OAuth2 replacement refresh token provided\n"));
	} else
		log_message(LOG_PROTOCOL, _("OAuth2 replacement refresh token not provided\n"));

	debug_print("OAuth2 - access token: %s\n", access_token);
	debug_print("OAuth2 - access token expiry: %i\n", expiry);
	
	sock_close(sock, TRUE);
	g_free(body);
	g_free(header);
	g_free(request);
	g_free(response);
	g_free(client_id);
	g_free(client_secret);
	g_free(access_token);
	g_free(refresh_token);

	return (ret);
}

gint oauth2_authorisation_url (Oauth2Service provider, gchar **url, const gchar *custom_client_id)
{
	gint i;
	gchar *client_id = NULL;
	gchar *tmp;
	gchar *uri;

	i = (int)provider - 1;
	if (i < 0 || i > (OAUTH2AUTH_LAST-1))
	  return (1);
	
	if(!custom_client_id)
	  client_id = oauth2_decode(OAUTH2info[i][OA2_CLIENT_ID]);
	
	uri = g_uri_escape_string (custom_client_id ? custom_client_id : client_id, NULL, FALSE);
	*url = g_strconcat ("https://", OAUTH2info[i][OA2_BASE_URL],OAUTH2info[i][OA2_AUTH_RESOURCE], "?client_id=",
			    uri, NULL);
	g_free(uri);
	if (client_id)
	  g_free(client_id);

	if(OAUTH2info[i][OA2_REDIRECT_URI][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_REDIRECT_URI], NULL, FALSE);
	  tmp = g_strconcat (*url, "&redirect_uri=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
    
	}  
	if(OAUTH2info[i][OA2_RESPONSE_TYPE][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_TYPE], NULL, FALSE);
	  tmp = g_strconcat (*url, "&response_type=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
	}  
	if(OAUTH2info[i][OA2_SCOPE_FOR_AUTH][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_SCOPE_FOR_AUTH], NULL, FALSE);
	  tmp = g_strconcat (*url, "&scope=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
	}  
	if(OAUTH2info[i][OA2_TENANT][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_TENANT], NULL, FALSE);
	  tmp = g_strconcat (*url, "&tenant=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
	}  
	if(OAUTH2info[i][OA2_RESPONSE_MODE][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_RESPONSE_MODE], NULL, FALSE);
	  tmp = g_strconcat (*url, "&response_mode=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
	}  
	if(OAUTH2info[i][OA2_STATE][0]) {
	  uri = g_uri_escape_string (OAUTH2info[i][OA2_STATE], NULL, FALSE);
	  tmp = g_strconcat (*url, "&state=", uri, NULL);
	  g_free(*url);
	  *url = tmp;
	  g_free(uri);
	}  

	return (0);
}

gint oauth2_check_passwds (PrefsAccount *ac_prefs)
{
	gchar *uid = g_strdup_printf("%d", ac_prefs->account_id);
	gint expiry;
	OAUTH2Data *OAUTH2Data = g_malloc(sizeof(* OAUTH2Data)); 
	gint ret;
	gchar *acc;

	oauth2_init (OAUTH2Data);

	OAUTH2Data->custom_client_id = ac_prefs->oauth2_client_id;
	OAUTH2Data->custom_client_secret = ac_prefs->oauth2_client_secret;
	
	if (passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_EXPIRY)) {
		acc = passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_EXPIRY);
		expiry = atoi(acc);
		g_free(acc);
		if (expiry >  (g_get_real_time () / G_USEC_PER_SEC)) {
			g_free(OAUTH2Data);
			log_message(LOG_PROTOCOL, _("OAuth2 access token still fresh\n"));
			g_free(uid);
			return (0);
		}
	}
	
	if (passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_REFRESH)) {
		log_message(LOG_PROTOCOL, _("OAuth2 obtaining access token using refresh token\n"));
		OAUTH2Data->refresh_token = passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_REFRESH);
		ret = oauth2_use_refresh_token (ac_prefs->oauth2_provider, OAUTH2Data);
	} else if (passwd_store_has_password(PWS_ACCOUNT, uid, PWS_ACCOUNT_OAUTH2_AUTH)) {
		log_message(LOG_PROTOCOL, _("OAuth2 trying for fresh access token with authorization code\n"));
		acc = passwd_store_get_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_AUTH);
		ret = oauth2_obtain_tokens (ac_prefs->oauth2_provider, OAUTH2Data, acc);
		g_free(acc);
	} else
		ret = 1;
	
	if (ret)
		log_message(LOG_PROTOCOL, _("OAuth2 access token not obtained\n"));
	else {
		if (ac_prefs->imap_auth_type == IMAP_AUTH_OAUTH2 ||
		    (ac_prefs->use_pop_auth && ac_prefs->pop_auth_type == POPAUTH_OAUTH2))
			passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_RECV, OAUTH2Data->access_token, FALSE);
		if (ac_prefs->use_smtp_auth && ac_prefs->smtp_auth_type == SMTPAUTH_OAUTH2)
			passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_SEND, OAUTH2Data->access_token, FALSE);
		passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_EXPIRY, OAUTH2Data->expiry_str, FALSE);
		//Some providers issue replacement refresh tokens with each access token. Re-store whether replaced or not. 
		if (OAUTH2Data->refresh_token != NULL)
			passwd_store_set_account(ac_prefs->account_id, PWS_ACCOUNT_OAUTH2_REFRESH, OAUTH2Data->refresh_token, FALSE);
		passwd_store_write_config();
		log_message(LOG_PROTOCOL, _("OAuth2 access and refresh token updated\n"));  
	}

	g_free(OAUTH2Data);
	g_free(uid);
	
	return (ret);
}

/* returns allocated string which must be freed */
guchar* oauth2_decode(const gchar *in)
{
         guchar *tmp;
	 gsize len;
	 
	 tmp = g_base64_decode(in, &len);
	 passcrypt_decrypt(tmp, len);
	 return tmp;
}

/* For testing */
void oauth2_encode(const gchar *in)
{
         guchar *tmp = g_strdup(in);
	 guchar *tmp2 = g_strdup(in);
	 gchar *result;
	 gsize len = strlen(in);
	 
	 passcrypt_encrypt(tmp, len);
	 result = g_base64_encode(tmp, len); 
	 tmp2 = oauth2_decode(result);
	 
	 log_message(LOG_PROTOCOL, _("OAuth2 original: %s\n"), in);
	 log_message(LOG_PROTOCOL, _("OAuth2 encoded: %s\n"), result);
	 log_message(LOG_PROTOCOL, _("OAuth2 decoded: %s\n\n"), tmp2);
	 
	 g_free(tmp);  
	 g_free(tmp2);
	 g_free(result);
}

gint oauth2_init (OAUTH2Data *OAUTH2Data)
{ 
	 OAUTH2Data->refresh_token = NULL;
	 OAUTH2Data->access_token = NULL;
	 OAUTH2Data->expiry_str = NULL;
	 OAUTH2Data->expiry = 0;
	 OAUTH2Data->custom_client_id = NULL;
	 OAUTH2Data->custom_client_secret = NULL;
	 
	 return (0);
}

#endif	/* USE_GNUTLS */
