/* This file is part of TALER (C) 2014-2024 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 General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-merchant-httpd_exchanges.c * @brief logic this HTTPD keeps for each exchange we interact with * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd.h" #include /** * How often do we retry DB transactions with soft errors? */ #define MAX_RETRIES 3 /** * Threshold after which exponential backoff should not increase. */ #define RETRY_BACKOFF_THRESHOLD GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_SECONDS, 60) /** * This is how long /keys long-polls for, so we should * allow this time between requests if there is no * answer. See exchange_api_handle.c. */ #define LONG_POLL_THRESHOLD GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_SECONDS, 120) /** * Information we keep for a pending #MMH_EXCHANGES_keys4exchange() operation. */ struct TMH_EXCHANGES_KeysOperation { /** * Kept in a DLL. */ struct TMH_EXCHANGES_KeysOperation *next; /** * Kept in a DLL. */ struct TMH_EXCHANGES_KeysOperation *prev; /** * Function to call with the result. */ TMH_EXCHANGES_Find2Continuation fc; /** * Closure for @e fc. */ void *fc_cls; /** * Exchange we wait for the /keys for. */ struct TMH_Exchange *my_exchange; /** * Task scheduled to asynchronously return the result to * the find continuation. */ struct GNUNET_SCHEDULER_Task *at; }; /** * Information about wire transfer fees of an exchange, by wire method. */ struct FeesByWireMethod { /** * Kept in a DLL. */ struct FeesByWireMethod *next; /** * Kept in a DLL. */ struct FeesByWireMethod *prev; /** * Wire method these fees are for. */ char *wire_method; /** * Applicable fees, NULL if unknown/error. */ struct TALER_EXCHANGE_WireAggregateFees *af; }; /** * Internal representation for an exchange */ struct TMH_Exchange { /** * Kept in a DLL. */ struct TMH_Exchange *next; /** * Kept in a DLL. */ struct TMH_Exchange *prev; /** * Head of FOs pending for this exchange. */ struct TMH_EXCHANGES_KeysOperation *keys_head; /** * Tail of FOs pending for this exchange. */ struct TMH_EXCHANGES_KeysOperation *keys_tail; /** * (base) URL of the exchange. */ char *url; /** * Currency offered by the exchange according to OUR configuration. */ char *currency; /** * The keys of this exchange. */ struct TALER_EXCHANGE_Keys *keys; /** * Head of wire fees from /wire request. */ struct FeesByWireMethod *wire_fees_head; /** * Tail of wire fees from /wire request. */ struct FeesByWireMethod *wire_fees_tail; /** * Task to retry downloading /keys again. */ struct GNUNET_SCHEDULER_Task *retry_task; /** * When are we willing to force downloading again? */ struct GNUNET_TIME_Absolute first_retry; /** * Current exponential back-off for @e retry_task. */ struct GNUNET_TIME_Relative retry_delay; /** * Master public key of the exchange. */ struct TALER_MasterPublicKeyP master_pub; /** * true if this exchange is from our configuration and * explicitly trusted, false if we need to check each * key to be sure it is trusted. */ bool trusted; }; /** * Head of exchanges we know about. */ static struct TMH_Exchange *exchange_head; /** * Tail of exchanges we know about. */ static struct TMH_Exchange *exchange_tail; /** * Our event handler listening for /keys downloads * being put into the database. */ static struct GNUNET_DB_EventHandler *keys_eh; /** * How many exchanges do we trust (for our configured * currency) as per our configuration? Used for a * sanity-check on startup. */ static int trusted_exchange_count; const struct TALER_MasterPublicKeyP * TMH_EXCHANGES_get_master_pub ( const struct TMH_Exchange *exchange) { GNUNET_break ( (exchange->trusted) || (NULL != exchange->keys) ); return &exchange->master_pub; } const char * TMH_EXCHANGES_get_currency ( const struct TMH_Exchange *exchange) { return exchange->currency; } /** * Lookup exchange by @a exchange_url. Create one * if it does not exist. * * @param exchange_url base URL to match against * @return fresh entry if exchange was not yet known */ static struct TMH_Exchange * lookup_exchange (const char *exchange_url) { for (struct TMH_Exchange *exchange = exchange_head; NULL != exchange; exchange = exchange->next) if (0 == strcmp (exchange->url, exchange_url)) return exchange; return NULL; } /** * Check if we have any remaining pending requests for the * given @a exchange, and if we have the required data, call * the callback. * * @param exchange the exchange to check for pending find operations */ static void process_find_operations (struct TMH_Exchange *exchange) { struct GNUNET_TIME_Timestamp now; now = GNUNET_TIME_timestamp_get (); for (struct FeesByWireMethod *fbw = exchange->wire_fees_head; NULL != fbw; fbw = fbw->next) { while ( (NULL != fbw->af) && (GNUNET_TIME_timestamp_cmp (fbw->af->end_date, <, now)) ) { struct TALER_EXCHANGE_WireAggregateFees *af = fbw->af; fbw->af = af->next; GNUNET_free (af); } if (NULL == fbw->af) { /* Disagreement on the current time */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Exchange has no wire fees configured for `%s' wire method\n", fbw->wire_method); } else if (GNUNET_TIME_timestamp_cmp (fbw->af->start_date, >, now)) { /* Disagreement on the current time */ GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Exchange's earliest fee is %s ahead of our time. Clock skew issue?\n", GNUNET_TIME_relative2s ( GNUNET_TIME_absolute_get_remaining ( fbw->af->start_date.abs_time), true)); } } /* for all wire methods */ { struct TMH_EXCHANGES_KeysOperation *kon; kon = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Processing find operations for `%s'\n", exchange->url); for (struct TMH_EXCHANGES_KeysOperation *ko = exchange->keys_head; NULL != ko; ko = kon) { kon = ko->next; ko->fc (ko->fc_cls, exchange->keys, exchange); TMH_EXCHANGES_keys4exchange_cancel (ko); } } } /** * Function called with information about the wire fees for each wire method. * Stores the wire fees within our internal data structures for later use. * * @param exchange connection to the exchange * @param master_pub public key of the exchange * @param num_methods number of wire methods supported * @param fbm wire fees by method * @return #GNUNET_OK on success */ static enum GNUNET_GenericReturnValue process_wire_fees ( struct TMH_Exchange *exchange, const struct TALER_MasterPublicKeyP *master_pub, unsigned int num_methods, const struct TALER_EXCHANGE_WireFeesByMethod fbm[static num_methods]) { for (unsigned int i = 0; iwire_fees_head; NULL != f; f = f->next) if (0 == strcasecmp (wire_method, f->wire_method)) break; if (NULL == f) { f = GNUNET_new (struct FeesByWireMethod); f->wire_method = GNUNET_strdup (wire_method); GNUNET_CONTAINER_DLL_insert (exchange->wire_fees_head, exchange->wire_fees_tail, f); } endp = f->af; while ( (NULL != endp) && (NULL != endp->next) ) endp = endp->next; while ( (NULL != endp) && (NULL != fees) && (GNUNET_TIME_timestamp_cmp (fees->start_date, <, endp->end_date)) ) fees = fees->next; if ( (NULL != endp) && (NULL != fees) && (GNUNET_TIME_timestamp_cmp (fees->start_date, !=, endp->end_date)) ) { /* Hole or overlap in the fee structure, not allowed! */ GNUNET_break_op (0); return GNUNET_SYSERR; } while (NULL != fees) { struct TALER_EXCHANGE_WireAggregateFees *af; af = GNUNET_new (struct TALER_EXCHANGE_WireAggregateFees); *af = *fees; af->next = NULL; if (NULL == endp) f->af = af; else endp->next = af; endp = af; fees = fees->next; } /* all fees for this method */ } /* for all methods (i) */ return GNUNET_OK; } /** * Retry getting keys from the given exchange in the closure. * * @param cls the `struct TMH_Exchange *` */ static void retry_exchange (void *cls) { struct TMH_Exchange *exchange = cls; struct GNUNET_DB_EventHeaderP es = { .size = ntohs (sizeof (es)), .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS) }; exchange->retry_task = NULL; exchange->retry_delay = GNUNET_TIME_randomized_backoff (exchange->retry_delay, RETRY_BACKOFF_THRESHOLD); exchange->first_retry = GNUNET_TIME_relative_to_absolute ( exchange->retry_delay); TMH_db->event_notify (TMH_db->cls, &es, exchange->url, strlen (exchange->url) + 1); } /** * Task to asynchronously return keys operation result to caller. * * @param cls a `struct TMH_EXCHANGES_KeysOperation` */ static void return_keys (void *cls) { struct TMH_EXCHANGES_KeysOperation *fo = cls; struct TMH_Exchange *exchange = fo->my_exchange; fo->at = NULL; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Returning key data for %s instantly\n", exchange->url); process_find_operations (exchange); } struct TMH_EXCHANGES_KeysOperation * TMH_EXCHANGES_keys4exchange ( const char *chosen_exchange, bool force_download, TMH_EXCHANGES_Find2Continuation fc, void *fc_cls) { struct TMH_Exchange *exchange; struct TMH_EXCHANGES_KeysOperation *fo; if (NULL == TMH_curl_ctx) { GNUNET_break (0); return NULL; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Trying to find chosen exchange `%s'\n", chosen_exchange); exchange = lookup_exchange (chosen_exchange); if (NULL == exchange) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Exchange `%s' not configured\n", chosen_exchange); return NULL; } if (! exchange->trusted) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Exchange `%s' not trusted\n", chosen_exchange); return NULL; } fo = GNUNET_new (struct TMH_EXCHANGES_KeysOperation); fo->fc = fc; fo->fc_cls = fc_cls; fo->my_exchange = exchange; GNUNET_CONTAINER_DLL_insert (exchange->keys_head, exchange->keys_tail, fo); if ( (NULL == exchange->keys) && (! force_download) ) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Waiting for `%skeys' already, failing query instantly\n", exchange->url); GNUNET_assert (NULL == fo->at); fo->at = GNUNET_SCHEDULER_add_now (&return_keys, fo); return fo; } if ( (NULL != exchange->keys) && (! force_download) && (GNUNET_TIME_absolute_is_future ( exchange->keys->key_data_expiration.abs_time)) ) { /* We have a valid reply, immediately return result */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "The exchange `%s' is ready\n", exchange->url); GNUNET_assert (NULL == fo->at); fo->at = GNUNET_SCHEDULER_add_now (&return_keys, fo); return fo; } if ( (force_download) && (GNUNET_TIME_absolute_is_future (exchange->first_retry)) && (NULL != exchange->keys) ) { /* Return results immediately. */ fo->at = GNUNET_SCHEDULER_add_now (&return_keys, fo); /* *no* return here, we MAY schedule a 'retry_task' in the next block if there isn't one yet */ } if (NULL == exchange->retry_task) exchange->retry_task = GNUNET_SCHEDULER_add_at (exchange->first_retry, &retry_exchange, exchange); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Next %skeys request scheduled for %s\n", exchange->url, GNUNET_TIME_absolute2s ( exchange->first_retry)); /* No activity to launch, we are already doing so. */ return fo; } void TMH_EXCHANGES_keys4exchange_cancel ( struct TMH_EXCHANGES_KeysOperation *fo) { struct TMH_Exchange *exchange = fo->my_exchange; if (NULL != fo->at) { GNUNET_SCHEDULER_cancel (fo->at); fo->at = NULL; } GNUNET_CONTAINER_DLL_remove (exchange->keys_head, exchange->keys_tail, fo); GNUNET_free (fo); } /** * Obtain applicable fees for @a exchange and @a wire_method. * * @param exchange the exchange to query * @param now current time * @param wire_method the wire method we want the fees for * @return NULL if we do not have fees for this method yet */ static const struct FeesByWireMethod * get_wire_fees (const struct TMH_Exchange *exchange, struct GNUNET_TIME_Timestamp now, const char *wire_method) { for (struct FeesByWireMethod *fbw = exchange->wire_fees_head; NULL != fbw; fbw = fbw->next) { if (0 == strcasecmp (fbw->wire_method, wire_method) ) { struct TALER_EXCHANGE_WireAggregateFees *af; /* Advance through list up to current time */ while ( (NULL != (af = fbw->af)) && (GNUNET_TIME_timestamp_cmp (now, >=, af->end_date)) ) { fbw->af = af->next; GNUNET_free (af); } return fbw; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange supports `%s' as a wire method (but we do not use that one)\n", fbw->wire_method); } return NULL; } /** * Free @a exchange. * * @param[in] exchange entry to free */ static void free_exchange_entry (struct TMH_Exchange *exchange) { struct FeesByWireMethod *f; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Releasing %s exchange %s\n", exchange->trusted ? "trusted" : "untrusted", exchange->url); GNUNET_CONTAINER_DLL_remove (exchange_head, exchange_tail, exchange); while (NULL != (f = exchange->wire_fees_head)) { struct TALER_EXCHANGE_WireAggregateFees *af; GNUNET_CONTAINER_DLL_remove (exchange->wire_fees_head, exchange->wire_fees_tail, f); while (NULL != (af = f->af)) { f->af = af->next; GNUNET_free (af); } GNUNET_free (f->wire_method); GNUNET_free (f); } TALER_EXCHANGE_keys_decref (exchange->keys); exchange->keys = NULL; if (NULL != exchange->retry_task) { GNUNET_SCHEDULER_cancel (exchange->retry_task); exchange->retry_task = NULL; } GNUNET_assert (NULL == exchange->keys_head); GNUNET_assert (NULL == exchange->keys_tail); GNUNET_free (exchange->currency); GNUNET_free (exchange->url); GNUNET_free (exchange); } enum GNUNET_GenericReturnValue TMH_EXCHANGES_lookup_wire_fee ( const struct TMH_Exchange *exchange, const char *wire_method, struct TALER_Amount *wire_fee) { const struct FeesByWireMethod *fbm; const struct TALER_EXCHANGE_WireAggregateFees *af; fbm = get_wire_fees (exchange, GNUNET_TIME_timestamp_get (), wire_method); if (NULL == fbm) return GNUNET_NO; af = fbm->af; *wire_fee = af->fees.wire; return GNUNET_OK; } enum GNUNET_GenericReturnValue TMH_exchange_check_debit ( const char *instance_id, const struct TMH_Exchange *exchange, const struct TMH_WireMethod *wm, struct TALER_Amount *max_amount) { const struct TALER_EXCHANGE_Keys *keys = exchange->keys; struct TALER_NormalizedPayto np; bool account_ok = false; bool have_kyc = false; struct TALER_Amount kyc_limit; bool unlimited = true; if (NULL == keys) return GNUNET_SYSERR; if (0 != strcasecmp (keys->currency, max_amount->currency)) { GNUNET_break (0); return GNUNET_SYSERR; } np = TALER_payto_normalize (wm->payto_uri); /* For all accounts of the exchange */ for (unsigned int i = 0; iaccounts_len; i++) { const struct TALER_EXCHANGE_WireAccount *account = &keys->accounts[i]; if (NULL != account->conversion_url) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange %s account requires currency conversion (not supported)\n", exchange->url); continue; /* never use accounts with conversion */ } if (GNUNET_YES != TALER_EXCHANGE_test_account_allowed (account, false, /* debit */ np)) continue; account_ok = true; /* Check legitimization limits we have with this account at this exchange, if we have any, apply them */ } GNUNET_free (np.normalized_payto); { bool kyc_ok = false; json_t *jlimits = NULL; enum GNUNET_DB_QueryStatus qs; qs = TMH_db->get_kyc_limits (TMH_db->cls, wm->payto_uri, instance_id, exchange->url, &kyc_ok, &jlimits); GNUNET_break (qs >= 0); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "get_kyc_limits for %s at %s returned %s/%s\n", wm->payto_uri.full_payto, exchange->url, kyc_ok ? "KYC OK" : "KYC missing", NULL == jlimits ? "default limits" : "custom limits"); if ( (qs > 0) && (NULL != jlimits) ) { json_t *jlimit; size_t idx; json_array_foreach (jlimits, idx, jlimit) { enum TALER_KYCLOGIC_KycTriggerEvent ot; struct TALER_Amount threshold; bool soft_limit = false; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_kycte ("operation_type", &ot), TALER_JSON_spec_amount_any ("threshold", &threshold), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_bool ("soft_limit", &soft_limit), NULL), GNUNET_JSON_spec_end () }; if (GNUNET_OK != GNUNET_JSON_parse (jlimit, spec, NULL, NULL)) { GNUNET_break (0); continue; } if (soft_limit) continue; if ( (TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT != ot) && (TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION != ot) ) continue; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "KYC rule %u with limit %s applies\n", (unsigned int) idx, TALER_amount2s (&threshold)); if (unlimited) { unlimited = false; kyc_limit = threshold; } else { TALER_amount_min (&kyc_limit, &kyc_limit, &threshold); } } json_decref (jlimits); } if (kyc_ok) have_kyc = true; } if (! unlimited) TALER_amount_min (max_amount, max_amount, &kyc_limit); /* apply both deposit and transaction limits */ if ( (! have_kyc) && (TALER_EXCHANGE_keys_evaluate_zero_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) || TALER_EXCHANGE_keys_evaluate_zero_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION)) ) { GNUNET_assert (GNUNET_OK == TALER_amount_set_zero ( max_amount->currency, max_amount)); } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Evaluating default limits of %s\n", exchange->url); TALER_EXCHANGE_keys_evaluate_hard_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT, max_amount); TALER_EXCHANGE_keys_evaluate_hard_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION, max_amount); if (TALER_EXCHANGE_keys_evaluate_zero_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT) || TALER_EXCHANGE_keys_evaluate_zero_limits ( keys, TALER_KYCLOGIC_KYC_TRIGGER_TRANSACTION)) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Operation is zero-limited by default\n"); GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (max_amount->currency, max_amount)); } } return account_ok ? GNUNET_YES : GNUNET_NO; } void TMH_exchange_get_trusted (TMH_ExchangeCallback cb, void *cb_cls) { for (const struct TMH_Exchange *exchange = exchange_head; NULL != exchange; exchange = exchange->next) { if (! exchange->trusted) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Exchange %s not trusted, skipping!\n", exchange->url); continue; } cb (cb_cls, exchange->url, exchange); } } bool TMH_test_exchange_configured_for_currency ( const char *currency) { for (const struct TMH_Exchange *exchange = exchange_head; NULL != exchange; exchange = exchange->next) { if (! exchange->trusted) continue; if (NULL == exchange->currency) continue; if (0 == strcmp (currency, exchange->currency)) return true; } return false; } /** * (Re)load of keys from DB. * * @param exchange exchange to reload keys of */ static void reload_exchange_keys (struct TMH_Exchange *exchange) { enum GNUNET_DB_QueryStatus qs; struct TALER_EXCHANGE_Keys *keys; qs = TMH_db->select_exchange_keys (TMH_db->cls, exchange->url, &keys); if (qs < 0) { GNUNET_break (0); return; } if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "No keys yet for `%s'\n", exchange->url); return; } exchange->retry_delay = GNUNET_TIME_UNIT_ZERO; if (NULL == exchange->currency) exchange->currency = GNUNET_strdup (keys->currency); if (0 != strcmp (keys->currency, exchange->currency)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "/keys cached in our database are for currency `%s', but we expected `%s'\n", keys->currency, exchange->currency); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Loaded /keys from database with %u accounts\n", keys->accounts_len); if (GNUNET_OK != process_wire_fees (exchange, &keys->master_pub, keys->fees_len, keys->fees)) { /* invalid wire fee specification given */ GNUNET_break_op (0); /* but: we can continue anyway, things may just not work, but probably better than to not keep going. */ return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Reloaded /keys of %s from database\n", exchange->url); TALER_EXCHANGE_keys_decref (exchange->keys); exchange->keys = keys; if ( (exchange->trusted) && (0 != GNUNET_memcmp (&exchange->master_pub, &keys->master_pub)) ) { /* master pub differs => do not trust the exchange (without auditor) */ GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Master public key of exchange `%s' differs from our configuration. Not trusting exchange.\n", exchange->url); exchange->trusted = false; } if (! exchange->trusted) { exchange->master_pub = keys->master_pub; for (struct TMH_Exchange *e = exchange_head; NULL != e; e = e->next) { if (e == exchange) continue; if (! e->trusted) continue; if (0 == GNUNET_memcmp (&e->master_pub, &exchange->master_pub)) exchange->trusted = true; /* same exchange, different URL => trust applies */ } } process_find_operations (exchange); } /** * Function called on each configuration section. Finds sections * about exchanges, parses the entries. * * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` * @param section name of the section */ static void accept_exchanges (void *cls, const char *section) { const struct GNUNET_CONFIGURATION_Handle *cfg = cls; char *url; char *mks; struct TMH_Exchange *exchange; char *currency; if (GNUNET_SYSERR == trusted_exchange_count) return; if (0 != strncasecmp (section, "merchant-exchange-", strlen ("merchant-exchange-"))) return; if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, section, "DISABLED")) return; if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "EXCHANGE_BASE_URL", &url)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "EXCHANGE_BASE_URL"); return; } exchange = lookup_exchange (url); if (NULL != exchange) { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "EXCHANGE_BASE_URL", "same base URL specified again"); GNUNET_free (url); return; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, section, "CURRENCY", ¤cy)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, section, "CURRENCY"); GNUNET_free (url); return; } exchange = GNUNET_new (struct TMH_Exchange); exchange->url = url; exchange->currency = currency; GNUNET_CONTAINER_DLL_insert (exchange_head, exchange_tail, exchange); if (GNUNET_OK == GNUNET_CONFIGURATION_get_value_string (cfg, section, "MASTER_KEY", &mks)) { if (GNUNET_OK == GNUNET_CRYPTO_eddsa_public_key_from_string ( mks, strlen (mks), &exchange->master_pub.eddsa_pub)) { exchange->trusted = true; trusted_exchange_count++; } else { GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, section, "MASTER_KEY", "malformed EdDSA key"); } GNUNET_free (mks); } else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "MASTER_KEY missing in section '%s', not trusting exchange\n", section); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setup exchange %s as %s\n", exchange->url, exchange->trusted ? "trusted" : "untrusted"); reload_exchange_keys (exchange); if (NULL != exchange->retry_task) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Exchange at `%s' configured in multiple configuration sections (see `%s')!\n", exchange->url, section); trusted_exchange_count = GNUNET_SYSERR; return; } exchange->retry_task = GNUNET_SCHEDULER_add_now (&retry_exchange, exchange); } /** * Trigger (re)loading of keys from DB. * * @param cls NULL * @param extra base URL of the exchange that changed * @param extra_len number of bytes in @a extra */ static void update_exchange_keys (void *cls, const void *extra, size_t extra_len) { const char *url = extra; struct TMH_Exchange *exchange; if ( (NULL == extra) || (0 == extra_len) ) { GNUNET_break (0); return; } if ('\0' != url[extra_len - 1]) { GNUNET_break (0); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Received keys change notification: reload `%s'\n", url); exchange = lookup_exchange (url); GNUNET_break (NULL != exchange); if (NULL != exchange) reload_exchange_keys (exchange); } bool TMH_EXCHANGES_is_below_limit ( const struct TALER_EXCHANGE_Keys *keys, enum TALER_KYCLOGIC_KycTriggerEvent operation_type, const struct TALER_Amount *amount) { if (NULL == keys) { /* should only be called after we have keys! */ GNUNET_break (0); return false; } for (unsigned int i = 0; ihard_limits_length; i++) { const struct TALER_EXCHANGE_AccountLimit *al = &keys->hard_limits[i]; if (operation_type != al->operation_type) continue; if (-1 == TALER_amount_cmp (&al->threshold, amount)) /* -1: threshold < amount */ return false; } return true; } void TMH_EXCHANGES_get_limit ( const char *exchange_url, enum TALER_KYCLOGIC_KycTriggerEvent operation_type, struct TALER_Amount *amount) { struct TMH_Exchange *exchange; const struct TALER_EXCHANGE_Keys *keys; exchange = lookup_exchange (exchange_url); if ( (NULL == exchange) || (NULL == (keys = exchange->keys)) ) { GNUNET_assert (GNUNET_OK == TALER_amount_set_zero ( amount->currency, amount)); return; } for (unsigned int i = 0; ihard_limits_length; i++) { const struct TALER_EXCHANGE_AccountLimit *al = &keys->hard_limits[i]; if (operation_type != al->operation_type) continue; TALER_amount_min (amount, amount, &al->threshold); } } enum GNUNET_GenericReturnValue TMH_EXCHANGES_init (const struct GNUNET_CONFIGURATION_Handle *cfg) { /* get exchanges from the merchant configuration and try to connect to them */ { struct GNUNET_DB_EventHeaderP es = { .size = ntohs (sizeof (es)), .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) }; keys_eh = TMH_db->event_listen (TMH_db->cls, &es, GNUNET_TIME_UNIT_FOREVER_REL, &update_exchange_keys, NULL); } GNUNET_CONFIGURATION_iterate_sections (cfg, &accept_exchanges, (void *) cfg); /* build JSON with list of trusted exchanges (will be included in contracts) */ return trusted_exchange_count; } void TMH_EXCHANGES_done () { if (NULL != keys_eh) { TMH_db->event_listen_cancel (keys_eh); keys_eh = NULL; } while (NULL != exchange_head) free_exchange_entry (exchange_head); } /* end of taler-merchant-httpd_exchanges.c */