exchange/src/lib/auditor_api_handle.c

548 lines
14 KiB
C
Raw Normal View History

2018-10-22 16:00:06 +02:00
/*
This file is part of TALER
Copyright (C) 2014-2020 Taler Systems SA
2018-10-22 16:00:06 +02:00
TALER 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, or (at your option) any later version.
TALER 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
TALER; see the file COPYING. If not, see
<http://www.gnu.org/licenses/>
*/
/**
* @file lib/auditor_api_handle.c
2018-10-22 16:00:06 +02:00
* @brief Implementation of the "handle" component of the auditor's HTTP API
* @author Sree Harsha Totakura <sreeharsha@totakura.in>
* @author Christian Grothoff
*/
#include "platform.h"
#include <microhttpd.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_json_lib.h"
#include "taler_auditor_service.h"
#include "taler_signatures.h"
#include "auditor_api_handle.h"
#include "auditor_api_curl_defaults.h"
2018-10-22 16:00:06 +02:00
#include "backoff.h"
/**
* Which revision of the Taler auditor protocol is implemented
* by this library? Used to determine compatibility.
*/
#define TALER_PROTOCOL_CURRENT 0
/**
* How many revisions back are we compatible to?
*/
#define TALER_PROTOCOL_AGE 0
/**
* Log error related to CURL operations.
*
* @param type log level
* @param function which function failed to run
* @param code what was the curl error code
*/
#define CURL_STRERROR(type, function, code) \
2019-08-25 16:18:24 +02:00
GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \
function, __FILE__, __LINE__, curl_easy_strerror (code));
2018-10-22 16:00:06 +02:00
/**
* Stages of initialization for the `struct TALER_AUDITOR_Handle`
*/
enum AuditorHandleState
{
/**
* Just allocated.
*/
MHS_INIT = 0,
/**
* Obtained the auditor's versioning data and version.
*/
MHS_VERSION = 1,
/**
* Failed to initialize (fatal).
*/
MHS_FAILED = 2
};
/**
* Data for the request to get the /version of a auditor.
*/
struct VersionRequest;
/**
* Handle to the auditor
*/
struct TALER_AUDITOR_Handle
{
/**
* The context of this handle
*/
struct GNUNET_CURL_Context *ctx;
/**
* The URL of the auditor (i.e. "http://auditor.taler.net/")
*/
char *url;
/**
* Function to call with the auditor's certification data,
* NULL if this has already been done.
*/
TALER_AUDITOR_VersionCallback version_cb;
/**
* Closure to pass to @e version_cb.
*/
void *version_cb_cls;
/**
* Data for the request to get the /version of a auditor,
* NULL once we are past stage #MHS_INIT.
*/
struct VersionRequest *vr;
2018-10-22 16:00:06 +02:00
/**
* Task for retrying /version request.
*/
struct GNUNET_SCHEDULER_Task *retry_task;
/**
* /version data of the auditor, only valid if
2018-10-22 16:00:06 +02:00
* @e handshake_complete is past stage #MHS_VERSION.
*/
2020-04-07 11:47:47 +02:00
char *version;
/**
* Version information for the callback.
*/
struct TALER_AUDITOR_VersionInformation vi;
2018-10-22 16:00:06 +02:00
/**
* Retry /version frequency.
*/
struct GNUNET_TIME_Relative retry_delay;
/**
* Stage of the auditor's initialization routines.
*/
enum AuditorHandleState state;
};
/* ***************** Internal /version fetching ************* */
/**
* Data for the request to get the /version of a auditor.
*/
struct VersionRequest
{
/**
* The connection to auditor this request handle will use
*/
struct TALER_AUDITOR_Handle *auditor;
/**
* The url for this handle
*/
char *url;
/**
* Entry for this request with the `struct GNUNET_CURL_Context`.
*/
struct GNUNET_CURL_Job *job;
};
/**
* Release memory occupied by a version request.
* Note that this does not cancel the request
* itself.
*
* @param vr request to free
2018-10-22 16:00:06 +02:00
*/
static void
free_version_request (struct VersionRequest *vr)
2018-10-22 16:00:06 +02:00
{
GNUNET_free (vr->url);
GNUNET_free (vr);
2018-10-22 16:00:06 +02:00
}
/**
* Decode the JSON in @a resp_obj from the /version response and store the data
* in the @a key_data.
*
* @param[in] resp_obj JSON object to parse
* @param[out] auditor where to store the results we decoded
* @param[out] vc where to store version compatibility data
* @return #TALER_EC_NONE on success
2018-10-22 16:00:06 +02:00
*/
static enum TALER_ErrorCode
2018-10-22 16:00:06 +02:00
decode_version_json (const json_t *resp_obj,
2020-04-07 11:47:47 +02:00
struct TALER_AUDITOR_Handle *auditor,
enum TALER_AUDITOR_VersionCompatibility *vc)
2018-10-22 16:00:06 +02:00
{
2020-04-07 11:47:47 +02:00
struct TALER_AUDITOR_VersionInformation *vi = &auditor->vi;
2018-10-22 16:00:06 +02:00
unsigned int age;
unsigned int revision;
unsigned int current;
const char *ver;
2018-10-22 16:00:06 +02:00
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("version",
2019-08-25 16:18:24 +02:00
&ver),
2019-01-24 18:52:30 +01:00
GNUNET_JSON_spec_fixed_auto ("auditor_public_key",
2019-08-25 16:18:24 +02:00
&vi->auditor_pub),
GNUNET_JSON_spec_end ()
2018-10-22 16:00:06 +02:00
};
if (JSON_OBJECT != json_typeof (resp_obj))
{
GNUNET_break_op (0);
2020-11-07 18:51:14 +01:00
return TALER_EC_GENERIC_JSON_INVALID;
2018-10-22 16:00:06 +02:00
}
/* check the version */
if (GNUNET_OK !=
GNUNET_JSON_parse (resp_obj,
2019-08-25 16:18:24 +02:00
spec,
NULL, NULL))
2018-10-22 16:00:06 +02:00
{
GNUNET_break_op (0);
2020-11-07 18:51:14 +01:00
return TALER_EC_GENERIC_JSON_INVALID;
2018-10-22 16:00:06 +02:00
}
2019-01-24 18:52:30 +01:00
if (3 != sscanf (ver,
2019-08-25 16:18:24 +02:00
"%u:%u:%u",
&current,
&revision,
&age))
2018-10-22 16:00:06 +02:00
{
GNUNET_break_op (0);
2020-11-07 18:51:14 +01:00
return TALER_EC_GENERIC_VERSION_MALFORMED;
2018-10-22 16:00:06 +02:00
}
2020-04-07 11:47:47 +02:00
auditor->version = GNUNET_strdup (ver);
vi->version = auditor->version;
2018-10-22 16:00:06 +02:00
*vc = TALER_AUDITOR_VC_MATCH;
if (TALER_PROTOCOL_CURRENT < current)
{
*vc |= TALER_AUDITOR_VC_NEWER;
if (TALER_PROTOCOL_CURRENT < current - age)
*vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
}
if (TALER_PROTOCOL_CURRENT > current)
{
*vc |= TALER_AUDITOR_VC_OLDER;
if (TALER_PROTOCOL_CURRENT - TALER_PROTOCOL_AGE > current)
*vc |= TALER_AUDITOR_VC_INCOMPATIBLE;
}
return TALER_EC_NONE;
2018-10-22 16:00:06 +02:00
}
2019-10-31 12:59:50 +01:00
2018-10-22 16:00:06 +02:00
/**
* Initiate download of /version from the auditor.
*
* @param cls auditor where to download /version from
*/
static void
request_version (void *cls);
/**
* Callback used when downloading the reply to a /version request
* is complete.
*
* @param cls the `struct VersionRequest`
* @param response_code HTTP response code, 0 on error
2020-01-18 17:06:24 +01:00
* @param gresp_obj parsed JSON result, NULL on error, must be a `const json_t *`
2018-10-22 16:00:06 +02:00
*/
static void
version_completed_cb (void *cls,
2019-08-25 16:18:24 +02:00
long response_code,
const void *gresp_obj)
2018-10-22 16:00:06 +02:00
{
const json_t *resp_obj = gresp_obj;
struct VersionRequest *vr = cls;
struct TALER_AUDITOR_Handle *auditor = vr->auditor;
2018-10-22 16:00:06 +02:00
enum TALER_AUDITOR_VersionCompatibility vc;
struct TALER_AUDITOR_HttpResponse hr = {
.reply = resp_obj,
.http_status = (unsigned int) response_code
};
2018-10-22 16:00:06 +02:00
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received version from URL `%s' with status %ld.\n",
vr->url,
2018-10-22 16:00:06 +02:00
response_code);
vc = TALER_AUDITOR_VC_PROTOCOL_ERROR;
switch (response_code)
{
case 0:
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* NOTE: this design is debatable. We MAY want to throw this error at the
client. We may then still additionally internally re-try. */
free_version_request (vr);
auditor->vr = NULL;
2018-10-22 16:00:06 +02:00
GNUNET_assert (NULL == auditor->retry_task);
auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
2018-10-22 16:00:06 +02:00
auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
&request_version,
auditor);
2018-10-22 16:00:06 +02:00
return;
case MHD_HTTP_OK:
if (NULL == resp_obj)
{
GNUNET_break_op (0);
2019-01-23 15:46:07 +01:00
TALER_LOG_WARNING ("NULL body for a 200-OK /version\n");
hr.http_status = 0;
2020-11-07 18:51:14 +01:00
hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
2018-10-22 16:00:06 +02:00
break;
}
hr.ec = decode_version_json (resp_obj,
2020-04-07 11:47:47 +02:00
auditor,
&vc);
if (TALER_EC_NONE != hr.ec)
2018-10-22 16:00:06 +02:00
{
GNUNET_break_op (0);
hr.http_status = 0;
2018-10-22 16:00:06 +02:00
break;
}
auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; /* restart quickly */
2018-10-22 16:00:06 +02:00
break;
default:
hr.ec = TALER_JSON_get_error_code (resp_obj);
hr.hint = TALER_JSON_get_error_hint (resp_obj);
2018-10-22 16:00:06 +02:00
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Unexpected response code %u/%d\n",
(unsigned int) response_code,
(int) hr.ec);
2018-10-22 16:00:06 +02:00
break;
}
if (MHD_HTTP_OK != response_code)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
2019-08-25 16:18:24 +02:00
"/version failed for auditor %p: %u!\n",
auditor,
(unsigned int) response_code);
auditor->vr = NULL;
free_version_request (vr);
2018-10-22 16:00:06 +02:00
auditor->state = MHS_FAILED;
/* notify application that we failed */
auditor->version_cb (auditor->version_cb_cls,
&hr,
2019-08-25 16:18:24 +02:00
NULL,
vc);
2018-10-22 16:00:06 +02:00
return;
}
auditor->vr = NULL;
free_version_request (vr);
2019-01-23 15:46:07 +01:00
TALER_LOG_DEBUG ("Switching auditor state to 'version'\n");
2018-10-22 16:00:06 +02:00
auditor->state = MHS_VERSION;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Auditor %p is now READY!\n",
auditor);
2018-10-22 16:00:06 +02:00
/* notify application about the key information */
auditor->version_cb (auditor->version_cb_cls,
&hr,
&auditor->vi,
vc);
2018-10-22 16:00:06 +02:00
}
/* ********************* library internal API ********* */
/**
* Get the context of a auditor.
*
* @param h the auditor handle to query
* @return ctx context to execute jobs in
*/
struct GNUNET_CURL_Context *
TALER_AUDITOR_handle_to_context_ (struct TALER_AUDITOR_Handle *h)
2018-10-22 16:00:06 +02:00
{
return h->ctx;
}
/**
* Check if the handle is ready to process requests.
*
* @param h the auditor handle to query
* @return #GNUNET_YES if we are ready, #GNUNET_NO if not
*/
int
TALER_AUDITOR_handle_is_ready_ (struct TALER_AUDITOR_Handle *h)
2018-10-22 16:00:06 +02:00
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
2019-07-28 15:39:28 +02:00
"Checking if auditor %p (%s) is now ready: %s\n",
h,
2019-07-28 15:39:28 +02:00
h->url,
(MHD_VERSION == h->state) ? "yes" : "no");
2018-10-22 16:00:06 +02:00
return (MHS_VERSION == h->state) ? GNUNET_YES : GNUNET_NO;
}
/**
* Obtain the URL to use for an API request.
*
* @param h handle for the auditor
* @param path Taler API path (i.e. "/deposit-confirmation")
2018-10-22 16:00:06 +02:00
* @return the full URL to use with cURL
*/
char *
TALER_AUDITOR_path_to_url_ (struct TALER_AUDITOR_Handle *h,
const char *path)
2018-10-22 16:00:06 +02:00
{
2020-01-16 21:57:40 +01:00
GNUNET_assert ('/' == path[0]);
2021-03-05 21:41:55 +01:00
return TALER_url_join (h->url,
path + 1,
NULL);
2018-10-22 16:00:06 +02:00
}
/* ********************* public API ******************* */
/**
* Initialise a connection to the auditor. Will connect to the
* auditor and obtain information about the auditor's master public
* key and the auditor's auditor. The respective information will
* be passed to the @a version_cb once available, and all future
* interactions with the auditor will be checked to be signed
* (where appropriate) by the respective master key.
*
* @param ctx the context
* @param url HTTP base URL for the auditor
2019-01-21 15:50:11 +01:00
* @param version_cb function to call with the
* auditor's version information
2018-10-22 16:00:06 +02:00
* @param version_cb_cls closure for @a version_cb
* @return the auditor handle; NULL upon error
*/
struct TALER_AUDITOR_Handle *
TALER_AUDITOR_connect (struct GNUNET_CURL_Context *ctx,
2019-07-28 15:39:28 +02:00
const char *url,
TALER_AUDITOR_VersionCallback version_cb,
void *version_cb_cls)
2018-10-22 16:00:06 +02:00
{
struct TALER_AUDITOR_Handle *auditor;
/* Disable 100 continue processing */
GNUNET_break (GNUNET_OK ==
GNUNET_CURL_append_header (ctx,
"Expect:"));
2018-10-22 16:00:06 +02:00
auditor = GNUNET_new (struct TALER_AUDITOR_Handle);
auditor->retry_delay = GNUNET_TIME_UNIT_SECONDS; /* start slowly */
2018-10-22 16:00:06 +02:00
auditor->ctx = ctx;
auditor->url = GNUNET_strdup (url);
auditor->version_cb = version_cb;
auditor->version_cb_cls = version_cb_cls;
auditor->retry_task = GNUNET_SCHEDULER_add_now (&request_version,
2019-07-28 15:39:28 +02:00
auditor);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Connecting to auditor at URL `%s' (%p).\n",
url,
auditor);
2018-10-22 16:00:06 +02:00
return auditor;
}
/**
* Initiate download of /version from the auditor.
*
* @param cls auditor where to download /version from
*/
static void
request_version (void *cls)
{
struct TALER_AUDITOR_Handle *auditor = cls;
struct VersionRequest *vr;
2018-10-22 16:00:06 +02:00
CURL *eh;
auditor->retry_task = NULL;
GNUNET_assert (NULL == auditor->vr);
vr = GNUNET_new (struct VersionRequest);
vr->auditor = auditor;
vr->url = TALER_AUDITOR_path_to_url_ (auditor,
"/version");
2021-03-05 21:41:55 +01:00
if (NULL == vr->url)
{
struct TALER_AUDITOR_HttpResponse hr = {
.ec = TALER_EC_GENERIC_CONFIGURATION_INVALID
};
auditor->version_cb (auditor->version_cb_cls,
&hr,
NULL,
TALER_AUDITOR_VC_PROTOCOL_ERROR);
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Requesting auditor version with URL `%s'.\n",
vr->url);
eh = TALER_AUDITOR_curl_easy_get_ (vr->url);
if (NULL == eh)
{
GNUNET_break (0);
auditor->retry_delay = EXCHANGE_LIB_BACKOFF (auditor->retry_delay);
auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay,
&request_version,
auditor);
return;
}
GNUNET_break (CURLE_OK ==
curl_easy_setopt (eh,
CURLOPT_TIMEOUT,
(long) 300));
vr->job = GNUNET_CURL_job_add (auditor->ctx,
2018-10-22 16:00:06 +02:00
eh,
&version_completed_cb,
vr);
auditor->vr = vr;
2018-10-22 16:00:06 +02:00
}
/**
* Disconnect from the auditor
*
* @param auditor the auditor handle
*/
void
TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor)
{
2019-07-28 15:39:28 +02:00
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Disconnecting from auditor at URL `%s' (%p).\n",
auditor->url,
auditor);
if (NULL != auditor->vr)
2018-10-22 16:00:06 +02:00
{
GNUNET_CURL_job_cancel (auditor->vr->job);
free_version_request (auditor->vr);
auditor->vr = NULL;
2018-10-22 16:00:06 +02:00
}
GNUNET_free (auditor->version);
2018-10-22 16:00:06 +02:00
if (NULL != auditor->retry_task)
{
GNUNET_SCHEDULER_cancel (auditor->retry_task);
auditor->retry_task = NULL;
}
GNUNET_free (auditor->url);
GNUNET_free (auditor);
}
/* end of auditor_api_handle.c */