/* This file is part of TALER Copyright (C) 2014 Christian Grothoff (and other contributing authors) 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, If not, see */ /** * @file mint/mint_api.c * @brief Implementation of the client interface to mint's HTTP API * @author Sree Harsha Totakura */ #include "platform.h" #include #include #include #include "taler_mint_service.h" #include "taler_signatures.h" #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)); /** * Print JSON parsing related error information */ #define JSON_WARN(error) \ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ "JSON parsing failed at %s:%u: %s (%s)", \ __FILE__, __LINE__, error.text, error.source) /** * Failsafe flag */ static int fail; /** * Context */ struct TALER_MINT_Context { /** * CURL multi handle */ CURLM *multi; /** * CURL share handle */ CURLSH *share; /** * Perform task handle */ struct GNUNET_SCHEDULER_Task *perform_task; }; /** * Type of requests we currently have */ enum RequestType { /** * No request */ REQUEST_TYPE_NONE, /** * Current request is to receive mint's keys */ REQUEST_TYPE_KEYSGET, /** * Current request is to submit a deposit permission and get its status */ REQUEST_TYPE_DEPOSIT }; /** * Handle to the mint */ struct TALER_MINT_Handle { /** * The context of this handle */ struct TALER_MINT_Context *ctx; /** * The hostname of the mint */ char *hostname; /** * The CURL handle */ CURL *curl; /** * Error buffer for CURL */ char emsg[CURL_ERROR_SIZE]; /** * Download buffer */ void *buf; /** * The currently active request */ union { /** * Used to denote no request if set to NULL */ void *none; /** * Denom keys get request if REQUEST_TYPE_KEYSGET */ struct TALER_MINT_KeysGetHandle *keys_get; /** * Deposit request if REQUEST_TYPE_DEPOSIT */ struct TALER_MINT_DepositHandle *deposit; } req; /** * The size of the download buffer */ size_t buf_size; /** * Active request type */ enum RequestType req_type; /** * The service port of the mint */ uint16_t port; /** * Are we connected to the mint? */ uint8_t connected; }; /** * A handle to get the keys of a mint */ struct TALER_MINT_KeysGetHandle { /** * The connection to mint this request handle will use */ struct TALER_MINT_Handle *mint; /** * The url for this handle */ char *url; TALER_MINT_KeysGetCallback cb; void *cls; TALER_MINT_ContinuationCallback cont_cb; void *cont_cls; }; /** * A handle to submit a deposit permission and get its status */ struct TALER_MINT_DepositHandle { /** *The connection to mint this request handle will use */ struct TALER_MINT_Handle *mint; /** * The url for this handle */ char *url; TALER_MINT_DepositResultCallback cb; void *cls; char *json_enc; struct curl_slist *headers; }; /** * Parses the timestamp encoded as ASCII string as UNIX timstamp. * * @param abs successfully parsed timestamp will be returned thru this parameter * @param tstamp_enc the ASCII encoding of the timestamp * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure */ static int parse_timestamp (struct GNUNET_TIME_Absolute *abs, const char *tstamp_enc) { unsigned long tstamp; if (1 != sscanf (tstamp_enc, "%lu", &tstamp)) return GNUNET_SYSERR; *abs = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get_zero_ (), GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, tstamp)); return GNUNET_OK; } #define EXITIF(cond) \ do { \ if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ } while (0) static int parse_json_signkey (struct TALER_MINT_SigningPublicKey **_sign_key, json_t *sign_key_obj, struct GNUNET_CRYPTO_EddsaPublicKey *master_key) { json_t *valid_from_obj; json_t *valid_until_obj; json_t *key_obj; json_t *sig_obj; const char *valid_from_enc; const char *valid_until_enc; const char *key_enc; const char *sig_enc; struct TALER_MINT_SigningPublicKey *sign_key; struct TALER_MINT_SignKeyIssue sign_key_issue; struct GNUNET_CRYPTO_EddsaSignature sig; struct GNUNET_TIME_Absolute valid_from; struct GNUNET_TIME_Absolute valid_until; EXITIF (JSON_OBJECT != json_typeof (sign_key_obj)); EXITIF (NULL == (valid_from_obj = json_object_get (sign_key_obj, "stamp_start"))); EXITIF (NULL == (valid_until_obj = json_object_get (sign_key_obj, "stamp_expire"))); EXITIF (NULL == (key_obj = json_object_get (sign_key_obj, "key"))); EXITIF (NULL == (sig_obj = json_object_get (sign_key_obj, "master_sig"))); EXITIF (NULL == (valid_from_enc = json_string_value (valid_from_obj))); EXITIF (NULL == (valid_until_enc = json_string_value (valid_until_obj))); EXITIF (NULL == (key_enc = json_string_value (key_obj))); EXITIF (NULL == (sig_enc = json_string_value (sig_obj))); EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc)); EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_until, valid_until_enc)); EXITIF (52 != strlen (key_enc)); /* strlen(base32(char[32])) = 52 */ EXITIF (103 != strlen (sig_enc)); /* strlen(base32(char[64])) = 103 */ EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, &sig, sizeof (sig))); (void) memset (&sign_key_issue, 0, sizeof (sign_key_issue)); EXITIF (GNUNET_SYSERR == GNUNET_CRYPTO_eddsa_public_key_from_string (key_enc, 52, &sign_key_issue.signkey_pub)); sign_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_SIGNKEY); sign_key_issue.purpose.size = htonl (sizeof (sign_key_issue) - offsetof (struct TALER_MINT_SignKeyIssue, purpose)); sign_key_issue.master_pub = *master_key; sign_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); sign_key_issue.expire = GNUNET_TIME_absolute_hton (valid_until); EXITIF (GNUNET_OK != GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_SIGNKEY, &sign_key_issue.purpose, &sig, master_key)); sign_key = GNUNET_new (struct TALER_MINT_SigningPublicKey); sign_key->valid_from = valid_from; sign_key->valid_until = valid_until; sign_key->key = sign_key_issue.signkey_pub; *_sign_key = sign_key; return GNUNET_OK; EXITIF_exit: return GNUNET_SYSERR; } static int parse_json_amount (json_t *amount_obj, struct TALER_Amount *amt) { json_t *obj; const char *currency_str; int value; int fraction; EXITIF (NULL == (obj = json_object_get (amount_obj, "currency"))); EXITIF (NULL == (currency_str = json_string_value (obj))); EXITIF (NULL == (obj = json_object_get (amount_obj, "value"))); EXITIF (JSON_INTEGER != json_typeof (obj)); EXITIF (0 > (value = json_integer_value (obj))); EXITIF (NULL == (obj = json_object_get (amount_obj, "fraction"))); EXITIF (JSON_INTEGER != json_typeof (obj)); EXITIF (0 > (fraction = json_integer_value (obj))); (void) memset (amt->currency, 0, sizeof (amt->currency)); (void) strncpy (amt->currency, currency_str, sizeof (amt->currency) - 1); amt->value = (uint32_t) value; amt->fraction = (uint32_t) fraction; return GNUNET_OK; EXITIF_exit: return GNUNET_SYSERR; } static int parse_json_denomkey (struct TALER_MINT_DenomPublicKey **_denom_key, json_t *denom_key_obj, struct GNUNET_CRYPTO_EddsaPublicKey *master_key) { json_t *obj; const char *sig_enc; const char *deposit_valid_until_enc; const char *withdraw_valid_until_enc; const char *valid_from_enc; const char *key_enc; char *buf; size_t buf_size; struct TALER_MINT_DenomPublicKey *denom_key; struct GNUNET_TIME_Absolute valid_from; struct GNUNET_TIME_Absolute withdraw_valid_until; struct GNUNET_TIME_Absolute deposit_valid_until; struct TALER_Amount value; struct TALER_Amount fee_withdraw; struct TALER_Amount fee_deposit; struct TALER_Amount fee_refresh; struct TALER_MINT_DenomKeyIssue denom_key_issue; struct GNUNET_CRYPTO_EddsaSignature sig; EXITIF (JSON_OBJECT != json_typeof (denom_key_obj)); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "master_sig"))); EXITIF (NULL == (sig_enc = json_string_value (obj))); EXITIF (103 != strlen (sig_enc)); EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (sig_enc, 103, &sig, sizeof (sig))); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_deposit"))); EXITIF (NULL == (deposit_valid_until_enc = json_string_value (obj))); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_expire_withdraw"))); EXITIF (NULL == (withdraw_valid_until_enc = json_string_value (obj))); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "stamp_start"))); EXITIF (NULL == (valid_from_enc = json_string_value (obj))); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "denom_pub"))); EXITIF (NULL == (key_enc = json_string_value (obj))); EXITIF (GNUNET_SYSERR == parse_timestamp (&valid_from, valid_from_enc)); EXITIF (GNUNET_SYSERR == parse_timestamp (&withdraw_valid_until, withdraw_valid_until_enc)); EXITIF (GNUNET_SYSERR == parse_timestamp (&deposit_valid_until, deposit_valid_until_enc)); memset (&denom_key_issue, 0, sizeof (denom_key_issue)); buf_size = (strlen (key_enc) * 5) / 8; buf = GNUNET_malloc (buf_size); EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data (key_enc, strlen (key_enc), buf, buf_size)); denom_key_issue.denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (buf, buf_size); GNUNET_free (buf); EXITIF (NULL == denom_key_issue.denom_pub); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "value"))); EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &value)); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_withdraw"))); EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_withdraw)); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_deposit"))); EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_deposit)); EXITIF (NULL == (obj = json_object_get (denom_key_obj, "fee_refresh"))); EXITIF (GNUNET_SYSERR == parse_json_amount (obj, &fee_refresh)); denom_key_issue.purpose.purpose = htonl (TALER_SIGNATURE_MASTER_DENOM); denom_key_issue.purpose.size = htonl (sizeof (struct TALER_MINT_DenomKeyIssue) - offsetof (struct TALER_MINT_DenomKeyIssue, purpose)); denom_key_issue.master = *master_key; denom_key_issue.start = GNUNET_TIME_absolute_hton (valid_from); denom_key_issue.expire_withdraw = GNUNET_TIME_absolute_hton (withdraw_valid_until); denom_key_issue.expire_spend = GNUNET_TIME_absolute_hton (deposit_valid_until); denom_key_issue.value = TALER_amount_hton (value); denom_key_issue.fee_withdraw = TALER_amount_hton (fee_withdraw); denom_key_issue.fee_deposit = TALER_amount_hton (fee_deposit); denom_key_issue.fee_refresh = TALER_amount_hton (fee_refresh); EXITIF (GNUNET_SYSERR == GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MASTER_DENOM, &denom_key_issue.purpose, &sig, master_key)); denom_key = GNUNET_new (struct TALER_MINT_DenomPublicKey); denom_key->key = denom_key_issue.denom_pub; denom_key->valid_from = valid_from; denom_key->withdraw_valid_until = withdraw_valid_until; denom_key->deposit_valid_until = deposit_valid_until; denom_key->value = value; denom_key->fee_withdraw = fee_withdraw; denom_key->fee_deposit = fee_deposit; denom_key->fee_refresh = fee_refresh; *_denom_key = denom_key; return GNUNET_OK; EXITIF_exit: return GNUNET_SYSERR; } static int parse_response_keys_get (const char *in, size_t size, struct TALER_MINT_SigningPublicKey ***_sign_keys, unsigned int *_n_sign_keys, struct TALER_MINT_DenomPublicKey ***_denom_keys, unsigned int *_n_denom_keys) { json_t *resp_obj; struct TALER_MINT_DenomPublicKey **denom_keys; struct GNUNET_CRYPTO_EddsaPublicKey master_key; struct GNUNET_TIME_Absolute list_issue_date; struct TALER_MINT_SigningPublicKey **sign_keys; unsigned int n_denom_keys; unsigned int n_sign_keys; json_error_t error; unsigned int index; int OK; denom_keys = NULL; n_denom_keys = 0; sign_keys = NULL; n_sign_keys = 0; OK = 0; resp_obj = json_loadb (in, size, JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); if (NULL == resp_obj) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to parse received data as JSON object\n"); return GNUNET_SYSERR; } EXITIF (JSON_OBJECT != json_typeof (resp_obj)); { /* parse the master public key */ json_t *master_key_obj; const char *master_key_enc; EXITIF (NULL == (master_key_obj = json_object_get (resp_obj, "master_pub"))); EXITIF (NULL == (master_key_enc = json_string_value (master_key_obj))); EXITIF (52 != strlen (master_key_enc)); /* strlen(base32(char[32])) = 52 */ EXITIF (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (master_key_enc, 52, &master_key)); } { /* parse the issue date of the response */ json_t *list_issue_date_obj; const char *tstamp_enc; EXITIF (NULL == (list_issue_date_obj = json_object_get(resp_obj, "list_issue_date"))); EXITIF (NULL == (tstamp_enc = json_string_value (list_issue_date_obj))); EXITIF (GNUNET_SYSERR == parse_timestamp (&list_issue_date, tstamp_enc)); } { /* parse the signing keys */ json_t *sign_keys_array; json_t *sign_key_obj; EXITIF (NULL == (sign_keys_array = json_object_get (resp_obj, "signkeys"))); EXITIF (JSON_ARRAY != json_typeof (sign_keys_array)); EXITIF (0 == (n_sign_keys = json_array_size (sign_keys_array))); sign_keys = GNUNET_malloc (sizeof (struct TALER_MINT_SigningPublicKey *) * (n_sign_keys + 1)); index = 0; json_array_foreach (sign_keys_array, index, sign_key_obj) { EXITIF (GNUNET_SYSERR == parse_json_signkey (&sign_keys[index], sign_key_obj, &master_key)); } } { /* parse the denomination keys */ json_t *denom_keys_array; json_t *denom_key_obj; EXITIF (NULL == (denom_keys_array = json_object_get (resp_obj, "denoms"))); EXITIF (JSON_ARRAY != json_typeof (denom_keys_array)); EXITIF (0 == (n_denom_keys = json_array_size (denom_keys_array))); denom_keys = GNUNET_malloc (sizeof (struct TALER_MINT_DenomPublicKey *) * (n_denom_keys + 1)); index = 0; json_array_foreach (denom_keys_array, index, denom_key_obj) { EXITIF (GNUNET_SYSERR == parse_json_denomkey (&denom_keys[index], denom_key_obj, &master_key)); } } OK = 1; EXITIF_exit: json_decref (resp_obj); if (!OK) { if (NULL != sign_keys) { for (index=0; NULL != sign_keys[index]; index++) GNUNET_free_non_null (sign_keys[index]); GNUNET_free (sign_keys); } if (NULL != denom_keys) { for (index=0; NULL != denom_keys[index]; index++) GNUNET_free_non_null (denom_keys[index]); GNUNET_free (denom_keys); } return GNUNET_SYSERR; } *_sign_keys = sign_keys; *_n_sign_keys = n_sign_keys; *_denom_keys = denom_keys; *_n_denom_keys = n_denom_keys; return GNUNET_OK; } int parse_deposit_response (void *buf, size_t size, int *r_status, json_t **r_obj) { json_t *obj; const char *status_str; json_error_t error; status_str = NULL; obj = NULL; obj = json_loadb (buf, size, JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, &error); if (NULL == obj) { JSON_WARN (error); return GNUNET_SYSERR; } EXITIF (-1 == json_unpack (obj, "{s:s}", "status", &status_str)); LOG_DEBUG ("Received deposit response: %s from mint\n", status_str); if (0 == strcmp ("DEPOSIT_OK", status_str)) *r_status = 1; else if (0 == strcmp ("DEPOSIT_QUEUED", status_str)) *r_status = 2; else *r_status = 0; *r_obj = obj; return GNUNET_OK; EXITIF_exit: json_decref (obj); return GNUNET_SYSERR; } #undef EXITIF static void mint_connect (struct TALER_MINT_Handle *mint) { struct TALER_MINT_Context *ctx = mint->ctx; GNUNET_assert (0 == mint->connected); GNUNET_assert (CURLM_OK == curl_multi_add_handle (ctx->multi, mint->curl)); mint->connected = GNUNET_YES; } static void mint_disconnect (struct TALER_MINT_Handle *mint) { struct TALER_MINT_Context *ctx = mint->ctx; GNUNET_assert (GNUNET_YES == mint->connected); GNUNET_break (CURLM_OK == curl_multi_remove_handle (ctx->multi, mint->curl)); mint->connected = GNUNET_NO; GNUNET_free_non_null (mint->buf); mint->buf = NULL; mint->buf_size = 0; mint->req_type = REQUEST_TYPE_NONE; mint->req.none = NULL; } static void cleanup_keys_get (struct TALER_MINT_KeysGetHandle *gh) { GNUNET_free (gh->url); GNUNET_free (gh); } static void cleanup_deposit (struct TALER_MINT_DepositHandle *dh) { curl_slist_free_all (dh->headers); GNUNET_free_non_null (dh->json_enc); GNUNET_free (dh->url); GNUNET_free (dh); } static void request_failed (struct TALER_MINT_Handle *mint, long resp_code) { switch (mint->req_type) { case REQUEST_TYPE_NONE: GNUNET_assert (0); break; case REQUEST_TYPE_KEYSGET: { struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; TALER_MINT_ContinuationCallback cont_cb; void *cont_cls; GNUNET_assert (NULL != gh); cont_cb = gh->cont_cb; cont_cls = gh->cont_cls; cleanup_keys_get (gh); mint_disconnect (mint); cont_cb (cont_cls, mint->emsg); } break; case REQUEST_TYPE_DEPOSIT: { struct TALER_MINT_DepositHandle *dh = mint->req.deposit; TALER_MINT_DepositResultCallback cb = dh->cb; void *cls = dh->cls; GNUNET_assert (NULL != dh); cleanup_deposit (dh); mint_disconnect (mint); cb (cls, 0, NULL, mint->emsg); } break; } } static void request_succeeded (struct TALER_MINT_Handle *mint, long resp_code) { char *emsg; emsg = NULL; switch (mint->req_type) { case REQUEST_TYPE_NONE: GNUNET_assert (0); break; case REQUEST_TYPE_KEYSGET: { struct TALER_MINT_KeysGetHandle *gh = mint->req.keys_get; TALER_MINT_ContinuationCallback cont_cb; void *cont_cls; struct TALER_MINT_SigningPublicKey **sign_keys; struct TALER_MINT_DenomPublicKey **denom_keys; unsigned int n_sign_keys; unsigned int n_denom_keys; GNUNET_assert (NULL != gh); cont_cb = gh->cont_cb; cont_cls = gh->cont_cls; if (200 == resp_code) { /* parse JSON object from the mint->buf which is of size mint->buf_size */ if (GNUNET_OK == parse_response_keys_get (mint->buf, mint->buf_size, &sign_keys, &n_sign_keys, &denom_keys, &n_denom_keys)) gh->cb (gh->cls, sign_keys, denom_keys); else emsg = GNUNET_strdup ("Error parsing response"); } else GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); cleanup_keys_get (gh); mint_disconnect (mint); cont_cb (cont_cls, emsg); } break; case REQUEST_TYPE_DEPOSIT: { struct TALER_MINT_DepositHandle *dh = mint->req.deposit; TALER_MINT_DepositResultCallback cb; void *cls; int status; json_t *obj; GNUNET_assert (NULL != dh); obj = NULL; cb = dh->cb; cls = dh->cls; status = 0; if (200 == resp_code) { /* parse JSON object from the mint->buf which is of size mint->buf_size */ if (GNUNET_OK != parse_deposit_response (mint->buf, mint->buf_size, &status, &obj)) emsg = GNUNET_strdup ("Error parsing response"); } else GNUNET_asprintf (&emsg, "Failed with response code: %ld", resp_code); cleanup_deposit (dh); mint_disconnect (mint); cb (cls, status, obj, emsg); } break; } GNUNET_free_non_null (emsg); } static void do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc); static void perform (struct TALER_MINT_Context *ctx) { fd_set fd_rs; fd_set fd_ws; struct GNUNET_NETWORK_FDSet rs; struct GNUNET_NETWORK_FDSet ws; CURLMsg *cmsg; struct TALER_MINT_Handle *mint; long timeout; long resp_code; static unsigned int n_old; int n_running; int n_completed; int max_fd; n_completed = 0; curl_multi_perform (ctx->multi, &n_running); GNUNET_assert (0 <= n_running); if ((0 == n_running) || (n_running < n_old)) { /* some requests were completed -- handle them */ while (NULL != (cmsg = curl_multi_info_read (ctx->multi, &n_completed))) { GNUNET_break (CURLMSG_DONE == cmsg->msg); /* curl only has CURLMSG_DONE */ GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, CURLINFO_PRIVATE, (char *) &mint)); GNUNET_assert (CURLE_OK == curl_easy_getinfo (cmsg->easy_handle, CURLINFO_RESPONSE_CODE, &resp_code)); GNUNET_assert (ctx == mint->ctx); /* did we get the correct one? */ if (CURLE_OK == cmsg->data.result) request_succeeded (mint, resp_code); else request_failed (mint, resp_code); } } n_old = n_running; /* reschedule perform() */ if (0 != n_old) { FD_ZERO (&fd_rs); FD_ZERO (&fd_ws); GNUNET_assert (CURLM_OK == curl_multi_fdset (ctx->multi, &fd_rs, &fd_ws, NULL, &max_fd)); if (-1 == max_fd) { ctx->perform_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 100), &do_perform, ctx); return; } GNUNET_assert (CURLM_OK == curl_multi_timeout (ctx->multi, &timeout)); if (-1 == timeout) { timeout = 1000 * 60 * 5; } GNUNET_NETWORK_fdset_zero (&rs); GNUNET_NETWORK_fdset_zero (&ws); GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1); GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1); ctx->perform_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_KEEP, GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, timeout), &rs, &ws, &do_perform, ctx); } } static void do_perform (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { struct TALER_MINT_Context *ctx = cls; GNUNET_assert (NULL != ctx->perform_task); ctx->perform_task = NULL; perform (ctx); } static void perform_now (struct TALER_MINT_Context *ctx) { if (NULL != ctx->perform_task) { GNUNET_SCHEDULER_cancel (ctx->perform_task); ctx->perform_task = NULL; } ctx->perform_task = GNUNET_SCHEDULER_add_now (&do_perform, ctx); } /* This function gets called by libcurl as soon as there is data received that */ /* needs to be saved. The size of the data pointed to by ptr is size */ /* multiplied with nmemb, it will not be zero terminated. Return the number */ /* of bytes actually taken care of. If that amount differs from the amount passed */ /* to your function, it'll signal an error to the library. This will abort the */ /* transfer and return CURLE_WRITE_ERROR. */ /* From 7.18.0, the function can return CURL_WRITEFUNC_PAUSE which then will */ /* cause writing to this connection to become paused. See */ /* curl_easy_pause(3) for further details. */ /* This function may be called with zero bytes data if the transferred file is */ /* empty. */ /* Set this option to NULL to get the internal default function. The internal */ /* default function will write the data to the FILE * given with */ /* CURLOPT_WRITEDATA. */ /* Set the userdata argument with the CURLOPT_WRITEDATA option. */ /* The callback function will be passed as much data as possible in all invokes, */ /* but you cannot possibly make any assumptions. It may be one byte, it may be */ /* thousands. The maximum amount of body data that can be passed to the write */ /* callback is defined in the curl.h header file: CURL_MAX_WRITE_SIZE (the usual */ /* default is 16K). If you however have CURLOPT_HEADER set, which sends */ /* header data to the write callback, you can get up to */ /* CURL_MAX_HTTP_HEADER bytes of header data passed into it. This usually */ /* means 100K. */ static size_t download (char *bufptr, size_t size, size_t nitems, void *cls) { struct TALER_MINT_Handle *mint = cls; size_t msize; void *buf; if (0 == size * nitems) { /* file is empty */ return 0; } msize = size * nitems; mint->buf = GNUNET_realloc (mint->buf, mint->buf_size + msize); buf = mint->buf + mint->buf_size; memcpy (buf, bufptr, msize); mint->buf_size += msize; return msize; } /** * Initialise a connection to the mint. * * @param ctx the context * @param hostname the hostname of the mint * @param port the point where the mint's HTTP service is running. * @param mint_key the public key of the mint. This is used to verify the * responses of the mint. * @return the mint handle; NULL upon error */ struct TALER_MINT_Handle * TALER_MINT_connect (struct TALER_MINT_Context *ctx, const char *hostname, uint16_t port, struct GNUNET_CRYPTO_EddsaPublicKey *mint_key) { struct TALER_MINT_Handle *mint; mint = GNUNET_new (struct TALER_MINT_Handle); mint->ctx = ctx; mint->hostname = GNUNET_strdup (hostname); mint->port = (0 != port) ? port : 80; mint->curl = curl_easy_init (); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_SHARE, ctx->share)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_ERRORBUFFER, mint->emsg)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_WRITEFUNCTION, &download)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_WRITEDATA, mint)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_PRIVATE, mint)); return mint; } /** * Disconnect from the mint * * @param mint the mint handle */ void TALER_MINT_disconnect (struct TALER_MINT_Handle *mint) { if (GNUNET_YES == mint->connected) mint_disconnect (mint); curl_easy_cleanup (mint->curl); GNUNET_free (mint->hostname); GNUNET_free (mint); } /** * Get the signing and denomination key of the mint. * * @param mint handle to the mint * @param cb the callback to call with each retrieved denomination key * @param cls closure for the above callback * @param cont_cb the callback to call after completing this asynchronous call * @param cont_cls the closure for the continuation callback * @return a handle to this asynchronous call; NULL upon eror */ struct TALER_MINT_KeysGetHandle * TALER_MINT_keys_get (struct TALER_MINT_Handle *mint, TALER_MINT_KeysGetCallback cb, void *cls, TALER_MINT_ContinuationCallback cont_cb, void *cont_cls) { struct TALER_MINT_KeysGetHandle *gh; GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); gh = GNUNET_new (struct TALER_MINT_KeysGetHandle); gh->mint = mint; mint->req_type = REQUEST_TYPE_KEYSGET; mint->req.keys_get = gh; gh->cb = cb; gh->cls = cls; gh->cont_cb = cont_cb; gh->cont_cls = cont_cls; GNUNET_asprintf (&gh->url, "http://%s:%hu/keys", mint->hostname, mint->port); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_URL, gh->url)); if (GNUNET_NO == mint->connected) mint_connect (mint); perform_now (mint->ctx); return gh; } /** * Cancel the asynchronous call initiated by TALER_MINT_keys_get(). This * should not be called if either of the @a TALER_MINT_KeysGetCallback or * @a TALER_MINT_ContinuationCallback passed to TALER_MINT_keys_get() have * been called. * * @param get the handle for retrieving the keys */ void TALER_MINT_keys_get_cancel (struct TALER_MINT_KeysGetHandle *get) { struct TALER_MINT_Handle *mint = get->mint; mint_disconnect (mint); cleanup_keys_get (get); } /** * Submit a deposit permission to the mint and get the mint's response * * @param mint the mint handle * @param cb the callback to call when a reply for this request is available * @param cls closure for the above callback * @param deposit_obj the deposit permission received from the customer along * with the wireformat JSON object * @return a handle for this request; NULL if the JSON object could not be * parsed or is of incorrect format or any other error. In this case, * the callback is not called. */ struct TALER_MINT_DepositHandle * TALER_MINT_deposit_submit_json (struct TALER_MINT_Handle *mint, TALER_MINT_DepositResultCallback cb, void *cls, json_t *deposit_obj) { struct TALER_MINT_DepositHandle *dh; GNUNET_assert (REQUEST_TYPE_NONE == mint->req_type); dh = GNUNET_new (struct TALER_MINT_DepositHandle); dh->mint = mint; mint->req_type = REQUEST_TYPE_DEPOSIT; mint->req.deposit = dh; dh->cb = cb; dh->cls = cls; GNUNET_asprintf (&dh->url, "http://%s:%hu/deposit", mint->hostname, mint->port); GNUNET_assert (NULL != (dh->json_enc = json_dumps (deposit_obj, JSON_COMPACT))); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_URL, dh->url)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDS, dh->json_enc)); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_POSTFIELDSIZE, strlen (dh->json_enc))); GNUNET_assert (NULL != (dh->headers = curl_slist_append (dh->headers, "Content-Type: application/json"))); GNUNET_assert (CURLE_OK == curl_easy_setopt (mint->curl, CURLOPT_HTTPHEADER, dh->headers)); if (GNUNET_NO == mint->connected) mint_connect (mint); perform_now (mint->ctx); return dh; } /** * Cancel a deposit permission request. This function cannot be used on a * request handle if a response is already served for it. * * @param the deposit permission request handle */ void TALER_MINT_deposit_submit_cancel (struct TALER_MINT_DepositHandle *deposit) { struct TALER_MINT_Handle *mint = deposit->mint; mint_disconnect (mint); cleanup_deposit (deposit); } /** * Initialise this library. This function should be called before using any of * the following functions. * * @return library context */ struct TALER_MINT_Context * TALER_MINT_init () { struct TALER_MINT_Context *ctx; CURLM *multi; CURLSH *share; if (fail) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "cURL was not initialised properly\n"); return NULL; } if (NULL == (multi = curl_multi_init ())) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL multi handle\n"); return NULL; } if (NULL == (share = curl_share_init ())) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot create a cURL share handle\n"); return NULL; } ctx = GNUNET_new (struct TALER_MINT_Context); ctx->multi = multi; ctx->share = share; return ctx; } /** * Cleanup library initialisation resources. This function should be called * after using this library to cleanup the resources occupied during library's * initialisation. * * @param ctx the library context */ void TALER_MINT_cleanup (struct TALER_MINT_Context *ctx) { curl_share_cleanup (ctx->share); curl_multi_cleanup (ctx->multi); if (NULL != ctx->perform_task) { GNUNET_break (0); /* investigate why this happens */ GNUNET_SCHEDULER_cancel (ctx->perform_task); } GNUNET_free (ctx); } __attribute__ ((constructor)) void TALER_MINT_constructor__ (void) { CURLcode ret; if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT))) { CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, "curl_global_init", ret); fail = 1; } } __attribute__ ((destructor)) void TALER_MINT_destructor__ (void) { if (fail) return; curl_global_cleanup (); }