blob: 6d61c2302702ef2c1b3c10f879548bfc5bfb15f9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "utils/cloudrest.h"
#include <string.h>
#include "utils/elog.h"
char *pg_cloud_server = NULL;
char *pg_cloud_token = NULL;
bool pg_cloud_createrole;
bool pg_cloud_auth = false;
static void finalize_cloud_curl();
static json_object *create_cloud_authentication_request_json(char *username, char *password)
{
json_object *jrequest = json_object_new_object();
json_object *jusername = json_object_new_string(username);
json_object_object_add(jrequest, "username", jusername);
json_object *jpassword = json_object_new_string(password);
json_object_object_add(jrequest, "password", jpassword);
json_object *jclustername = json_object_new_string(pg_cloud_clustername);
json_object_object_add(jrequest, "clustername", jclustername);
return jrequest;
}
static json_object *create_cloud_usersync_request_json(char *username, char *password, bool *createuser, char *action)
{
json_object *jrequest = json_object_new_object();
json_object *jaction = json_object_new_string(action);
json_object_object_add(jrequest, "action", jaction);
json_object *jtoken = json_object_new_string(pg_cloud_token);
json_object_object_add(jrequest, "token", jtoken);
json_object *jusername = json_object_new_string(username);
json_object_object_add(jrequest, "username", jusername);
if (password)
{
json_object *jpassword = json_object_new_string(password);
json_object_object_add(jrequest, "password", jpassword);
}
json_object *jclustername = json_object_new_string(pg_cloud_clustername);
json_object_object_add(jrequest, "clustername", jclustername);
if (createuser)
{
json_object *jcreateuser = json_object_new_boolean(*createuser);
json_object_object_add(jrequest, "cancreateuser", jcreateuser);
}
return jrequest;
}
static json_object *create_cloud_userexist_request_json(char *username)
{
json_object *jrequest = json_object_new_object();
json_object *jusername = json_object_new_string(username);
json_object_object_add(jrequest, "username", jusername);
json_object *jclustername = json_object_new_string(pg_cloud_clustername);
json_object_object_add(jrequest, "clustername", jclustername);
return jrequest;
}
static size_t write_callback(char *contents, size_t size, size_t nitems,
void *userp)
{
size_t realsize = size * nitems;
CURL_HANDLE curl = (CURL_HANDLE) userp;
Assert(curl != NULL);
elog(DEBUG3, "cloud restful response size is %d. response buffer size is %d.", curl->response.response_size, curl->response.buffer_size);
int original_size = curl->response.buffer_size;
while(curl->response.response_size + realsize >= curl->response.buffer_size)
{
/* double the buffer size if the buffer is not enough.*/
curl->response.buffer_size = curl->response.buffer_size * 2;
}
if(original_size < curl->response.buffer_size)
{
/* repalloc is not same as realloc, repalloc's first parameter cannot be NULL */
curl->response.buffer = repalloc(curl->response.buffer, curl->response.buffer_size);
}
elog(DEBUG3, "cloud restful response size is %d. response buffer size is %d.", curl->response.response_size, curl->response.buffer_size);
if (curl->response.buffer == NULL)
{
/* allocate memory failed. probably out of memory */
elog(WARNING, "cannot allocate memory for cloud response");
return 0;
}
memcpy(curl->response.buffer + curl->response.response_size, contents, realsize);
curl->response.response_size += realsize;
curl->response.buffer[curl->response.response_size] = '\0';
elog(DEBUG3, "read from cloud restful response: %s", curl->response.buffer);
return realsize;
}
/**
* @return 0 curl success; -1 curl failed
*/
static int call_cloud_rest(CURL_HANDLE curl_handle, const char* request, char *action)
{
int ret = -1;
CURLcode res;
Assert(request != NULL);
/*
* Re-initializes all options previously set on a specified CURL handle
* to the default values. This puts back the handle to the same state as
* it was in when it was just created with curl_easy_init.It does not
* change the following information kept in the handle: live connections,
* the Session ID cache, the DNS cache, the cookies and shares.
*/
curl_easy_reset(curl_handle->curl_handle);
/* timeout: hard-coded temporarily and maybe should be a guc in future */
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_TIMEOUT, 30L);
/* specify URL to get */
StringInfoData tname;
initStringInfo(&tname);
appendStringInfo(&tname, "%s", pg_cloud_server);
appendStringInfo(&tname, "/");
appendStringInfo(&tname, "%s", action);
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_URL, tname.data);
elog(DEBUG3, "in call_cloud_rest: %s", tname.data);
pfree(tname.data);
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type:application/json");
if (pg_cloud_token) {
char buf[512];
memset(buf, 0, sizeof(buf));
sprintf(buf, "token:%s", pg_cloud_token);
elog(DEBUG3, "in call_cloud_rest: %s", buf);
headers = curl_slist_append(headers, buf);
}
headers = curl_slist_append(headers, "connection:close");
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_POSTFIELDS, request);
/* send all data to this function */
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_WRITEFUNCTION,
write_callback);
curl_easy_setopt(curl_handle->curl_handle, CURLOPT_WRITEDATA,
(void * )curl_handle);
res = curl_easy_perform(curl_handle->curl_handle);
/* check for errors */
if (res != CURLE_OK)
{
elog(ERROR, "cloud server from %s/%s is unavailable : %s.\n",
pg_cloud_server, action, curl_easy_strerror(res));
}
else
{
ret = 0;
elog(DEBUG3, "retrieved %d bytes data from cloud restful response.",
curl_handle->response.response_size);
}
return ret;
}
static int parse_cloud_auth_response(char* buffer, int *result, char **errormsg)
{
if (buffer == NULL || strlen(buffer) == 0)
return -1;
elog(DEBUG3, "parse cloud restful response content : %s", buffer);
struct json_object *response = json_tokener_parse(buffer);
if (response == NULL)
{
elog(WARNING, "failed to parse json tokener.");
return -1;
}
struct json_object *jtoken = NULL;
if (!json_object_object_get_ex(response, "token", &jtoken))
{
elog(WARNING, "failed to get json \"token\" field.");
return -1;
}
char *token = json_object_get_string(jtoken);
size_t len = strlen(token);
MemoryContext old;
old = MemoryContextSwitchTo(TopMemoryContext);
pg_cloud_token = (char *)palloc0(len + 1);
memcpy(pg_cloud_token, token, len);
MemoryContextSwitchTo(old);
elog(DEBUG3, "in parse_cloud_auth_response, token(%p): %s", pg_cloud_token, pg_cloud_token);
struct json_object *jcreaterole = NULL;
if (!json_object_object_get_ex(response, "cancreateuser", &jcreaterole))
{
elog(WARNING, "failed to get json \"cancreateuser\" field.");
return -1;
}
pg_cloud_createrole = json_object_get_boolean(jcreaterole);
elog(DEBUG3, "pg_cloud_createrole=%d", pg_cloud_createrole);
struct json_object *jresult = NULL;
if (!json_object_object_get_ex(response, "result", &jresult))
{
elog(WARNING, "failed to get json \"result\" field.");
return -1;
}
json_bool ok = json_object_get_int(jresult);
if (ok == 1)
{
*result = CLOUDCHECK_OK;
}
else
{
struct json_object *jerror = NULL;
if (!json_object_object_get_ex(response, "error", &jerror))
{
elog(WARNING, "failed to get json \"token\" field.");
return -1;
}
char *err = json_object_get_string(jerror);
size_t len = strlen(err);
*errormsg = (char *)palloc0(len + 1);
memcpy(*errormsg, err, len);
(*errormsg)[len] = '\0';
*result = CLOUDCHECK_NO_PRIV;
elog(INFO, "errmsg=%s, size=%d", *errormsg, strlen(*errormsg));
}
return 0;
}
static int parse_cloud_sync_response(char* buffer, int *result, char **errormsg)
{
if (buffer == NULL || strlen(buffer) == 0)
return -1;
elog(DEBUG3, "parse cloud restful response content : %s", buffer);
struct json_object *response = json_tokener_parse(buffer);
if (response == NULL)
{
elog(WARNING, "failed to parse json tokener.");
return -1;
}
struct json_object *jresult = NULL;
if (!json_object_object_get_ex(response, "result", &jresult))
{
elog(WARNING, "failed to get json \"result\" field.");
return -1;
}
int ok = json_object_get_boolean(jresult);
elog(DEBUG3, "in parse_cloud_sync_response, ret=%d", ok);
if (ok == 0)
{
*result = CLOUDSYNC_OK;
}
else
{
struct json_object *jerror = NULL;
if (!json_object_object_get_ex(response, "error", &jerror))
{
elog(WARNING, "failed to get json \"token\" field.");
return -1;
}
char *err = json_object_get_string(jerror);
size_t len = strlen(err);
*errormsg = (char *)palloc0(len + 1);
memcpy(*errormsg, err, len);
(*errormsg)[len] = '\0';
if (ok == 1)
*result = CLOUDSYNC_USEREXIST;
else
*result = CLOUDSYNC_FAIL;
}
return 0;
}
static int parse_cloud_exist_response(char* buffer, int *result, char **errormsg)
{
if (buffer == NULL || strlen(buffer) == 0)
return -1;
elog(DEBUG3, "parse cloud restful response content : %s", buffer);
struct json_object *response = json_tokener_parse(buffer);
if (response == NULL)
{
elog(WARNING, "failed to parse json tokener.");
return -1;
}
struct json_object *jresult = NULL;
if (!json_object_object_get_ex(response, "result", &jresult))
{
elog(WARNING, "failed to get json \"result\" field.");
return -1;
}
json_bool ok = json_object_get_boolean(jresult);
if (ok == 1)
{
*result = CLOUDUSER_EXIST;
}
else
{
*result = CLOUDUSER_NOTEXIST;
}
return 0;
}
void init_cloud_curl() {
memset(&curl_context_cloud, 0, sizeof(curl_context_t));
curl_global_init(CURL_GLOBAL_ALL);
/* init the curl session */
curl_context_cloud.curl_handle = curl_easy_init();
if (curl_context_cloud.curl_handle == NULL) {
/* cleanup curl stuff */
/* no need to cleanup curl_handle since it's null. just cleanup curl global.*/
curl_global_cleanup();
elog(ERROR, "initialize global curl context failed.");
}
curl_context_cloud.hasInited = true;
MemoryContext old;
old = MemoryContextSwitchTo(TopMemoryContext);
curl_context_cloud.response.buffer = palloc0(CURL_RES_BUFFER_SIZE);
MemoryContextSwitchTo(old);
curl_context_cloud.response.buffer_size = CURL_RES_BUFFER_SIZE;
elog(DEBUG3, "initialize global curl context for privileges check.");
on_proc_exit(finalize_cloud_curl, 0);
}
void finalize_cloud_curl() {
if (curl_context_cloud.hasInited) {
if (curl_context_cloud.response.buffer != NULL) {
pfree(curl_context_cloud.response.buffer);
}
/* cleanup curl stuff */
if (curl_context_cloud.curl_handle) {
curl_easy_cleanup(curl_context_cloud.curl_handle);
}
/* we're done with libcurl, so clean it up */
curl_global_cleanup();
curl_context_cloud.hasInited = false;
elog(DEBUG3, "finalize the global struct for curl handle context.");
}
}
int check_authentication_from_cloud(char *username, char *password,
bool *createuser, CouldAuthAction authAction, char * action,
char **errormsg)
{
json_object* jrequest;
switch (authAction)
{
case AUTHENTICATION_CHECK:
jrequest = create_cloud_authentication_request_json(username, password);
break;
case USER_SYNC:
jrequest = create_cloud_usersync_request_json(username, password,
createuser, action);
break;
case USER_EXIST:
jrequest = create_cloud_userexist_request_json(username);
break;
default:
elog(ERROR, "Invalid cloud authentication action:%d", authAction);
}
Assert(jrequest != NULL);
const char *request = json_object_to_json_string(jrequest);
Assert(request != NULL);
elog(
DEBUG3, "send json request to cloud : %s", request);
/* call GET method to send request*/
Assert(curl_context_cloud.hasInited);
switch (authAction)
{
case AUTHENTICATION_CHECK:
if (call_cloud_rest(&curl_context_cloud, request, "cloudauthenticate") < 0)
{
return -1;
}
break;
case USER_SYNC:
if (call_cloud_rest(&curl_context_cloud, request, "syncuser") < 0)
{
return -1;
}
break;
case USER_EXIST:
if (call_cloud_rest(&curl_context_cloud, request, "userexist") < 0)
{
return -1;
}
break;
default:
elog(ERROR, "Invalid cloud authentication action:%d", authAction);
}
/* free the JSON object */
json_object_put(jrequest);
/* parse the JSON-format result */
int result,
ret;
switch (authAction)
{
case AUTHENTICATION_CHECK:
ret = parse_cloud_auth_response(curl_context_cloud.response.buffer,
&result, errormsg);
break;
case USER_SYNC:
ret = parse_cloud_sync_response(curl_context_cloud.response.buffer,
&result, errormsg);
break;
case USER_EXIST:
ret = parse_cloud_exist_response(curl_context_cloud.response.buffer,
&result, errormsg);
break;
default:
elog(ERROR, "Invalid cloud authentication action:%d", authAction);
}
if (ret < 0)
{
elog(
ERROR, "parse cloud response failed, cloud response content is %s",
curl_context_cloud.response.buffer == NULL? "empty.":curl_context_cloud.response.buffer);
}
if (curl_context_cloud.response.buffer != NULL)
{
/* reset response size to reuse the buffer. */
curl_context_cloud.response.response_size = 0;
}
elog(DEBUG3, "in check_authentication_from_cloud: ret=%d", result);
return result;
}