From e83964badb5c266992f5b1312b31aa6a14d392e5 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 22 Oct 2018 16:00:06 +0200 Subject: skeleton for libtalerauditor --- src/auditor-lib/auditor_api_handle.c | 570 +++++++++++++++++++++++++++++++++++ 1 file changed, 570 insertions(+) create mode 100644 src/auditor-lib/auditor_api_handle.c (limited to 'src/auditor-lib/auditor_api_handle.c') diff --git a/src/auditor-lib/auditor_api_handle.c b/src/auditor-lib/auditor_api_handle.c new file mode 100644 index 000000000..4db528a31 --- /dev/null +++ b/src/auditor-lib/auditor_api_handle.c @@ -0,0 +1,570 @@ +/* + This file is part of TALER + Copyright (C) 2014-2018 GNUnet e.V. + + 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 + +*/ +/** + * @file auditor-lib/auditor_api_handle.c + * @brief Implementation of the "handle" component of the auditor's HTTP API + * @author Sree Harsha Totakura + * @author Christian Grothoff + */ +#include "platform.h" +#include +#include +#include "taler_json_lib.h" +#include "taler_auditor_service.h" +#include "taler_signatures.h" +#include "auditor_api_handle.h" +#include "curl_defaults.h" +#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) \ + GNUNET_log (type, "Curl function `%s' has failed at `%s:%d' with error: %s", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * 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 *kr; + + /** + * Task for retrying /version request. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Key data of the auditor, only valid if + * @e handshake_complete is past stage #MHS_VERSION. + */ + struct TALER_AUDITOR_Version key_data; + + /** + * 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 kr request to free + */ +static void +free_version_request (struct VersionRequest *kr) +{ + GNUNET_free (kr->url); + GNUNET_free (kr); +} + + +/** + * Parse a auditor's auditor information encoded in JSON. + * + * @param[out] auditor where to return the result + * @param check_sig should we check signatures + * @param[in] auditor_obj json to parse + * @param key_data information about denomination version + * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if the signature is + * invalid or the json malformed. + */ +static int +parse_json_auditor (struct TALER_AUDITOR_AuditorInformation *auditor, + int check_sigs, + json_t *auditor_obj, + const struct TALER_AUDITOR_Version *key_data) +{ + const char *auditor_url; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("auditor_pub", + &auditor->auditor_pub), + GNUNET_JSON_spec_string ("auditor_url", + &auditor_url), + GNUNET_JSON_spec_json ("denomination_version", + &version), + GNUNET_JSON_spec_end() + }; + + if (GNUNET_OK != + GNUNET_JSON_parse (auditor_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + auditor->auditor_url = GNUNET_strdup (auditor_url); + GNUNET_JSON_parse_free (spec); + return GNUNET_OK; +} + + +/** + * 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 check_sig #GNUNET_YES if we should check the signature + * @param[out] key_data where to store the results we decoded + * @param[out] where to store version compatibility data + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error (malformed JSON) + */ +static int +decode_version_json (const json_t *resp_obj, + int check_sig, + struct TALER_AUDITOR_Version *key_data, + enum TALER_AUDITOR_VersionCompatibility *vc) +{ + struct TALER_AuditorPublicKeyP pub; + unsigned int age; + unsigned int revision; + unsigned int current; + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_string ("version", + &ver), + GNUNET_JSON_spec_fixed_auto ("master_public_key", + &key_data->master_pub), + GNUNET_JSON_spec_end() + }; + + if (JSON_OBJECT != json_typeof (resp_obj)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + /* check the version */ + if (GNUNET_OK != + GNUNET_JSON_parse (resp_obj, + spec, + NULL, NULL)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (3 != sscanf (ver, + "%u:%u:%u", + ¤t, + &revision, + &age)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + *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; + } + key_data->version = GNUNET_strdup (ver); + return GNUNET_OK; +} + + +/** + * Free key data object. + * + * @param key_data data to free (pointer itself excluded) + */ +static void +free_key_data (struct TALER_AUDITOR_Keys *key_data) +{ + GNUNET_free_non_null (key_data->version); + key_data->version = NULL; +} + + +/** + * 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 + * @param resp_obj parsed JSON result, NULL on error + */ +static void +version_completed_cb (void *cls, + long response_code, + const json_t *resp_obj) +{ + struct VersionRequest *kr = cls; + struct TALER_AUDITOR_Handle *auditor = kr->auditor; + enum TALER_AUDITOR_VersionCompatibility vc; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received version from URL `%s' with status %ld.\n", + kr->url, + response_code); + vc = TALER_AUDITOR_VC_PROTOCOL_ERROR; + switch (response_code) + { + case 0: + free_version_request (kr); + auditor->kr = NULL; + GNUNET_assert (NULL == auditor->retry_task); + auditor->retry_delay = AUDITOR_LIB_BACKOFF (auditor->retry_delay); + auditor->retry_task = GNUNET_SCHEDULER_add_delayed (auditor->retry_delay, + &request_version, + auditor); + return; + case MHD_HTTP_OK: + if (NULL == resp_obj) + { + response_code = 0; + break; + } + /* We keep the denomination version and auditor signatures from the + previous iteration (/version cherry picking) */ + if (GNUNET_OK != + decode_version_json (resp_obj, + GNUNET_YES, + &kd, + &vc)) + { + response_code = 0; + break; + } + auditor->retry_delay = GNUNET_TIME_UNIT_ZERO; + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + (unsigned int) response_code); + break; + } + auditor->key_data = kd; + + if (MHD_HTTP_OK != response_code) + { + auditor->kr = NULL; + free_version_request (kr); + auditor->state = MHS_FAILED; + free_key_data (&kd_old); + /* notify application that we failed */ + auditor->version_cb (auditor->version_cb_cls, + NULL, + vc); + return; + } + + auditor->kr = NULL; + free_version_request (kr); + auditor->state = MHS_VERSION; + /* notify application about the key information */ + auditor->version_cb (auditor->version_cb_cls, + &auditor->key_data, + vc); + free_key_data (&kd_old); +} + + +/* ********************* 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 * +MAH_handle_to_context (struct TALER_AUDITOR_Handle *h) +{ + 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 +MAH_handle_is_ready (struct TALER_AUDITOR_Handle *h) +{ + 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. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url (struct TALER_AUDITOR_Handle *h, + const char *path) +{ + return MAH_path_to_url2 (h->url, + path); +} + + +/** + * Obtain the URL to use for an API request. + * + * @param base_url base URL of the auditor (i.e. "http://auditor/") + * @param path Taler API path (i.e. "/reserve/withdraw") + * @return the full URL to use with cURL + */ +char * +MAH_path_to_url2 (const char *base_url, + const char *path) +{ + char *url; + + if ( ('/' == path[0]) && + (0 < strlen (base_url)) && + ('/' == base_url[strlen (base_url) - 1]) ) + path++; /* avoid generating URL with "//" from concat */ + GNUNET_asprintf (&url, + "%s%s", + base_url, + path); + return url; +} + + +/* ********************* 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 + * @param version_cb function to call with the auditor's versionification information + * @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, + const char *url, + TALER_AUDITOR_VersionificationCallback version_cb, + void *version_cb_cls) +{ + struct TALER_AUDITOR_Handle *auditor; + + auditor = GNUNET_new (struct TALER_AUDITOR_Handle); + 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, + auditor); + 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 *kr; + CURL *eh; + + auditor->retry_task = NULL; + GNUNET_assert (NULL == auditor->kr); + kr = GNUNET_new (struct VersionRequest); + kr->auditor = auditor; + kr->url = MAH_path_to_url (auditor, + "/version"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Requesting version with URL `%s'.\n", + kr->url); + eh = TEL_curl_easy_get (kr->url); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_VERBOSE, + 0)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_TIMEOUT, + (long) 300)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HEADERDATA, + kr)); + kr->job = GNUNET_CURL_job_add (auditor->ctx, + eh, + GNUNET_NO, + (GC_JCC) &version_completed_cb, + kr); + auditor->kr = kr; +} + + +/** + * Disconnect from the auditor + * + * @param auditor the auditor handle + */ +void +TALER_AUDITOR_disconnect (struct TALER_AUDITOR_Handle *auditor) +{ + if (NULL != auditor->kr) + { + GNUNET_CURL_job_cancel (auditor->kr->job); + free_version_request (auditor->kr); + auditor->kr = NULL; + } + free_key_data (&auditor->key_data); + 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 */ -- cgit v1.2.3