diff options
author | Özgür Kesim <oec-taler@kesim.org> | 2023-07-14 09:25:43 +0200 |
---|---|---|
committer | Özgür Kesim <oec-taler@kesim.org> | 2023-07-14 09:25:43 +0200 |
commit | 34f44ccb279c655b5b948a42d576bac371d684a7 (patch) | |
tree | 274f1118419190442bc78a794b70ebdec8266c59 | |
parent | c8250cae25573a74e5fd536ebc007d639fd85957 (diff) | |
parent | de24415e17fbf32c8a145cf8e5dadf2070c29e08 (diff) |
Merge branch 'master' into age-withdraw
23 files changed, 1329 insertions, 1515 deletions
diff --git a/doc/prebuilt b/doc/prebuilt -Subproject fa4729db5637c82d5fc6f5bb7021f6c350c8c5a +Subproject 66e99d09d4351bb6e6c5fd442f14ec7cf1363a8 diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am index ba74a10f5..732f8dba7 100644 --- a/src/exchange/Makefile.am +++ b/src/exchange/Makefile.am @@ -184,7 +184,6 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \ taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h \ - taler-exchange-httpd_wire.c taler-exchange-httpd_wire.h \ taler-exchange-httpd_withdraw.c taler-exchange-httpd_withdraw.h taler_exchange_httpd_LDADD = \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c index dfdf3546d..7d5992935 100644 --- a/src/exchange/taler-exchange-httpd.c +++ b/src/exchange/taler-exchange-httpd.c @@ -70,7 +70,6 @@ #include "taler-exchange-httpd_reserves_status.h" #include "taler-exchange-httpd_terms.h" #include "taler-exchange-httpd_transfers_get.h" -#include "taler-exchange-httpd_wire.h" #include "taler-exchange-httpd_withdraw.h" #include "taler_exchangedb_lib.h" #include "taler_exchangedb_plugin.h" diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h index 95380e0a8..596765b27 100644 --- a/src/exchange/taler-exchange-httpd_config.h +++ b/src/exchange/taler-exchange-httpd_config.h @@ -41,7 +41,7 @@ * * Returned via both /config and /keys endpoints. */ -#define EXCHANGE_PROTOCOL_VERSION "15:0:0" +#define EXCHANGE_PROTOCOL_VERSION "16:0:1" /** diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c index 265b71fa3..ff5d1f410 100644 --- a/src/exchange/taler-exchange-httpd_keys.c +++ b/src/exchange/taler-exchange-httpd_keys.c @@ -388,6 +388,117 @@ struct SuspendedKeysRequests struct GNUNET_TIME_Absolute timeout; }; + +/** + * Information we track about wire fees. + */ +struct WireFeeSet +{ + + /** + * Kept in a DLL. + */ + struct WireFeeSet *next; + + /** + * Kept in a DLL. + */ + struct WireFeeSet *prev; + + /** + * Actual fees. + */ + struct TALER_WireFeeSet fees; + + /** + * Start date of fee validity (inclusive). + */ + struct GNUNET_TIME_Timestamp start_date; + + /** + * End date of fee validity (exclusive). + */ + struct GNUNET_TIME_Timestamp end_date; + + /** + * Wire method the fees apply to. + */ + char *method; +}; + + +/** + * State we keep per thread to cache the /wire response. + */ +struct WireStateHandle +{ + /** + * Cached reply for /wire response. + */ + struct MHD_Response *wire_reply; + + /** + * JSON reply for /wire response. + */ + json_t *json_reply; + + /** + * ETag for this response (if any). + */ + char *etag; + + /** + * head of DLL of wire fees. + */ + struct WireFeeSet *wfs_head; + + /** + * Tail of DLL of wire fees. + */ + struct WireFeeSet *wfs_tail; + + /** + * Earliest timestamp of all the wire methods when we have no more fees. + */ + struct GNUNET_TIME_Absolute cache_expiration; + + /** + * @e cache_expiration time, formatted. + */ + char dat[128]; + + /** + * For which (global) wire_generation was this data structure created? + * Used to check when we are outdated and need to be re-generated. + */ + uint64_t wire_generation; + + /** + * HTTP status to return with this response. + */ + unsigned int http_status; + +}; + + +/** + * Stores the latest generation of our wire response. + */ +static struct WireStateHandle *wire_state; + +/** + * Handler listening for wire updates by other exchange + * services. + */ +static struct GNUNET_DB_EventHandler *wire_eh; + +/** + * Counter incremented whenever we have a reason to re-build the #wire_state + * because something external changed. + */ +static uint64_t wire_generation; + + /** * Stores the latest generation of our key state. */ @@ -466,6 +577,545 @@ static bool terminating; /** + * Free memory associated with @a wsh + * + * @param[in] wsh wire state to destroy + */ +static void +destroy_wire_state (struct WireStateHandle *wsh) +{ + struct WireFeeSet *wfs; + + while (NULL != (wfs = wsh->wfs_head)) + { + GNUNET_CONTAINER_DLL_remove (wsh->wfs_head, + wsh->wfs_tail, + wfs); + GNUNET_free (wfs->method); + GNUNET_free (wfs); + } + MHD_destroy_response (wsh->wire_reply); + json_decref (wsh->json_reply); + GNUNET_free (wsh->etag); + GNUNET_free (wsh); +} + + +/** + * Function called whenever another exchange process has updated + * the wire data in the database. + * + * @param cls NULL + * @param extra unused + * @param extra_size number of bytes in @a extra unused + */ +static void +wire_update_event_cb (void *cls, + const void *extra, + size_t extra_size) +{ + (void) cls; + (void) extra; + (void) extra_size; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Received /wire update event\n"); + TEH_check_invariants (); + wire_generation++; + key_generation++; + TEH_resume_keys_requests (false); +} + + +enum GNUNET_GenericReturnValue +TEH_wire_init () +{ + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED), + }; + + wire_eh = TEH_plugin->event_listen (TEH_plugin->cls, + GNUNET_TIME_UNIT_FOREVER_REL, + &es, + &wire_update_event_cb, + NULL); + if (NULL == wire_eh) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +void +TEH_wire_done () +{ + if (NULL != wire_state) + { + destroy_wire_state (wire_state); + wire_state = NULL; + } + if (NULL != wire_eh) + { + TEH_plugin->event_listen_cancel (TEH_plugin->cls, + wire_eh); + wire_eh = NULL; + } +} + + +/** + * Add information about a wire account to @a cls. + * + * @param cls a `json_t *` object to expand with wire account details + * @param payto_uri the exchange bank account URI to add + * @param conversion_url URL of a conversion service, NULL if there is no conversion + * @param debit_restrictions JSON array with debit restrictions on the account + * @param credit_restrictions JSON array with credit restrictions on the account + * @param master_sig master key signature affirming that this is a bank + * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) + */ +static void +add_wire_account (void *cls, + const char *payto_uri, + const char *conversion_url, + const json_t *debit_restrictions, + const json_t *credit_restrictions, + const struct TALER_MasterSignatureP *master_sig) +{ + json_t *a = cls; + + if (0 != + json_array_append_new ( + a, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + conversion_url)), + GNUNET_JSON_pack_array_incref ("debit_restrictions", + (json_t *) debit_restrictions), + GNUNET_JSON_pack_array_incref ("credit_restrictions", + (json_t *) credit_restrictions), + GNUNET_JSON_pack_data_auto ("master_sig", + master_sig)))) + { + GNUNET_break (0); /* out of memory!? */ + return; + } +} + + +/** + * Closure for #add_wire_fee(). + */ +struct AddContext +{ + /** + * Wire method the fees are for. + */ + char *wire_method; + + /** + * Wire state we are building. + */ + struct WireStateHandle *wsh; + + /** + * Array to append the fee to. + */ + json_t *a; + + /** + * Context we hash "everything" we add into. This is used + * to compute the etag. Technically, we only hash the + * master_sigs, as they imply the rest. + */ + struct GNUNET_HashContext *hc; + + /** + * Set to the maximum end-date seen. + */ + struct GNUNET_TIME_Absolute max_seen; +}; + + +/** + * Add information about a wire account to @a cls. + * + * @param cls a `struct AddContext` + * @param fees the wire fees we charge + * @param start_date from when are these fees valid (start date) + * @param end_date until when are these fees valid (end date, exclusive) + * @param master_sig master key signature affirming that this is the correct + * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES) + */ +static void +add_wire_fee (void *cls, + const struct TALER_WireFeeSet *fees, + struct GNUNET_TIME_Timestamp start_date, + struct GNUNET_TIME_Timestamp end_date, + const struct TALER_MasterSignatureP *master_sig) +{ + struct AddContext *ac = cls; + struct WireFeeSet *wfs; + + GNUNET_CRYPTO_hash_context_read (ac->hc, + master_sig, + sizeof (*master_sig)); + ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen, + end_date.abs_time); + wfs = GNUNET_new (struct WireFeeSet); + wfs->start_date = start_date; + wfs->end_date = end_date; + wfs->fees = *fees; + wfs->method = GNUNET_strdup (ac->wire_method); + GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head, + ac->wsh->wfs_tail, + wfs); + if (0 != + json_array_append_new ( + ac->a, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("wire_fee", + &fees->wire), + TALER_JSON_pack_amount ("closing_fee", + &fees->closing), + GNUNET_JSON_pack_timestamp ("start_date", + start_date), + GNUNET_JSON_pack_timestamp ("end_date", + end_date), + GNUNET_JSON_pack_data_auto ("sig", + master_sig)))) + { + GNUNET_break (0); /* out of memory!? */ + return; + } +} + + +/** + * Create the /wire response from our database state. + * + * @return NULL on error + */ +static struct WireStateHandle * +build_wire_state (void) +{ + json_t *wire_accounts_array; + json_t *wire_fee_object; + uint64_t wg = wire_generation; /* must be obtained FIRST */ + enum GNUNET_DB_QueryStatus qs; + struct WireStateHandle *wsh; + struct GNUNET_HashContext *hc; + json_t *wads; + + wsh = GNUNET_new (struct WireStateHandle); + wsh->wire_generation = wg; + wire_accounts_array = json_array (); + GNUNET_assert (NULL != wire_accounts_array); + qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls, + &add_wire_account, + wire_accounts_array); + if (0 > qs) + { + GNUNET_break (0); + json_decref (wire_accounts_array); + wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + wsh->wire_reply + = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_wire_accounts"); + return wsh; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Build /wire data with %u accounts\n", + (unsigned int) json_array_size (wire_accounts_array)); + wire_fee_object = json_object (); + GNUNET_assert (NULL != wire_fee_object); + wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS; + hc = GNUNET_CRYPTO_hash_context_start (); + { + json_t *account; + size_t index; + + json_array_foreach (wire_accounts_array, index, account) { + char *wire_method; + const char *payto_uri = json_string_value (json_object_get (account, + "payto_uri")); + + GNUNET_assert (NULL != payto_uri); + wire_method = TALER_payto_get_method (payto_uri); + if (NULL == wire_method) + { + wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + wsh->wire_reply + = TALER_MHD_make_error ( + TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED, + payto_uri); + json_decref (wire_accounts_array); + json_decref (wire_fee_object); + GNUNET_CRYPTO_hash_context_abort (hc); + return wsh; + } + if (NULL == json_object_get (wire_fee_object, + wire_method)) + { + struct AddContext ac = { + .wire_method = wire_method, + .wsh = wsh, + .a = json_array (), + .hc = hc + }; + + GNUNET_assert (NULL != ac.a); + qs = TEH_plugin->get_wire_fees (TEH_plugin->cls, + wire_method, + &add_wire_fee, + &ac); + if (0 > qs) + { + GNUNET_break (0); + json_decref (ac.a); + json_decref (wire_fee_object); + json_decref (wire_accounts_array); + GNUNET_free (wire_method); + wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + wsh->wire_reply + = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, + "get_wire_fees"); + GNUNET_CRYPTO_hash_context_abort (hc); + return wsh; + } + if (0 == json_array_size (ac.a)) + { + json_decref (ac.a); + json_decref (wire_accounts_array); + json_decref (wire_fee_object); + wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + wsh->wire_reply + = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, + wire_method); + GNUNET_free (wire_method); + GNUNET_CRYPTO_hash_context_abort (hc); + return wsh; + } + wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen, + wsh->cache_expiration); + GNUNET_assert (0 == + json_object_set_new (wire_fee_object, + wire_method, + ac.a)); + } + GNUNET_free (wire_method); + } + } + + wads = json_array (); /* #7271 */ + GNUNET_assert (NULL != wads); + wsh->json_reply = GNUNET_JSON_PACK ( + GNUNET_JSON_pack_array_incref ("accounts", + wire_accounts_array), + GNUNET_JSON_pack_array_incref ("wads", + wads), + GNUNET_JSON_pack_object_incref ("fees", + wire_fee_object)); + wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK ( + GNUNET_JSON_pack_array_steal ("accounts", + wire_accounts_array), + GNUNET_JSON_pack_array_steal ("wads", + wads), + GNUNET_JSON_pack_object_steal ("fees", + wire_fee_object), + GNUNET_JSON_pack_data_auto ("master_public_key", + &TEH_master_public_key)); + { + struct GNUNET_TIME_Timestamp m; + + m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration); + TALER_MHD_get_date_string (m.abs_time, + wsh->dat); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Setting 'Expires' header for '/wire' to '%s'\n", + wsh->dat); + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_EXPIRES, + wsh->dat)); + } + /* Set cache control headers: our response varies depending on these headers */ + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_VARY, + MHD_HTTP_HEADER_ACCEPT_ENCODING)); + /* Information is always public, revalidate after 1 day */ + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=86400")); + + { + struct GNUNET_HashCode h; + char etag[sizeof (h) * 2]; + char *end; + + GNUNET_CRYPTO_hash_context_finish (hc, + &h); + end = GNUNET_STRINGS_data_to_string (&h, + sizeof (h), + etag, + sizeof (etag)); + *end = '\0'; + wsh->etag = GNUNET_strdup (etag); + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_ETAG, + etag)); + } + wsh->http_status = MHD_HTTP_OK; + return wsh; +} + + +void +TEH_wire_update_state (void) +{ + struct GNUNET_DB_EventHeaderP es = { + .size = htons (sizeof (es)), + .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED), + }; + + TEH_plugin->event_notify (TEH_plugin->cls, + &es, + NULL, + 0); + wire_generation++; + key_generation++; +} + + +/** + * Return the current key state for this thread. Possibly + * re-builds the key state if we have reason to believe + * that something changed. + * + * @return NULL on error + */ +struct WireStateHandle * +get_wire_state (void) +{ + struct WireStateHandle *old_wsh; + + old_wsh = wire_state; + if ( (NULL == old_wsh) || + (old_wsh->wire_generation < wire_generation) ) + { + struct WireStateHandle *wsh; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Rebuilding /wire, generation upgrade from %llu to %llu\n", + (unsigned long long) (NULL == old_wsh) ? 0LL : + old_wsh->wire_generation, + (unsigned long long) wire_generation); + TEH_check_invariants (); + wsh = build_wire_state (); + wire_state = wsh; + if (NULL != old_wsh) + destroy_wire_state (old_wsh); + TEH_check_invariants (); + return wsh; + } + return old_wsh; +} + + +MHD_RESULT +TEH_handler_wire (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct WireStateHandle *wsh; + + (void) args; + wsh = get_wire_state (); + if (NULL == wsh) + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, + NULL); + { + const char *etag; + + etag = MHD_lookup_connection_value (rc->connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_IF_NONE_MATCH); + if ( (NULL != etag) && + (MHD_HTTP_OK == wsh->http_status) && + (NULL != wsh->etag) && + (0 == strcmp (etag, + wsh->etag)) ) + { + MHD_RESULT ret; + struct MHD_Response *resp; + + resp = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + TALER_MHD_add_global_headers (resp); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_EXPIRES, + wsh->dat)); + GNUNET_break (MHD_YES == + MHD_add_response_header (resp, + MHD_HTTP_HEADER_ETAG, + wsh->etag)); + ret = MHD_queue_response (rc->connection, + MHD_HTTP_NOT_MODIFIED, + resp); + GNUNET_break (MHD_YES == ret); + MHD_destroy_response (resp); + return ret; + } + } + return MHD_queue_response (rc->connection, + wsh->http_status, + wsh->wire_reply); +} + + +const struct TALER_WireFeeSet * +TEH_wire_fees_by_time ( + struct GNUNET_TIME_Timestamp ts, + const char *method) +{ + struct WireStateHandle *wsh = get_wire_state (); + + for (struct WireFeeSet *wfs = wsh->wfs_head; + NULL != wfs; + wfs = wfs->next) + { + if (0 != strcmp (method, + wfs->method)) + continue; + if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date, + >, + ts)) || + (GNUNET_TIME_timestamp_cmp (ts, + >=, + wfs->end_date)) ) + continue; + return &wfs->fees; + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No wire fees for method `%s' at %s configured\n", + method, + GNUNET_TIME_timestamp2s (ts)); + return NULL; +} + + +/** * Function called to forcefully resume suspended keys requests. * * @param cls unused, NULL @@ -1673,6 +2323,7 @@ add_denom_key_cb (void *cls, */ static enum GNUNET_GenericReturnValue setup_general_response_headers (struct TEH_KeyStateHandle *ksh, + struct WireStateHandle *wsh, struct MHD_Response *response) { char dat[128]; @@ -1692,12 +2343,17 @@ setup_general_response_headers (struct TEH_KeyStateHandle *ksh, { struct GNUNET_TIME_Relative r; struct GNUNET_TIME_Absolute a; + struct GNUNET_TIME_Timestamp km; struct GNUNET_TIME_Timestamp m; + struct GNUNET_TIME_Timestamp we; r = GNUNET_TIME_relative_min (TEH_max_keys_caching, ksh->rekey_frequency); a = GNUNET_TIME_relative_to_absolute (r); - m = GNUNET_TIME_absolute_to_timestamp (a); + km = GNUNET_TIME_absolute_to_timestamp (a); + we = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration); + m = GNUNET_TIME_timestamp_min (we, + km); TALER_MHD_get_date_string (m.abs_time, dat); GNUNET_log (GNUNET_ERROR_TYPE_INFO, @@ -1777,8 +2433,10 @@ create_krd (struct TEH_KeyStateHandle *ksh, struct TALER_ExchangeSignatureP exchange_sig; struct TALER_ExchangePublicKeyP grouped_exchange_pub; struct TALER_ExchangeSignatureP grouped_exchange_sig; + struct WireStateHandle *wsh; json_t *keys; + wsh = get_wire_state (); GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( last_cherry_pick_date.abs_time)); GNUNET_assert (NULL != signkeys); @@ -1850,6 +2508,12 @@ create_krd (struct TEH_KeyStateHandle *ksh, ksh->signature_expires); } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Build /keys data with %u wire accounts\n", + (unsigned int) json_array_size ( + json_object_get (wsh->json_reply, + "accounts"))); + keys = GNUNET_JSON_PACK ( GNUNET_JSON_pack_string ("version", EXCHANGE_PROTOCOL_VERSION), @@ -1874,6 +2538,15 @@ create_krd (struct TEH_KeyStateHandle *ksh, recoup), GNUNET_JSON_pack_array_incref ("denoms", denoms), + GNUNET_JSON_pack_array_incref ("wads", + json_object_get (wsh->json_reply, + "wads")), + GNUNET_JSON_pack_array_incref ("accounts", + json_object_get (wsh->json_reply, + "accounts")), + GNUNET_JSON_pack_object_incref ("wire_fees", + json_object_get (wsh->json_reply, + "fees")), GNUNET_JSON_pack_array_incref ("denominations", grouped_denominations), GNUNET_JSON_pack_array_incref ("auditors", @@ -2010,6 +2683,7 @@ create_krd (struct TEH_KeyStateHandle *ksh, GNUNET_assert (NULL != krd.response_uncompressed); GNUNET_assert (GNUNET_OK == setup_general_response_headers (ksh, + wsh, krd.response_uncompressed)); GNUNET_break (MHD_YES == MHD_add_response_header (krd.response_uncompressed, @@ -2032,7 +2706,18 @@ create_krd (struct TEH_KeyStateHandle *ksh, "deflate")) ); GNUNET_assert (GNUNET_OK == setup_general_response_headers (ksh, + wsh, krd.response_compressed)); + /* Set cache control headers: our response varies depending on these headers */ + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_VARY, + MHD_HTTP_HEADER_ACCEPT_ENCODING)); + /* Information is always public, revalidate after 1 day */ + GNUNET_break (MHD_YES == + MHD_add_response_header (wsh->wire_reply, + MHD_HTTP_HEADER_CACHE_CONTROL, + "public,max-age=86400")); GNUNET_break (MHD_YES == MHD_add_response_header (krd.response_compressed, MHD_HTTP_HEADER_ETAG, @@ -2290,8 +2975,18 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) /* Now that we have found/created the right group, add the denomination to the list */ { + struct HelperDenomination *hd; struct GNUNET_JSON_PackSpec key_spec; - + bool private_key_lost; + + hd = GNUNET_CONTAINER_multihashmap_get (ksh->helpers->denom_keys, + &dk->h_denom_pub.hash); + private_key_lost + = (NULL == hd) || + GNUNET_TIME_absolute_is_past ( + GNUNET_TIME_absolute_add ( + hd->start_time.abs_time, + hd->validity_duration)); switch (meta.cipher) { case TALER_DENOMINATION_RSA: @@ -2314,6 +3009,12 @@ finish_keys_response (struct TEH_KeyStateHandle *ksh) entry = GNUNET_JSON_PACK ( GNUNET_JSON_pack_data_auto ("master_sig", &dk->master_sig), + GNUNET_JSON_pack_allow_null ( + private_key_lost + ? GNUNET_JSON_pack_bool ("lost", + true) + : GNUNET_JSON_pack_string ("dummy", + NULL)), GNUNET_JSON_pack_timestamp ("stamp_start", dk->meta.start), GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw", @@ -2666,7 +3367,7 @@ keys_get_state (bool management_only) if ( (old_ksh->key_generation < key_generation) || (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) ) { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Rebuilding /keys, generation upgrade from %llu to %llu\n", (unsigned long long) old_ksh->key_generation, (unsigned long long) key_generation); @@ -3195,7 +3896,9 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, { struct GNUNET_TIME_Timestamp last_issue_date; const char *etag; + struct WireStateHandle *wsh; + wsh = get_wire_state (); etag = MHD_lookup_connection_value (rc->connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH); @@ -3293,6 +3996,7 @@ TEH_keys_get_handler (struct TEH_RequestContext *rc, TALER_MHD_add_global_headers (resp); GNUNET_break (GNUNET_OK == setup_general_response_headers (ksh, + wsh, resp)); GNUNET_break (MHD_YES == MHD_add_response_header (resp, diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h index d4a578127..f30eeb626 100644 --- a/src/exchange/taler-exchange-httpd_keys.h +++ b/src/exchange/taler-exchange-httpd_keys.h @@ -154,6 +154,60 @@ struct TEH_KeyStateHandle; void TEH_check_invariants (void); +/** + * Clean up wire subsystem. + */ +void +TEH_wire_done (void); + + +/** + * Look up wire fee structure by @a ts. + * + * @param ts timestamp to lookup wire fees at + * @param method wire method to lookup fees for + * @return the wire fee details, or + * NULL if none are configured for @a ts and @a method + */ +const struct TALER_WireFeeSet * +TEH_wire_fees_by_time ( + struct GNUNET_TIME_Timestamp ts, + const char *method); + + +/** + * Initialize wire subsystem. + * + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TEH_wire_init (void); + + +/** + * Something changed in the database. Rebuild the wire replies. This function + * should be called if the exchange learns about a new signature from our + * master key. + * + * (We do not do so immediately, but merely signal to all threads that they + * need to rebuild their wire state upon the next call to + * #TEH_handler_wire()). + */ +void +TEH_wire_update_state (void); + + +/** + * Handle a "/wire" request. + * + * @param rc request context + * @param args array of additional options (must be empty for this function) + * @return MHD result code + */ +MHD_RESULT +TEH_handler_wire (struct TEH_RequestContext *rc, + const char *const args[]); + /** * Return the current key state for this thread. Possibly re-builds the key diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c index 077a56b25..5bca5736b 100644 --- a/src/exchange/taler-exchange-httpd_management_wire_disable.c +++ b/src/exchange/taler-exchange-httpd_management_wire_disable.c @@ -28,7 +28,7 @@ #include "taler_mhd_lib.h" #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_wire.h" +#include "taler-exchange-httpd_keys.h" /** diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c index a67d1ad69..b0db390a2 100644 --- a/src/exchange/taler-exchange-httpd_management_wire_enable.c +++ b/src/exchange/taler-exchange-httpd_management_wire_enable.c @@ -29,7 +29,7 @@ #include "taler_signatures.h" #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_wire.h" +#include "taler-exchange-httpd_keys.h" /** diff --git a/src/exchange/taler-exchange-httpd_management_wire_fees.c b/src/exchange/taler-exchange-httpd_management_wire_fees.c index dcfa87ef5..cb87592a5 100644 --- a/src/exchange/taler-exchange-httpd_management_wire_fees.c +++ b/src/exchange/taler-exchange-httpd_management_wire_fees.c @@ -29,7 +29,7 @@ #include "taler_signatures.h" #include "taler-exchange-httpd_management.h" #include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_wire.h" +#include "taler-exchange-httpd_keys.h" /** diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c index d246263f1..503826874 100644 --- a/src/exchange/taler-exchange-httpd_purses_merge.c +++ b/src/exchange/taler-exchange-httpd_purses_merge.c @@ -34,7 +34,6 @@ #include "taler-exchange-httpd_responses.h" #include "taler_exchangedb_lib.h" #include "taler-exchange-httpd_keys.h" -#include "taler-exchange-httpd_wire.h" /** diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c index 760f705c6..c84b22dad 100644 --- a/src/exchange/taler-exchange-httpd_reserves_close.c +++ b/src/exchange/taler-exchange-httpd_reserves_close.c @@ -27,7 +27,7 @@ #include "taler_mhd_lib.h" #include "taler_json_lib.h" #include "taler_dbevents.h" -#include "taler-exchange-httpd_wire.h" +#include "taler-exchange-httpd_keys.h" #include "taler-exchange-httpd_reserves_close.h" #include "taler-exchange-httpd_responses.h" diff --git a/src/exchange/taler-exchange-httpd_wire.c b/src/exchange/taler-exchange-httpd_wire.c deleted file mode 100644 index 17875a720..000000000 --- a/src/exchange/taler-exchange-httpd_wire.c +++ /dev/null @@ -1,663 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015-2023 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_wire.c - * @brief Handle /wire requests - * @author Christian Grothoff - */ -#include "platform.h" -#include <gnunet/gnunet_json_lib.h> -#include "taler_dbevents.h" -#include "taler-exchange-httpd_responses.h" -#include "taler-exchange-httpd_keys.h" -#include "taler-exchange-httpd_wire.h" -#include "taler_json_lib.h" -#include "taler_mhd_lib.h" -#include <jansson.h> - -/** - * Information we track about wire fees. - */ -struct WireFeeSet -{ - - /** - * Kept in a DLL. - */ - struct WireFeeSet *next; - - /** - * Kept in a DLL. - */ - struct WireFeeSet *prev; - - /** - * Actual fees. - */ - struct TALER_WireFeeSet fees; - - /** - * Start date of fee validity (inclusive). - */ - struct GNUNET_TIME_Timestamp start_date; - - /** - * End date of fee validity (exclusive). - */ - struct GNUNET_TIME_Timestamp end_date; - - /** - * Wire method the fees apply to. - */ - char *method; -}; - - -/** - * State we keep per thread to cache the /wire response. - */ -struct WireStateHandle -{ - /** - * Cached reply for /wire response. - */ - struct MHD_Response *wire_reply; - - /** - * ETag for this response (if any). - */ - char *etag; - - /** - * head of DLL of wire fees. - */ - struct WireFeeSet *wfs_head; - - /** - * Tail of DLL of wire fees. - */ - struct WireFeeSet *wfs_tail; - - /** - * Earliest timestamp of all the wire methods when we have no more fees. - */ - struct GNUNET_TIME_Absolute cache_expiration; - - /** - * @e cache_expiration time, formatted. - */ - char dat[128]; - - /** - * For which (global) wire_generation was this data structure created? - * Used to check when we are outdated and need to be re-generated. - */ - uint64_t wire_generation; - - /** - * HTTP status to return with this response. - */ - unsigned int http_status; - -}; - - -/** - * Stores the latest generation of our wire response. - */ -static struct WireStateHandle *wire_state; - -/** - * Handler listening for wire updates by other exchange - * services. - */ -static struct GNUNET_DB_EventHandler *wire_eh; - -/** - * Counter incremented whenever we have a reason to re-build the #wire_state - * because something external changed. - */ -static uint64_t wire_generation; - - -/** - * Free memory associated with @a wsh - * - * @param[in] wsh wire state to destroy - */ -static void -destroy_wire_state (struct WireStateHandle *wsh) -{ - struct WireFeeSet *wfs; - - while (NULL != (wfs = wsh->wfs_head)) - { - GNUNET_CONTAINER_DLL_remove (wsh->wfs_head, - wsh->wfs_tail, - wfs); - GNUNET_free (wfs->method); - GNUNET_free (wfs); - } - MHD_destroy_response (wsh->wire_reply); - GNUNET_free (wsh->etag); - GNUNET_free (wsh); -} - - -/** - * Function called whenever another exchange process has updated - * the wire data in the database. - * - * @param cls NULL - * @param extra unused - * @param extra_size number of bytes in @a extra unused - */ -static void -wire_update_event_cb (void *cls, - const void *extra, - size_t extra_size) -{ - (void) cls; - (void) extra; - (void) extra_size; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Received /wire update event\n"); - TEH_check_invariants (); - wire_generation++; -} - - -enum GNUNET_GenericReturnValue -TEH_wire_init () -{ - struct GNUNET_DB_EventHeaderP es = { - .size = htons (sizeof (es)), - .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED), - }; - - wire_eh = TEH_plugin->event_listen (TEH_plugin->cls, - GNUNET_TIME_UNIT_FOREVER_REL, - &es, - &wire_update_event_cb, - NULL); - if (NULL == wire_eh) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - return GNUNET_OK; -} - - -void -TEH_wire_done () -{ - if (NULL != wire_state) - { - destroy_wire_state (wire_state); - wire_state = NULL; - } - if (NULL != wire_eh) - { - TEH_plugin->event_listen_cancel (TEH_plugin->cls, - wire_eh); - wire_eh = NULL; - } -} - - -/** - * Add information about a wire account to @a cls. - * - * @param cls a `json_t *` object to expand with wire account details - * @param payto_uri the exchange bank account URI to add - * @param conversion_url URL of a conversion service, NULL if there is no conversion - * @param debit_restrictions JSON array with debit restrictions on the account - * @param credit_restrictions JSON array with credit restrictions on the account - * @param master_sig master key signature affirming that this is a bank - * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS) - */ -static void -add_wire_account (void *cls, - const char *payto_uri, - const char *conversion_url, - const json_t *debit_restrictions, - const json_t *credit_restrictions, - const struct TALER_MasterSignatureP *master_sig) -{ - json_t *a = cls; - - if (0 != - json_array_append_new ( - a, - GNUNET_JSON_PACK ( - GNUNET_JSON_pack_string ("payto_uri", - payto_uri), - GNUNET_JSON_pack_allow_null ( - GNUNET_JSON_pack_string ("conversion_url", - conversion_url)), - GNUNET_JSON_pack_array_incref ("debit_restrictions", - (json_t *) debit_restrictions), - GNUNET_JSON_pack_array_incref ("credit_restrictions", - (json_t *) credit_restrictions), - GNUNET_JSON_pack_data_auto ("master_sig", - master_sig)))) - { - GNUNET_break (0); /* out of memory!? */ - return; - } -} - - -/** - * Closure for #add_wire_fee(). - */ -struct AddContext -{ - /** - * Wire method the fees are for. - */ - char *wire_method; - - /** - * Wire state we are building. - */ - struct WireStateHandle *wsh; - - /** - * Array to append the fee to. - */ - json_t *a; - - /** - * Context we hash "everything" we add into. This is used - * to compute the etag. Technically, we only hash the - * master_sigs, as they imply the rest. - */ - struct GNUNET_HashContext *hc; - - /** - * Set to the maximum end-date seen. - */ - struct GNUNET_TIME_Absolute max_seen; -}; - - -/** - * Add information about a wire account to @a cls. - * - * @param cls a `struct AddContext` - * @param fees the wire fees we charge - * @param start_date from when are these fees valid (start date) - * @param end_date until when are these fees valid (end date, exclusive) - * @param master_sig master key signature affirming that this is the correct - * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES) - */ -static void -add_wire_fee (void *cls, - const struct TALER_WireFeeSet *fees, - struct GNUNET_TIME_Timestamp start_date, - struct GNUNET_TIME_Timestamp end_date, - const struct TALER_MasterSignatureP *master_sig) -{ - struct AddContext *ac = cls; - struct WireFeeSet *wfs; - - GNUNET_CRYPTO_hash_context_read (ac->hc, - master_sig, - sizeof (*master_sig)); - ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen, - end_date.abs_time); - wfs = GNUNET_new (struct WireFeeSet); - wfs->start_date = start_date; - wfs->end_date = end_date; - wfs->fees = *fees; - wfs->method = GNUNET_strdup (ac->wire_method); - GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head, - ac->wsh->wfs_tail, - wfs); - if (0 != - json_array_append_new ( - ac->a, - GNUNET_JSON_PACK ( - TALER_JSON_pack_amount ("wire_fee", - &fees->wire), - TALER_JSON_pack_amount ("closing_fee", - &fees->closing), - GNUNET_JSON_pack_timestamp ("start_date", - start_date), - GNUNET_JSON_pack_timestamp ("end_date", - end_date), - GNUNET_JSON_pack_data_auto ("sig", - master_sig)))) - { - GNUNET_break (0); /* out of memory!? */ - return; - } -} - - -/** - * Create the /wire response from our database state. - * - * @return NULL on error - */ -static struct WireStateHandle * -build_wire_state (void) -{ - json_t *wire_accounts_array; - json_t *wire_fee_object; - uint64_t wg = wire_generation; /* must be obtained FIRST */ - enum GNUNET_DB_QueryStatus qs; - struct WireStateHandle *wsh; - struct GNUNET_HashContext *hc; - - wsh = GNUNET_new (struct WireStateHandle); - wsh->wire_generation = wg; - wire_accounts_array = json_array (); - GNUNET_assert (NULL != wire_accounts_array); - qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls, - &add_wire_account, - wire_accounts_array); - if (0 > qs) - { - GNUNET_break (0); - json_decref (wire_accounts_array); - wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - wsh->wire_reply - = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_wire_accounts"); - return wsh; - } - if (0 == json_array_size (wire_accounts_array)) - { - json_decref (wire_accounts_array); - wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - wsh->wire_reply - = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED, - NULL); - return wsh; - } - wire_fee_object = json_object (); - GNUNET_assert (NULL != wire_fee_object); - wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS; - hc = GNUNET_CRYPTO_hash_context_start (); - { - json_t *account; - size_t index; - - json_array_foreach (wire_accounts_array, index, account) { - char *wire_method; - const char *payto_uri = json_string_value (json_object_get (account, - "payto_uri")); - - GNUNET_assert (NULL != payto_uri); - wire_method = TALER_payto_get_method (payto_uri); - if (NULL == wire_method) - { - wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - wsh->wire_reply - = TALER_MHD_make_error ( - TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED, - payto_uri); - json_decref (wire_accounts_array); - json_decref (wire_fee_object); - GNUNET_CRYPTO_hash_context_abort (hc); - return wsh; - } - if (NULL == json_object_get (wire_fee_object, - wire_method)) - { - struct AddContext ac = { - .wire_method = wire_method, - .wsh = wsh, - .a = json_array (), - .hc = hc - }; - - GNUNET_assert (NULL != ac.a); - qs = TEH_plugin->get_wire_fees (TEH_plugin->cls, - wire_method, - &add_wire_fee, - &ac); - if (0 > qs) - { - GNUNET_break (0); - json_decref (ac.a); - json_decref (wire_fee_object); - json_decref (wire_accounts_array); - GNUNET_free (wire_method); - wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - wsh->wire_reply - = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, - "get_wire_fees"); - GNUNET_CRYPTO_hash_context_abort (hc); - return wsh; - } - if (0 == json_array_size (ac.a)) - { - json_decref (ac.a); - json_decref (wire_accounts_array); - json_decref (wire_fee_object); - wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR; - wsh->wire_reply - = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, - wire_method); - GNUNET_free (wire_method); - GNUNET_CRYPTO_hash_context_abort (hc); - return wsh; - } - wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen, - wsh->cache_expiration); - GNUNET_assert (0 == - json_object_set_new (wire_fee_object, - wire_method, - ac.a)); - } - GNUNET_free (wire_method); - } - } - - - wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK ( - GNUNET_JSON_pack_array_steal ("accounts", - wire_accounts_array), - GNUNET_JSON_pack_array_steal ("wads", /* #7271 */ - json_array ()), - GNUNET_JSON_pack_object_steal ("fees", - wire_fee_object), - GNUNET_JSON_pack_data_auto ("master_public_key", - &TEH_master_public_key)); - { - struct GNUNET_TIME_Timestamp m; - - m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration); - TALER_MHD_get_date_string (m.abs_time, - wsh->dat); - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Setting 'Expires' header for '/wire' to '%s'\n", - wsh->dat); - GNUNET_break (MHD_YES == - MHD_add_response_header (wsh->wire_reply, - MHD_HTTP_HEADER_EXPIRES, - wsh->dat)); - } - /* Set cache control headers: our response varies depending on these headers */ - GNUNET_break (MHD_YES == - MHD_add_response_header (wsh->wire_reply, - MHD_HTTP_HEADER_VARY, - MHD_HTTP_HEADER_ACCEPT_ENCODING)); - /* Information is always public, revalidate after 1 day */ - GNUNET_break (MHD_YES == - MHD_add_response_header (wsh->wire_reply, - MHD_HTTP_HEADER_CACHE_CONTROL, - "public,max-age=86400")); - - { - struct GNUNET_HashCode h; - char etag[sizeof (h) * 2]; - char *end; - - GNUNET_CRYPTO_hash_context_finish (hc, - &h); - end = GNUNET_STRINGS_data_to_string (&h, - sizeof (h), - etag, - sizeof (etag)); - *end = '\0'; - wsh->etag = GNUNET_strdup (etag); - GNUNET_break (MHD_YES == - MHD_add_response_header (wsh->wire_reply, - MHD_HTTP_HEADER_ETAG, - etag)); - } - wsh->http_status = MHD_HTTP_OK; - return wsh; -} - - -void -TEH_wire_update_state (void) -{ - struct GNUNET_DB_EventHeaderP es = { - .size = htons (sizeof (es)), - .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED), - }; - - TEH_plugin->event_notify (TEH_plugin->cls, - &es, - NULL, - 0); - wire_generation++; -} - - -/** - * Return the current key state for this thread. Possibly - * re-builds the key state if we have reason to believe - * that something changed. - * - * @return NULL on error - */ -struct WireStateHandle * -get_wire_state (void) -{ - struct WireStateHandle *old_wsh; - - old_wsh = wire_state; - if ( (NULL == old_wsh) || - (old_wsh->wire_generation < wire_generation) ) - { - struct WireStateHandle *wsh; - - TEH_check_invariants (); - wsh = build_wire_state (); - wire_state = wsh; - if (NULL != old_wsh) - destroy_wire_state (old_wsh); - TEH_check_invariants (); - return wsh; - } - return old_wsh; -} - - -MHD_RESULT -TEH_handler_wire (struct TEH_RequestContext *rc, - const char *const args[]) -{ - struct WireStateHandle *wsh; - - (void) args; - wsh = get_wire_state (); - if (NULL == wsh) - return TALER_MHD_reply_with_error (rc->connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION, - NULL); - { - const char *etag; - - etag = MHD_lookup_connection_value (rc->connection, - MHD_HEADER_KIND, - MHD_HTTP_HEADER_IF_NONE_MATCH); - if ( (NULL != etag) && - (MHD_HTTP_OK == wsh->http_status) && - (NULL != wsh->etag) && - (0 == strcmp (etag, - wsh->etag)) ) - { - MHD_RESULT ret; - struct MHD_Response *resp; - - resp = MHD_create_response_from_buffer (0, - NULL, - MHD_RESPMEM_PERSISTENT); - TALER_MHD_add_global_headers (resp); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, - MHD_HTTP_HEADER_EXPIRES, - wsh->dat)); - GNUNET_break (MHD_YES == - MHD_add_response_header (resp, - MHD_HTTP_HEADER_ETAG, - wsh->etag)); - ret = MHD_queue_response (rc->connection, - MHD_HTTP_NOT_MODIFIED, - resp); - GNUNET_break (MHD_YES == ret); - MHD_destroy_response (resp); - return ret; - } - } - return MHD_queue_response (rc->connection, - wsh->http_status, - wsh->wire_reply); -} - - -const struct TALER_WireFeeSet * -TEH_wire_fees_by_time ( - struct GNUNET_TIME_Timestamp ts, - const char *method) -{ - struct WireStateHandle *wsh = get_wire_state (); - - for (struct WireFeeSet *wfs = wsh->wfs_head; - NULL != wfs; - wfs = wfs->next) - { - if (0 != strcmp (method, - wfs->method)) - continue; - if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date, - >, - ts)) || - (GNUNET_TIME_timestamp_cmp (ts, - >=, - wfs->end_date)) ) - continue; - return &wfs->fees; - } - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "No wire fees for method `%s' at %s configured\n", - method, - GNUNET_TIME_timestamp2s (ts)); - return NULL; -} - - -/* end of taler-exchange-httpd_wire.c */ diff --git a/src/exchange/taler-exchange-httpd_wire.h b/src/exchange/taler-exchange-httpd_wire.h deleted file mode 100644 index 75595fe69..000000000 --- a/src/exchange/taler-exchange-httpd_wire.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014--2021 Taler Systems SA - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-exchange-httpd_wire.h - * @brief Handle /wire requests - * @author Christian Grothoff - */ -#ifndef TALER_EXCHANGE_HTTPD_WIRE_H -#define TALER_EXCHANGE_HTTPD_WIRE_H - -#include <gnunet/gnunet_util_lib.h> -#include <microhttpd.h> -#include "taler-exchange-httpd.h" - - -/** - * Clean up wire subsystem. - */ -void -TEH_wire_done (void); - - -/** - * Look up wire fee structure by @a ts. - * - * @param ts timestamp to lookup wire fees at - * @param method wire method to lookup fees for - * @return the wire fee details, or - * NULL if none are configured for @a ts and @a method - */ -const struct TALER_WireFeeSet * -TEH_wire_fees_by_time ( - struct GNUNET_TIME_Timestamp ts, - const char *method); - - -/** - * Initialize wire subsystem. - * - * @return #GNUNET_OK on success - */ -enum GNUNET_GenericReturnValue -TEH_wire_init (void); - - -/** - * Something changed in the database. Rebuild the wire replies. This function - * should be called if the exchange learns about a new signature from our - * master key. - * - * (We do not do so immediately, but merely signal to all threads that they - * need to rebuild their wire state upon the next call to - * #TEH_handler_wire()). - */ -void -TEH_wire_update_state (void); - - -/** - * Handle a "/wire" request. - * - * @param rc request context - * @param args array of additional options (must be empty for this function) - * @return MHD result code - */ -MHD_RESULT -TEH_handler_wire (struct TEH_RequestContext *rc, - const char *const args[]); - - -#endif diff --git a/src/include/taler_exchange_service.h b/src/include/taler_exchange_service.h index 13a238ba7..2164874cc 100644 --- a/src/include/taler_exchange_service.h +++ b/src/include/taler_exchange_service.h @@ -121,6 +121,13 @@ struct TALER_EXCHANGE_DenomPublicKey struct TALER_DenomFeeSet fees; /** + * Set to true if the private denomination key has been + * lost by the exchange and thus the key cannot be + * used for withdrawing at this time. + */ + bool lost; + + /** * Set to true if this denomination key has been * revoked by the exchange. */ @@ -230,6 +237,173 @@ struct TALER_EXCHANGE_GlobalFee /** + * List sorted by @a start_date with fees to be paid for aggregate wire transfers. + */ +struct TALER_EXCHANGE_WireAggregateFees +{ + /** + * This is a linked list. + */ + struct TALER_EXCHANGE_WireAggregateFees *next; + + /** + * Fee to be paid whenever the exchange wires funds to the merchant. + */ + struct TALER_WireFeeSet fees; + + /** + * Time when this fee goes into effect (inclusive) + */ + struct GNUNET_TIME_Timestamp start_date; + + /** + * Time when this fee stops being in effect (exclusive). + */ + struct GNUNET_TIME_Timestamp end_date; + + /** + * Signature affirming the above fee structure. + */ + struct TALER_MasterSignatureP master_sig; +}; + + +/** + * Information about wire fees by wire method. + */ +struct TALER_EXCHANGE_WireFeesByMethod +{ + /** + * Wire method with the given @e fees. + */ + char *method; + + /** + * Linked list of wire fees the exchange charges for + * accounts of the wire @e method. + */ + struct TALER_EXCHANGE_WireAggregateFees *fees_head; + +}; + + +/** + * Type of an account restriction. + */ +enum TALER_EXCHANGE_AccountRestrictionType +{ + /** + * Invalid restriction. + */ + TALER_EXCHANGE_AR_INVALID = 0, + + /** + * Account must not be used for this operation. + */ + TALER_EXCHANGE_AR_DENY = 1, + + /** + * Other account must match given regular expression. + */ + TALER_EXCHANGE_AR_REGEX = 2 +}; + +/** + * Restrictions that apply to using a given exchange bank account. + */ +struct TALER_EXCHANGE_AccountRestriction +{ + + /** + * Type of the account restriction. + */ + enum TALER_EXCHANGE_AccountRestrictionType type; + + /** + * Restriction details depending on @e type. + */ + union + { + /** + * Details if type is #TALER_EXCHANGE_AR_REGEX. + */ + struct + { + /** + * Regular expression that the payto://-URI of the partner account must + * follow. The regular expression should follow posix-egrep, but + * without support for character classes, GNU extensions, + * back-references or intervals. See + * https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html + * for a description of the posix-egrep syntax. Applications may support + * regexes with additional features, but exchanges must not use such + * regexes. + */ + char *posix_egrep; + + /** + * Hint for a human to understand the restriction. + */ + char *human_hint; + + /** + * Internationalizations for the @e human_hint. Map from IETF BCP 47 + * language tax to localized human hints. + */ + json_t *human_hint_i18n; + } regex; + } details; + +}; + + +/** + * Information about a wire account of the exchange. + */ +struct TALER_EXCHANGE_WireAccount +{ + /** + * payto://-URI of the exchange. + */ + char *payto_uri; + + /** + * URL of a conversion service in case using this account is subject to + * currency conversion. NULL for no conversion needed. + */ + char *conversion_url; + + /** + * Array of restrictions that apply when crediting + * this account. + */ + struct TALER_EXCHANGE_AccountRestriction *credit_restrictions; + + /** + * Array of restrictions that apply when debiting + * this account. + */ + struct TALER_EXCHANGE_AccountRestriction *debit_restrictions; + + /** + * Length of the @e credit_restrictions array. + */ + unsigned int credit_restrictions_length; + + /** + * Length of the @e debit_restrictions array. + */ + unsigned int debit_restrictions_length; + + /** + * Signature of the exchange over the account (was checked by the API). + */ + struct TALER_MasterSignatureP master_sig; + +}; + + +/** * @brief Information about keys from the exchange. */ struct TALER_EXCHANGE_Keys @@ -304,6 +478,16 @@ struct TALER_EXCHANGE_Keys struct TALER_Amount *wallet_balance_limit_without_kyc; /** + * Array of accounts of the exchange. + */ + struct TALER_EXCHANGE_WireAccount *accounts; + + /** + * Array of wire fees by wire method. + */ + struct TALER_EXCHANGE_WireFeesByMethod *fees; + + /** * How long after a reserve went idle will the exchange close it? * This is an approximate number, not cryptographically signed by * the exchange (advisory-only, may change anytime). @@ -333,6 +517,16 @@ struct TALER_EXCHANGE_Keys struct TALER_AgeMask age_mask; /** + * Length of @e accounts array. + */ + unsigned int accounts_len; + + /** + * Length of @e fees array. + */ + unsigned int fees_len; + + /** * Length of the @e wallet_balance_limit_without_kyc * array. */ @@ -702,174 +896,7 @@ TALER_EXCHANGE_get_signing_key_info ( const struct TALER_ExchangePublicKeyP *exchange_pub); -/* ********************* /wire *********************** */ - - -/** - * List sorted by @a start_date with fees to be paid for aggregate wire transfers. - */ -struct TALER_EXCHANGE_WireAggregateFees -{ - /** - * This is a linked list. - */ - struct TALER_EXCHANGE_WireAggregateFees *next; - - /** - * Fee to be paid whenever the exchange wires funds to the merchant. - */ - struct TALER_WireFeeSet fees; - - /** - * Time when this fee goes into effect (inclusive) - */ - struct GNUNET_TIME_Timestamp start_date; - - /** - * Time when this fee stops being in effect (exclusive). - */ - struct GNUNET_TIME_Timestamp end_date; - - /** - * Signature affirming the above fee structure. - */ - struct TALER_MasterSignatureP master_sig; -}; - - -/** - * Information about wire fees by wire method. - */ -struct TALER_EXCHANGE_WireFeesByMethod -{ - /** - * Wire method with the given @e fees. - */ - const char *method; - - /** - * Linked list of wire fees the exchange charges for - * accounts of the wire @e method. - */ - struct TALER_EXCHANGE_WireAggregateFees *fees_head; - -}; - - -/** - * Type of an account restriction. - */ -enum TALER_EXCHANGE_AccountRestrictionType -{ - /** - * Invalid restriction. - */ - TALER_EXCHANGE_AR_INVALID = 0, - - /** - * Account must not be used for this operation. - */ - TALER_EXCHANGE_AR_DENY = 1, - - /** - * Other account must match given regular expression. - */ - TALER_EXCHANGE_AR_REGEX = 2 -}; - -/** - * Restrictions that apply to using a given exchange bank account. - */ -struct TALER_EXCHANGE_AccountRestriction -{ - - /** - * Type of the account restriction. - */ - enum TALER_EXCHANGE_AccountRestrictionType type; - - /** - * Restriction details depending on @e type. - */ - union - { - /** - * Details if type is #TALER_EXCHANGE_AR_REGEX. - */ - struct - { - /** - * Regular expression that the payto://-URI of the partner account must - * follow. The regular expression should follow posix-egrep, but - * without support for character classes, GNU extensions, - * back-references or intervals. See - * https://www.gnu.org/software/findutils/manual/html_node/find_html/posix_002degrep-regular-expression-syntax.html - * for a description of the posix-egrep syntax. Applications may support - * regexes with additional features, but exchanges must not use such - * regexes. - */ - const char *posix_egrep; - - /** - * Hint for a human to understand the restriction. - */ - const char *human_hint; - - /** - * Internationalizations for the @e human_hint. Map from IETF BCP 47 - * language tax to localized human hints. - */ - const json_t *human_hint_i18n; - } regex; - } details; - -}; - - -/** - * Information about a wire account of the exchange. - */ -struct TALER_EXCHANGE_WireAccount -{ - /** - * payto://-URI of the exchange. - */ - const char *payto_uri; - - /** - * URL of a conversion service in case using this account is subject to - * currency conversion. NULL for no conversion needed. - */ - const char *conversion_url; - - /** - * Array of restrictions that apply when crediting - * this account. - */ - struct TALER_EXCHANGE_AccountRestriction *credit_restrictions; - - /** - * Array of restrictions that apply when debiting - * this account. - */ - struct TALER_EXCHANGE_AccountRestriction *debit_restrictions; - - /** - * Length of the @e credit_restrictions array. - */ - unsigned int credit_restrictions_length; - - /** - * Length of the @e debit_restrictions array. - */ - unsigned int debit_restrictions_length; - - /** - * Signature of the exchange over the account (was checked by the API). - */ - struct TALER_MasterSignatureP master_sig; - -}; +/* ********************* wire helpers *********************** */ /** @@ -901,116 +928,6 @@ TALER_EXCHANGE_free_accounts ( struct TALER_EXCHANGE_WireAccount was[static was_len]); -/** - * Response to a /wire request. - */ -struct TALER_EXCHANGE_WireResponse -{ - /** - * HTTP response details. - */ - struct TALER_EXCHANGE_HttpResponse hr; - - /** - * Response details depending on status. - */ - union - { - - /** - * Details for #MHD_HTTP_OK. - */ - struct - { - - /** - * Array of accounts of the exchange. - */ - const struct TALER_EXCHANGE_WireAccount *accounts; - - /** - * Array of wire fees by wire method. - */ - const struct TALER_EXCHANGE_WireFeesByMethod *fees; - - /** - * Length of @e accounts array. - */ - unsigned int accounts_len; - - /** - * Length of @e fees array. - */ - unsigned int fees_len; - - } ok; - - } details; -}; - - -/** - * Callbacks of this type are used to serve the result of submitting a wire - * format inquiry request to a exchange. - * - * If the request fails to generate a valid response from the - * exchange, the http_status will also be zero. - * - * @param cls closure - * @param wr response data - */ -typedef void -(*TALER_EXCHANGE_WireCallback) ( - void *cls, - const struct TALER_EXCHANGE_WireResponse *wr); - - -/** - * @brief A Wire format inquiry handle - */ -struct TALER_EXCHANGE_WireHandle; - - -/** - * Obtain information about a exchange's wire instructions. A - * exchange may provide wire instructions for creating a reserve. The - * wire instructions also indicate which wire formats merchants may - * use with the exchange. This API is typically used by a wallet for - * wiring funds, and possibly by a merchant to determine supported - * wire formats. - * - * Note that while we return the (main) response verbatim to the - * caller for further processing, we do already verify that the - * response is well-formed (i.e. that signatures included in the - * response are all valid). If the exchange's reply is not - * well-formed, we return an HTTP status code of zero to @a cb. - * - * @param ctx curl context - * @param url exchange base URL - * @param keys the keys of the exchange - * @param wire_cb the callback to call when a reply for this request is available - * @param wire_cb_cls closure for the above callback - * @return a handle for this request - */ -struct TALER_EXCHANGE_WireHandle * -TALER_EXCHANGE_wire ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - TALER_EXCHANGE_WireCallback wire_cb, - void *wire_cb_cls); - - -/** - * Cancel a wire information request. This function cannot be used - * on a request handle if a response is already served for it. - * - * @param wh the wire information request handle - */ -void -TALER_EXCHANGE_wire_cancel (struct TALER_EXCHANGE_WireHandle *wh); - - /* ********************* /coins/$COIN_PUB/deposit *********************** */ diff --git a/src/include/taler_testing_lib.h b/src/include/taler_testing_lib.h index 374ab7afe..4a1c2c8ff 100644 --- a/src/include/taler_testing_lib.h +++ b/src/include/taler_testing_lib.h @@ -1175,24 +1175,6 @@ TALER_TESTING_cmd_withdraw_with_retry (struct TALER_TESTING_Command cmd); /** - * Create a "wire" command. - * - * @param label the command label. - * @param expected_method which wire-transfer method is expected - * to be offered by the exchange. - * @param expected_fee the fee the exchange should charge. - * @param expected_response_code the HTTP response the exchange - * should return. - * @return the command. - */ -struct TALER_TESTING_Command -TALER_TESTING_cmd_wire (const char *label, - const char *expected_method, - const char *expected_fee, - unsigned int expected_response_code); - - -/** * Create a GET "reserves" command. * * @param label the command label. diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am index 7a4febdd3..f69a4e81a 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -76,8 +76,7 @@ libtalerexchange_la_SOURCES = \ exchange_api_reserves_status.c \ exchange_api_transfers_get.c \ exchange_api_withdraw.c \ - exchange_api_withdraw2.c \ - exchange_api_wire.c + exchange_api_withdraw2.c libtalerexchange_la_LIBADD = \ libtalerauditor.la \ $(top_builddir)/src/json/libtalerjson.la \ diff --git a/src/lib/exchange_api_common.c b/src/lib/exchange_api_common.c index 7c86ec597..337fbabaf 100644 --- a/src/lib/exchange_api_common.c +++ b/src/lib/exchange_api_common.c @@ -2241,15 +2241,17 @@ parse_restrictions (const json_t *jresta, if (0 == strcmp (type, "regex")) { + const char *regex; + const char *hint; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ( "payto_regex", - &ar->details.regex.posix_egrep), + ®ex), GNUNET_JSON_spec_string ( "human_hint", - &ar->details.regex.human_hint), + &hint), GNUNET_JSON_spec_mark_optional ( - GNUNET_JSON_spec_object_const ( + GNUNET_JSON_spec_json ( "human_hint_i18n", &ar->details.regex.human_hint_i18n), NULL), @@ -2266,6 +2268,8 @@ parse_restrictions (const json_t *jresta, goto fail; } ar->type = TALER_EXCHANGE_AR_REGEX; + ar->details.regex.posix_egrep = GNUNET_strdup (regex); + ar->details.regex.human_hint = GNUNET_strdup (hint); continue; } /* unsupported type */ @@ -2297,14 +2301,16 @@ TALER_EXCHANGE_parse_accounts ( i++) { struct TALER_EXCHANGE_WireAccount *wa = &was[i]; + const char *payto_uri; + const char *conversion_url; const json_t *credit_restrictions; const json_t *debit_restrictions; struct GNUNET_JSON_Specification spec_account[] = { GNUNET_JSON_spec_string ("payto_uri", - &wa->payto_uri), + &payto_uri), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_string ("conversion_url", - &wa->conversion_url), + &conversion_url), NULL), GNUNET_JSON_spec_array_const ("credit_restrictions", &credit_restrictions), @@ -2330,7 +2336,7 @@ TALER_EXCHANGE_parse_accounts ( { char *err; - err = TALER_payto_validate (wa->payto_uri); + err = TALER_payto_validate (payto_uri); if (NULL != err) { GNUNET_break_op (0); @@ -2341,12 +2347,13 @@ TALER_EXCHANGE_parse_accounts ( if ( (NULL != master_pub) && (GNUNET_OK != - TALER_exchange_wire_signature_check (wa->payto_uri, - wa->conversion_url, - debit_restrictions, - credit_restrictions, - master_pub, - &wa->master_sig)) ) + TALER_exchange_wire_signature_check ( + payto_uri, + conversion_url, + debit_restrictions, + credit_restrictions, + master_pub, + &wa->master_sig)) ) { /* bogus reply */ GNUNET_break_op (0); @@ -2365,11 +2372,44 @@ TALER_EXCHANGE_parse_accounts ( GNUNET_break_op (0); return GNUNET_SYSERR; } + wa->payto_uri = GNUNET_strdup (payto_uri); + if (NULL != conversion_url) + wa->conversion_url = GNUNET_strdup (conversion_url); } /* end 'for all accounts */ return GNUNET_OK; } +/** + * Free array of account restrictions. + * + * @param ar_len length of @a ar + * @param[in] ar array to free contents of (but not @a ar itself) + */ +static void +free_restrictions (unsigned int ar_len, + struct TALER_EXCHANGE_AccountRestriction ar[static ar_len]) +{ + for (unsigned int i = 0; i<ar_len; i++) + { + struct TALER_EXCHANGE_AccountRestriction *a = &ar[i]; + switch (a->type) + { + case TALER_EXCHANGE_AR_INVALID: + GNUNET_break (0); + break; + case TALER_EXCHANGE_AR_DENY: + break; + case TALER_EXCHANGE_AR_REGEX: + GNUNET_free (ar->details.regex.posix_egrep); + GNUNET_free (ar->details.regex.human_hint); + json_decref (ar->details.regex.human_hint_i18n); + break; + } + } +} + + void TALER_EXCHANGE_free_accounts ( unsigned int was_len, @@ -2379,8 +2419,18 @@ TALER_EXCHANGE_free_accounts ( { struct TALER_EXCHANGE_WireAccount *wa = &was[i]; - GNUNET_free (wa->credit_restrictions); - GNUNET_free (wa->debit_restrictions); + GNUNET_free (wa->payto_uri); + GNUNET_free (wa->conversion_url); + free_restrictions (wa->credit_restrictions_length, + wa->credit_restrictions); + GNUNET_array_grow (wa->credit_restrictions, + wa->credit_restrictions_length, + 0); + free_restrictions (wa->debit_restrictions_length, + wa->debit_restrictions); + GNUNET_array_grow (wa->debit_restrictions, + wa->debit_restrictions_length, + 0); } } diff --git a/src/lib/exchange_api_handle.c b/src/lib/exchange_api_handle.c index 996e62734..0ad2f7a34 100644 --- a/src/lib/exchange_api_handle.c +++ b/src/lib/exchange_api_handle.c @@ -40,7 +40,7 @@ * Which version of the Taler protocol is implemented * by this library? Used to determine compatibility. */ -#define EXCHANGE_PROTOCOL_CURRENT 15 +#define EXCHANGE_PROTOCOL_CURRENT 16 /** * How many versions are we backwards compatible with? @@ -123,6 +123,115 @@ struct TALER_EXCHANGE_GetKeysHandle }; +/** + * Frees @a wfm array. + * + * @param wfm fee array to release + * @param wfm_len length of the @a wfm array + */ +static void +free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm, + unsigned int wfm_len) +{ + for (unsigned int i = 0; i<wfm_len; i++) + { + struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i]; + + while (NULL != wfmi->fees_head) + { + struct TALER_EXCHANGE_WireAggregateFees *fe + = wfmi->fees_head; + + wfmi->fees_head = fe->next; + GNUNET_free (fe); + } + GNUNET_free (wfmi->method); + } + GNUNET_free (wfm); +} + + +/** + * Parse wire @a fees and return array. + * + * @param master_pub master public key to use to check signatures + * @param fees json AggregateTransferFee to parse + * @param[out] fees_len set to length of returned array + * @return NULL on error + */ +static struct TALER_EXCHANGE_WireFeesByMethod * +parse_fees (const struct TALER_MasterPublicKeyP *master_pub, + const json_t *fees, + unsigned int *fees_len) +{ + struct TALER_EXCHANGE_WireFeesByMethod *fbm; + unsigned int fbml = json_object_size (fees); + unsigned int i = 0; + const char *key; + const json_t *fee_array; + + fbm = GNUNET_new_array (fbml, + struct TALER_EXCHANGE_WireFeesByMethod); + *fees_len = fbml; + json_object_foreach ((json_t *) fees, key, fee_array) { + struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++]; + unsigned int idx; + json_t *fee; + + fe->method = GNUNET_strdup (key); + fe->fees_head = NULL; + json_array_foreach (fee_array, idx, fee) + { + struct TALER_EXCHANGE_WireAggregateFees *wa + = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); + struct GNUNET_JSON_Specification spec[] = { + GNUNET_JSON_spec_fixed_auto ("sig", + &wa->master_sig), + TALER_JSON_spec_amount_any ("wire_fee", + &wa->fees.wire), + TALER_JSON_spec_amount_any ("closing_fee", + &wa->fees.closing), + GNUNET_JSON_spec_timestamp ("start_date", + &wa->start_date), + GNUNET_JSON_spec_timestamp ("end_date", + &wa->end_date), + GNUNET_JSON_spec_end () + }; + + wa->next = fe->fees_head; + fe->fees_head = wa; + if (GNUNET_OK != + GNUNET_JSON_parse (fee, + spec, + NULL, + NULL)) + { + GNUNET_break_op (0); + free_fees (fbm, + i); + return NULL; + } + if (GNUNET_OK != + TALER_exchange_offline_wire_fee_verify ( + key, + wa->start_date, + wa->end_date, + &wa->fees, + master_pub, + &wa->master_sig)) + { + GNUNET_break_op (0); + free_fees (fbm, + i); + return NULL; + } + } /* for all fees over time */ + } /* for all methods */ + GNUNET_assert (i == fbml); + return fbm; +} + + void TEAH_get_auditors_for_dc ( struct TALER_EXCHANGE_Keys *keys, @@ -246,6 +355,10 @@ parse_json_denomkey_partially ( &denom_key->valid_from), GNUNET_JSON_spec_timestamp ("stamp_expire_legal", &denom_key->expire_legal), + GNUNET_JSON_spec_mark_optional ( + GNUNET_JSON_spec_bool ("lost", + &denom_key->lost), + NULL), TALER_JSON_spec_denom_pub_cipher (NULL, cipher, &denom_key->key), @@ -537,6 +650,9 @@ decode_keys_json (const json_t *resp_obj, const json_t *manifests = NULL; bool no_extensions = false; bool no_signature = false; + const json_t *accounts; + const json_t *fees; + const json_t *wads; if (JSON_OBJECT != json_typeof (resp_obj)) { @@ -608,6 +724,12 @@ decode_keys_json (const json_t *resp_obj, GNUNET_JSON_spec_fixed_auto ( "master_public_key", &key_data->master_pub), + GNUNET_JSON_spec_array_const ("accounts", + &accounts), + GNUNET_JSON_spec_object_const ("wire_fees", + &fees), + GNUNET_JSON_spec_array_const ("wads", + &wads), GNUNET_JSON_spec_timestamp ( "list_issue_date", &key_data->list_issue_date), @@ -736,6 +858,26 @@ decode_keys_json (const json_t *resp_obj, } } + /* Parse wire accounts */ + key_data->fees = parse_fees (&key_data->master_pub, + fees, + &key_data->fees_len); + EXITIF (NULL == key_data->fees); + /* parse accounts */ + GNUNET_array_grow (key_data->accounts, + key_data->accounts_len, + json_array_size (accounts)); + EXITIF (GNUNET_OK != + TALER_EXCHANGE_parse_accounts (&key_data->master_pub, + accounts, + key_data->accounts_len, + key_data->accounts)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Parsed %u wire accounts from JSON\n", + (unsigned int) json_array_size (accounts)); + + /* Parse the supported extension(s): age-restriction. */ /* TODO: maybe lift all this into a FP in TALER_Extension ? */ if (! no_extensions) @@ -1542,6 +1684,13 @@ TALER_EXCHANGE_keys_decref (struct TALER_EXCHANGE_Keys *keys) GNUNET_array_grow (keys->auditors, keys->auditors_size, 0); + TALER_EXCHANGE_free_accounts (keys->accounts_len, + keys->accounts); + GNUNET_array_grow (keys->accounts, + keys->accounts_len, + 0); + free_fees (keys->fees, + keys->fees_len); json_decref (keys->extensions); GNUNET_free (keys->wallet_balance_limit_without_kyc); GNUNET_free (keys->version); @@ -1684,6 +1833,66 @@ add_grp (void *cls, } +/** + * Convert array of account restrictions @a ars to JSON. + * + * @param ar_len length of @a ars + * @param ars account restrictions to convert + * @return JSON representation + */ +static json_t * +ar_to_json (unsigned int ar_len, + const struct TALER_EXCHANGE_AccountRestriction ars[static ar_len]) +{ + json_t *rval; + + rval = json_array (); + GNUNET_assert (NULL != rval); + for (unsigned int i = 0; i<ar_len; i++) + { + const struct TALER_EXCHANGE_AccountRestriction *ar = &ars[i]; + + switch (ar->type) + { + case TALER_EXCHANGE_AR_INVALID: + GNUNET_break (0); + json_decref (rval); + return NULL; + case TALER_EXCHANGE_AR_DENY: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("type", + "deny")))); + break; + case TALER_EXCHANGE_AR_REGEX: + GNUNET_assert ( + 0 == + json_array_append_new ( + rval, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ( + "type", + "regex"), + GNUNET_JSON_pack_string ( + "regex", + ar->details.regex.posix_egrep), + GNUNET_JSON_pack_string ( + "human_hint", + ar->details.regex.human_hint), + GNUNET_JSON_pack_object_incref ( + "human_hint_i18n", + (json_t *) ar->details.regex.human_hint_i18n) + ))); + break; + } + } + return rval; +} + + json_t * TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) { @@ -1693,16 +1902,14 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) json_t *denominations_by_group; json_t *auditors; json_t *recoup; + json_t *wire_fees; + json_t *accounts; json_t *global_fees; json_t *wblwk = NULL; now = GNUNET_TIME_timestamp_get (); signkeys = json_array (); - if (NULL == signkeys) - { - GNUNET_break (0); - return NULL; - } + GNUNET_assert (NULL != signkeys); for (unsigned int i = 0; i<kd->num_sign_keys; i++) { const struct TALER_EXCHANGE_SigningPublicKey *sk = &kd->sign_keys[i]; @@ -1723,28 +1930,14 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) sk->valid_until), GNUNET_JSON_pack_timestamp ("stamp_end", sk->valid_legal)); - if (NULL == signkey) - { - GNUNET_break (0); - continue; - } - if (0 != json_array_append_new (signkeys, - signkey)) - { - GNUNET_break (0); - json_decref (signkey); - json_decref (signkeys); - return NULL; - } - } - denominations_by_group = json_array (); - if (NULL == denominations_by_group) - { - GNUNET_break (0); - json_decref (signkeys); - return NULL; + GNUNET_assert (NULL != signkey); + GNUNET_assert (0 == + json_array_append_new (signkeys, + signkey)); } + denominations_by_group = json_array (); + GNUNET_assert (NULL != denominations_by_group); { struct GNUNET_CONTAINER_MultiHashMap *dbg; @@ -1904,6 +2097,82 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) GNUNET_JSON_pack_data_auto ("master_sig", &gf->master_sig)))); } + + accounts = json_array (); + GNUNET_assert (NULL != accounts); + for (unsigned int i = 0; i<kd->accounts_len; i++) + { + const struct TALER_EXCHANGE_WireAccount *acc + = &kd->accounts[i]; + json_t *credit_restrictions; + json_t *debit_restrictions; + + credit_restrictions + = ar_to_json (acc->credit_restrictions_length, + acc->credit_restrictions); + GNUNET_assert (NULL != credit_restrictions); + debit_restrictions + = ar_to_json (acc->debit_restrictions_length, + acc->debit_restrictions); + GNUNET_assert (NULL != debit_restrictions); + GNUNET_assert ( + 0 == + json_array_append_new ( + accounts, + GNUNET_JSON_PACK ( + GNUNET_JSON_pack_string ("payto_uri", + acc->payto_uri), + GNUNET_JSON_pack_allow_null ( + GNUNET_JSON_pack_string ("conversion_url", + acc->conversion_url)), + GNUNET_JSON_pack_array_steal ("debit_restrictions", + debit_restrictions), + GNUNET_JSON_pack_array_steal ("credit_restrictions", + credit_restrictions), + GNUNET_JSON_pack_data_auto ("master_sig", + &acc->master_sig)))); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Serialized %u/%u wire accounts to JSON\n", + (unsigned int) json_array_size (accounts), + kd->accounts_len); + + wire_fees = json_object (); + GNUNET_assert (NULL != wire_fees); + for (unsigned int i = 0; i<kd->fees_len; i++) + { + const struct TALER_EXCHANGE_WireFeesByMethod *fbw + = &kd->fees[i]; + json_t *wf; + + wf = json_array (); + GNUNET_assert (NULL != wf); + for (struct TALER_EXCHANGE_WireAggregateFees *p = fbw->fees_head; + NULL != p; + p = p->next) + { + GNUNET_assert ( + 0 == + json_array_append_new ( + wf, + GNUNET_JSON_PACK ( + TALER_JSON_pack_amount ("wire_fee", + &p->fees.wire), + TALER_JSON_pack_amount ("closing_fee", + &p->fees.closing), + GNUNET_JSON_pack_timestamp ("start_date", + p->start_date), + GNUNET_JSON_pack_timestamp ("end_date", + p->end_date), + GNUNET_JSON_pack_data_auto ("sig", + &p->master_sig)))); + } + GNUNET_assert (0 == + json_object_set_new (wire_fees, + fbw->method, + wf)); + } + recoup = json_array (); GNUNET_assert (NULL != recoup); for (unsigned int i = 0; i<kd->num_denom_keys; i++) @@ -1949,6 +2218,12 @@ TALER_EXCHANGE_keys_to_json (const struct TALER_EXCHANGE_Keys *kd) global_fees), GNUNET_JSON_pack_array_steal ("signkeys", signkeys), + GNUNET_JSON_pack_object_steal ("wire_fees", + wire_fees), + GNUNET_JSON_pack_array_steal ("accounts", + accounts), + GNUNET_JSON_pack_array_steal ("wads", + json_array ()), GNUNET_JSON_pack_array_steal ("denominations", denominations_by_group), GNUNET_JSON_pack_allow_null ( diff --git a/src/lib/exchange_api_wire.c b/src/lib/exchange_api_wire.c deleted file mode 100644 index a428f9fb8..000000000 --- a/src/lib/exchange_api_wire.c +++ /dev/null @@ -1,392 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014-2023 Taler Systems SA - - 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/exchange_api_wire.c - * @brief Implementation of the /wire request of the exchange's HTTP API - * @author Christian Grothoff - */ -#include "platform.h" -#include <jansson.h> -#include <microhttpd.h> /* just for HTTP status codes */ -#include <gnunet/gnunet_util_lib.h> -#include <gnunet/gnunet_curl_lib.h> -#include "taler_exchange_service.h" -#include "taler_json_lib.h" -#include "taler_signatures.h" -#include "exchange_api_handle.h" -#include "exchange_api_curl_defaults.h" - - -/** - * @brief A Wire Handle - */ -struct TALER_EXCHANGE_WireHandle -{ - - /** - * The keys of the exchange this request handle will use - */ - struct TALER_EXCHANGE_Keys *keys; - - /** - * The url for this request. - */ - char *url; - - /** - * Handle for the request. - */ - struct GNUNET_CURL_Job *job; - - /** - * Function to call with the result. - */ - TALER_EXCHANGE_WireCallback cb; - - /** - * Closure for @a cb. - */ - void *cb_cls; - -}; - - -/** - * Frees @a wfm array. - * - * @param wfm fee array to release - * @param wfm_len length of the @a wfm array - */ -static void -free_fees (struct TALER_EXCHANGE_WireFeesByMethod *wfm, - unsigned int wfm_len) -{ - for (unsigned int i = 0; i<wfm_len; i++) - { - struct TALER_EXCHANGE_WireFeesByMethod *wfmi = &wfm[i]; - - while (NULL != wfmi->fees_head) - { - struct TALER_EXCHANGE_WireAggregateFees *fe - = wfmi->fees_head; - - wfmi->fees_head = fe->next; - GNUNET_free (fe); - } - } - GNUNET_free (wfm); -} - - -/** - * Parse wire @a fees and return array. - * - * @param master_pub master public key to use to check signatures - * @param fees json AggregateTransferFee to parse - * @param[out] fees_len set to length of returned array - * @return NULL on error - */ -static struct TALER_EXCHANGE_WireFeesByMethod * -parse_fees (const struct TALER_MasterPublicKeyP *master_pub, - const json_t *fees, - unsigned int *fees_len) -{ - struct TALER_EXCHANGE_WireFeesByMethod *fbm; - unsigned int fbml = json_object_size (fees); - unsigned int i = 0; - const char *key; - const json_t *fee_array; - - fbm = GNUNET_new_array (fbml, - struct TALER_EXCHANGE_WireFeesByMethod); - *fees_len = fbml; - json_object_foreach ((json_t *) fees, key, fee_array) { - struct TALER_EXCHANGE_WireFeesByMethod *fe = &fbm[i++]; - unsigned int idx; - json_t *fee; - - fe->method = key; - fe->fees_head = NULL; - json_array_foreach (fee_array, idx, fee) - { - struct TALER_EXCHANGE_WireAggregateFees *wa - = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("sig", - &wa->master_sig), - TALER_JSON_spec_amount_any ("wire_fee", - &wa->fees.wire), - TALER_JSON_spec_amount_any ("closing_fee", - &wa->fees.closing), - GNUNET_JSON_spec_timestamp ("start_date", - &wa->start_date), - GNUNET_JSON_spec_timestamp ("end_date", - &wa->end_date), - GNUNET_JSON_spec_end () - }; - - wa->next = fe->fees_head; - fe->fees_head = wa; - if (GNUNET_OK != - GNUNET_JSON_parse (fee, - spec, - NULL, - NULL)) - { - GNUNET_break_op (0); - free_fees (fbm, - i); - return NULL; - } - if (GNUNET_OK != - TALER_exchange_offline_wire_fee_verify ( - key, - wa->start_date, - wa->end_date, - &wa->fees, - master_pub, - &wa->master_sig)) - { - GNUNET_break_op (0); - free_fees (fbm, - i); - return NULL; - } - } /* for all fees over time */ - } /* for all methods */ - GNUNET_assert (i == fbml); - return fbm; -} - - -/** - * Function called when we're done processing the - * HTTP /wire request. - * - * @param cls the `struct TALER_EXCHANGE_WireHandle` - * @param response_code HTTP response code, 0 on error - * @param response parsed JSON result, NULL on error - */ -static void -handle_wire_finished (void *cls, - long response_code, - const void *response) -{ - struct TALER_EXCHANGE_WireHandle *wh = cls; - const json_t *j = response; - struct TALER_EXCHANGE_WireResponse wr = { - .hr.reply = j, - .hr.http_status = (unsigned int) response_code - }; - - TALER_LOG_DEBUG ("Checking raw /wire response\n"); - wh->job = NULL; - switch (response_code) - { - case 0: - wr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE; - break; - case MHD_HTTP_OK: - { - const json_t *accounts; - const json_t *fees; - const json_t *wads; - struct TALER_EXCHANGE_WireFeesByMethod *fbm; - struct TALER_MasterPublicKeyP master_pub; - struct GNUNET_JSON_Specification spec[] = { - GNUNET_JSON_spec_fixed_auto ("master_public_key", - &master_pub), - GNUNET_JSON_spec_array_const ("accounts", - &accounts), - GNUNET_JSON_spec_object_const ("fees", - &fees), - GNUNET_JSON_spec_array_const ("wads", - &wads), - GNUNET_JSON_spec_end () - }; - - if (GNUNET_OK != - GNUNET_JSON_parse (j, - spec, - NULL, NULL)) - { - /* bogus reply */ - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - if (0 != GNUNET_memcmp (&wh->keys->master_pub, - &master_pub)) - { - /* bogus reply: master public key in /wire differs from that in /keys */ - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - - wr.details.ok.accounts_len - = json_array_size (accounts); - if (0 == wr.details.ok.accounts_len) - { - /* bogus reply */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - fbm = parse_fees (&master_pub, - fees, - &wr.details.ok.fees_len); - wr.details.ok.fees = fbm; - if (NULL == fbm) - { - /* bogus reply */ - GNUNET_break_op (0); - GNUNET_JSON_parse_free (spec); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - break; - } - - /* parse accounts */ - { - struct TALER_EXCHANGE_WireAccount was[wr.details.ok.accounts_len]; - - wr.details.ok.accounts = was; - if (GNUNET_OK != - TALER_EXCHANGE_parse_accounts (&master_pub, - accounts, - wr.details.ok.accounts_len, - was)) - { - GNUNET_break_op (0); - wr.hr.http_status = 0; - wr.hr.ec = TALER_EC_GENERIC_REPLY_MALFORMED; - } - else if (NULL != wh->cb) - { - wh->cb (wh->cb_cls, - &wr); - wh->cb = NULL; - } - TALER_EXCHANGE_free_accounts ( - wr.details.ok.accounts_len, - was); - } /* end of 'parse accounts */ - free_fees (fbm, - wr.details.ok.fees_len); - GNUNET_JSON_parse_free (spec); - } /* end of MHD_HTTP_OK */ - break; - case MHD_HTTP_BAD_REQUEST: - /* This should never happen, either us or the exchange is buggy - (or API version conflict); just pass JSON reply to the application */ - wr.hr.ec = TALER_JSON_get_error_code (j); - wr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_NOT_FOUND: - /* Nothing really to verify, this should never - happen, we should pass the JSON reply to the application */ - wr.hr.ec = TALER_JSON_get_error_code (j); - wr.hr.hint = TALER_JSON_get_error_hint (j); - break; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - /* Server had an internal issue; we should retry, but this API - leaves this to the application */ - wr.hr.ec = TALER_JSON_get_error_code (j); - wr.hr.hint = TALER_JSON_get_error_hint (j); - break; - default: - /* unexpected response code */ - GNUNET_break_op (0); - wr.hr.ec = TALER_JSON_get_error_code (j); - wr.hr.hint = TALER_JSON_get_error_hint (j); - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Unexpected response code %u/%d for exchange wire\n", - (unsigned int) response_code, - (int) wr.hr.ec); - break; - } - if (NULL != wh->cb) - wh->cb (wh->cb_cls, - &wr); - TALER_EXCHANGE_wire_cancel (wh); -} - - -struct TALER_EXCHANGE_WireHandle * -TALER_EXCHANGE_wire ( - struct GNUNET_CURL_Context *ctx, - const char *url, - struct TALER_EXCHANGE_Keys *keys, - TALER_EXCHANGE_WireCallback wire_cb, - void *wire_cb_cls) -{ - struct TALER_EXCHANGE_WireHandle *wh; - CURL *eh; - - wh = GNUNET_new (struct TALER_EXCHANGE_WireHandle); - wh->cb = wire_cb; - wh->cb_cls = wire_cb_cls; - wh->url = TALER_url_join (url, - "wire", - NULL); - if (NULL == wh->url) - { - GNUNET_free (wh); - return NULL; - } - eh = TALER_EXCHANGE_curl_easy_get_ (wh->url); - if (NULL == eh) - { - GNUNET_break (0); - GNUNET_free (wh->url); - GNUNET_free (wh); - return NULL; - } - GNUNET_break (CURLE_OK == - curl_easy_setopt (eh, - CURLOPT_TIMEOUT, - 60 /* seconds */)); - wh->keys = TALER_EXCHANGE_keys_incref (keys); - wh->job = GNUNET_CURL_job_add_with_ct_json (ctx, - eh, - &handle_wire_finished, - wh); - return wh; -} - - -void -TALER_EXCHANGE_wire_cancel ( - struct TALER_EXCHANGE_WireHandle *wh) -{ - if (NULL != wh->job) - { - GNUNET_CURL_job_cancel (wh->job); - wh->job = NULL; - } - GNUNET_free (wh->url); - TALER_EXCHANGE_keys_decref (wh->keys); - GNUNET_free (wh); -} - - -/* end of exchange_api_wire.c */ diff --git a/src/mhd/mhd_run.c b/src/mhd/mhd_run.c index 7747358ff..8388fbff6 100644 --- a/src/mhd/mhd_run.c +++ b/src/mhd/mhd_run.c @@ -162,8 +162,8 @@ TALER_MHD_daemon_trigger (void) if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); - mhd_task = NULL; - run_daemon (NULL); + mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon, + NULL); } else { diff --git a/src/testing/Makefile.am b/src/testing/Makefile.am index a66aa7428..c659f0ac6 100644 --- a/src/testing/Makefile.am +++ b/src/testing/Makefile.am @@ -113,7 +113,6 @@ libtalertesting_la_SOURCES = \ testing_api_cmd_take_aml_decision.c \ testing_api_cmd_transfer_get.c \ testing_api_cmd_wait.c \ - testing_api_cmd_wire.c \ testing_api_cmd_wire_add.c \ testing_api_cmd_wire_del.c \ testing_api_cmd_withdraw.c \ diff --git a/src/testing/test_exchange_api.c b/src/testing/test_exchange_api.c index 218913509..6e3cce412 100644 --- a/src/testing/test_exchange_api.c +++ b/src/testing/test_exchange_api.c @@ -107,21 +107,6 @@ run (void *cls, struct TALER_TESTING_Interpreter *is) { /** - * Checks made against /wire response. - */ - struct TALER_TESTING_Command wire[] = { - /** - * Check if 'x-taler-bank' wire method is offered - * by the exchange. - */ - TALER_TESTING_cmd_wire ("wire-taler-bank-1", - "x-taler-bank", - NULL, - MHD_HTTP_OK), - TALER_TESTING_cmd_end () - }; - - /** * Test withdrawal plus spending. */ struct TALER_TESTING_Command withdraw[] = { @@ -1238,8 +1223,6 @@ run (void *cls, NULL, true, true), - TALER_TESTING_cmd_batch ("wire", - wire), TALER_TESTING_cmd_batch ("withdraw", withdraw), TALER_TESTING_cmd_batch ("spend", diff --git a/src/testing/test_exchange_api_keys_cherry_picking.c b/src/testing/test_exchange_api_keys_cherry_picking.c index f2a8b88e7..02311db2b 100644 --- a/src/testing/test_exchange_api_keys_cherry_picking.c +++ b/src/testing/test_exchange_api_keys_cherry_picking.c @@ -80,13 +80,6 @@ run (void *cls, "get-exchange-1", true, true), - /** - * Use one of the deserialized keys. - */ - TALER_TESTING_cmd_wire ("wire-with-serialized-keys", - "x-taler-bank", - NULL, - MHD_HTTP_OK), TALER_TESTING_cmd_end () }; |