commit 2cb7bc8f47e4455c2272fc22755b39c2f76adb6b
parent 43bb9eca02d40c97db776723f3fb3c1edbac8265
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 26 Mar 2026 17:03:41 +0100
rename backend files to be more consistent with endpoint names
Diffstat:
211 files changed, 33130 insertions(+), 33128 deletions(-)
diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am
@@ -145,72 +145,72 @@ taler_exchange_wirewatch_LDADD = \
taler_exchange_httpd_SOURCES = \
taler-exchange-httpd.c taler-exchange-httpd.h \
- taler-exchange-httpd_aml-accounts-get.c taler-exchange-httpd_aml-accounts-get.h \
- taler-exchange-httpd_aml-attributes-get.c taler-exchange-httpd_aml-attributes-get.h \
- taler-exchange-httpd_aml-decision.c taler-exchange-httpd_aml-decision.h \
- taler-exchange-httpd_aml-decisions-get.c \
- taler-exchange-httpd_aml-legitimization-measures-get.c taler-exchange-httpd_aml-legitimization-measures-get.h \
- taler-exchange-httpd_aml-statistics-get.c taler-exchange-httpd_aml-statistics-get.h \
- taler-exchange-httpd_aml-measures-get.c taler-exchange-httpd_aml-measures-get.h \
- taler-exchange-httpd_aml-transfer-get.c taler-exchange-httpd_aml-transfer-get.h \
- taler-exchange-httpd_auditors.c taler-exchange-httpd_auditors.h \
- taler-exchange-httpd_batch-deposit.c taler-exchange-httpd_batch-deposit.h \
- taler-exchange-httpd_blinding-prepare.c taler-exchange-httpd_blinding-prepare.h \
- taler-exchange-httpd_coins_get.c taler-exchange-httpd_coins_get.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h \
+ taler-exchange-httpd_post-aml-OFFICER_PUB-decision.c taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-decisions.c \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.c taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-measures.c taler-exchange-httpd_get-aml-OFFICER_PUB-measures.h \
+ taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.c taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h \
+ taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h \
+ taler-exchange-httpd_post-batch-deposit.c taler-exchange-httpd_post-batch-deposit.h \
+ taler-exchange-httpd_post-blinding-prepare.c taler-exchange-httpd_post-blinding-prepare.h \
+ taler-exchange-httpd_get-coins-COIN_PUB-history.c taler-exchange-httpd_get-coins-COIN_PUB-history.h \
taler-exchange-httpd_common_deposit.c taler-exchange-httpd_common_deposit.h \
taler-exchange-httpd_common_kyc.c taler-exchange-httpd_common_kyc.h \
- taler-exchange-httpd_config.c taler-exchange-httpd_config.h \
- taler-exchange-httpd_contract.c taler-exchange-httpd_contract.h \
+ taler-exchange-httpd_get-config.c taler-exchange-httpd_get-config.h \
+ taler-exchange-httpd_get-contracts-CONTRACT_PUB.c taler-exchange-httpd_get-contracts-CONTRACT_PUB.h \
taler-exchange-httpd_db.c taler-exchange-httpd_db.h \
- taler-exchange-httpd_deposits_get.c taler-exchange-httpd_deposits_get.h \
+ taler-exchange-httpd_get-deposits.c taler-exchange-httpd_get-deposits.h \
taler-exchange-httpd_extensions.c taler-exchange-httpd_extensions.h \
- taler-exchange-httpd_keys.c taler-exchange-httpd_keys.h \
- taler-exchange-httpd_kyc-check.c taler-exchange-httpd_kyc-check.h \
- taler-exchange-httpd_kyc-info.c taler-exchange-httpd_kyc-info.h \
- taler-exchange-httpd_kyc-proof.c taler-exchange-httpd_kyc-proof.h \
- taler-exchange-httpd_kyc-start.c taler-exchange-httpd_kyc-start.h \
- taler-exchange-httpd_kyc-upload.c taler-exchange-httpd_kyc-upload.h \
- taler-exchange-httpd_kyc-wallet.c taler-exchange-httpd_kyc-wallet.h \
+ taler-exchange-httpd_get-keys.c taler-exchange-httpd_get-keys.h \
+ taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.c taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h \
+ taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.c taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.h \
+ taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.c taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.h \
+ taler-exchange-httpd_post-kyc-start-ID.c taler-exchange-httpd_post-kyc-start-ID.h \
+ taler-exchange-httpd_post-kyc-upload-ID.c taler-exchange-httpd_post-kyc-upload-ID.h \
+ taler-exchange-httpd_post-kyc-wallet.c taler-exchange-httpd_post-kyc-wallet.h \
taler-exchange-httpd_kyc-webhook.c taler-exchange-httpd_kyc-webhook.h \
taler-exchange-httpd_management.h \
- taler-exchange-httpd_management_aml-officers.c \
- taler-exchange-httpd_management_auditors.c \
- taler-exchange-httpd_management_auditors_AP_disable.c \
- taler-exchange-httpd_management_denominations_HDP_revoke.c \
- taler-exchange-httpd_management_drain.c \
- taler-exchange-httpd_management_extensions.c \
- taler-exchange-httpd_management_global_fees.c \
- taler-exchange-httpd_management_partners.c \
- taler-exchange-httpd_management_post_keys.c \
- taler-exchange-httpd_management_signkey_EP_revoke.c \
- taler-exchange-httpd_management_wire_enable.c \
- taler-exchange-httpd_management_wire_disable.c \
- taler-exchange-httpd_management_wire_fees.c \
- taler-exchange-httpd_melt.c taler-exchange-httpd_melt.h \
- taler-exchange-httpd_metrics.c taler-exchange-httpd_metrics.h \
+ taler-exchange-httpd_post-management-aml-officers.c \
+ taler-exchange-httpd_post-management-auditors.c \
+ taler-exchange-httpd_post-management-auditors-AUDITOR_PUB-disable.c \
+ taler-exchange-httpd_post-management-denominations-H_DENOM_PUB-revoke.c \
+ taler-exchange-httpd_post-management-drain.c \
+ taler-exchange-httpd_post-management-extensions.c \
+ taler-exchange-httpd_post-management-global-fees.c \
+ taler-exchange-httpd_post-management-partners.c \
+ taler-exchange-httpd_post-management-keys.c \
+ taler-exchange-httpd_post-management-signkeys-EXCHANGE_PUB-revoke.c \
+ taler-exchange-httpd_post-management-wire.c \
+ taler-exchange-httpd_post-management-wire-disable.c \
+ taler-exchange-httpd_post-management-wire-fee.c \
+ taler-exchange-httpd_post-melt.c taler-exchange-httpd_post-melt.h \
+ taler-exchange-httpd_get-metrics.c taler-exchange-httpd_get-metrics.h \
taler-exchange-httpd_mhd.c taler-exchange-httpd_mhd.h \
- taler-exchange-httpd_purses_create.c taler-exchange-httpd_purses_create.h \
- taler-exchange-httpd_purses_deposit.c taler-exchange-httpd_purses_deposit.h \
- taler-exchange-httpd_purses_delete.c taler-exchange-httpd_purses_delete.h \
- taler-exchange-httpd_purses_get.c taler-exchange-httpd_purses_get.h \
- taler-exchange-httpd_purses_merge.c taler-exchange-httpd_purses_merge.h \
- taler-exchange-httpd_recoup.c taler-exchange-httpd_recoup.h \
- taler-exchange-httpd_recoup-refresh.c taler-exchange-httpd_recoup-refresh.h \
- taler-exchange-httpd_refund.c taler-exchange-httpd_refund.h \
- taler-exchange-httpd_reserves_attest.c taler-exchange-httpd_reserves_attest.h \
- taler-exchange-httpd_reserves_close.c taler-exchange-httpd_reserves_close.h \
- taler-exchange-httpd_reserves_get.c taler-exchange-httpd_reserves_get.h \
- taler-exchange-httpd_reserves_get_attest.c taler-exchange-httpd_reserves_get_attest.h \
- taler-exchange-httpd_reserves_history.c taler-exchange-httpd_reserves_history.h \
- taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \
- taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \
+ taler-exchange-httpd_post-purses-PURSE_PUB-create.c taler-exchange-httpd_post-purses-PURSE_PUB-create.h \
+ taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h \
+ taler-exchange-httpd_delete-purses-PURSE_PUB.c taler-exchange-httpd_delete-purses-PURSE_PUB.h \
+ taler-exchange-httpd_get-purses-PURSE_PUB-merge.c taler-exchange-httpd_get-purses-PURSE_PUB-merge.h \
+ taler-exchange-httpd_post-purses-PURSE_PUB-merge.c taler-exchange-httpd_post-purses-PURSE_PUB-merge.h \
+ taler-exchange-httpd_post-recoup-withdraw.c taler-exchange-httpd_post-recoup-withdraw.h \
+ taler-exchange-httpd_post-recoup-refresh.c taler-exchange-httpd_post-recoup-refresh.h \
+ taler-exchange-httpd_post-coins-COIN_PUB-refund.c taler-exchange-httpd_post-coins-COIN_PUB-refund.h \
+ taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h \
+ taler-exchange-httpd_post-reserves-RESERVE_PUB-close.c taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h \
+ taler-exchange-httpd_get-reserves-RESERVE_PUB.c taler-exchange-httpd_get-reserves-RESERVE_PUB.h \
+ taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h \
+ taler-exchange-httpd_get-reserves-RESERVE_PUB-history.c taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h \
+ taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h \
+ taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.c taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h \
taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \
- taler-exchange-httpd_reveal-melt.c taler-exchange-httpd_reveal-melt.h \
- taler-exchange-httpd_reveal-withdraw.c taler-exchange-httpd_reveal-withdraw.h \
- taler-exchange-httpd_spa.c taler-exchange-httpd_spa.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_withdraw.c taler-exchange-httpd_withdraw.h
+ taler-exchange-httpd_post-reveal-melt.c taler-exchange-httpd_post-reveal-melt.h \
+ taler-exchange-httpd_post-reveal-withdraw.c taler-exchange-httpd_post-reveal-withdraw.h \
+ taler-exchange-httpd_get-SPA.c taler-exchange-httpd_get-SPA.h \
+ taler-exchange-httpd_get-TERMS.c taler-exchange-httpd_get-TERMS.h \
+ taler-exchange-httpd_get-transfers-WTID.c taler-exchange-httpd_get-transfers-WTID.h \
+ taler-exchange-httpd_post-withdraw.c taler-exchange-httpd_post-withdraw.h
taler_exchange_httpd_LDADD = \
diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c
@@ -31,55 +31,56 @@
#include "taler/taler_kyclogic_lib.h"
#include "taler/taler_templating_lib.h"
#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_withdraw.h"
-#include "taler-exchange-httpd_aml-accounts-get.h"
-#include "taler-exchange-httpd_aml-attributes-get.h"
-#include "taler-exchange-httpd_aml-decision.h"
-#include "taler-exchange-httpd_aml-legitimization-measures-get.h"
-#include "taler-exchange-httpd_aml-statistics-get.h"
-#include "taler-exchange-httpd_aml-transfer-get.h"
-#include "taler-exchange-httpd_aml-measures-get.h"
-#include "taler-exchange-httpd_auditors.h"
-#include "taler-exchange-httpd_batch-deposit.h"
-#include "taler-exchange-httpd_blinding-prepare.h"
-#include "taler-exchange-httpd_coins_get.h"
-#include "taler-exchange-httpd_config.h"
-#include "taler-exchange-httpd_contract.h"
-#include "taler-exchange-httpd_deposits_get.h"
+#include "taler-exchange-httpd_post-withdraw.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.h"
+#include \
+ "taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-measures.h"
+#include "taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h"
+#include "taler-exchange-httpd_post-batch-deposit.h"
+#include "taler-exchange-httpd_post-blinding-prepare.h"
+#include "taler-exchange-httpd_get-coins-COIN_PUB-history.h"
+#include "taler-exchange-httpd_get-config.h"
+#include "taler-exchange-httpd_get-contracts-CONTRACT_PUB.h"
+#include "taler-exchange-httpd_get-deposits.h"
#include "taler-exchange-httpd_extensions.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_kyc-check.h"
-#include "taler-exchange-httpd_kyc-info.h"
-#include "taler-exchange-httpd_kyc-proof.h"
-#include "taler-exchange-httpd_kyc-start.h"
-#include "taler-exchange-httpd_kyc-upload.h"
-#include "taler-exchange-httpd_kyc-wallet.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h"
+#include "taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.h"
+#include "taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.h"
+#include "taler-exchange-httpd_post-kyc-start-ID.h"
+#include "taler-exchange-httpd_post-kyc-upload-ID.h"
+#include "taler-exchange-httpd_post-kyc-wallet.h"
#include "taler-exchange-httpd_kyc-webhook.h"
-#include "taler-exchange-httpd_aml-decision.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h"
#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_melt.h"
-#include "taler-exchange-httpd_metrics.h"
+#include "taler-exchange-httpd_post-melt.h"
+#include "taler-exchange-httpd_get-metrics.h"
#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_purses_create.h"
-#include "taler-exchange-httpd_purses_deposit.h"
-#include "taler-exchange-httpd_purses_get.h"
-#include "taler-exchange-httpd_purses_delete.h"
-#include "taler-exchange-httpd_purses_merge.h"
-#include "taler-exchange-httpd_recoup.h"
-#include "taler-exchange-httpd_recoup-refresh.h"
-#include "taler-exchange-httpd_refund.h"
-#include "taler-exchange-httpd_reserves_attest.h"
-#include "taler-exchange-httpd_reserves_close.h"
-#include "taler-exchange-httpd_reserves_get.h"
-#include "taler-exchange-httpd_reserves_get_attest.h"
-#include "taler-exchange-httpd_reserves_history.h"
-#include "taler-exchange-httpd_reserves_open.h"
-#include "taler-exchange-httpd_reserves_purse.h"
-#include "taler-exchange-httpd_reveal-withdraw.h"
-#include "taler-exchange-httpd_reveal-melt.h"
-#include "taler-exchange-httpd_spa.h"
-#include "taler-exchange-httpd_terms.h"
-#include "taler-exchange-httpd_transfers_get.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-create.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h"
+#include "taler-exchange-httpd_get-purses-PURSE_PUB-merge.h"
+#include "taler-exchange-httpd_delete-purses-PURSE_PUB.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-merge.h"
+#include "taler-exchange-httpd_post-recoup-withdraw.h"
+#include "taler-exchange-httpd_post-recoup-refresh.h"
+#include "taler-exchange-httpd_post-coins-COIN_PUB-refund.h"
+#include "taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h"
+#include "taler-exchange-httpd_get-reserves-RESERVE_PUB.h"
+#include "taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h"
+#include "taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h"
+#include "taler-exchange-httpd_post-reveal-withdraw.h"
+#include "taler-exchange-httpd_post-reveal-melt.h"
+#include "taler-exchange-httpd_get-SPA.h"
+#include "taler-exchange-httpd_get-TERMS.h"
+#include "taler-exchange-httpd_get-transfers-WTID.h"
#include "taler/taler_exchangedb_lib.h"
#include "taler/taler_exchangedb_plugin.h"
#include "taler/taler_extensions.h"
diff --git a/src/exchange/taler-exchange-httpd_aml-accounts-get.c b/src/exchange/taler-exchange-httpd_aml-accounts-get.c
@@ -1,486 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2024, 2025, 2026 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_aml-accounts-get.c
- * @brief Return summary information about accounts
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-accounts-get.h"
-
-
-/**
- * Maximum number of records we return in one go. Must be
- * small enough to ensure that XML/CSV encoded results stay
- * below the 16MB limit of GNUNET_malloc().
- */
-#define MAX_RECORDS (1024 * 64)
-
-#define CSV_HEADER \
- "File number,Customer,Comments,Risky,Acquisition date,Exit date\r\n"
-#define CSV_FOOTER "\r\n"
-
-#define XML_HEADER "<?xml version=\"1.0\" encoding=\"UTF 8\"?>" \
- "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" \
- " xmlns:c=\"urn:schemas-microsoft-com:office:component:spreadsheet\"" \
- " xmlns:html=\"http://www.w3.org/TR/REC-html40\"" \
- " xmlns:x2=\"http://schemas.microsoft.com/office/excel/2003/xml\"" \
- " xmlns:o=\"urn:schemas-microsoft-com:office:office\"" \
- " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"" \
- " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">" \
- "<Styles><Style ss:ID=\"DateFormat\"><NumberFormat ss:Format=\"yyyy-mm-dd\"/></Style></Styles>\n" \
- "<Worksheet ss:Name=\"Accounts\">\n" \
- "<Table>\n" \
- "<Row>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">File number</Data></Cell>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Customer</Data></Cell>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Comments</Data></Cell>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Increased risk business relationship</Data></Cell>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Acquisition date</Data></Cell>\n" \
- "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Exit date</Data></Cell>\n" \
- "</Row>\n"
-#define XML_FOOTER "</Table></Worksheet></Workbook>"
-
-/**
- * Closure for the record_cb().
- */
-struct ResponseContext
-{
- /**
- * Format of the response we are to generate.
- */
- enum
- {
- RCF_JSON,
- RCF_XML,
- RCF_CSV
- } format;
-
- /**
- * Where we store the response data.
- */
- union
- {
- /**
- * If @e format is #RCF_JSON.
- */
- json_t *json;
-
- /**
- * If @e format is #RCF_XML.
- */
- struct GNUNET_Buffer xml;
-
- /**
- * If @e format is #RCF_CSV.
- */
- struct GNUNET_Buffer csv;
-
- } details;
-};
-
-
-/**
- * Free resources from @a rc
- *
- * @param[in] rc context to clean up
- */
-static void
-free_rc (struct ResponseContext *rc)
-{
- switch (rc->format)
- {
- case RCF_JSON:
- json_decref (rc->details.json);
- break;
- case RCF_XML:
- GNUNET_buffer_clear (&rc->details.xml);
- break;
- case RCF_CSV:
- GNUNET_buffer_clear (&rc->details.csv);
- break;
- }
-}
-
-
-/**
- * Return account summary information.
- *
- * @param cls closure
- * @param row_id current row in AML status table
- * @param h_payto account for which the attribute data is stored
- * @param open_time when was the account opened formally,
- * GNUNET_TIME_UNIT_FOREVER_TS if it was never opened
- * @param close_time when was the account formally closed,
- * GNUNET_TIME_UNIT_ZERO_TS if it was never closed
- * @param comments comments on the account
- * @param high_risk is this a high-risk business relationship
- * @param to_investigate TRUE if this account should be investigated
- * @param payto the payto URI of the account
- */
-static void
-record_cb (
- void *cls,
- uint64_t row_id,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- struct GNUNET_TIME_Timestamp open_time,
- struct GNUNET_TIME_Timestamp close_time,
- const char *comments,
- bool high_risk,
- bool to_investigate,
- struct TALER_FullPayto payto)
-{
- struct ResponseContext *rc = cls;
-
- if ( (NULL == comments) &&
- (GNUNET_TIME_absolute_is_never (open_time.abs_time)) )
- comments = "transacted amounts below limits that trigger account opening";
- if (NULL == comments)
- comments = "";
- switch (rc->format)
- {
- case RCF_JSON:
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- rc->details.json,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_payto",
- h_payto),
- TALER_JSON_pack_full_payto ("full_payto",
- payto),
- GNUNET_JSON_pack_bool ("high_risk",
- high_risk),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("comments",
- comments)),
- GNUNET_JSON_pack_int64 ("rowid",
- row_id),
- GNUNET_JSON_pack_timestamp ("open_time",
- open_time),
- GNUNET_JSON_pack_timestamp ("close_time",
- close_time),
- GNUNET_JSON_pack_bool ("to_investigate",
- to_investigate)
- )));
- return;
- case RCF_XML:
- {
- char *epayto;
- char *ecomments = NULL;
- char opentime_s[128];
- char closetime_s[128];
- const struct tm *tm;
- time_t tt;
-
- epayto = TALER_escape_xml (payto.full_payto);
- if ( (NULL == comments) &&
- (GNUNET_TIME_absolute_is_never (open_time.abs_time)) )
- comments =
- "transacted amounts below limits that trigger account opening";
- ecomments = TALER_escape_xml (comments);
- tt = (time_t) GNUNET_TIME_timestamp_to_s (open_time);
- tm = gmtime (&tt);
- strftime (opentime_s,
- sizeof (opentime_s),
- "%Y-%m-%dT%H:%M:%S",
- tm);
- tt = (time_t) GNUNET_TIME_timestamp_to_s (close_time);
- tm = gmtime (&tt);
- strftime (closetime_s,
- sizeof (closetime_s),
- "%Y-%m-%dT%H:%M:%S",
- tm);
- GNUNET_buffer_write_fstr (
- &rc->details.xml,
- "<Row>"
- "<Cell><Data ss:Type=\"Number\">%llu</Data></Cell>"
- "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
- "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
- "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
- "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"%s\">%s</Data></Cell>"
- "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"%s\">%s</Data></Cell>"
- "</Row>\n",
- (unsigned long long) row_id,
- epayto,
- NULL == ecomments
- ? ""
- : ecomments,
- high_risk ? "TRUE" : "FALSE",
- high_risk ? "1" : "0",
- GNUNET_TIME_absolute_is_never (open_time.abs_time)
- ? "String"
- : "DateTime",
- GNUNET_TIME_absolute_is_never (open_time.abs_time)
- ? "never"
- : opentime_s,
- GNUNET_TIME_absolute_is_never (close_time.abs_time)
- ? "String"
- : "DateTime",
- GNUNET_TIME_absolute_is_never (close_time.abs_time)
- ? "never"
- : closetime_s);
- GNUNET_free (ecomments);
- GNUNET_free (epayto);
- break;
- } /* end case RCF_XML */
- case RCF_CSV:
- {
- char *ecomments;
- char otbuf[64];
- char ctbuf[64];
- size_t len = strlen (comments);
- size_t wpos = 0;
-
- GNUNET_snprintf (otbuf,
- sizeof (otbuf),
- "%s",
- GNUNET_TIME_timestamp2s (open_time));
- GNUNET_snprintf (ctbuf,
- sizeof (ctbuf),
- "%s",
- GNUNET_TIME_timestamp2s (close_time));
- /* Escape 'comments' to double '"' as per RFC 4180, 2.7. */
- ecomments = GNUNET_malloc (2 * len + 1);
- for (size_t off = 0; off<len; off++)
- {
- if ('"' == comments[off])
- ecomments[wpos++] = '"';
- ecomments[wpos++] = comments[off];
- }
- GNUNET_buffer_write_fstr (&rc->details.csv,
- "%llu,\"%s\",\"%s\",%s,%s,%s\r\n",
- (unsigned long long) row_id,
- payto.full_payto,
- ecomments,
- high_risk ? "X":" ",
- GNUNET_TIME_absolute_is_never (open_time.
- abs_time)
- ? "-"
- : otbuf,
- GNUNET_TIME_absolute_is_never (close_time.
- abs_time)
- ? "-"
- : ctbuf);
- GNUNET_free (ecomments);
- break;
- } /* end case RCF_CSV */
- } /* end switch */
-}
-
-
-MHD_RESULT
-TEH_handler_aml_accounts_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- struct ResponseContext rctx;
- int64_t limit = -20;
- uint64_t offset;
- enum TALER_EXCHANGE_YesNoAll open_filter;
- enum TALER_EXCHANGE_YesNoAll high_risk_filter;
- enum TALER_EXCHANGE_YesNoAll investigation_filter;
-
- memset (&rctx,
- 0,
- sizeof (rctx));
- if (NULL != args[0])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- args[0]);
- }
- TALER_MHD_parse_request_snumber (rc->connection,
- "limit",
- &limit);
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- TALER_MHD_parse_request_number (rc->connection,
- "offset",
- &offset);
- if (offset > INT64_MAX)
- {
- GNUNET_break_op (0); /* broken client */
- offset = INT64_MAX;
- }
- TALER_MHD_parse_request_yna (rc->connection,
- "open",
- TALER_EXCHANGE_YNA_ALL,
- &open_filter);
- TALER_MHD_parse_request_yna (rc->connection,
- "investigation",
- TALER_EXCHANGE_YNA_ALL,
- &investigation_filter);
- TALER_MHD_parse_request_yna (rc->connection,
- "high_risk",
- TALER_EXCHANGE_YNA_ALL,
- &high_risk_filter);
- {
- const char *mime;
-
- mime = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT);
- if (NULL == mime)
- mime = "application/json";
- if (0 == strcmp (mime,
- "application/json"))
- {
- rctx.format = RCF_JSON;
- rctx.details.json = json_array ();
- GNUNET_assert (NULL != rctx.details.json);
- }
- else if (0 == strcmp (mime,
- "application/vnd.ms-excel"))
- {
- rctx.format = RCF_XML;
- GNUNET_buffer_write_str (&rctx.details.xml,
- XML_HEADER);
- }
- else if (0 == strcmp (mime,
- "text/csv"))
- {
- rctx.format = RCF_CSV;
- GNUNET_buffer_write_str (&rctx.details.csv,
- CSV_HEADER);
- }
- else
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_ACCEPTABLE,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- mime);
- }
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- if (limit > MAX_RECORDS)
- limit = MAX_RECORDS;
- if (limit < -MAX_RECORDS)
- limit = -MAX_RECORDS;
- qs = TEH_plugin->select_kyc_accounts (
- TEH_plugin->cls,
- investigation_filter,
- open_filter,
- high_risk_filter,
- offset,
- limit,
- &record_cb,
- &rctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- free_rc (&rctx);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_kyx_accounts");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- free_rc (&rctx);
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- } /* end switch (qs) */
-
- switch (rctx.format)
- {
- case RCF_JSON:
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("accounts",
- rctx.details.json));
- case RCF_XML:
- {
- struct MHD_Response *resp;
- MHD_RESULT mret;
-
- GNUNET_buffer_write_str (&rctx.details.xml,
- XML_FOOTER);
- /* FIXME: add support for compression */
- resp = MHD_create_response_from_buffer (rctx.details.xml.position,
- rctx.details.xml.mem,
- MHD_RESPMEM_MUST_FREE);
- TALER_MHD_add_global_headers (resp,
- false);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "application/vnd.ms-excel"));
- mret = MHD_queue_response (rc->connection,
- MHD_HTTP_OK,
- resp);
- MHD_destroy_response (resp);
- return mret;
- }
- case RCF_CSV:
- {
- struct MHD_Response *resp;
- MHD_RESULT mret;
-
- GNUNET_buffer_write_str (&rctx.details.csv,
- CSV_FOOTER);
- /* FIXME: add support for compression */
- resp = MHD_create_response_from_buffer (rctx.details.csv.position,
- rctx.details.csv.mem,
- MHD_RESPMEM_MUST_FREE);
- TALER_MHD_add_global_headers (resp,
- false);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/csv"));
- mret = MHD_queue_response (rc->connection,
- MHD_HTTP_OK,
- resp);
- MHD_destroy_response (resp);
- return mret;
- }
- } /* end switch (rctx.format) */
- }
- GNUNET_break (0);
- return MHD_NO;
-}
-
-
-/* end of taler-exchange-httpd_aml-accounts_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-accounts-get.h b/src/exchange/taler-exchange-httpd_aml-accounts-get.h
@@ -1,44 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_aml-accounts-get.h
- * @brief Handle GET /aml/$OFFICER_PUB/accounts requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_ACCOUNTS_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_ACCOUNTS_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/accounts" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching decisions.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be the state)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_accounts_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-attributes-get.c b/src/exchange/taler-exchange-httpd_aml-attributes-get.c
@@ -1,814 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2024, 2025 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_aml-attributes-get.c
- * @brief Return summary information about KYC attributes
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-attributes-get.h"
-#include "taler-exchange-httpd_metrics.h"
-
-/**
- * Maximum number of records we return in one request.
- */
-#define MAX_RECORDS 1024
-
-
-/**
- * Closure for the detail_cb().
- */
-struct ResponseContext
-{
- /**
- * Format of the response we are to generate.
- */
- enum
- {
- RCF_JSON,
- RCF_PDF
- } format;
-
- /**
- * Stored in a DLL while suspended.
- */
- struct ResponseContext *next;
-
- /**
- * Stored in a DLL while suspended.
- */
- struct ResponseContext *prev;
-
- /**
- * Context for this request.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Async context used to run Typst.
- */
- struct TALER_MHD_TypstContext *tc;
-
- /**
- * Response to return.
- */
- struct MHD_Response *response;
-
- /**
- * HTTP status to use with @e response.
- */
- unsigned int http_status;
-
- /**
- * True if this is for a wallet.
- */
- bool is_wallet;
-
- /**
- * Where we store the response data.
- */
- union
- {
- /**
- * If @e format is #RCF_JSON.
- */
- json_t *json;
-
- /**
- * If @e format is #RCF_PDF.
- */
- struct
- {
-
- /**
- * Typst forms to compile.
- */
- struct TALER_MHD_TypstDocument docs[MAX_RECORDS];
-
- /**
- * JSON data for each Typst form.
- */
- json_t *jdata[MAX_RECORDS];
-
- /**
- * Next write offset into @e docs and @e jdata.
- */
- size_t off;
-
- /**
- * Global attributes we need to inject in to each @e jdata.
- */
- json_t *global_attrs;
-
- } pdf;
-
- } details;
-};
-
-
-/**
- * DLL of requests awaiting Typst.
- */
-static struct ResponseContext *rctx_head;
-
-/**
- * DLL of requests awaiting Typst.
- */
-static struct ResponseContext *rctx_tail;
-
-
-void
-TEH_handler_aml_attributes_get_cleanup ()
-{
- struct ResponseContext *rctx;
-
- while (NULL != (rctx = rctx_head))
- {
- GNUNET_CONTAINER_DLL_remove (rctx_head,
- rctx_tail,
- rctx);
- MHD_resume_connection (rctx->rc->connection);
- }
-}
-
-
-/**
- * Free resources from @a rc
- *
- * @param[in] rc context to clean up
- */
-static void
-free_rc (struct TEH_RequestContext *rc)
-{
- struct ResponseContext *rctx = rc->rh_ctx;
-
- if (NULL != rctx->tc)
- {
- TALER_MHD_typst_cancel (rctx->tc);
- rctx->tc = NULL;
- }
- if (NULL != rctx->response)
- {
- MHD_destroy_response (rctx->response);
- rctx->response = NULL;
- }
- switch (rctx->format)
- {
- case RCF_JSON:
- json_decref (rctx->details.json);
- break;
- case RCF_PDF:
- for (size_t i = 0; i<rctx->details.pdf.off; i++)
- {
- json_decref (rctx->details.pdf.jdata[i]);
- rctx->details.pdf.jdata[i] = NULL;
- }
- json_decref (rctx->details.pdf.global_attrs);
- rctx->details.pdf.global_attrs = NULL;
- break;
- }
- GNUNET_free (rctx);
-}
-
-
-/**
- * Dump attachment @a attach into @a rc.
- *
- * @param[in,out] rc response context to update
- * @param attach attachment to dump
- * @return true on success
- */
-static bool
-dump_attachment (struct ResponseContext *rc,
- const json_t *attach)
-{
- if (! json_is_string (attach))
- return false;
- if (rc->details.pdf.off >= MAX_RECORDS)
- {
- GNUNET_break (0);
- return false;
- }
- rc->details.pdf.jdata[rc->details.pdf.off]
- = json_incref ((json_t *) attach);
- rc->details.pdf.docs[rc->details.pdf.off].form_name
- = NULL; /* inline PDF */
- rc->details.pdf.docs[rc->details.pdf.off].form_version
- = NULL; /* none */
- rc->details.pdf.docs[rc->details.pdf.off].data
- = rc->details.pdf.jdata[rc->details.pdf.off];
- rc->details.pdf.off++;
- return true;
-}
-
-
-/**
- * Recursively scan @a attrs for ".FILE" members and dump those
- * attachments into @a rc.
- *
- * @param[in,out] rc response context to update
- * @param attrs attributes to scan recursively
- * @return true on success
- */
-static bool
-dump_attachments (struct ResponseContext *rc,
- const json_t *attrs)
-{
- bool ret = true;
-
- if (json_is_array (attrs))
- {
- const json_t *e;
- size_t i;
-
- json_array_foreach ((json_t *) attrs, i, e)
- if (! dump_attachments (rc,
- e))
- ret = false;
- }
- if (json_is_object (attrs))
- {
- const json_t *e;
- const char *k;
-
- json_object_foreach ((json_t *) attrs, k, e)
- {
- if (0 == strcmp (k,
- "CONTENTS"))
- {
- const char *v;
-
- /* Make sure this is the supported encoding */
- v = json_string_value (json_object_get (attrs,
- "ENCODING"));
- if (NULL == v)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "`CONTENTS' attribute found without `ENCODING', skipping dumping attachment\n");
- continue; /* maybe not a file!? */
- }
- if (0 != strcmp (v,
- "base64"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Attachment with unsupported encoding `%s' encountered, skipping attachment in PDF generation\n",
- v);
- continue;
- }
- v = json_string_value (json_object_get (attrs,
- "MIME_TYPE"));
- if (NULL == v)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "`CONTENTS' attribute found without `MIME_TYPE', skipping dumping attachment\n");
- continue;
- }
- if (0 != strcmp (v,
- "application/pdf"))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Attachment with unsupported mime type `%s' encountered, skipping attachment in PDF generation\n",
- v);
- continue;
- }
- /* Filename is optional, only log it */
- v = json_string_value (json_object_get (attrs,
- "FILENAME"));
- if (NULL != v)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Dumping attachment `%s'\n",
- v);
- }
- if (! dump_attachment (rc,
- e))
- ret = false;
- }
- else if (! dump_attachments (rc,
- e))
- ret = false;
- }
- }
- return ret;
-}
-
-
-/**
- * Return AML account attributes.
- *
- * @param cls closure
- * @param row_id current row in kyc_attributes table
- * @param collection_time when were the attributes collected
- * @param by_aml_officer was the attribute set filed by the AML officer
- * @param staff_name name of the officer, NULL if not @a by_aml_officer
- * @param enc_attributes_size length of @a enc_attributes
- * @param enc_attributes the encrypted collected attributes
- */
-static void
-detail_cb (
- void *cls,
- uint64_t row_id,
- struct GNUNET_TIME_Timestamp collection_time,
- bool by_aml_officer,
- const char *staff_name,
- size_t enc_attributes_size,
- const void *enc_attributes)
-{
- static char *datadir = NULL;
- struct ResponseContext *rc = cls;
- json_t *attrs;
- const char *form_id;
-
- if (NULL == datadir)
- {
- datadir = GNUNET_OS_installation_get_path (
- TALER_EXCHANGE_project_data (),
- GNUNET_OS_IPK_DATADIR);
- }
- attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
- enc_attributes,
- enc_attributes_size);
- if (NULL == attrs)
- {
- GNUNET_break (0);
- return;
- }
- form_id = json_string_value (json_object_get (attrs,
- "FORM_ID"));
- if (NULL == form_id)
- {
- GNUNET_break (0);
- return;
- }
- switch (rc->format)
- {
- case RCF_JSON:
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- rc->details.json,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_int64 ("rowid",
- row_id),
- GNUNET_JSON_pack_bool ("by_aml_officer",
- by_aml_officer),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_steal ("attributes",
- attrs)),
- GNUNET_JSON_pack_timestamp ("collection_time",
- collection_time)
- )));
- break;
- case RCF_PDF:
- if (rc->details.pdf.off >= MAX_RECORDS)
- {
- GNUNET_break (0);
- return;
- }
- GNUNET_assert (0 ==
- json_object_set_new (attrs,
- "DATADIR",
- json_string (datadir)));
- GNUNET_assert (0 ==
- json_object_set_new (attrs,
- "BY_AML_OFFICER",
- json_boolean (by_aml_officer)));
- GNUNET_assert (0 ==
- json_object_set_new (attrs,
- "FILING_DATE",
- json_string (
- GNUNET_STRINGS_timestamp_to_string (
- collection_time))));
- if (by_aml_officer)
- GNUNET_assert (0 ==
- json_object_set_new (attrs,
- "AML_STAFF_NAME",
- json_string (staff_name)));
- {
- char *have;
-
- GNUNET_asprintf (&have,
- "HAVE_%s",
- form_id);
- GNUNET_assert (0 ==
- json_object_set_new (rc->details.pdf.global_attrs,
- have,
- json_true ()));
- GNUNET_free (have);
- }
- rc->details.pdf.jdata[rc->details.pdf.off]
- = attrs;
- rc->details.pdf.docs[rc->details.pdf.off].form_name
- = form_id;
- rc->details.pdf.docs[rc->details.pdf.off].form_version
- = NULL; /* not yet supported! */
- rc->details.pdf.docs[rc->details.pdf.off].data
- = rc->details.pdf.jdata[rc->details.pdf.off];
- rc->details.pdf.off++;
- if (! dump_attachments (rc,
- attrs))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to dump some attachment!\n");
- }
- break;
- }
-}
-
-
-/**
- * Function called with the result of a #TALER_MHD_typst() operation.
- *
- * @param cls closure
- * @param tr result of the operation
- */
-static void
-pdf_cb (void *cls,
- const struct TALER_MHD_TypstResponse *tr)
-{
- struct ResponseContext *rctx = cls;
-
- rctx->tc = NULL;
- GNUNET_CONTAINER_DLL_remove (rctx_head,
- rctx_tail,
- rctx);
- MHD_resume_connection (rctx->rc->connection);
- TALER_MHD_daemon_trigger ();
- if (TALER_EC_NONE != tr->ec)
- {
- rctx->http_status
- = TALER_ErrorCode_get_http_status (tr->ec);
- rctx->response
- = TALER_MHD_make_error (tr->ec,
- tr->details.hint);
- return;
- }
- rctx->http_status
- = MHD_HTTP_OK;
- rctx->response
- = TALER_MHD_response_from_pdf_file (tr->details.filename);
-}
-
-
-/**
- * Function called with the latest AML decision on an
- * account. Used to build the cover page.
- *
- * @param cls a `struct ResponseContext *`
- * @param outcome_serial_id row ID of the decision
- * @param decision_time when was the decision taken
- * @param justification what was the given justification
- * @param decider_pub which key signed the decision
- * @param jproperties what are the new account properties
- * @param jnew_rules what are the new account rules
- * @param to_investigate should AML staff investigate
- * after the decision
- * @param is_active is this the active decision
- */
-static void
-build_cover_page (
- void *cls,
- uint64_t outcome_serial_id,
- struct GNUNET_TIME_Timestamp decision_time,
- const char *justification,
- const struct TALER_AmlOfficerPublicKeyP *decider_pub,
- const json_t *jproperties,
- const json_t *jnew_rules,
- bool to_investigate,
- bool is_active)
-{
- struct ResponseContext *rc = cls;
- json_t *cover;
- json_t *defr = NULL;
-
- if (NULL == jnew_rules)
- {
- defr = TALER_KYCLOGIC_get_default_legi_rules (rc->is_wallet);
- jnew_rules = defr;
- }
- cover = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("properties",
- (json_t *) jproperties)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("rules",
- (json_t *) jnew_rules)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("last_justification",
- justification)),
- GNUNET_JSON_pack_bool ("is_active",
- is_active),
- GNUNET_JSON_pack_bool ("to_investigate",
- to_investigate));
- if (NULL != defr)
- json_decref (defr);
- /* first page, so we really cannot have hit the maximum yet */
- GNUNET_assert (rc->details.pdf.off < MAX_RECORDS);
- rc->details.pdf.jdata[rc->details.pdf.off]
- = cover;
- rc->details.pdf.docs[rc->details.pdf.off].form_name
- = "_cover_";
- rc->details.pdf.docs[rc->details.pdf.off].form_version
- = NULL; /* not yet supported! */
- rc->details.pdf.docs[rc->details.pdf.off].data
- = rc->details.pdf.jdata[rc->details.pdf.off];
- rc->details.pdf.off++;
-}
-
-
-MHD_RESULT
-TEH_handler_aml_attributes_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- struct ResponseContext *rctx = rc->rh_ctx;
- int64_t limit = -20;
- uint64_t offset;
- struct TALER_NormalizedPaytoHashP h_payto;
- uint64_t file_number;
-
- if (NULL == rctx)
- {
- rctx = GNUNET_new (struct ResponseContext);
- rctx->rc = rc;
- rc->rh_ctx = rctx;
- rc->rh_cleaner = &free_rc;
- }
- else
- {
- if (NULL == rctx->response)
- {
- GNUNET_break (0);
- return MHD_NO;
- }
- return MHD_queue_response (rc->connection,
- rctx->http_status,
- rctx->response);
- }
- if ( (NULL == args[0]) ||
- (NULL != args[1]) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &h_payto,
- sizeof (h_payto)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
- "h_payto");
- }
-
- TALER_MHD_parse_request_snumber (rc->connection,
- "limit",
- &limit);
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- TALER_MHD_parse_request_number (rc->connection,
- "offset",
- &offset);
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->lookup_aml_file_number (TEH_plugin->cls,
- &h_payto,
- &file_number,
- &rctx->is_wallet);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_aml_file_number");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* Account unknown, return 404 */
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_TARGET_ACCOUNT_UNKNOWN,
- args[0]);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
-
- {
- const char *mime;
-
- mime = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_ACCEPT);
- if (NULL == mime)
- mime = "application/json";
- if (0 == strcmp (mime,
- "application/json"))
- {
- rctx->format = RCF_JSON;
- rctx->details.json = json_array ();
- GNUNET_assert (NULL != rctx->details.json);
- }
- else if (0 == strcmp (mime,
- "application/pdf"))
- {
- enum GNUNET_DB_QueryStatus qs;
-
- rctx->format = RCF_PDF;
- rctx->details.pdf.global_attrs = json_object ();
- GNUNET_assert (NULL != rctx->details.pdf.global_attrs);
- if (NULL != TEH_global_pdf_form_data)
- GNUNET_assert (0 ==
- json_object_update (rctx->details.pdf.global_attrs,
- TEH_global_pdf_form_data));
- GNUNET_assert (0 ==
- json_object_set_new (rctx->details.pdf.global_attrs,
- "FILE_NUMBER",
- json_integer (file_number)));
- /* Lookup latest AML decision & account rules & properties
- to build the cover page */
- qs = TEH_plugin->lookup_aml_history (TEH_plugin->cls,
- &h_payto,
- UINT64_MAX, /* offset */
- -1, /* latest decision only */
- &build_cover_page,
- rctx);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_aml_attributes");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- /* no decision was ever taken, build empty cover page */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No decisions taken, creating empty cover page\n");
- build_cover_page (rctx,
- 0,
- GNUNET_TIME_UNIT_ZERO_TS,
- NULL,
- NULL,
- NULL,
- NULL,
- false,
- false);
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
- else
- {
- GNUNET_break_op (0);
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_NOT_ACCEPTABLE,
- GNUNET_JSON_pack_string ("hint",
- mime));
- }
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- if (limit > MAX_RECORDS)
- limit = MAX_RECORDS;
- if (limit < -MAX_RECORDS)
- limit = -MAX_RECORDS;
- qs = TEH_plugin->select_aml_attributes (
- TEH_plugin->cls,
- &h_payto,
- offset,
- limit,
- &detail_cb,
- rctx);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Obtained %d/%d attributes\n",
- qs,
- (int) (limit < 0 ? -limit : limit));
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_aml_attributes");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- if (RCF_JSON == rctx->format)
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
-
- switch (rctx->format)
- {
- case RCF_JSON:
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_incref ("details",
- rctx->details.json));
- case RCF_PDF:
- /* Cover page: off of 0 is impossible */
- GNUNET_assert (0 != rctx->details.pdf.off);
- for (unsigned int i = 0; i<rctx->details.pdf.off; i++)
- {
- /* The jdata[i] could be a *string* in case of attachments,
- ignore those here! */
- if (NULL == rctx->details.pdf.docs[i].form_name)
- {
- GNUNET_assert (json_is_string (rctx->details.pdf.jdata[i]));
- }
- else
- {
- GNUNET_assert (json_is_object (rctx->details.pdf.jdata[i]));
- GNUNET_assert (0 ==
- json_object_update_missing (
- rctx->details.pdf.jdata[i],
- rctx->details.pdf.global_attrs)
- );
- }
- }
- rctx->tc = TALER_MHD_typst (TALER_EXCHANGE_project_data (),
- TEH_cfg,
- false,
- "exchange",
- rctx->details.pdf.off,
- rctx->details.pdf.docs,
- &pdf_cb,
- rctx);
- if (NULL == rctx->tc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client requested PDF, but Typst is unavailable\n");
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_IMPLEMENTED,
- TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
- NULL);
- }
- GNUNET_CONTAINER_DLL_insert (rctx_head,
- rctx_tail,
- rctx);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
- }
- GNUNET_assert (0);
- return MHD_NO;
-}
-
-
-/* end of taler-exchange-httpd_aml-attributes_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-attributes-get.h b/src/exchange/taler-exchange-httpd_aml-attributes-get.h
@@ -1,52 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_aml-attributes-get.h
- * @brief Handle /aml/$OFFICER_PUB/attributes/$H_PAYTO requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_ATTRIBUTES_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_ATTRIBUTES_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/attributes/$H_PAYTO" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching decisions.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be the state)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_attributes_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-/**
- * Stop async running attribute GET requests.
- */
-void
-TEH_handler_aml_attributes_get_cleanup (void);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.c b/src/exchange/taler-exchange-httpd_aml-decision.c
@@ -1,535 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023-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 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_aml-decision.c
- * @brief Handle POST request about an AML decision.
- * @author Christian Grothoff
- * @author Florian Dold
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_aml-decision.h"
-
-
-/**
- * Context used for processing the AML decision request.
- */
-struct AmlDecisionContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct AmlDecisionContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct AmlDecisionContext *prev;
-
- /**
- * HTTP status code to use with @e response.
- */
- unsigned int response_code;
-
- /**
- * Response to return, NULL if none yet.
- */
- struct MHD_Response *response;
-
- /**
- * Request we are processing.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for async KYC processing.
- */
- struct TEH_KycMeasureRunContext *kat;
-
-};
-
-/**
- * Kept in a DLL.
- */
-static struct AmlDecisionContext *adc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct AmlDecisionContext *adc_tail;
-
-
-void
-TEH_aml_decision_cleanup ()
-{
- struct AmlDecisionContext *adc;
-
- while (NULL != (adc = adc_head))
- {
- MHD_resume_connection (adc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (adc_head,
- adc_tail,
- adc);
- }
-}
-
-
-/**
- * Function called to clean up aml decision context.
- *
- * @param[in,out] rc context to clean up
- */
-static void
-aml_decision_cleaner (struct TEH_RequestContext *rc)
-{
- struct AmlDecisionContext *adc = rc->rh_ctx;
-
- if (NULL != adc->kat)
- {
- TEH_kyc_run_measure_cancel (adc->kat);
- adc->kat = NULL;
- }
- if (NULL != adc->response)
- {
- MHD_destroy_response (adc->response);
- adc->response = NULL;
- }
- GNUNET_free (adc);
-}
-
-
-/**
- * Function called after the KYC-AML trigger is done.
- *
- * @param cls closure
- * @param ec error code or 0 on success
- * @param detail error message or NULL on success / no info
- */
-static void
-aml_trigger_callback (
- void *cls,
- enum TALER_ErrorCode ec,
- const char *detail)
-{
- struct AmlDecisionContext *adc = cls;
-
- adc->kat = NULL;
- GNUNET_assert (NULL == adc->response);
-
- if (TALER_EC_NONE != ec)
- {
- adc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- adc->response = TALER_MHD_make_error (
- ec,
- detail);
- }
- else
- {
- adc->response_code = MHD_HTTP_NO_CONTENT;
- adc->response = MHD_create_response_from_buffer_static (
- 0,
- "");
- TALER_MHD_add_global_headers (adc->response,
- true);
- }
-
- GNUNET_assert (NULL != adc->response);
-
- MHD_resume_connection (adc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (adc_head,
- adc_tail,
- adc);
- TALER_MHD_daemon_trigger ();
-}
-
-
-MHD_RESULT
-TEH_handler_post_aml_decision (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const json_t *root)
-{
- struct MHD_Connection *connection = rc->connection;
- struct AmlDecisionContext *adc = rc->rh_ctx;
- const char *justification;
- const char *new_measures = NULL;
- bool to_investigate;
- struct GNUNET_TIME_Timestamp decision_time;
- struct GNUNET_TIME_Timestamp attributes_expiration
- = GNUNET_TIME_UNIT_ZERO_TS;
- const json_t *new_rules;
- const json_t *events = NULL;
- const json_t *properties = NULL;
- const json_t *attributes = NULL;
- struct TALER_FullPayto payto_uri = {
- .full_payto = NULL
- };
- struct TALER_NormalizedPaytoHashP h_payto;
- struct TALER_AmlOfficerSignatureP officer_sig;
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
- uint64_t legi_measure_serial_id = 0;
- MHD_RESULT ret;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string (
- "new_measures",
- &new_measures),
- NULL),
- GNUNET_JSON_spec_string ("justification",
- &justification),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_full_payto_uri ("payto_uri",
- &payto_uri),
- NULL),
- GNUNET_JSON_spec_fixed_auto ("h_payto",
- &h_payto),
- GNUNET_JSON_spec_object_const ("new_rules",
- &new_rules),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("properties",
- &properties),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("events",
- &events),
- NULL),
- GNUNET_JSON_spec_bool ("keep_investigating",
- &to_investigate),
- GNUNET_JSON_spec_fixed_auto ("officer_sig",
- &officer_sig),
- GNUNET_JSON_spec_timestamp ("decision_time",
- &decision_time),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("attributes_expiration",
- &attributes_expiration),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("attributes",
- &attributes),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_TIME_Timestamp expiration_time;
- json_t *jmeasures = NULL;
- bool is_wallet;
- size_t eas = 0;
- void *ea = NULL;
-
- if (NULL == adc)
- {
- /* Initialize context */
- adc = GNUNET_new (struct AmlDecisionContext);
- adc->rc = rc;
- rc->rh_ctx = adc;
- rc->rh_cleaner = aml_decision_cleaner;
- }
-
- if (NULL != adc->response)
- {
- ret = MHD_queue_response (rc->connection,
- adc->response_code,
- adc->response);
- goto done;
- }
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- ret = MHD_NO; /* hard failure */
- goto done;
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- ret = MHD_YES /* failure */;
- goto done;
- }
- if ( (NULL != attributes) &&
- (! json_is_string (json_object_get (attributes,
- "FORM_ID"))) )
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "attributes['FORM_ID']");
- goto done;
- }
- }
- if (NULL != payto_uri.full_payto)
- {
- struct TALER_NormalizedPaytoHashP h_payto2;
-
- TALER_full_payto_normalize_and_hash (payto_uri,
- &h_payto2);
- if (0 !=
- GNUNET_memcmp (&h_payto,
- &h_payto2))
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payto_uri");
- goto done;
- }
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_officer_aml_decision_verify (
- justification,
- decision_time,
- &h_payto,
- new_rules,
- properties,
- new_measures,
- to_investigate,
- officer_pub,
- &officer_sig,
- attributes_expiration,
- attributes))
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
- NULL);
- goto done;
- }
-
- lrs = TALER_KYCLOGIC_rules_parse (new_rules);
- if (NULL == lrs)
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "legitimization rule malformed");
- goto done;
- }
-
- expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
- if ( (NULL != new_measures) &&
- (0 != strlen (new_measures)) )
- {
- jmeasures
- = TALER_KYCLOGIC_get_jmeasures (lrs,
- new_measures);
- if (NULL == jmeasures)
- {
- GNUNET_break_op (0);
- /* Request specified a new_measure for which the given
- rule set does not work as it does not define the measure */
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "new_measures/new_rules");
- goto done;
- }
- }
-
- {
- size_t num_events = json_array_size (events);
- const char *sevents[GNUNET_NZL (num_events)];
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp last_date;
- bool invalid_officer = true;
- bool unknown_account = false;
- struct GNUNET_HashCode h_attr = { 0 };
- const char *form_id = NULL;
-
- if (NULL != attributes)
- {
- form_id = json_string_value (json_object_get (attributes,
- "FORM_ID"));
- TALER_json_hash (attributes,
- &h_attr);
- TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
- attributes,
- &ea,
- &eas);
- }
-
- for (size_t i = 0; i<num_events; i++)
- {
- sevents[i] = json_string_value (json_array_get (events,
- i));
- if (NULL == sevents[i])
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "events");
- goto done;
- }
- }
- /* We keep 'new_measures' around mostly so that
- the auditor can later verify officer_sig */
- qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
- payto_uri,
- &h_payto,
- decision_time,
- expiration_time,
- properties,
- new_rules,
- to_investigate,
- new_measures,
- jmeasures,
- justification,
- officer_pub,
- &officer_sig,
- num_events,
- sevents,
- form_id,
- eas, /* enc_attributes_size*/
- ea, /* enc_attributes*/
- NULL != attributes
- ? &h_attr
- : NULL, /* attributes_hash */
- attributes_expiration,
- &invalid_officer,
- &unknown_account,
- &last_date,
- &legi_measure_serial_id,
- &is_wallet);
- json_decref (jmeasures);
- if (qs <= 0)
- {
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_aml_decision");
- goto done;
- }
- if (invalid_officer)
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
- NULL);
- goto done;
- }
- if (unknown_account)
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
- "h_payto");
- goto done;
- }
- if (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- decision_time))
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
- NULL);
- goto done;
- }
- }
- /* Run instant measure if necessary */
- {
- const struct TALER_KYCLOGIC_Measure *instant_ms = NULL;
-
- if (NULL != new_measures)
- {
- instant_ms = TALER_KYCLOGIC_get_instant_measure (lrs,
- new_measures);
- }
-
- if (NULL != instant_ms)
- {
- /* We have an 'instant' measure which means we must run the
- AML program immediately instead of waiting for the account owner
- to select some measure and contribute their KYC data. */
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Running instant measure after AML decision\n");
- adc->kat = TEH_kyc_run_measure_directly (
- &rc->async_scope_id,
- instant_ms,
- &h_payto,
- is_wallet,
- &aml_trigger_callback,
- adc
- );
- if (NULL == adc->kat)
- {
- GNUNET_break (0);
- ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
- "TEH_kyc_finished");
- goto done;
- }
- MHD_suspend_connection (adc->rc->connection);
- GNUNET_CONTAINER_DLL_insert (adc_head,
- adc_tail,
- adc);
- ret = MHD_YES;
- goto done;
- }
- }
- ret = TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- goto done;
-
-done:
- TALER_KYCLOGIC_rules_free (lrs);
- GNUNET_free (ea);
- return ret;
-}
-
-
-/* end of taler-exchange-httpd_aml-decision.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-decision.h b/src/exchange/taler-exchange-httpd_aml-decision.h
@@ -1,67 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_aml-decision.h
- * @brief Handle /aml/$OFFICER_PUB/decision requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_DECISION_H
-#define TALER_EXCHANGE_HTTPD_AML_DECISION_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a POST "/aml/$OFFICER_PUB/decision" request. Parses the decision
- * details, checks the signatures and if appropriately authorized executes
- * the decision.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_post_aml_decision (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const json_t *root);
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/decisions" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching decisions.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be the state)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_decisions_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-/**
- * Clean up running POST /aml/$OFFICER_PUB/decisions requests.
- */
-void
-TEH_aml_decision_cleanup (void);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-decisions-get.c b/src/exchange/taler-exchange-httpd_aml-decisions-get.c
@@ -1,205 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2024, 2025 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_aml-decisions-get.c
- * @brief Return summary information about AML decisions
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-decision.h"
-#include "taler-exchange-httpd_metrics.h"
-
-/**
- * Maximum number of records we return in one request.
- */
-#define MAX_RECORDS 1024
-
-/**
- * Return AML status.
- *
- * @param cls closure
- * @param row_id current row in AML status table
- * @param justification human-readable reason for the decision
- * @param h_payto account for which the attribute data is stored
- * @param decision_time when was the decision taken
- * @param expiration_time when will the rules expire
- * @param jproperties properties set for the account,
- * NULL if no properties were set
- * @param to_investigate true if AML staff should look at the account
- * @param is_active true if this is the currently active decision about the account
- * @param is_wallet true if the @a h_payto is for a wallet
- * @param payto the payto URI of the account affected by the decision
- * @param account_rules current active rules for the account
- */
-static void
-record_cb (
- void *cls,
- uint64_t row_id,
- const char *justification,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- struct GNUNET_TIME_Timestamp decision_time,
- struct GNUNET_TIME_Absolute expiration_time,
- const json_t *jproperties,
- bool to_investigate,
- bool is_active,
- bool is_wallet,
- struct TALER_FullPayto payto,
- const json_t *account_rules)
-{
- json_t *records = cls;
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- records,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_payto",
- h_payto),
- TALER_JSON_pack_full_payto ("full_payto",
- payto),
- GNUNET_JSON_pack_bool ("is_wallet",
- is_wallet),
- GNUNET_JSON_pack_int64 ("rowid",
- row_id),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("justification",
- justification)),
- GNUNET_JSON_pack_timestamp ("decision_time",
- decision_time),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("properties",
- (json_t *) jproperties)),
- GNUNET_JSON_pack_object_incref ("limits",
- (json_t *) account_rules),
- GNUNET_JSON_pack_bool ("to_investigate",
- to_investigate),
- GNUNET_JSON_pack_bool ("is_active",
- is_active)
- )));
-}
-
-
-MHD_RESULT
-TEH_handler_aml_decisions_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- int64_t limit = -20;
- uint64_t offset;
- struct TALER_NormalizedPaytoHashP h_payto;
- bool have_payto = false;
- enum TALER_EXCHANGE_YesNoAll active_filter;
- enum TALER_EXCHANGE_YesNoAll investigation_filter;
-
- if (NULL != args[0])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- args[0]);
- }
- TALER_MHD_parse_request_snumber (rc->connection,
- "limit",
- &limit);
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- TALER_MHD_parse_request_number (rc->connection,
- "offset",
- &offset);
- if (offset > INT64_MAX)
- {
- GNUNET_break_op (0); /* broken client */
- offset = INT64_MAX;
- }
- TALER_MHD_parse_request_arg_auto (rc->connection,
- "h_payto",
- &h_payto,
- have_payto);
- TALER_MHD_parse_request_yna (rc->connection,
- "active",
- TALER_EXCHANGE_YNA_ALL,
- &active_filter);
- TALER_MHD_parse_request_yna (rc->connection,
- "investigation",
- TALER_EXCHANGE_YNA_ALL,
- &investigation_filter);
- {
- json_t *records;
- enum GNUNET_DB_QueryStatus qs;
-
- records = json_array ();
- GNUNET_assert (NULL != records);
- if (limit > MAX_RECORDS)
- limit = MAX_RECORDS;
- if (limit < -MAX_RECORDS)
- limit = -MAX_RECORDS;
- qs = TEH_plugin->select_aml_decisions (
- TEH_plugin->cls,
- have_payto
- ? &h_payto
- : NULL,
- investigation_filter,
- active_filter,
- offset,
- limit,
- &record_cb,
- records);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- json_decref (records);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_aml_decisions");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- json_decref (records);
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("records",
- records));
- }
-}
-
-
-/* end of taler-exchange-httpd_aml-decisions_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-legitimization-measures-get.c b/src/exchange/taler-exchange-httpd_aml-legitimization-measures-get.c
@@ -1,180 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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 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_aml-legitimization-measures-get.c
- * @brief Return information about legitimization measures
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-legitimization-measures-get.h"
-#include "taler-exchange-httpd_metrics.h"
-
-/**
- * Maximum number of measures we return in one request.
- */
-#define MAX_MEASURES 1024
-
-/**
- * Return LEGITIMIZATION measure.
- *
- * @param cls closure
- * @param h_payto hash of account the measure applies to
- * @param start_time when was the process started
- * @param jmeasures object of type ``LegitimizationMeasures``
- * @param is_finished true if the measure was finished
- * @param measure_serial_id row ID of the measure in the exchange table
- */
-static void
-record_cb (
- void *cls,
- struct TALER_NormalizedPaytoHashP *h_payto,
- struct GNUNET_TIME_Absolute start_time,
- const json_t *jmeasures,
- bool is_finished,
- uint64_t measure_serial_id)
-{
- json_t *measures = cls;
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- measures,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_payto",
- h_payto),
- GNUNET_JSON_pack_uint64 ("rowid",
- measure_serial_id),
- GNUNET_JSON_pack_timestamp ("start_time",
- GNUNET_TIME_absolute_to_timestamp (
- start_time)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_object_incref ("measures",
- (json_t *) jmeasures)),
- GNUNET_JSON_pack_bool ("is_finished",
- is_finished)
- )));
-}
-
-
-MHD_RESULT
-TEH_handler_aml_legitimization_measures_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- int64_t limit = -20;
- uint64_t offset;
- struct TALER_NormalizedPaytoHashP h_payto;
- bool have_payto = false;
- enum TALER_EXCHANGE_YesNoAll active_filter;
-
- if (NULL != args[0])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- args[0]);
- }
- TALER_MHD_parse_request_snumber (rc->connection,
- "limit",
- &limit);
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- TALER_MHD_parse_request_number (rc->connection,
- "offset",
- &offset);
- if (offset > INT64_MAX)
- {
- GNUNET_break_op (0); /* broken client */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Received invalid offset %llu > %llu\n",
- (unsigned long long) offset,
- (unsigned long long) INT64_MAX);
- offset = INT64_MAX;
- }
- TALER_MHD_parse_request_arg_auto (rc->connection,
- "h_payto",
- &h_payto,
- have_payto);
- TALER_MHD_parse_request_yna (rc->connection,
- "active",
- TALER_EXCHANGE_YNA_ALL,
- &active_filter);
- {
- json_t *measures;
- enum GNUNET_DB_QueryStatus qs;
-
- measures = json_array ();
- GNUNET_assert (NULL != measures);
- if (limit > MAX_MEASURES)
- limit = MAX_MEASURES;
- if (limit < -MAX_MEASURES)
- limit = -MAX_MEASURES;
- qs = TEH_plugin->select_aml_measures (
- TEH_plugin->cls,
- have_payto
- ? &h_payto
- : NULL,
- active_filter,
- offset,
- limit,
- &record_cb,
- measures);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- json_decref (measures);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_aml_measures");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- json_decref (measures);
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("measures",
- measures));
- }
-}
-
-
-/* end of taler-exchange-httpd_legitimization-measures_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-legitimization-measures-get.h b/src/exchange/taler-exchange-httpd_aml-legitimization-measures-get.h
@@ -1,43 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_aml-legitimization-measures-get.h
- * @brief Handle /aml/$OFFICER_PUB/legitimizations requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_LEGITIMIZATION_MEASURES_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_LEGITIMIZATION_MEASURES_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/legitimizations" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching measuress.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be the state)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_legitimization_measures_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-measures-get.c b/src/exchange/taler-exchange-httpd_aml-measures-get.c
@@ -1,75 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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 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_aml-measures-get.c
- * @brief Return summary information about KYC measures
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler-exchange-httpd_aml-measures-get.h"
-
-
-MHD_RESULT
-TEH_handler_aml_measures_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- static json_t *roots;
- static json_t *programs;
- static json_t *checks;
- static json_t *default_rules;
-
- if (NULL == roots)
- {
- TALER_KYCLOGIC_get_measure_configuration (&roots,
- &programs,
- &checks,
- &default_rules);
- }
- if (NULL != args[0])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_incref ("default_rules",
- default_rules),
- GNUNET_JSON_pack_object_incref ("roots",
- roots),
- GNUNET_JSON_pack_object_incref ("programs",
- programs),
- GNUNET_JSON_pack_object_incref ("checks",
- checks));
-}
-
-
-/* end of taler-exchange-httpd_aml-measures_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-measures-get.h b/src/exchange/taler-exchange-httpd_aml-measures-get.h
@@ -1,45 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_aml-measures-get.h
- * @brief Handle /aml/$OFFICER_PUB/measures requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_MEASURES_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_MEASURES_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/measures" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching decisions.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be empty)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_measures_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-statistics-get.c b/src/exchange/taler-exchange-httpd_aml-statistics-get.c
@@ -1,173 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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 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_aml-statistics-get.c
- * @brief Return summary information about KYC statistics
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-statistics-get.h"
-#include "taler-exchange-httpd_metrics.h"
-
-/**
- * Maximum number of statistics that can be requested in one go.
- */
-#define MAX_STATS 64
-
-
-/**
- * Function called with AML statistics (counters).
- *
- * @param cls JSON array to build
- * @param name name of the counter
- * @param cnt number of events for @a name in the query range
- */
-static void
-add_stat (
- void *cls,
- const char *name,
- uint64_t cnt)
-{
- json_t *stats = cls;
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- stats,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("event",
- name),
- GNUNET_JSON_pack_uint64 ("counter",
- cnt))));
-}
-
-
-MHD_RESULT
-TEH_handler_aml_kyc_statistics_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- struct GNUNET_TIME_Timestamp start_date = GNUNET_TIME_UNIT_ZERO_TS;
- struct GNUNET_TIME_Timestamp end_date = GNUNET_TIME_UNIT_FOREVER_TS;
- const char *all_names = args[0];
- json_t *stats;
- size_t max_names;
-
- if ( (NULL == args[0]) ||
- (NULL != args[1]) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- TALER_MHD_parse_request_timestamp (rc->connection,
- "start_date",
- &start_date);
- TALER_MHD_parse_request_timestamp (rc->connection,
- "end_date",
- &end_date);
- if (GNUNET_TIME_absolute_is_never (end_date.abs_time))
- end_date = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Looking for stats on %s from [%llu,%llu)\n",
- all_names,
- (unsigned long long) start_date.abs_time.abs_value_us,
- (unsigned long long) end_date.abs_time.abs_value_us);
- /* Estimate number of names in 'all_names' */
- max_names = 1;
- for (size_t i = 0; '\0' != all_names[i]; i++)
- if (' ' == all_names[i])
- max_names++;
- if (max_names > MAX_STATS)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_URI_TOO_LONG,
- TALER_EC_GENERIC_URI_TOO_LONG,
- rc->url);
- }
- stats = json_array ();
- GNUNET_assert (NULL != stats);
- {
- char *buf;
- const char *names[GNUNET_NZL (max_names)];
- enum GNUNET_DB_QueryStatus qs;
- size_t num_names = 0;
-
- buf = GNUNET_strdup (all_names);
- for (const char *tok = strtok (buf,
- " ");
- NULL != tok;
- tok = strtok (NULL,
- " "))
- {
- GNUNET_assert (num_names < max_names);
- names[num_names++] = tok;
- }
- qs = TEH_plugin->select_aml_statistics (
- TEH_plugin->cls,
- num_names,
- names,
- start_date,
- end_date,
- &add_stat,
- stats);
- GNUNET_free (buf);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_aml_statistics");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("statistics",
- stats));
- }
-}
-
-
-/* end of taler-exchange-httpd_aml-statistics_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-statistics-get.h b/src/exchange/taler-exchange-httpd_aml-statistics-get.h
@@ -1,45 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_aml-statistics-get.h
- * @brief Handle /aml/$OFFICER_PUB/kyc-statistics/$NAME requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_STATISTICS_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_STATISTICS_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/kyc-statistics/$NAME" request. Parses the request
- * details, checks the signatures and if appropriately authorized returns
- * the matching decisions.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (name of the statistic)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_kyc_statistics_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_aml-transfer-get.c b/src/exchange/taler-exchange-httpd_aml-transfer-get.c
@@ -1,258 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_aml-transfer-get.c
- * @brief Return information about exchange wire transfers
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_aml-transfer-get.h"
-#include "taler-exchange-httpd_metrics.h"
-
-
-enum TransferType
-{
- TT_CREDIT,
- TT_DEBIT,
- TT_KYCAUTH
-};
-
-
-/**
- * Maximum number of transfers we return in one request.
- */
-#define MAX_TRANSFERS 1024
-
-/**
- * Return transfer data.
- *
- * @param cls closure
- * @param row_id current row in AML status table
- * @param payto_uri account involved with the wire transfer
- * @param execution_time when was the transfer made
- * @param amount wire amount of the transfer
- */
-static void
-record_cb (
- void *cls,
- uint64_t row_id,
- const char *payto_uri,
- struct GNUNET_TIME_Absolute execution_time,
- const struct TALER_Amount *amount)
-{
- json_t *transfers = cls;
-
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- transfers,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("payto_uri",
- payto_uri),
- GNUNET_JSON_pack_int64 ("rowid",
- row_id),
- GNUNET_JSON_pack_timestamp ("execution_time",
- GNUNET_TIME_absolute_to_timestamp (
- execution_time)),
- TALER_JSON_pack_amount ("amount",
- amount)
- )));
-}
-
-
-/**
- * Handle HTTP request by AML officer for transfer data.
- *
- * @param rc request context
- * @param officer_pub the AML officer
- * @param tt which type of transfer data to return
- * @param args further arguments provided (should be empty)
- * @return MHD status
- */
-static MHD_RESULT
-aml_transfer_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- enum TransferType tt,
- const char *const args[])
-{
- int64_t limit = -20;
- uint64_t offset;
- struct TALER_Amount threshold;
- struct TALER_NormalizedPaytoHashP h_payto;
- bool have_payto = false;
-
- if (NULL != args[0])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- args[0]);
- }
- TALER_MHD_parse_request_arg_auto (rc->connection,
- "h_payto",
- &h_payto,
- have_payto);
- TALER_MHD_parse_request_snumber (rc->connection,
- "limit",
- &limit);
- if (limit > 0)
- offset = 0;
- else
- offset = INT64_MAX;
- TALER_MHD_parse_request_number (rc->connection,
- "offset",
- &offset);
- if (offset > INT64_MAX)
- {
- GNUNET_break_op (0); /* broken client */
- offset = INT64_MAX;
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &threshold));
- TALER_MHD_parse_request_amount (rc->connection,
- "threshold",
- &threshold);
- {
- json_t *transfers;
- enum GNUNET_DB_QueryStatus qs;
- const char *query;
-
- transfers = json_array ();
- GNUNET_assert (NULL != transfers);
- if (limit > MAX_TRANSFERS)
- limit = MAX_TRANSFERS;
- if (limit < -MAX_TRANSFERS)
- limit = -MAX_TRANSFERS;
- switch (tt)
- {
- case TT_DEBIT:
- qs = TEH_plugin->select_exchange_debit_transfers (
- TEH_plugin->cls,
- &threshold,
- offset,
- limit,
- have_payto ? &h_payto : NULL,
- &record_cb,
- transfers);
- query = "select_exchange_debit_transfers";
- break;
- case TT_CREDIT:
- qs = TEH_plugin->select_exchange_credit_transfers (
- TEH_plugin->cls,
- &threshold,
- offset,
- limit,
- have_payto ? &h_payto : NULL,
- &record_cb,
- transfers);
- query = "select_exchange_credit_transfers";
- break;
- case TT_KYCAUTH:
- qs = TEH_plugin->select_exchange_kycauth_transfers (
- TEH_plugin->cls,
- &threshold,
- offset,
- limit,
- have_payto ? &h_payto : NULL,
- &record_cb,
- transfers);
- query = "select_exchange_kycauth_transfers";
- break;
- }
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- json_decref (transfers);
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- query);
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- json_decref (transfers);
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("transfers",
- transfers));
- }
-}
-
-
-MHD_RESULT
-TEH_handler_aml_transfer_credit_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- return aml_transfer_get (rc,
- officer_pub,
- TT_CREDIT,
- args);
-}
-
-
-MHD_RESULT
-TEH_handler_aml_transfer_kycauth_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- return aml_transfer_get (rc,
- officer_pub,
- TT_KYCAUTH,
- args);
-}
-
-
-MHD_RESULT
-TEH_handler_aml_transfer_debit_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[])
-{
- return aml_transfer_get (rc,
- officer_pub,
- TT_DEBIT,
- args);
-}
-
-
-/* end of taler-exchange-httpd_aml-decisions_get.c */
diff --git a/src/exchange/taler-exchange-httpd_aml-transfer-get.h b/src/exchange/taler-exchange-httpd_aml-transfer-get.h
@@ -1,79 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_aml-transfer-get.h
- * @brief Handle /aml/$OFFICER_PUB/transfer-* requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AML_TRANSFER_GET_H
-#define TALER_EXCHANGE_HTTPD_AML_TRANSFER_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/transfer-credit" request. Parses the
- * request details, checks the signatures and if appropriately authorized
- * returns the matching wire transfers.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be empty)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_transfer_credit_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/transfer-kycauth" request. Parses the
- * request details, checks the signatures and if appropriately authorized
- * returns the matching wire transfers.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be empty)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_transfer_kycauth_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-/**
- * Handle a GET "/aml/$OFFICER_PUB/transfer-debit" request. Parses the
- * request details, checks the signatures and if appropriately authorized
- * returns the matching wire transfers.
- *
- * @param rc request context
- * @param officer_pub public key of the AML officer who made the request
- * @param args GET arguments (should be empty)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_aml_transfer_debit_get (
- struct TEH_RequestContext *rc,
- const struct TALER_AmlOfficerPublicKeyP *officer_pub,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_auditors.c b/src/exchange/taler-exchange-httpd_auditors.c
@@ -1,232 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 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_auditors.c
- * @brief Handle request to add auditor signature on a denomination.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_auditors.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Closure for the #add_auditor_denom_sig transaction.
- */
-struct AddAuditorDenomContext
-{
- /**
- * Auditor's signature affirming the AUDITORS XXX operation
- * (includes timestamp).
- */
- struct TALER_AuditorSignatureP auditor_sig;
-
- /**
- * Denomination this is about.
- */
- const struct TALER_DenominationHashP *h_denom_pub;
-
- /**
- * Auditor this is about.
- */
- const struct TALER_AuditorPublicKeyP *auditor_pub;
-
-};
-
-
-/**
- * Function implementing database transaction to add an auditors. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddAuditorDenomContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_auditor_denom_sig (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddAuditorDenomContext *awc = cls;
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
- enum GNUNET_DB_QueryStatus qs;
- char *auditor_url;
- bool enabled;
-
- qs = TEH_plugin->lookup_denomination_key (
- TEH_plugin->cls,
- awc->h_denom_pub,
- &meta);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup denomination key");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- GNUNET_h2s (&awc->h_denom_pub->hash));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = TEH_plugin->lookup_auditor_status (
- TEH_plugin->cls,
- awc->auditor_pub,
- &auditor_url,
- &enabled);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup auditor");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_PRECONDITION_FAILED,
- TALER_EC_EXCHANGE_AUDITORS_AUDITOR_UNKNOWN,
- TALER_B2S (awc->auditor_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! enabled)
- {
- GNUNET_free (auditor_url);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_AUDITORS_AUDITOR_INACTIVE,
- TALER_B2S (awc->auditor_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_auditor_denom_validity_verify (
- auditor_url,
- awc->h_denom_pub,
- &TEH_master_public_key,
- meta.start,
- meta.expire_withdraw,
- meta.expire_deposit,
- meta.expire_legal,
- &meta.value,
- &meta.fees,
- awc->auditor_pub,
- &awc->auditor_sig))
- {
- GNUNET_free (auditor_url);
- /* signature invalid */
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_free (auditor_url);
-
- qs = TEH_plugin->insert_auditor_denom_sig (TEH_plugin->cls,
- awc->h_denom_pub,
- awc->auditor_pub,
- &awc->auditor_sig);
- if (qs < 0)
- {
- GNUNET_break (0);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "add auditor signature");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_auditors (
- struct MHD_Connection *connection,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHashP *h_denom_pub,
- const json_t *root)
-{
- struct AddAuditorDenomContext awc = {
- .auditor_pub = auditor_pub,
- .h_denom_pub = h_denom_pub
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("auditor_sig",
- &awc.auditor_sig),
- GNUNET_JSON_spec_end ()
- };
- MHD_RESULT res;
- enum GNUNET_GenericReturnValue ret;
-
- ret = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
- ret = TEH_DB_run_transaction (connection,
- "add auditor denom sig",
- TEH_MT_REQUEST_OTHER,
- &res,
- &add_auditor_denom_sig,
- &awc);
- if (GNUNET_SYSERR == ret)
- return res;
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_auditors.c */
diff --git a/src/exchange/taler-exchange-httpd_auditors.h b/src/exchange/taler-exchange-httpd_auditors.h
@@ -1,46 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 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_auditors.h
- * @brief Handlers for the /auditors/ endpoints
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_AUDITORS_H
-#define TALER_EXCHANGE_HTTPD_AUDITORS_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/auditors/$AUDITOR_PUB/$H_DENOM_PUB" request.
- *
- * @param connection the MHD connection to handle
- * @param root uploaded JSON data
- * @param auditor_pub public key of the auditor
- * @param h_denom_pub hash of the denomination public key
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_auditors (
- struct MHD_Connection *connection,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHashP *h_denom_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.c b/src/exchange/taler-exchange-httpd_batch-deposit.c
@@ -1,1207 +0,0 @@
-/*
- This file is part of TALER
- Copyright (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 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_batch-deposit.c
- * @brief Handle /batch-deposit requests; parses the POST and JSON and
- * verifies the coin signatures before handing things off
- * to the database.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_extensions_policy.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_batch-deposit.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for #batch_deposit_transaction.
- */
-struct BatchDepositContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct BatchDepositContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct BatchDepositContext *prev;
-
- /**
- * The request we are working on.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for the legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Array with the individual coin deposit fees.
- */
- struct TALER_Amount *deposit_fees;
-
- /**
- * Information about deposited coins.
- */
- struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
-
- /**
- * Additional details for policy extension relevant for this
- * deposit operation, possibly NULL!
- */
- json_t *policy_json;
-
- /**
- * Response to return, if set.
- */
- struct MHD_Response *response;
-
- /**
- * KYC status of the reserve used for the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Hash over @e policy_details, might be all zero
- */
- struct TALER_ExtensionPolicyHashP h_policy;
-
- /**
- * Hash over the merchant's payto://-URI with the wire salt.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * When @e policy_details are persisted, this contains the id of the record
- * in the policy_details table.
- */
- uint64_t policy_details_serial_id;
-
- /**
- * Hash over the normalized payto://-URI of the account we are
- * depositing into.
- */
- struct TALER_NormalizedPaytoHashP nph;
-
- /**
- * Our timestamp (when we received the request).
- * Possibly updated by the transaction if the
- * request is idempotent (was repeated).
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Total amount that is accumulated with this deposit,
- * without fee.
- */
- struct TALER_Amount accumulated_total_without_fee;
-
- /**
- * Details about the batch deposit operation.
- */
- struct TALER_EXCHANGEDB_BatchDeposit bd;
-
- /**
- * If @e policy_json was present, the corresponding policy extension
- * calculates these details. These will be persisted in the policy_details
- * table.
- */
- struct TALER_PolicyDetails policy_details;
-
- /**
- * HTTP status to return with @e response, or 0.
- */
- unsigned int http_status;
-
- /**
- * Our current state in the state machine.
- */
- enum
- {
- BDC_PHASE_INIT = 0,
- BDC_PHASE_PARSE = 1,
- BDC_PHASE_POLICY = 2,
- BDC_PHASE_KYC = 3,
- BDC_PHASE_TRANSACT = 4,
- BDC_PHASE_REPLY_SUCCESS = 5,
- BDC_PHASE_SUSPENDED,
- BDC_PHASE_CHECK_KYC_RESULT,
- BDC_PHASE_GENERATE_REPLY_FAILURE,
- BDC_PHASE_RETURN_YES,
- BDC_PHASE_RETURN_NO,
- } phase;
-
- /**
- * True, if no policy was present in the request. Then
- * @e policy_json is NULL and @e h_policy will be all zero.
- */
- bool has_no_policy;
-
- /**
- * KYC failed because a KYC auth transfer is needed
- * to establish the merchant_pub.
- */
- bool bad_kyc_auth;
-};
-
-
-/**
- * Head of list of suspended batch deposit operations.
- */
-static struct BatchDepositContext *bdc_head;
-
-/**
- * Tail of list of suspended batch deposit operations.
- */
-static struct BatchDepositContext *bdc_tail;
-
-
-void
-TEH_batch_deposit_cleanup ()
-{
- struct BatchDepositContext *bdc;
-
- while (NULL != (bdc = bdc_head))
- {
- GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase);
- bdc->phase = BDC_PHASE_RETURN_NO;
- MHD_resume_connection (bdc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (bdc_head,
- bdc_tail,
- bdc);
- }
-}
-
-
-/**
- * Terminate the main loop by returning the final
- * result.
- *
- * @param[in,out] bdc context to update phase for
- * @param mres MHD status to return
- */
-static void
-finish_loop (struct BatchDepositContext *bdc,
- MHD_RESULT mres)
-{
- bdc->phase = (MHD_YES == mres)
- ? BDC_PHASE_RETURN_YES
- : BDC_PHASE_RETURN_NO;
-}
-
-
-/**
- * Send confirmation of batch deposit success to client. This function will
- * create a signed message affirming the given information and return it to
- * the client. By this, the exchange affirms that the coins had sufficient
- * (residual) value for the specified transaction and that it will execute the
- * requested batch deposit operation with the given wiring details.
- *
- * @param[in,out] bdc information about the batch deposit
- */
-static void
-bdc_phase_reply_success (
- struct BatchDepositContext *bdc)
-{
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
- const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
- enum TALER_ErrorCode ec;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
- csigs[i] = &bd->cdis[i].csig;
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_deposit_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &bd->h_contract_terms,
- &bdc->h_wire,
- bdc->has_no_policy ? NULL : &bdc->h_policy,
- bdc->exchange_timestamp,
- bd->wire_deadline,
- bd->refund_deadline,
- &bdc->accumulated_total_without_fee,
- bd->num_cdis,
- csigs,
- &bdc->bd.merchant_pub,
- &pub,
- &sig)))
- {
- GNUNET_break (0);
- finish_loop (bdc,
- TALER_MHD_reply_with_ec (bdc->rc->connection,
- ec,
- NULL));
- return;
- }
- finish_loop (bdc,
- TALER_MHD_REPLY_JSON_PACK (
- bdc->rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- bdc->exchange_timestamp),
- TALER_JSON_pack_amount ("accumulated_total_without_fee",
- &bdc->accumulated_total_without_fee),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig)));
-}
-
-
-/**
- * Execute database transaction for /batch-deposit. Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls a `struct BatchDepositContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-batch_deposit_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct BatchDepositContext *bdc = cls;
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
- enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
- uint32_t bad_balance_coin_index = UINT32_MAX;
- bool balance_ok;
- bool in_conflict;
-
- /* If the deposit has a policy associated to it, persist it. This will
- * insert or update the record. */
- if (! bdc->has_no_policy)
- {
- qs = TEH_plugin->persist_policy_details (
- TEH_plugin->cls,
- &bdc->policy_details,
- &bdc->bd.policy_details_serial_id,
- &bdc->accumulated_total_without_fee,
- &bdc->policy_details.fulfillment_state);
- if (qs < 0)
- return qs;
-
- bdc->bd.policy_blocked =
- bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
- }
-
- /* FIXME-#9373: replace by batch insert! */
- for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
- {
- const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
- = &bdc->cdis[i];
- uint64_t known_coin_id;
-
- qs = TEH_make_coin_known (&cdi->coin,
- connection,
- &known_coin_id,
- mhd_ret);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "make coin known (%s) returned %d\n",
- TALER_B2S (&cdi->coin.coin_pub),
- qs);
- if (qs < 0)
- return qs;
- }
- qs = TEH_plugin->do_deposit (
- TEH_plugin->cls,
- bd,
- bdc->deposit_fees,
- &bdc->exchange_timestamp,
- &bdc->accumulated_total_without_fee,
- &balance_ok,
- &bad_balance_coin_index,
- &in_conflict);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING (
- "Failed to store /batch-deposit information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "batch-deposit");
- return qs;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "do_deposit returned: %d / %s[%u] / %s\n",
- qs,
- balance_ok ? "balance ok" : "balance insufficient",
- (unsigned int) bad_balance_coin_index,
- in_conflict ? "in conflict" : "no conflict");
- if (in_conflict)
- {
- struct TALER_MerchantWireHashP h_wire;
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
- TEH_plugin->get_wire_hash_for_contract (
- TEH_plugin->cls,
- &bd->merchant_pub,
- &bd->h_contract_terms,
- &h_wire))
- {
- TALER_LOG_WARNING (
- "Failed to retrieve conflicting contract details from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "batch-deposit");
- return qs;
- }
-
- *mhd_ret
- = TEH_RESPONSE_reply_coin_conflicting_contract (
- connection,
- TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
- &h_wire);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- GNUNET_assert (bad_balance_coin_index < bdc->bd.num_cdis);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "returning history of conflicting coin (%s)\n",
- TALER_B2S (&bdc->cdis[bad_balance_coin_index].coin.coin_pub));
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash,
- &bdc->cdis[bad_balance_coin_index].coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
- return qs;
-}
-
-
-/**
- * Run database transaction.
- *
- * @param[in,out] bdc request context
- */
-static void
-bdc_phase_transact (struct BatchDepositContext *bdc)
-{
- MHD_RESULT mhd_ret;
-
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure"));
- return;
- }
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (bdc->rc->connection,
- "execute batch deposit",
- TEH_MT_REQUEST_BATCH_DEPOSIT,
- &mhd_ret,
- &batch_deposit_transaction,
- bdc))
- {
- finish_loop (bdc,
- mhd_ret);
- return;
- }
- bdc->phase++;
-}
-
-
-/**
- * Check if the @a bdc is replayed and we already have an
- * answer. If so, replay the existing answer and return the
- * HTTP response.
- *
- * @param bdc parsed request data
- * @return true if the request is idempotent with an existing request
- * false if we did not find the request in the DB and did not set @a mret
- */
-static bool
-check_request_idempotent (
- struct BatchDepositContext *bdc)
-{
- const struct TEH_RequestContext *rc = bdc->rc;
- enum GNUNET_DB_QueryStatus qs;
- bool is_idempotent;
-
- qs = TEH_plugin->do_check_deposit_idempotent (
- TEH_plugin->cls,
- &bdc->bd,
- &bdc->exchange_timestamp,
- &is_idempotent);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_check_deposit_idempotent"));
- return true;
- }
- if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
- (! is_idempotent) )
- return false;
- bdc->phase = BDC_PHASE_REPLY_SUCCESS;
- return true;
-}
-
-
-/**
- * Check the KYC result.
- *
- * @param bdc storage for request processing
- */
-static void
-bdc_phase_check_kyc_result (struct BatchDepositContext *bdc)
-{
- /* return final positive response */
- if ( (! bdc->kyc.ok) ||
- (bdc->bad_kyc_auth) )
- {
- if (check_request_idempotent (bdc))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Request is idempotent!\n");
- return;
- }
- /* KYC required */
- finish_loop (bdc,
- TEH_RESPONSE_reply_kyc_required (
- bdc->rc->connection,
- &bdc->nph,
- &bdc->kyc,
- bdc->bad_kyc_auth));
- return;
- }
- bdc->phase = BDC_PHASE_TRANSACT;
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls closure
- * @param lcr legitimization check result
- */
-static void
-deposit_legi_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct BatchDepositContext *bdc = cls;
-
- bdc->lch = NULL;
- GNUNET_assert (BDC_PHASE_SUSPENDED ==
- bdc->phase);
- MHD_resume_connection (bdc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (bdc_head,
- bdc_tail,
- bdc);
- TALER_MHD_daemon_trigger ();
- if (NULL != lcr->response)
- {
- bdc->response = lcr->response;
- bdc->http_status = lcr->http_status;
- bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE;
- return;
- }
- bdc->kyc = lcr->kyc;
- bdc->bad_kyc_auth = lcr->bad_kyc_auth;
- bdc->phase = BDC_PHASE_CHECK_KYC_RESULT;
-}
-
-
-/**
- * Function called to iterate over KYC-relevant transaction amounts for a
- * particular time range. Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls closure, identifies the event type and account to iterate
- * over events for
- * @param limit maximum time-range for which events should be fetched
- * (timestamp in the past)
- * @param cb function to call on each event found, events must be returned
- * in reverse chronological order
- * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-deposit_amount_cb (
- void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct BatchDepositContext *bdc = cls;
- enum GNUNET_GenericReturnValue ret;
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signaling amount %s for KYC check during deposit\n",
- TALER_amount2s (&bdc->accumulated_total_without_fee));
- ret = cb (cb_cls,
- &bdc->accumulated_total_without_fee,
- bdc->exchange_timestamp.abs_time);
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- qs = TEH_plugin->select_deposit_amounts_for_kyc_check (
- TEH_plugin->cls,
- &bdc->nph,
- limit,
- cb,
- cb_cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got %d additional transactions for this deposit and limit %llu\n",
- qs,
- (unsigned long long) limit.abs_value_us);
- GNUNET_break (qs >= 0);
- return qs;
-}
-
-
-/**
- * Run KYC check.
- *
- * @param[in,out] bdc request context
- */
-static void
-bdc_phase_kyc (struct BatchDepositContext *bdc)
-{
- if (GNUNET_YES != TEH_enable_kyc)
- {
- bdc->phase++;
- return;
- }
- TALER_full_payto_normalize_and_hash (bdc->bd.receiver_wire_account,
- &bdc->nph);
- bdc->lch = TEH_legitimization_check2 (
- &bdc->rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
- bdc->bd.receiver_wire_account,
- &bdc->nph,
- &bdc->bd.merchant_pub,
- &deposit_amount_cb,
- bdc,
- &deposit_legi_cb,
- bdc);
- GNUNET_assert (NULL != bdc->lch);
- GNUNET_CONTAINER_DLL_insert (bdc_head,
- bdc_tail,
- bdc);
- MHD_suspend_connection (bdc->rc->connection);
- bdc->phase = BDC_PHASE_SUSPENDED;
-}
-
-
-/**
- * Handle policy.
- *
- * @param[in,out] bdc request context
- */
-static void
-bdc_phase_policy (struct BatchDepositContext *bdc)
-{
- const char *error_hint = NULL;
-
- if (bdc->has_no_policy)
- {
- bdc->phase++;
- return;
- }
- if (GNUNET_OK !=
- TALER_extensions_create_policy_details (
- TEH_currency,
- bdc->policy_json,
- &bdc->policy_details,
- &error_hint))
- {
- GNUNET_break_op (0);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
- error_hint));
- return;
- }
-
- TALER_deposit_policy_hash (bdc->policy_json,
- &bdc->h_policy);
- bdc->phase++;
-}
-
-
-/**
- * Parse per-coin deposit information from @a jcoin
- * into @a deposit. Fill in generic information from
- * @a ctx.
- *
- * @param bdc information about the overall batch
- * @param jcoin coin data to parse
- * @param[out] cdi where to store the result
- * @param[out] deposit_fee where to write the deposit fee
- * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
- * #GNUNET_SYSERR on failure and no error could be returned
- */
-static enum GNUNET_GenericReturnValue
-parse_coin (const struct BatchDepositContext *bdc,
- json_t *jcoin,
- struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
- struct TALER_Amount *deposit_fee)
-{
- const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("contribution",
- TEH_currency,
- &cdi->amount_with_fee),
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &cdi->coin.denom_pub_hash),
- TALER_JSON_spec_denom_sig ("ub_sig",
- &cdi->coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("coin_pub",
- &cdi->coin.coin_pub),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &cdi->coin.h_age_commitment),
- &cdi->coin.no_age_commitment),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &cdi->csig),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- if (GNUNET_OK !=
- (res = TALER_MHD_parse_json_data (bdc->rc->connection,
- jcoin,
- spec)))
- return res;
- /* check denomination exists and is valid */
- {
- struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
-
- dk = TEH_keys_denomination_by_hash (
- &cdi->coin.denom_pub_hash,
- bdc->rc->connection,
- &mret);
- if (NULL == dk)
- {
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES == mret)
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (0 > TALER_amount_cmp (&dk->meta.value,
- &cdi->amount_with_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
- {
- /* This denomination is past the expiration time for deposits */
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- bdc->rc->connection,
- &cdi->coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "DEPOSIT"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- bdc->rc->connection,
- &cdi->coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "DEPOSIT"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (dk->recoup_possible)
- {
- /* This denomination has been revoked */
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- bdc->rc->connection,
- &cdi->coin.denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- "DEPOSIT"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- if (dk->denom_pub.bsign_pub_key->cipher !=
- cdi->coin.denom_sig.unblinded_sig->cipher)
- {
- /* denomination cipher and denomination signature cipher not the same */
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- *deposit_fee = dk->meta.fees.deposit;
- /* check coin signature */
- switch (dk->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
- break;
- case GNUNET_CRYPTO_BSA_CS:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
- break;
- default:
- break;
- }
- if (GNUNET_YES !=
- TALER_test_coin_valid (&cdi->coin,
- &dk->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- }
- if (0 < TALER_amount_cmp (deposit_fee,
- &cdi->amount_with_fee))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
- NULL))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (
- &cdi->amount_with_fee,
- deposit_fee,
- &bdc->h_wire,
- &bd->h_contract_terms,
- &bd->wallet_data_hash,
- cdi->coin.no_age_commitment
- ? NULL
- : &cdi->coin.h_age_commitment,
- NULL != bdc->policy_json ? &bdc->h_policy : NULL,
- &cdi->coin.denom_pub_hash,
- bd->wallet_timestamp,
- &bd->merchant_pub,
- bd->refund_deadline,
- &cdi->coin.coin_pub,
- &cdi->csig))
- {
- TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
- GNUNET_JSON_parse_free (spec);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
- TALER_B2S (&cdi->coin.coin_pub)))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Run processing phase that parses the request.
- *
- * @param[in,out] bdc request context
- * @param root JSON object that was POSTed
- */
-static void
-bdc_phase_parse (struct BatchDepositContext *bdc,
- const json_t *root)
-{
- struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
- const json_t *coins;
- const json_t *policy_json;
- bool no_refund_deadline = true;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_full_payto_uri ("merchant_payto_uri",
- &bd->receiver_wire_account),
- GNUNET_JSON_spec_fixed_auto ("wire_salt",
- &bd->wire_salt),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &bd->merchant_pub),
- GNUNET_JSON_spec_fixed_auto ("merchant_sig",
- &bd->merchant_sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &bd->h_contract_terms),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
- &bd->wallet_data_hash),
- &bd->no_wallet_data_hash),
- GNUNET_JSON_spec_array_const ("coins",
- &coins),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("policy",
- &policy_json),
- &bdc->has_no_policy),
- GNUNET_JSON_spec_timestamp ("timestamp",
- &bd->wallet_timestamp),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("extra_wire_subject_metadata",
- &bd->extra_wire_subject_metadata),
- &bd->no_wallet_data_hash),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_timestamp ("refund_deadline",
- &bd->refund_deadline),
- &no_refund_deadline),
- GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
- &bd->wire_deadline),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (bdc->rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- /* hard failure */
- GNUNET_break (0);
- finish_loop (bdc,
- MHD_NO);
- return;
- }
- if (GNUNET_NO == res)
- {
- /* failure */
- GNUNET_break_op (0);
- finish_loop (bdc,
- MHD_YES);
- return;
- }
- }
- if (! TALER_is_valid_subject_metadata_string (
- bd->extra_wire_subject_metadata))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "extra_wire_subject_metadata"));
- return;
- }
- if (GNUNET_OK !=
- TALER_merchant_contract_verify (
- &bd->h_contract_terms,
- &bd->merchant_pub,
- &bd->merchant_sig))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "merchant_sig"));
- return;
- }
- bdc->policy_json
- = json_incref ((json_t *) policy_json);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Batch deposit into contract %s\n",
- GNUNET_h2s (&bd->h_contract_terms.hash));
-
- /* validate merchant's wire details (as far as we can) */
- {
- char *emsg;
-
- emsg = TALER_payto_validate (bd->receiver_wire_account);
- if (NULL != emsg)
- {
- MHD_RESULT ret;
-
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- ret = TALER_MHD_reply_with_error (bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- emsg);
- GNUNET_free (emsg);
- finish_loop (bdc,
- ret);
- return;
- }
- }
- if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
- >,
- bd->wire_deadline))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
- NULL));
- return;
- }
- if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
- NULL));
- return;
- }
- TALER_full_payto_hash (bd->receiver_wire_account,
- &bd->wire_target_h_payto);
- TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
- &bd->wire_salt,
- &bdc->h_wire);
- bd->num_cdis = json_array_size (coins);
- if (0 == bd->num_cdis)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "coins"));
- return;
- }
- if (TALER_MAX_COINS < bd->num_cdis)
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- finish_loop (bdc,
- TALER_MHD_reply_with_error (
- bdc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "coins"));
- return;
- }
-
- bdc->cdis
- = GNUNET_new_array (bd->num_cdis,
- struct TALER_EXCHANGEDB_CoinDepositInformation);
- bdc->deposit_fees
- = GNUNET_new_array (bd->num_cdis,
- struct TALER_Amount);
- bd->cdis = bdc->cdis;
- for (unsigned i = 0; i<bd->num_cdis; i++)
- {
- struct TALER_Amount amount_without_fee;
- enum GNUNET_GenericReturnValue res;
-
- res = parse_coin (bdc,
- json_array_get (coins,
- i),
- &bdc->cdis[i],
- &bdc->deposit_fees[i]);
- if (GNUNET_OK != res)
- {
- finish_loop (bdc,
- (GNUNET_NO == res)
- ? MHD_YES
- : MHD_NO);
- return;
- }
- GNUNET_assert (0 <=
- TALER_amount_subtract (
- &amount_without_fee,
- &bdc->cdis[i].amount_with_fee,
- &bdc->deposit_fees[i]));
-
- GNUNET_assert (0 <=
- TALER_amount_add (
- &bdc->accumulated_total_without_fee,
- &bdc->accumulated_total_without_fee,
- &amount_without_fee));
- }
-
- GNUNET_JSON_parse_free (spec);
- bdc->phase++;
-}
-
-
-/**
- * Function called to clean up a context.
- *
- * @param rc request context with data to clean up
- */
-static void
-bdc_cleaner (struct TEH_RequestContext *rc)
-{
- struct BatchDepositContext *bdc = rc->rh_ctx;
-
- if (NULL != bdc->lch)
- {
- TEH_legitimization_check_cancel (bdc->lch);
- bdc->lch = NULL;
- }
- if (0 != bdc->cdis)
- {
- for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
- TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig);
- GNUNET_free (bdc->cdis);
- }
- GNUNET_free (bdc->deposit_fees);
- json_decref (bdc->policy_json);
- GNUNET_free (bdc);
-}
-
-
-MHD_RESULT
-TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[])
-{
- struct BatchDepositContext *bdc = rc->rh_ctx;
-
- (void) args;
- if (NULL == bdc)
- {
- bdc = GNUNET_new (struct BatchDepositContext);
- bdc->rc = rc;
- rc->rh_ctx = bdc;
- rc->rh_cleaner = &bdc_cleaner;
- bdc->phase = BDC_PHASE_PARSE;
- bdc->exchange_timestamp = GNUNET_TIME_timestamp_get ();
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &bdc->accumulated_total_without_fee));
- }
- while (1)
- {
- switch (bdc->phase)
- {
- case BDC_PHASE_INIT:
- GNUNET_break (0);
- bdc->phase = BDC_PHASE_RETURN_NO;
- break;
- case BDC_PHASE_PARSE:
- bdc_phase_parse (bdc,
- root);
- break;
- case BDC_PHASE_POLICY:
- bdc_phase_policy (bdc);
- break;
- case BDC_PHASE_KYC:
- bdc_phase_kyc (bdc);
- break;
- case BDC_PHASE_TRANSACT:
- bdc_phase_transact (bdc);
- break;
- case BDC_PHASE_REPLY_SUCCESS:
- bdc_phase_reply_success (bdc);
- break;
- case BDC_PHASE_SUSPENDED:
- return MHD_YES;
- case BDC_PHASE_CHECK_KYC_RESULT:
- bdc_phase_check_kyc_result (bdc);
- break;
- case BDC_PHASE_GENERATE_REPLY_FAILURE:
- return MHD_queue_response (bdc->rc->connection,
- bdc->http_status,
- bdc->response);
- case BDC_PHASE_RETURN_YES:
- return MHD_YES;
- case BDC_PHASE_RETURN_NO:
- return MHD_NO;
- }
- }
-}
-
-
-/* end of taler-exchange-httpd_batch-deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_batch-deposit.h b/src/exchange/taler-exchange-httpd_batch-deposit.h
@@ -1,57 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014 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_batch-deposit.h
- * @brief Handle /batch-deposit requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
-#define TALER_EXCHANGE_HTTPD_BATCH_DEPOSIT_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resumes all suspended batch deposit requests
- * during cleanup.
- */
-void
-TEH_batch_deposit_cleanup (void);
-
-
-/**
- * Handle a "/batch-deposit" request. Parses the JSON, and, if
- * successful, passes the JSON data to #deposit_transaction() to
- * further check the details of the operation specified. If everything checks
- * out, this will ultimately lead to the "/batch-deposit" being executed, or
- * rejected.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param args arguments, empty in this case
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_blinding-prepare.c b/src/exchange/taler-exchange-httpd_blinding-prepare.c
@@ -1,223 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_blinding-prepare.c
- * @brief Handle /blinding-prepare requests
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_blinding-prepare.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-MHD_RESULT
-TEH_handler_blinding_prepare (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[])
-{
- struct TALER_BlindingMasterSeedP blinding_seed;
- const char *cipher;
- const char *operation;
- const json_t *j_nks;
- size_t num;
- bool is_melt;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("cipher",
- &cipher),
- GNUNET_JSON_spec_string ("operation",
- &operation),
- GNUNET_JSON_spec_fixed_auto ("seed",
- &blinding_seed),
- GNUNET_JSON_spec_array_const ("nks",
- &j_nks),
- GNUNET_JSON_spec_end ()
- };
-
- (void) args;
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_OK != res)
- return (GNUNET_SYSERR == res)
- ? MHD_NO
- : MHD_YES;
- }
- if (0 == strcmp (operation,
- "melt"))
- {
- is_melt = true;
- }
- else if (0 == strcmp (operation,
- "withdraw"))
- {
- is_melt = false;
- }
- else
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "operation");
- }
-
- num = json_array_size (j_nks);
- if ((0 == num) ||
- (TALER_MAX_COINS < num))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
- "nks");
- }
- if (0 != strcmp (cipher, "CS"))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- " cipher");
- }
-
- {
- uint32_t cs_indices[num];
- struct TALER_DenominationHashP h_denom_pubs[num];
- struct GNUNET_CRYPTO_CsSessionNonce nonces[num];
-
- for (size_t i = 0; i < num; i++)
- {
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification nks_spec[] = {
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &h_denom_pubs[i]),
- GNUNET_JSON_spec_uint32 ("coin_offset",
- &cs_indices[i]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_array (rc->connection,
- j_nks,
- nks_spec,
- i,
- -1);
- if (GNUNET_OK != res)
- return (GNUNET_SYSERR == res)
- ? MHD_NO
- : MHD_YES;
-
- if (TALER_MAX_COINS < cs_indices[i])
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "nks");
- }
- }
-
- TALER_cs_derive_nonces_from_seed (&blinding_seed,
- is_melt,
- num,
- cs_indices,
- nonces);
- {
- struct TEH_KeyStateHandle *ksh;
- struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[num];
- size_t err_idx;
- enum TALER_ErrorCode ec;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
-
- ec =
- TEH_keys_denomination_cs_batch_r_pub (ksh,
- num,
- h_denom_pubs,
- nonces,
- is_melt,
- r_pubs,
- &err_idx);
- switch (ec)
- {
- case TALER_EC_NONE:
- break;
- case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN:
- return TEH_RESPONSE_reply_unknown_denom_pub_hash (
- rc->connection,
- &h_denom_pubs[err_idx]);
- break;
- case TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION:
- return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
- rc->connection,
- &h_denom_pubs[err_idx]);
- break;
- case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED:
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- rc->connection,
- &h_denom_pubs[err_idx],
- ec,
- "blinding-prepare");
- break;
- case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE:
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- rc->connection,
- &h_denom_pubs[err_idx],
- ec,
- "blinding-prepare");
- break;
- default:
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (rc->connection,
- ec,
- NULL);
- break;
- }
-
- /* Finally, create the response */
- {
- struct TALER_BlindingPrepareResponse response = {
- .num = num,
- .cipher = GNUNET_CRYPTO_BSA_CS,
- .details.cs = r_pubs,
- };
-
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_blinding_prepare_response (NULL,
- &response));
- }
- }
- }
-}
-
-
-/* end of taler-exchange-httpd_blinding_prepare.c */
diff --git a/src/exchange/taler-exchange-httpd_blinding-prepare.h b/src/exchange/taler-exchange-httpd_blinding-prepare.h
@@ -1,40 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_blinding-prepare.h
- * @brief Handle /blinding-prepare requests
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_BLINDING_PREPARE_H
-#define TALER_EXCHANGE_HTTPD_BLINDING_PREPARE_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-/**
- * Handle a "/blinding-prepare" request.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param args empty array
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_blinding_prepare (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_coins_get.c b/src/exchange/taler-exchange-httpd_coins_get.c
@@ -1,763 +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 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_coins_get.c
- * @brief Handle GET /coins/$COIN_PUB/history requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_coins_get.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Add the headers we want to set for every response.
- *
- * @param cls the key state to use
- * @param[in,out] response the response to modify
- */
-static void
-add_response_headers (void *cls,
- struct MHD_Response *response)
-{
- (void) cls;
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "no-cache"));
-}
-
-
-/**
- * Compile the transaction history of a coin into a JSON object.
- *
- * @param coin_pub public key of the coin
- * @param tl transaction history to JSON-ify
- * @return json representation of the @a rh, NULL on error
- */
-static json_t *
-compile_transaction_history (
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_TransactionList *tl)
-{
- json_t *history;
-
- history = json_array ();
- if (NULL == history)
- {
- GNUNET_break (0); /* out of memory!? */
- return NULL;
- }
- for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
- NULL != pos;
- pos = pos->next)
- {
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_TT_DEPOSIT:
- {
- const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
- pos->details.deposit;
- struct TALER_MerchantWireHashP h_wire;
-
- TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
- &deposit->wire_salt,
- &h_wire);
-#if ENABLE_SANITY_CHECKS
- /* internal sanity check before we hand out a bogus sig... */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_deposit_verify (
- &deposit->amount_with_fee,
- &deposit->deposit_fee,
- &h_wire,
- &deposit->h_contract_terms,
- deposit->no_wallet_data_hash
- ? NULL
- : &deposit->wallet_data_hash,
- deposit->no_age_commitment
- ? NULL
- : &deposit->h_age_commitment,
- &deposit->h_policy,
- &deposit->h_denom_pub,
- deposit->timestamp,
- &deposit->merchant_pub,
- deposit->refund_deadline,
- coin_pub,
- &deposit->csig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "DEPOSIT"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &deposit->amount_with_fee),
- TALER_JSON_pack_amount ("deposit_fee",
- &deposit->deposit_fee),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &deposit->merchant_pub),
- GNUNET_JSON_pack_timestamp ("timestamp",
- deposit->timestamp),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("refund_deadline",
- deposit->refund_deadline)),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &deposit->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("h_wire",
- &h_wire),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &deposit->h_denom_pub),
- GNUNET_JSON_pack_allow_null (
- deposit->has_policy
- ? GNUNET_JSON_pack_data_auto ("h_policy",
- &deposit->h_policy)
- : GNUNET_JSON_pack_string (
- "h_policy",
- NULL)),
- GNUNET_JSON_pack_allow_null (
- deposit->no_wallet_data_hash
- ? GNUNET_JSON_pack_string (
- "wallet_data_hash",
- NULL)
- : GNUNET_JSON_pack_data_auto ("wallet_data_hash",
- &deposit->wallet_data_hash)),
- GNUNET_JSON_pack_allow_null (
- deposit->no_age_commitment
- ? GNUNET_JSON_pack_string (
- "h_age_commitment",
- NULL)
- : GNUNET_JSON_pack_data_auto ("h_age_commitment",
- &deposit->h_age_commitment)),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &deposit->csig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_MELT:
- {
- const struct TALER_EXCHANGEDB_MeltListEntry *melt =
- pos->details.melt;
- const struct TALER_AgeCommitmentHashP *phac;
- const struct TALER_BlindingMasterSeedP *pbs;
-
-#if ENABLE_SANITY_CHECKS
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- &melt->amount_with_fee,
- &melt->melt_fee,
- &melt->rc,
- &melt->h_denom_pub,
- &melt->h_age_commitment,
- coin_pub,
- &melt->coin_sig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- phac = (melt->no_age_commitment)
- ? NULL
- : &melt->h_age_commitment;
- pbs = melt->no_blinding_seed
- ? NULL
- : &melt->blinding_seed;
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "MELT"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &melt->amount_with_fee),
- TALER_JSON_pack_amount ("melt_fee",
- &melt->melt_fee),
- GNUNET_JSON_pack_data_auto ("rc",
- &melt->rc),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &melt->h_denom_pub),
- GNUNET_JSON_pack_data_auto ("refresh_seed",
- &melt->refresh_seed),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("blinding_seed",
- pbs)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- phac)),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &melt->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_REFUND:
- {
- const struct TALER_EXCHANGEDB_RefundListEntry *refund =
- pos->details.refund;
- struct TALER_Amount value;
-
-#if ENABLE_SANITY_CHECKS
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (
- coin_pub,
- &refund->h_contract_terms,
- refund->rtransaction_id,
- &refund->refund_amount,
- &refund->merchant_pub,
- &refund->merchant_sig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
-#endif
- if (0 >
- TALER_amount_subtract (&value,
- &refund->refund_amount,
- &refund->refund_fee))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "REFUND"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &value),
- TALER_JSON_pack_amount ("refund_fee",
- &refund->refund_fee),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &refund->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- &refund->merchant_pub),
- GNUNET_JSON_pack_uint64 ("rtransaction_id",
- refund->rtransaction_id),
- GNUNET_JSON_pack_data_auto ("merchant_sig",
- &refund->merchant_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.old_coin_recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_refresh_sign (
- &TEH_keys_exchange_sign_,
- pr->timestamp,
- &pr->value,
- &pr->coin.coin_pub,
- &pr->old_coin_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
- the denomination key's RSA signature over coin_pub, but as the
- wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. */
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP-REFRESH-RECEIVER"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &pr->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &pr->coin.coin_pub),
- GNUNET_JSON_pack_timestamp ("timestamp",
- pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
- {
- const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
- pos->details.recoup;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_sign (
- &TEH_keys_exchange_sign_,
- recoup->timestamp,
- &recoup->value,
- coin_pub,
- &recoup->reserve_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP-WITHDRAW"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &recoup->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &recoup->coin_sig),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &recoup->h_denom_pub),
- GNUNET_JSON_pack_data_auto ("coin_blind",
- &recoup->coin_blind),
- // FIXME-9828: spec says we should have h_commitment?
- // FIXME-9828: spec says we should have coin_index?
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &recoup->reserve_pub),
- GNUNET_JSON_pack_timestamp ("timestamp",
- recoup->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
- {
- struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
- pos->details.recoup_refresh;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_refresh_sign (
- &TEH_keys_exchange_sign_,
- pr->timestamp,
- &pr->value,
- coin_pub,
- &pr->old_coin_pub,
- &epub,
- &esig))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- /* NOTE: we could also provide coin_pub's coin_sig, denomination key
- hash and the denomination key's RSA signature over coin_pub, but as
- the wallet should really already have this information (and cannot
- check or do anything with it anyway if it doesn't), it seems
- strictly unnecessary. */
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP-REFRESH"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &pr->value),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("old_coin_pub",
- &pr->old_coin_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &pr->coin_sig),
- // FIXME-#9828: spec says to return h_denom_pub
- GNUNET_JSON_pack_data_auto ("coin_blind",
- &pr->coin_blind),
- // FIXME-#9828: spec says to return h_commitment
- // FIXME-#9828: spec says to return coin_index
- // FIXME-#9828: spec says to return new_coin_blinding_secret
- // FIXME-#9828: spec says to return new_coin_ev
- GNUNET_JSON_pack_timestamp ("timestamp",
- pr->timestamp))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
-
- case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
- {
- struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
- = pos->details.purse_deposit;
- const struct TALER_AgeCommitmentHashP *phac = NULL;
-
- if (! pd->no_age_commitment)
- phac = &pd->h_age_commitment;
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "PURSE-DEPOSIT"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &pd->amount),
- GNUNET_JSON_pack_string ("exchange_base_url",
- NULL == pd->exchange_base_url
- ? TEH_base_url
- : pd->exchange_base_url),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- phac)),
- TALER_JSON_pack_amount ("deposit_fee",
- &pd->deposit_fee),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &pd->purse_pub),
- GNUNET_JSON_pack_bool ("refunded",
- pd->refunded),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &pd->coin_sig),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &pd->h_denom_pub))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
-
- case TALER_EXCHANGEDB_TT_PURSE_REFUND:
- {
- const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
- pos->details.purse_refund;
- struct TALER_Amount value;
- enum TALER_ErrorCode ec;
- struct TALER_ExchangePublicKeyP epub;
- struct TALER_ExchangeSignatureP esig;
-
- if (0 >
- TALER_amount_subtract (&value,
- &prefund->refund_amount,
- &prefund->refund_fee))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- ec = TALER_exchange_online_purse_refund_sign (
- &TEH_keys_exchange_sign_,
- &value,
- &prefund->refund_fee,
- coin_pub,
- &prefund->purse_pub,
- &epub,
- &esig);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "PURSE-REFUND"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("amount",
- &value),
- TALER_JSON_pack_amount ("refund_fee",
- &prefund->refund_fee),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &esig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &epub),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &prefund->purse_pub))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- }
- break;
-
- case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
- {
- struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
- = pos->details.reserve_open;
-
- if (0 !=
- json_array_append_new (
- history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RESERVE-OPEN-DEPOSIT"),
- GNUNET_JSON_pack_uint64 ("history_offset",
- pos->coin_history_id),
- TALER_JSON_pack_amount ("coin_contribution",
- &role->coin_contribution),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &role->reserve_sig),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &role->coin_sig))))
- {
- GNUNET_break (0);
- json_decref (history);
- return NULL;
- }
- break;
- }
- }
- }
- return history;
-}
-
-
-MHD_RESULT
-TEH_handler_coins_get (struct TEH_RequestContext *rc,
- const struct TALER_CoinSpendPublicKeyP *coin_pub)
-{
- struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
- uint64_t start_off = 0;
- uint64_t etag_in;
- uint64_t etag_out;
- char etagp[24];
- struct MHD_Response *resp;
- unsigned int http_status;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_Amount balance;
-
- TALER_MHD_parse_request_number (rc->connection,
- "start",
- &start_off);
- /* Check signature */
- {
- struct TALER_CoinSpendSignatureP coin_sig;
- bool required = true;
-
- TALER_MHD_parse_request_header_auto (rc->connection,
- TALER_COIN_HISTORY_SIGNATURE_HEADER,
- &coin_sig,
- required);
- if (GNUNET_OK !=
- TALER_wallet_coin_history_verify (start_off,
- coin_pub,
- &coin_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
- NULL);
- }
- }
-
- /* Get etag */
- {
- const char *etags;
-
- etags = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- if (NULL != etags)
- {
- char dummy;
- unsigned long long ev;
-
- if (1 != sscanf (etags,
- "\"%llu\"%c",
- &ev,
- &dummy))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client send malformed `If-None-Match' header `%s'\n",
- etags);
- etag_in = start_off;
- }
- else
- {
- etag_in = (uint64_t) ev;
- }
- }
- else
- {
- etag_in = start_off;
- }
- }
-
- /* Get history from DB between etag and now */
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
- true,
- coin_pub,
- start_off,
- etag_in,
- &etag_out,
- &balance,
- &h_denom_pub,
- &tl);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_coin_history");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- "get_coin_history");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* Handled below */
- break;
- }
- }
-
- GNUNET_snprintf (etagp,
- sizeof (etagp),
- "\"%llu\"",
- (unsigned long long) etag_out);
- if (etag_in == etag_out)
- {
- return TEH_RESPONSE_reply_not_modified (rc->connection,
- etagp,
- &add_response_headers,
- NULL);
- }
- if (NULL == tl)
- {
- /* 204: empty history */
- resp = MHD_create_response_from_buffer_static (0,
- "");
- http_status = MHD_HTTP_NO_CONTENT;
- }
- else
- {
- /* 200: regular history */
- json_t *history;
-
- history = compile_transaction_history (coin_pub,
- tl);
- TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
- tl);
- tl = NULL;
- if (NULL == history)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- "Failed to compile coin history");
- }
- resp = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &h_denom_pub),
- TALER_JSON_pack_amount ("balance",
- &balance),
- GNUNET_JSON_pack_array_steal ("history",
- history));
- http_status = MHD_HTTP_OK;
- }
- add_response_headers (NULL,
- resp);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- etagp));
- {
- MHD_RESULT ret;
-
- ret = MHD_queue_response (rc->connection,
- http_status,
- resp);
- GNUNET_break (MHD_YES == ret);
- MHD_destroy_response (resp);
- return ret;
- }
-}
-
-
-/* end of taler-exchange-httpd_coins_get.c */
diff --git a/src/exchange/taler-exchange-httpd_coins_get.h b/src/exchange/taler-exchange-httpd_coins_get.h
@@ -1,53 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_coins_get.h
- * @brief Handle GET /coins/$COIN_PUB requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_COINS_GET_H
-#define TALER_EXCHANGE_HTTPD_COINS_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Shutdown reserves-get subsystem. Resumes all
- * suspended long-polling clients and cleans up
- * data structures.
- */
-void
-TEH_reserves_get_cleanup (void);
-
-
-/**
- * Handle a GET "/coins/$COIN_PUB/history" request. Parses the
- * given "coins_pub" in @a args (which should contain the
- * EdDSA public key of a reserve) and then respond with the
- * transaction history of the coin.
- *
- * @param rc request context
- * @param coin_pub public key of the coin
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_coins_get (struct TEH_RequestContext *rc,
- const struct TALER_CoinSpendPublicKeyP *coin_pub);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_common_deposit.c b/src/exchange/taler-exchange-httpd_common_deposit.c
@@ -21,7 +21,7 @@
#include "taler/platform.h"
#include "taler-exchange-httpd_common_deposit.h"
#include "taler-exchange-httpd.h"
-#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_get-keys.h"
enum GNUNET_GenericReturnValue
diff --git a/src/exchange/taler-exchange-httpd_config.c b/src/exchange/taler-exchange-httpd_config.c
@@ -1,77 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-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 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_config.c
- * @brief Handle /config requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_json_lib.h>
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_config.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include <jansson.h>
-
-
-MHD_RESULT
-TEH_handler_config (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- static struct MHD_Response *resp;
-
- (void) args;
- if (NULL == resp)
- {
- resp = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal (
- "wallet_balance_limit_without_kyc",
- TALER_KYCLOGIC_get_wallet_thresholds ())),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("shopping_url",
- TEH_shopping_url)),
- GNUNET_JSON_pack_object_steal (
- "currency_specification",
- TALER_JSON_currency_specs_to_json (TEH_cspec)),
- GNUNET_JSON_pack_string (
- "currency",
- TEH_currency),
- GNUNET_JSON_pack_string (
- "name",
- "taler-exchange"),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "aml_spa_dialect",
- TEH_aml_spa_dialect)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("shopping_url",
- TEH_shopping_url)),
- GNUNET_JSON_pack_string (
- "implementation",
- "urn:net:taler:specs:taler-exchange:c-reference"),
- GNUNET_JSON_pack_string (
- "version",
- EXCHANGE_PROTOCOL_VERSION));
- }
- return MHD_queue_response (rc->connection,
- MHD_HTTP_OK,
- resp);
-}
-
-
-/* end of taler-exchange-httpd_config.c */
diff --git a/src/exchange/taler-exchange-httpd_config.h b/src/exchange/taler-exchange-httpd_config.h
@@ -1,58 +0,0 @@
-/*
- This file is part of TALER
- (C) 2025 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 EXCHANGEABILITY 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 taler-exchange-httpd_config.h
- * @brief headers for /config handler
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_CONFIG_H
-#define TALER_EXCHANGE_HTTPD_CONFIG_H
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Taler protocol version in the format CURRENT:REVISION:AGE
- * as used by GNU libtool. See
- * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
- *
- * Please be very careful when updating and follow
- * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
- * precisely. Note that this version has NOTHING to do with the
- * release version, and the format is NOT the same that semantic
- * versioning uses either.
- *
- * When changing this version, you likely want to also update
- * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
- * exchange_api_handle.c!
- *
- * Returned via both /config and /keys endpoints.
- */
-#define EXCHANGE_PROTOCOL_VERSION "34:0:0"
-
-
-/**
- * Manages a /config call.
- *
- * @param rc context of the handler
- * @param[in,out] args remaining arguments (ignored)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_config (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_contract.c b/src/exchange/taler-exchange-httpd_contract.c
@@ -1,99 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_contract.c
- * @brief Handle GET /contracts/$C_PUB requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_contract.h"
-#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-MHD_RESULT
-TEH_handler_contracts_get (struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct TALER_ContractDiffiePublicP contract_pub;
- struct TALER_PurseContractPublicKeyP purse_pub;
- void *econtract;
- size_t econtract_size;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_PurseContractSignatureP econtract_sig;
- MHD_RESULT res;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &contract_pub,
- sizeof (contract_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_CONTRACTS_INVALID_CONTRACT_PUB,
- args[0]);
- }
-
- qs = TEH_plugin->select_contract (TEH_plugin->cls,
- &contract_pub,
- &purse_pub,
- &econtract_sig,
- &econtract_size,
- &econtract);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_contract");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_contract");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_CONTRACTS_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break; /* handled below */
- }
- res = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &purse_pub),
- GNUNET_JSON_pack_data_auto ("econtract_sig",
- &econtract_sig),
- GNUNET_JSON_pack_data_varsize ("econtract",
- econtract,
- econtract_size));
- GNUNET_free (econtract);
- return res;
-}
-
-
-/* end of taler-exchange-httpd_contract.c */
diff --git a/src/exchange/taler-exchange-httpd_contract.h b/src/exchange/taler-exchange-httpd_contract.h
@@ -1,44 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_contract.h
- * @brief Handle /coins/$COIN_PUB/contract requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_CONTRACT_H
-#define TALER_EXCHANGE_HTTPD_CONTRACT_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/contracts/$C_PUB" request. Returns the
- * encrypted contract.
- *
- * @param rc request context
- * @param args array of additional options (length: 1, first is the contract_pub)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_contracts_get (struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_db.h b/src/exchange/taler-exchange-httpd_db.h
@@ -23,7 +23,7 @@
#include <microhttpd.h>
#include "taler/taler_exchangedb_plugin.h"
-#include "taler-exchange-httpd_metrics.h"
+#include "taler-exchange-httpd_get-metrics.h"
#include <gnunet/gnunet_mhd_compat.h>
diff --git a/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.c b/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.c
@@ -0,0 +1,149 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_delete-purses-PURSE_PUB.c
+ * @brief Handle DELETE /purses/$PID requests; parses the request and
+ * verifies the signature before handing deletion to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_dbevents.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_delete-purses-PURSE_PUB.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+MHD_RESULT
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseContractSignatureP purse_sig;
+ bool found;
+ bool decided;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &purse_pub,
+ sizeof (purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+ args[0]);
+ }
+ TALER_MHD_parse_request_header_auto_t (connection,
+ "Taler-Purse-Signature",
+ &purse_sig);
+ if (GNUNET_OK !=
+ TALER_wallet_purse_delete_verify (&purse_pub,
+ &purse_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_DELETE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->do_purse_delete (TEH_plugin->cls,
+ &purse_pub,
+ &purse_sig,
+ &decided,
+ &found);
+ if (qs <= 0)
+ {
+ TALER_LOG_WARNING (
+ "Failed to store delete purse information in database\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse delete");
+ }
+ }
+ if (! found)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ }
+ if (decided)
+ {
+ return TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED,
+ NULL);
+ }
+ {
+ /* Possible minor optimization: integrate notification with
+ transaction above... */
+ struct TALER_PurseEventP rep_deposited = {
+ .header.size = htons (sizeof (rep_deposited)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = purse_pub
+ };
+ struct TALER_PurseEventP rep_merged = {
+ .header.size = htons (sizeof (rep_merged)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
+ .purse_pub = purse_pub
+ };
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse deletion %s\n",
+ TALER_B2S (&purse_pub));
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep_deposited.header,
+ NULL,
+ 0);
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep_merged.header,
+ NULL,
+ 0);
+ }
+ /* success */
+ return TALER_MHD_reply_static (connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_purses_delete.c */
diff --git a/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.h b/src/exchange/taler-exchange-httpd_delete-purses-PURSE_PUB.h
@@ -0,0 +1,42 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_delete-purses-PURSE_PUB.h
+ * @brief Handle DELETE /purses/$PID requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_DELETE_PURSES_PURSE_PUB_H
+#define TALER_EXCHANGE_HTTPD_DELETE_PURSES_PURSE_PUB_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a DELETE "/purses/$PURSE_PUB" request.
+ *
+ * @param rc request details about the request to handle
+ * @param args argument with the public key of the purse
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_delete (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.c b/src/exchange/taler-exchange-httpd_deposits_get.c
@@ -1,536 +0,0 @@
-/*
- This file is part of TALER
- Copyright (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 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_deposits_get.c
- * @brief Handle wire deposit tracking-related requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_dbevents.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_util.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_deposits_get.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Closure for #handle_wtid_data.
- */
-struct DepositWtidContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct DepositWtidContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct DepositWtidContext *prev;
-
- /**
- * Context for the request we are processing.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Subscription for the database event we are waiting for.
- */
- struct GNUNET_DB_EventHandler *eh;
-
- /**
- * Hash over the proposal data of the contract for which this deposit is made.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Hash over the wiring information of the merchant.
- */
- struct TALER_MerchantWireHashP h_wire;
-
- /**
- * The Merchant's public key. The deposit inquiry request is to be
- * signed by the corresponding private key (using EdDSA).
- */
- struct TALER_MerchantPublicKeyP merchant;
-
- /**
- * Public key for KYC operations on the target bank
- * account for the wire transfer. All zero if no
- * public key is accepted yet. In that case, the
- * client should use the @e merchant public key for
- * the KYC auth wire transfer.
- */
- union TALER_AccountPublicKeyP account_pub;
-
- /**
- * The coin's public key. This is the value that must have been
- * signed (blindly) by the Exchange.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Set by #handle_wtid data to the wire transfer ID.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * Signature by the merchant.
- */
- struct TALER_MerchantSignatureP merchant_sig;
-
- /**
- * Set by #handle_wtid data to the coin's contribution to the wire transfer.
- */
- struct TALER_Amount coin_contribution;
-
- /**
- * Set by #handle_wtid data to the fee charged to the coin.
- */
- struct TALER_Amount coin_fee;
-
- /**
- * Set by #handle_wtid data to the wire transfer execution time.
- */
- struct GNUNET_TIME_Timestamp execution_time;
-
- /**
- * Timeout of the request, for long-polling.
- */
- struct GNUNET_TIME_Absolute timeout;
-
- /**
- * Set by #handle_wtid to the coin contribution to the transaction
- * (that is, @e coin_contribution minus @e coin_fee).
- */
- struct TALER_Amount coin_delta;
-
- /**
- * KYC status information for the receiving account.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
- * if we were woken up due to shutdown.
- */
- enum GNUNET_GenericReturnValue suspended;
-
- /**
- * What do we long-poll for? Defaults to
- * #TALER_DGLPT_OK if not given.
- */
- enum TALER_DepositGetLongPollTarget lpt;
-};
-
-
-/**
- * Head of DLL of suspended requests.
- */
-static struct DepositWtidContext *dwc_head;
-
-/**
- * Tail of DLL of suspended requests.
- */
-static struct DepositWtidContext *dwc_tail;
-
-
-void
-TEH_deposits_get_cleanup ()
-{
- struct DepositWtidContext *n;
-
- for (struct DepositWtidContext *ctx = dwc_head;
- NULL != ctx;
- ctx = n)
- {
- n = ctx->next;
- GNUNET_assert (GNUNET_YES == ctx->suspended);
- ctx->suspended = GNUNET_SYSERR;
- MHD_resume_connection (ctx->rc->connection);
- GNUNET_CONTAINER_DLL_remove (dwc_head,
- dwc_tail,
- ctx);
- }
-}
-
-
-/**
- * A merchant asked for details about a deposit. Provide
- * them. Generates the 200 reply.
- *
- * @param ctx details to respond with
- * @return MHD result code
- */
-static MHD_RESULT
-reply_deposit_details (
- const struct DepositWtidContext *ctx)
-{
- struct MHD_Connection *connection = ctx->rc->connection;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_confirm_wire_sign (
- &TEH_keys_exchange_sign_,
- &ctx->h_wire,
- &ctx->h_contract_terms,
- &ctx->wtid,
- &ctx->coin_pub,
- ctx->execution_time,
- &ctx->coin_delta,
- &pub,
- &sig)))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("wtid",
- &ctx->wtid),
- GNUNET_JSON_pack_timestamp ("execution_time",
- ctx->execution_time),
- TALER_JSON_pack_amount ("coin_contribution",
- &ctx->coin_delta),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Function called on events received from Postgres.
- * Wakes up long pollers.
- *
- * @param cls the `struct DepositWtidContext *`
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-db_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- struct DepositWtidContext *ctx = cls;
- struct GNUNET_AsyncScopeSave old_scope;
-
- (void) extra;
- (void) extra_size;
- if (GNUNET_YES != ctx->suspended)
- return; /* might get multiple wake-up events */
- GNUNET_CONTAINER_DLL_remove (dwc_head,
- dwc_tail,
- ctx);
- GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
- &old_scope);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming request handling\n");
- TEH_check_invariants ();
- ctx->suspended = GNUNET_NO;
- MHD_resume_connection (ctx->rc->connection);
- TALER_MHD_daemon_trigger ();
- TEH_check_invariants ();
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-/**
- * Lookup and return the wire transfer identifier.
- *
- * @param ctx context of the signed request to execute
- * @return MHD result code
- */
-static MHD_RESULT
-handle_track_transaction_request (
- struct DepositWtidContext *ctx)
-{
- struct MHD_Connection *connection = ctx->rc->connection;
- enum GNUNET_DB_QueryStatus qs;
- bool pending;
- struct TALER_Amount fee;
-
- qs = TEH_plugin->lookup_transfer_by_deposit (
- TEH_plugin->cls,
- &ctx->h_contract_terms,
- &ctx->h_wire,
- &ctx->coin_pub,
- &ctx->merchant,
- &pending,
- &ctx->wtid,
- &ctx->execution_time,
- &ctx->coin_contribution,
- &fee,
- &ctx->kyc,
- &ctx->account_pub);
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_transfer_by_deposit");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
- NULL);
- }
-
- if (0 >
- TALER_amount_subtract (&ctx->coin_delta,
- &ctx->coin_contribution,
- &fee))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "wire fees exceed aggregate in database");
- }
- if (pending)
- {
- if (GNUNET_TIME_absolute_is_future (ctx->timeout))
- {
- bool do_suspend = false;
- switch (ctx->lpt)
- {
- case TALER_DGLPT_NONE:
- break;
- case TALER_DGLPT_KYC_REQUIRED_OR_OK:
- do_suspend = ctx->kyc.ok;
- break;
- case TALER_DGLPT_OK:
- do_suspend = true;
- break;
- }
- if (do_suspend)
- {
- GNUNET_assert (GNUNET_NO == ctx->suspended);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending request handling\n");
- GNUNET_CONTAINER_DLL_insert (dwc_head,
- dwc_tail,
- ctx);
- ctx->suspended = GNUNET_YES;
- MHD_suspend_connection (connection);
- return MHD_YES;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC required with row %llu\n",
- (unsigned long long) ctx->kyc.requirement_row);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_ACCEPTED,
- GNUNET_JSON_pack_allow_null (
- (0 == ctx->kyc.requirement_row)
- ? GNUNET_JSON_pack_string ("requirement_row",
- NULL)
- : GNUNET_JSON_pack_uint64 ("requirement_row",
- ctx->kyc.requirement_row)),
- GNUNET_JSON_pack_allow_null (
- (GNUNET_is_zero (&ctx->account_pub))
- ? GNUNET_JSON_pack_string ("account_pub",
- NULL)
- : GNUNET_JSON_pack_data_auto ("account_pub",
- &ctx->account_pub)),
- GNUNET_JSON_pack_bool ("kyc_ok",
- ctx->kyc.ok),
- GNUNET_JSON_pack_timestamp ("execution_time",
- ctx->execution_time));
- }
- return reply_deposit_details (ctx);
-}
-
-
-/**
- * Function called to clean up a context.
- *
- * @param rc request context with data to clean up
- */
-static void
-dwc_cleaner (struct TEH_RequestContext *rc)
-{
- struct DepositWtidContext *ctx = rc->rh_ctx;
-
- GNUNET_assert (GNUNET_YES != ctx->suspended);
- if (NULL != ctx->eh)
- {
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- ctx->eh);
- ctx->eh = NULL;
- }
- GNUNET_free (ctx);
-}
-
-
-MHD_RESULT
-TEH_handler_deposits_get (struct TEH_RequestContext *rc,
- const char *const args[4])
-{
- struct DepositWtidContext *ctx = rc->rh_ctx;
-
- if (NULL == ctx)
- {
- ctx = GNUNET_new (struct DepositWtidContext);
- ctx->rc = rc;
- ctx->lpt = TALER_DGLPT_OK; /* default */
- rc->rh_ctx = ctx;
- rc->rh_cleaner = &dwc_cleaner;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &ctx->h_wire,
- sizeof (ctx->h_wire)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
- args[0]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[1],
- strlen (args[1]),
- &ctx->merchant,
- sizeof (ctx->merchant)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
- args[1]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[2],
- strlen (args[2]),
- &ctx->h_contract_terms,
- sizeof (ctx->h_contract_terms)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
- args[2]);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[3],
- strlen (args[3]),
- &ctx->coin_pub,
- sizeof (ctx->coin_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
- args[3]);
- }
- TALER_MHD_parse_request_arg_auto_t (rc->connection,
- "merchant_sig",
- &ctx->merchant_sig);
- TALER_MHD_parse_request_timeout (rc->connection,
- &ctx->timeout);
- {
- uint64_t num = 0;
- int val;
-
- TALER_MHD_parse_request_number (rc->connection,
- "lpt",
- &num);
- val = (int) num;
- if ( (val < 0) ||
- (val > TALER_DGLPT_MAX) )
- {
- /* Protocol violation, but we can be graceful and
- just ignore the long polling! */
- GNUNET_break_op (0);
- val = TALER_DGLPT_NONE;
- }
- ctx->lpt = (enum TALER_DepositGetLongPollTarget) val;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long polling for target %d with timeout %s\n",
- val,
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- ctx->timeout),
- true));
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- {
- if (GNUNET_OK !=
- TALER_merchant_deposit_verify (&ctx->merchant,
- &ctx->coin_pub,
- &ctx->h_contract_terms,
- &ctx->h_wire,
- &ctx->merchant_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
- NULL);
- }
- }
- if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
- (TALER_DGLPT_NONE != ctx->lpt) )
- {
- struct TALER_CoinDepositEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
- .merchant_pub = ctx->merchant
- };
-
- ctx->eh = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (ctx->timeout),
- &rep.header,
- &db_event_cb,
- ctx);
- GNUNET_break (NULL != ctx->eh);
- }
- }
-
- return handle_track_transaction_request (ctx);
-}
-
-
-/* end of taler-exchange-httpd_deposits_get.c */
diff --git a/src/exchange/taler-exchange-httpd_deposits_get.h b/src/exchange/taler-exchange-httpd_deposits_get.h
@@ -1,50 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017, 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_deposits_get.h
- * @brief Handle wire transfer tracking-related requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_DEPOSITS_GET_H
-#define TALER_EXCHANGE_HTTPD_DEPOSITS_GET_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume long pollers on GET /deposits.
- */
-void
-TEH_deposits_get_cleanup (void);
-
-
-/**
- * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
- * request.
- *
- * @param rc request context
- * @param args array of additional options (length: 4, contains:
- * h_wire, merchant_pub, h_contract_terms and coin_pub)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_deposits_get (struct TEH_RequestContext *rc,
- const char *const args[4]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_extensions.c b/src/exchange/taler-exchange-httpd_extensions.c
@@ -20,7 +20,7 @@
#include "taler/platform.h"
#include <gnunet/gnunet_json_lib.h>
#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_get-keys.h"
#include "taler-exchange-httpd_responses.h"
#include "taler-exchange-httpd_extensions.h"
#include "taler/taler_extensions_policy.h"
diff --git a/src/exchange/taler-exchange-httpd_get-SPA.c b/src/exchange/taler-exchange-httpd_get-SPA.c
@@ -0,0 +1,128 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2023, 2024 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 EXCHANGEABILITY 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 taler-exchange-httpd_get-SPA.c
+ * @brief logic to load single page apps (/)
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include "taler/taler_util.h"
+#include "taler/taler_mhd_lib.h"
+#include <gnunet/gnunet_mhd_compat.h>
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_get-SPA.h"
+
+
+/**
+ * Resources of the AML SPA.
+ */
+static struct TALER_MHD_Spa *aml_spa;
+
+/**
+ * Resources of the KYC SPA.
+ */
+static struct TALER_MHD_Spa *kyc_spa;
+
+
+MHD_RESULT
+TEH_handler_aml_spa (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ const char *path = args[0];
+
+ return TALER_MHD_spa_handler (aml_spa,
+ rc->connection,
+ path);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_spa (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ const char *path = args[0];
+ struct TALER_AccountAccessTokenP tok;
+
+ if (NULL == path)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT,
+ "no account access token specified");
+ }
+ if (GNUNET_OK ==
+ GNUNET_STRINGS_string_to_data (path,
+ strlen (path),
+ &tok,
+ sizeof (tok)))
+ {
+ /* The access token is used internally by the SPA,
+ we simply map all access tokens to "index.html" */
+ path = "index.html";
+ }
+ return TALER_MHD_spa_handler (kyc_spa,
+ rc->connection,
+ path);
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_spa_init ()
+{
+ aml_spa = TALER_MHD_spa_load (TALER_EXCHANGE_project_data (),
+ "aml-spa/");
+ if (NULL == aml_spa)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ kyc_spa = TALER_MHD_spa_load (TALER_EXCHANGE_project_data (),
+ "kyc-spa/");
+ if (NULL == kyc_spa)
+ {
+ GNUNET_break (0);
+ TALER_MHD_spa_free (aml_spa);
+ aml_spa = NULL;
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/* Suppresses warning */
+void __attribute__ ((destructor))
+get_spa_fini (void);
+
+/**
+ * Nicely shut down.
+ */
+void __attribute__ ((destructor))
+get_spa_fini (void)
+{
+ if (NULL != kyc_spa)
+ {
+ TALER_MHD_spa_free (kyc_spa);
+ kyc_spa = NULL;
+ }
+ if (NULL != aml_spa)
+ {
+ TALER_MHD_spa_free (aml_spa);
+ aml_spa = NULL;
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_get-SPA.h b/src/exchange/taler-exchange-httpd_get-SPA.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 EXCHANGEABILITY 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 taler-exchange-httpd_get-SPA.h
+ * @brief logic to preload and serve static files
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_SPA_H
+#define TALER_EXCHANGE_HTTPD_GET_SPA_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Return our single-page-app user interface
+ * for AML staff (see contrib/wallet-core/).
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TEH_handler_aml_spa (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Return our single-page-app user interface
+ * for the KYC process (see contrib/wallet-core/).
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
+ */
+MHD_RESULT
+TEH_handler_kyc_spa (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Preload and compress SPA files.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_spa_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-TERMS.c b/src/exchange/taler-exchange-httpd_get-TERMS.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 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_get-TERMS.c
+ * @brief Handle /terms requests to return the terms of service
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-TERMS.h"
+
+/**
+ * Our terms of service.
+ */
+static struct TALER_MHD_Legal *tos;
+
+
+/**
+ * Our privacy policy.
+ */
+static struct TALER_MHD_Legal *pp;
+
+
+MHD_RESULT
+TEH_handler_terms (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ (void) args;
+ return TALER_MHD_reply_legal (rc->connection,
+ tos);
+}
+
+
+MHD_RESULT
+TEH_handler_privacy (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ (void) args;
+ return TALER_MHD_reply_legal (rc->connection,
+ pp);
+}
+
+
+void
+TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ tos = TALER_MHD_legal_load (cfg,
+ "exchange",
+ "TERMS_DIR",
+ "TERMS_ETAG");
+ if (NULL == tos)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Terms of service not configured\n");
+ pp = TALER_MHD_legal_load (cfg,
+ "exchange",
+ "PRIVACY_DIR",
+ "PRIVACY_ETAG");
+ if (NULL == pp)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Privacy policy not configured\n");
+}
+
+
+/* end of taler-exchange-httpd_terms.c */
diff --git a/src/exchange/taler-exchange-httpd_get-TERMS.h b/src/exchange/taler-exchange-httpd_get-TERMS.h
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 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_get-TERMS.h
+ * @brief Handle /terms requests to return the terms of service
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_TERMS_H
+#define TALER_EXCHANGE_HTTPD_GET_TERMS_H
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Handle a "/terms" 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_terms (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Handle a "/privacy" 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_privacy (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.c
@@ -0,0 +1,486 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024, 2025, 2026 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_get-aml-OFFICER_PUB-accounts.c
+ * @brief Return summary information about accounts
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.h"
+
+
+/**
+ * Maximum number of records we return in one go. Must be
+ * small enough to ensure that XML/CSV encoded results stay
+ * below the 16MB limit of GNUNET_malloc().
+ */
+#define MAX_RECORDS (1024 * 64)
+
+#define CSV_HEADER \
+ "File number,Customer,Comments,Risky,Acquisition date,Exit date\r\n"
+#define CSV_FOOTER "\r\n"
+
+#define XML_HEADER "<?xml version=\"1.0\" encoding=\"UTF 8\"?>" \
+ "<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\"" \
+ " xmlns:c=\"urn:schemas-microsoft-com:office:component:spreadsheet\"" \
+ " xmlns:html=\"http://www.w3.org/TR/REC-html40\"" \
+ " xmlns:x2=\"http://schemas.microsoft.com/office/excel/2003/xml\"" \
+ " xmlns:o=\"urn:schemas-microsoft-com:office:office\"" \
+ " xmlns:x=\"urn:schemas-microsoft-com:office:excel\"" \
+ " xmlns:ss=\"urn:schemas-microsoft-com:office:spreadsheet\">" \
+ "<Styles><Style ss:ID=\"DateFormat\"><NumberFormat ss:Format=\"yyyy-mm-dd\"/></Style></Styles>\n" \
+ "<Worksheet ss:Name=\"Accounts\">\n" \
+ "<Table>\n" \
+ "<Row>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">File number</Data></Cell>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Customer</Data></Cell>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Comments</Data></Cell>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Increased risk business relationship</Data></Cell>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Acquisition date</Data></Cell>\n" \
+ "<Cell ss:StyleID=\"Header\"><Data ss:Type=\"String\">Exit date</Data></Cell>\n" \
+ "</Row>\n"
+#define XML_FOOTER "</Table></Worksheet></Workbook>"
+
+/**
+ * Closure for the record_cb().
+ */
+struct ResponseContext
+{
+ /**
+ * Format of the response we are to generate.
+ */
+ enum
+ {
+ RCF_JSON,
+ RCF_XML,
+ RCF_CSV
+ } format;
+
+ /**
+ * Where we store the response data.
+ */
+ union
+ {
+ /**
+ * If @e format is #RCF_JSON.
+ */
+ json_t *json;
+
+ /**
+ * If @e format is #RCF_XML.
+ */
+ struct GNUNET_Buffer xml;
+
+ /**
+ * If @e format is #RCF_CSV.
+ */
+ struct GNUNET_Buffer csv;
+
+ } details;
+};
+
+
+/**
+ * Free resources from @a rc
+ *
+ * @param[in] rc context to clean up
+ */
+static void
+free_rc (struct ResponseContext *rc)
+{
+ switch (rc->format)
+ {
+ case RCF_JSON:
+ json_decref (rc->details.json);
+ break;
+ case RCF_XML:
+ GNUNET_buffer_clear (&rc->details.xml);
+ break;
+ case RCF_CSV:
+ GNUNET_buffer_clear (&rc->details.csv);
+ break;
+ }
+}
+
+
+/**
+ * Return account summary information.
+ *
+ * @param cls closure
+ * @param row_id current row in AML status table
+ * @param h_payto account for which the attribute data is stored
+ * @param open_time when was the account opened formally,
+ * GNUNET_TIME_UNIT_FOREVER_TS if it was never opened
+ * @param close_time when was the account formally closed,
+ * GNUNET_TIME_UNIT_ZERO_TS if it was never closed
+ * @param comments comments on the account
+ * @param high_risk is this a high-risk business relationship
+ * @param to_investigate TRUE if this account should be investigated
+ * @param payto the payto URI of the account
+ */
+static void
+record_cb (
+ void *cls,
+ uint64_t row_id,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp open_time,
+ struct GNUNET_TIME_Timestamp close_time,
+ const char *comments,
+ bool high_risk,
+ bool to_investigate,
+ struct TALER_FullPayto payto)
+{
+ struct ResponseContext *rc = cls;
+
+ if ( (NULL == comments) &&
+ (GNUNET_TIME_absolute_is_never (open_time.abs_time)) )
+ comments = "transacted amounts below limits that trigger account opening";
+ if (NULL == comments)
+ comments = "";
+ switch (rc->format)
+ {
+ case RCF_JSON:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rc->details.json,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ TALER_JSON_pack_full_payto ("full_payto",
+ payto),
+ GNUNET_JSON_pack_bool ("high_risk",
+ high_risk),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("comments",
+ comments)),
+ GNUNET_JSON_pack_int64 ("rowid",
+ row_id),
+ GNUNET_JSON_pack_timestamp ("open_time",
+ open_time),
+ GNUNET_JSON_pack_timestamp ("close_time",
+ close_time),
+ GNUNET_JSON_pack_bool ("to_investigate",
+ to_investigate)
+ )));
+ return;
+ case RCF_XML:
+ {
+ char *epayto;
+ char *ecomments = NULL;
+ char opentime_s[128];
+ char closetime_s[128];
+ const struct tm *tm;
+ time_t tt;
+
+ epayto = TALER_escape_xml (payto.full_payto);
+ if ( (NULL == comments) &&
+ (GNUNET_TIME_absolute_is_never (open_time.abs_time)) )
+ comments =
+ "transacted amounts below limits that trigger account opening";
+ ecomments = TALER_escape_xml (comments);
+ tt = (time_t) GNUNET_TIME_timestamp_to_s (open_time);
+ tm = gmtime (&tt);
+ strftime (opentime_s,
+ sizeof (opentime_s),
+ "%Y-%m-%dT%H:%M:%S",
+ tm);
+ tt = (time_t) GNUNET_TIME_timestamp_to_s (close_time);
+ tm = gmtime (&tt);
+ strftime (closetime_s,
+ sizeof (closetime_s),
+ "%Y-%m-%dT%H:%M:%S",
+ tm);
+ GNUNET_buffer_write_fstr (
+ &rc->details.xml,
+ "<Row>"
+ "<Cell><Data ss:Type=\"Number\">%llu</Data></Cell>"
+ "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
+ "<Cell><Data ss:Type=\"String\">%s</Data></Cell>"
+ "<Cell ss:Formula=\"=%s()\"><Data ss:Type=\"Boolean\">%s</Data></Cell>"
+ "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"%s\">%s</Data></Cell>"
+ "<Cell ss:StyleID=\"DateFormat\"><Data ss:Type=\"%s\">%s</Data></Cell>"
+ "</Row>\n",
+ (unsigned long long) row_id,
+ epayto,
+ NULL == ecomments
+ ? ""
+ : ecomments,
+ high_risk ? "TRUE" : "FALSE",
+ high_risk ? "1" : "0",
+ GNUNET_TIME_absolute_is_never (open_time.abs_time)
+ ? "String"
+ : "DateTime",
+ GNUNET_TIME_absolute_is_never (open_time.abs_time)
+ ? "never"
+ : opentime_s,
+ GNUNET_TIME_absolute_is_never (close_time.abs_time)
+ ? "String"
+ : "DateTime",
+ GNUNET_TIME_absolute_is_never (close_time.abs_time)
+ ? "never"
+ : closetime_s);
+ GNUNET_free (ecomments);
+ GNUNET_free (epayto);
+ break;
+ } /* end case RCF_XML */
+ case RCF_CSV:
+ {
+ char *ecomments;
+ char otbuf[64];
+ char ctbuf[64];
+ size_t len = strlen (comments);
+ size_t wpos = 0;
+
+ GNUNET_snprintf (otbuf,
+ sizeof (otbuf),
+ "%s",
+ GNUNET_TIME_timestamp2s (open_time));
+ GNUNET_snprintf (ctbuf,
+ sizeof (ctbuf),
+ "%s",
+ GNUNET_TIME_timestamp2s (close_time));
+ /* Escape 'comments' to double '"' as per RFC 4180, 2.7. */
+ ecomments = GNUNET_malloc (2 * len + 1);
+ for (size_t off = 0; off<len; off++)
+ {
+ if ('"' == comments[off])
+ ecomments[wpos++] = '"';
+ ecomments[wpos++] = comments[off];
+ }
+ GNUNET_buffer_write_fstr (&rc->details.csv,
+ "%llu,\"%s\",\"%s\",%s,%s,%s\r\n",
+ (unsigned long long) row_id,
+ payto.full_payto,
+ ecomments,
+ high_risk ? "X":" ",
+ GNUNET_TIME_absolute_is_never (open_time.
+ abs_time)
+ ? "-"
+ : otbuf,
+ GNUNET_TIME_absolute_is_never (close_time.
+ abs_time)
+ ? "-"
+ : ctbuf);
+ GNUNET_free (ecomments);
+ break;
+ } /* end case RCF_CSV */
+ } /* end switch */
+}
+
+
+MHD_RESULT
+TEH_handler_aml_accounts_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ struct ResponseContext rctx;
+ int64_t limit = -20;
+ uint64_t offset;
+ enum TALER_EXCHANGE_YesNoAll open_filter;
+ enum TALER_EXCHANGE_YesNoAll high_risk_filter;
+ enum TALER_EXCHANGE_YesNoAll investigation_filter;
+
+ memset (&rctx,
+ 0,
+ sizeof (rctx));
+ if (NULL != args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[0]);
+ }
+ TALER_MHD_parse_request_snumber (rc->connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (rc->connection,
+ "offset",
+ &offset);
+ if (offset > INT64_MAX)
+ {
+ GNUNET_break_op (0); /* broken client */
+ offset = INT64_MAX;
+ }
+ TALER_MHD_parse_request_yna (rc->connection,
+ "open",
+ TALER_EXCHANGE_YNA_ALL,
+ &open_filter);
+ TALER_MHD_parse_request_yna (rc->connection,
+ "investigation",
+ TALER_EXCHANGE_YNA_ALL,
+ &investigation_filter);
+ TALER_MHD_parse_request_yna (rc->connection,
+ "high_risk",
+ TALER_EXCHANGE_YNA_ALL,
+ &high_risk_filter);
+ {
+ const char *mime;
+
+ mime = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "application/json";
+ if (0 == strcmp (mime,
+ "application/json"))
+ {
+ rctx.format = RCF_JSON;
+ rctx.details.json = json_array ();
+ GNUNET_assert (NULL != rctx.details.json);
+ }
+ else if (0 == strcmp (mime,
+ "application/vnd.ms-excel"))
+ {
+ rctx.format = RCF_XML;
+ GNUNET_buffer_write_str (&rctx.details.xml,
+ XML_HEADER);
+ }
+ else if (0 == strcmp (mime,
+ "text/csv"))
+ {
+ rctx.format = RCF_CSV;
+ GNUNET_buffer_write_str (&rctx.details.csv,
+ CSV_HEADER);
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ mime);
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (limit > MAX_RECORDS)
+ limit = MAX_RECORDS;
+ if (limit < -MAX_RECORDS)
+ limit = -MAX_RECORDS;
+ qs = TEH_plugin->select_kyc_accounts (
+ TEH_plugin->cls,
+ investigation_filter,
+ open_filter,
+ high_risk_filter,
+ offset,
+ limit,
+ &record_cb,
+ &rctx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ free_rc (&rctx);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_kyx_accounts");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ free_rc (&rctx);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ } /* end switch (qs) */
+
+ switch (rctx.format)
+ {
+ case RCF_JSON:
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("accounts",
+ rctx.details.json));
+ case RCF_XML:
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mret;
+
+ GNUNET_buffer_write_str (&rctx.details.xml,
+ XML_FOOTER);
+ /* FIXME: add support for compression */
+ resp = MHD_create_response_from_buffer (rctx.details.xml.position,
+ rctx.details.xml.mem,
+ MHD_RESPMEM_MUST_FREE);
+ TALER_MHD_add_global_headers (resp,
+ false);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/vnd.ms-excel"));
+ mret = MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return mret;
+ }
+ case RCF_CSV:
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT mret;
+
+ GNUNET_buffer_write_str (&rctx.details.csv,
+ CSV_FOOTER);
+ /* FIXME: add support for compression */
+ resp = MHD_create_response_from_buffer (rctx.details.csv.position,
+ rctx.details.csv.mem,
+ MHD_RESPMEM_MUST_FREE);
+ TALER_MHD_add_global_headers (resp,
+ false);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/csv"));
+ mret = MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return mret;
+ }
+ } /* end switch (rctx.format) */
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-exchange-httpd_aml-accounts_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-accounts.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_get-aml-OFFICER_PUB-accounts.h
+ * @brief Handle GET /aml/$OFFICER_PUB/accounts requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_ACCOUNTS_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_ACCOUNTS_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/accounts" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be the state)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_accounts_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c
@@ -0,0 +1,815 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024, 2025 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_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.c
+ * @brief Return summary information about KYC attributes
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include \
+ "taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h"
+#include "taler-exchange-httpd_get-metrics.h"
+
+/**
+ * Maximum number of records we return in one request.
+ */
+#define MAX_RECORDS 1024
+
+
+/**
+ * Closure for the detail_cb().
+ */
+struct ResponseContext
+{
+ /**
+ * Format of the response we are to generate.
+ */
+ enum
+ {
+ RCF_JSON,
+ RCF_PDF
+ } format;
+
+ /**
+ * Stored in a DLL while suspended.
+ */
+ struct ResponseContext *next;
+
+ /**
+ * Stored in a DLL while suspended.
+ */
+ struct ResponseContext *prev;
+
+ /**
+ * Context for this request.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Async context used to run Typst.
+ */
+ struct TALER_MHD_TypstContext *tc;
+
+ /**
+ * Response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * HTTP status to use with @e response.
+ */
+ unsigned int http_status;
+
+ /**
+ * True if this is for a wallet.
+ */
+ bool is_wallet;
+
+ /**
+ * Where we store the response data.
+ */
+ union
+ {
+ /**
+ * If @e format is #RCF_JSON.
+ */
+ json_t *json;
+
+ /**
+ * If @e format is #RCF_PDF.
+ */
+ struct
+ {
+
+ /**
+ * Typst forms to compile.
+ */
+ struct TALER_MHD_TypstDocument docs[MAX_RECORDS];
+
+ /**
+ * JSON data for each Typst form.
+ */
+ json_t *jdata[MAX_RECORDS];
+
+ /**
+ * Next write offset into @e docs and @e jdata.
+ */
+ size_t off;
+
+ /**
+ * Global attributes we need to inject in to each @e jdata.
+ */
+ json_t *global_attrs;
+
+ } pdf;
+
+ } details;
+};
+
+
+/**
+ * DLL of requests awaiting Typst.
+ */
+static struct ResponseContext *rctx_head;
+
+/**
+ * DLL of requests awaiting Typst.
+ */
+static struct ResponseContext *rctx_tail;
+
+
+void
+TEH_handler_aml_attributes_get_cleanup ()
+{
+ struct ResponseContext *rctx;
+
+ while (NULL != (rctx = rctx_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rctx_head,
+ rctx_tail,
+ rctx);
+ MHD_resume_connection (rctx->rc->connection);
+ }
+}
+
+
+/**
+ * Free resources from @a rc
+ *
+ * @param[in] rc context to clean up
+ */
+static void
+free_rc (struct TEH_RequestContext *rc)
+{
+ struct ResponseContext *rctx = rc->rh_ctx;
+
+ if (NULL != rctx->tc)
+ {
+ TALER_MHD_typst_cancel (rctx->tc);
+ rctx->tc = NULL;
+ }
+ if (NULL != rctx->response)
+ {
+ MHD_destroy_response (rctx->response);
+ rctx->response = NULL;
+ }
+ switch (rctx->format)
+ {
+ case RCF_JSON:
+ json_decref (rctx->details.json);
+ break;
+ case RCF_PDF:
+ for (size_t i = 0; i<rctx->details.pdf.off; i++)
+ {
+ json_decref (rctx->details.pdf.jdata[i]);
+ rctx->details.pdf.jdata[i] = NULL;
+ }
+ json_decref (rctx->details.pdf.global_attrs);
+ rctx->details.pdf.global_attrs = NULL;
+ break;
+ }
+ GNUNET_free (rctx);
+}
+
+
+/**
+ * Dump attachment @a attach into @a rc.
+ *
+ * @param[in,out] rc response context to update
+ * @param attach attachment to dump
+ * @return true on success
+ */
+static bool
+dump_attachment (struct ResponseContext *rc,
+ const json_t *attach)
+{
+ if (! json_is_string (attach))
+ return false;
+ if (rc->details.pdf.off >= MAX_RECORDS)
+ {
+ GNUNET_break (0);
+ return false;
+ }
+ rc->details.pdf.jdata[rc->details.pdf.off]
+ = json_incref ((json_t *) attach);
+ rc->details.pdf.docs[rc->details.pdf.off].form_name
+ = NULL; /* inline PDF */
+ rc->details.pdf.docs[rc->details.pdf.off].form_version
+ = NULL; /* none */
+ rc->details.pdf.docs[rc->details.pdf.off].data
+ = rc->details.pdf.jdata[rc->details.pdf.off];
+ rc->details.pdf.off++;
+ return true;
+}
+
+
+/**
+ * Recursively scan @a attrs for ".FILE" members and dump those
+ * attachments into @a rc.
+ *
+ * @param[in,out] rc response context to update
+ * @param attrs attributes to scan recursively
+ * @return true on success
+ */
+static bool
+dump_attachments (struct ResponseContext *rc,
+ const json_t *attrs)
+{
+ bool ret = true;
+
+ if (json_is_array (attrs))
+ {
+ const json_t *e;
+ size_t i;
+
+ json_array_foreach ((json_t *) attrs, i, e)
+ if (! dump_attachments (rc,
+ e))
+ ret = false;
+ }
+ if (json_is_object (attrs))
+ {
+ const json_t *e;
+ const char *k;
+
+ json_object_foreach ((json_t *) attrs, k, e)
+ {
+ if (0 == strcmp (k,
+ "CONTENTS"))
+ {
+ const char *v;
+
+ /* Make sure this is the supported encoding */
+ v = json_string_value (json_object_get (attrs,
+ "ENCODING"));
+ if (NULL == v)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "`CONTENTS' attribute found without `ENCODING', skipping dumping attachment\n");
+ continue; /* maybe not a file!? */
+ }
+ if (0 != strcmp (v,
+ "base64"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attachment with unsupported encoding `%s' encountered, skipping attachment in PDF generation\n",
+ v);
+ continue;
+ }
+ v = json_string_value (json_object_get (attrs,
+ "MIME_TYPE"));
+ if (NULL == v)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "`CONTENTS' attribute found without `MIME_TYPE', skipping dumping attachment\n");
+ continue;
+ }
+ if (0 != strcmp (v,
+ "application/pdf"))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Attachment with unsupported mime type `%s' encountered, skipping attachment in PDF generation\n",
+ v);
+ continue;
+ }
+ /* Filename is optional, only log it */
+ v = json_string_value (json_object_get (attrs,
+ "FILENAME"));
+ if (NULL != v)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Dumping attachment `%s'\n",
+ v);
+ }
+ if (! dump_attachment (rc,
+ e))
+ ret = false;
+ }
+ else if (! dump_attachments (rc,
+ e))
+ ret = false;
+ }
+ }
+ return ret;
+}
+
+
+/**
+ * Return AML account attributes.
+ *
+ * @param cls closure
+ * @param row_id current row in kyc_attributes table
+ * @param collection_time when were the attributes collected
+ * @param by_aml_officer was the attribute set filed by the AML officer
+ * @param staff_name name of the officer, NULL if not @a by_aml_officer
+ * @param enc_attributes_size length of @a enc_attributes
+ * @param enc_attributes the encrypted collected attributes
+ */
+static void
+detail_cb (
+ void *cls,
+ uint64_t row_id,
+ struct GNUNET_TIME_Timestamp collection_time,
+ bool by_aml_officer,
+ const char *staff_name,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
+{
+ static char *datadir = NULL;
+ struct ResponseContext *rc = cls;
+ json_t *attrs;
+ const char *form_id;
+
+ if (NULL == datadir)
+ {
+ datadir = GNUNET_OS_installation_get_path (
+ TALER_EXCHANGE_project_data (),
+ GNUNET_OS_IPK_DATADIR);
+ }
+ attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ if (NULL == attrs)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ form_id = json_string_value (json_object_get (attrs,
+ "FORM_ID"));
+ if (NULL == form_id)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ switch (rc->format)
+ {
+ case RCF_JSON:
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ rc->details.json,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_int64 ("rowid",
+ row_id),
+ GNUNET_JSON_pack_bool ("by_aml_officer",
+ by_aml_officer),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_steal ("attributes",
+ attrs)),
+ GNUNET_JSON_pack_timestamp ("collection_time",
+ collection_time)
+ )));
+ break;
+ case RCF_PDF:
+ if (rc->details.pdf.off >= MAX_RECORDS)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_object_set_new (attrs,
+ "DATADIR",
+ json_string (datadir)));
+ GNUNET_assert (0 ==
+ json_object_set_new (attrs,
+ "BY_AML_OFFICER",
+ json_boolean (by_aml_officer)));
+ GNUNET_assert (0 ==
+ json_object_set_new (attrs,
+ "FILING_DATE",
+ json_string (
+ GNUNET_STRINGS_timestamp_to_string (
+ collection_time))));
+ if (by_aml_officer)
+ GNUNET_assert (0 ==
+ json_object_set_new (attrs,
+ "AML_STAFF_NAME",
+ json_string (staff_name)));
+ {
+ char *have;
+
+ GNUNET_asprintf (&have,
+ "HAVE_%s",
+ form_id);
+ GNUNET_assert (0 ==
+ json_object_set_new (rc->details.pdf.global_attrs,
+ have,
+ json_true ()));
+ GNUNET_free (have);
+ }
+ rc->details.pdf.jdata[rc->details.pdf.off]
+ = attrs;
+ rc->details.pdf.docs[rc->details.pdf.off].form_name
+ = form_id;
+ rc->details.pdf.docs[rc->details.pdf.off].form_version
+ = NULL; /* not yet supported! */
+ rc->details.pdf.docs[rc->details.pdf.off].data
+ = rc->details.pdf.jdata[rc->details.pdf.off];
+ rc->details.pdf.off++;
+ if (! dump_attachments (rc,
+ attrs))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to dump some attachment!\n");
+ }
+ break;
+ }
+}
+
+
+/**
+ * Function called with the result of a #TALER_MHD_typst() operation.
+ *
+ * @param cls closure
+ * @param tr result of the operation
+ */
+static void
+pdf_cb (void *cls,
+ const struct TALER_MHD_TypstResponse *tr)
+{
+ struct ResponseContext *rctx = cls;
+
+ rctx->tc = NULL;
+ GNUNET_CONTAINER_DLL_remove (rctx_head,
+ rctx_tail,
+ rctx);
+ MHD_resume_connection (rctx->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ if (TALER_EC_NONE != tr->ec)
+ {
+ rctx->http_status
+ = TALER_ErrorCode_get_http_status (tr->ec);
+ rctx->response
+ = TALER_MHD_make_error (tr->ec,
+ tr->details.hint);
+ return;
+ }
+ rctx->http_status
+ = MHD_HTTP_OK;
+ rctx->response
+ = TALER_MHD_response_from_pdf_file (tr->details.filename);
+}
+
+
+/**
+ * Function called with the latest AML decision on an
+ * account. Used to build the cover page.
+ *
+ * @param cls a `struct ResponseContext *`
+ * @param outcome_serial_id row ID of the decision
+ * @param decision_time when was the decision taken
+ * @param justification what was the given justification
+ * @param decider_pub which key signed the decision
+ * @param jproperties what are the new account properties
+ * @param jnew_rules what are the new account rules
+ * @param to_investigate should AML staff investigate
+ * after the decision
+ * @param is_active is this the active decision
+ */
+static void
+build_cover_page (
+ void *cls,
+ uint64_t outcome_serial_id,
+ struct GNUNET_TIME_Timestamp decision_time,
+ const char *justification,
+ const struct TALER_AmlOfficerPublicKeyP *decider_pub,
+ const json_t *jproperties,
+ const json_t *jnew_rules,
+ bool to_investigate,
+ bool is_active)
+{
+ struct ResponseContext *rc = cls;
+ json_t *cover;
+ json_t *defr = NULL;
+
+ if (NULL == jnew_rules)
+ {
+ defr = TALER_KYCLOGIC_get_default_legi_rules (rc->is_wallet);
+ jnew_rules = defr;
+ }
+ cover = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("properties",
+ (json_t *) jproperties)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("rules",
+ (json_t *) jnew_rules)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("last_justification",
+ justification)),
+ GNUNET_JSON_pack_bool ("is_active",
+ is_active),
+ GNUNET_JSON_pack_bool ("to_investigate",
+ to_investigate));
+ if (NULL != defr)
+ json_decref (defr);
+ /* first page, so we really cannot have hit the maximum yet */
+ GNUNET_assert (rc->details.pdf.off < MAX_RECORDS);
+ rc->details.pdf.jdata[rc->details.pdf.off]
+ = cover;
+ rc->details.pdf.docs[rc->details.pdf.off].form_name
+ = "_cover_";
+ rc->details.pdf.docs[rc->details.pdf.off].form_version
+ = NULL; /* not yet supported! */
+ rc->details.pdf.docs[rc->details.pdf.off].data
+ = rc->details.pdf.jdata[rc->details.pdf.off];
+ rc->details.pdf.off++;
+}
+
+
+MHD_RESULT
+TEH_handler_aml_attributes_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ struct ResponseContext *rctx = rc->rh_ctx;
+ int64_t limit = -20;
+ uint64_t offset;
+ struct TALER_NormalizedPaytoHashP h_payto;
+ uint64_t file_number;
+
+ if (NULL == rctx)
+ {
+ rctx = GNUNET_new (struct ResponseContext);
+ rctx->rc = rc;
+ rc->rh_ctx = rctx;
+ rc->rh_cleaner = &free_rc;
+ }
+ else
+ {
+ if (NULL == rctx->response)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ return MHD_queue_response (rc->connection,
+ rctx->http_status,
+ rctx->response);
+ }
+ if ( (NULL == args[0]) ||
+ (NULL != args[1]) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &h_payto,
+ sizeof (h_payto)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
+ "h_payto");
+ }
+
+ TALER_MHD_parse_request_snumber (rc->connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (rc->connection,
+ "offset",
+ &offset);
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_aml_file_number (TEH_plugin->cls,
+ &h_payto,
+ &file_number,
+ &rctx->is_wallet);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_aml_file_number");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Account unknown, return 404 */
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_TARGET_ACCOUNT_UNKNOWN,
+ args[0]);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ {
+ const char *mime;
+
+ mime = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_ACCEPT);
+ if (NULL == mime)
+ mime = "application/json";
+ if (0 == strcmp (mime,
+ "application/json"))
+ {
+ rctx->format = RCF_JSON;
+ rctx->details.json = json_array ();
+ GNUNET_assert (NULL != rctx->details.json);
+ }
+ else if (0 == strcmp (mime,
+ "application/pdf"))
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ rctx->format = RCF_PDF;
+ rctx->details.pdf.global_attrs = json_object ();
+ GNUNET_assert (NULL != rctx->details.pdf.global_attrs);
+ if (NULL != TEH_global_pdf_form_data)
+ GNUNET_assert (0 ==
+ json_object_update (rctx->details.pdf.global_attrs,
+ TEH_global_pdf_form_data));
+ GNUNET_assert (0 ==
+ json_object_set_new (rctx->details.pdf.global_attrs,
+ "FILE_NUMBER",
+ json_integer (file_number)));
+ /* Lookup latest AML decision & account rules & properties
+ to build the cover page */
+ qs = TEH_plugin->lookup_aml_history (TEH_plugin->cls,
+ &h_payto,
+ UINT64_MAX, /* offset */
+ -1, /* latest decision only */
+ &build_cover_page,
+ rctx);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_aml_attributes");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no decision was ever taken, build empty cover page */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No decisions taken, creating empty cover page\n");
+ build_cover_page (rctx,
+ 0,
+ GNUNET_TIME_UNIT_ZERO_TS,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ false,
+ false);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_NOT_ACCEPTABLE,
+ GNUNET_JSON_pack_string ("hint",
+ mime));
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (limit > MAX_RECORDS)
+ limit = MAX_RECORDS;
+ if (limit < -MAX_RECORDS)
+ limit = -MAX_RECORDS;
+ qs = TEH_plugin->select_aml_attributes (
+ TEH_plugin->cls,
+ &h_payto,
+ offset,
+ limit,
+ &detail_cb,
+ rctx);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Obtained %d/%d attributes\n",
+ qs,
+ (int) (limit < 0 ? -limit : limit));
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_aml_attributes");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (RCF_JSON == rctx->format)
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ switch (rctx->format)
+ {
+ case RCF_JSON:
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_incref ("details",
+ rctx->details.json));
+ case RCF_PDF:
+ /* Cover page: off of 0 is impossible */
+ GNUNET_assert (0 != rctx->details.pdf.off);
+ for (unsigned int i = 0; i<rctx->details.pdf.off; i++)
+ {
+ /* The jdata[i] could be a *string* in case of attachments,
+ ignore those here! */
+ if (NULL == rctx->details.pdf.docs[i].form_name)
+ {
+ GNUNET_assert (json_is_string (rctx->details.pdf.jdata[i]));
+ }
+ else
+ {
+ GNUNET_assert (json_is_object (rctx->details.pdf.jdata[i]));
+ GNUNET_assert (0 ==
+ json_object_update_missing (
+ rctx->details.pdf.jdata[i],
+ rctx->details.pdf.global_attrs)
+ );
+ }
+ }
+ rctx->tc = TALER_MHD_typst (TALER_EXCHANGE_project_data (),
+ TEH_cfg,
+ false,
+ "exchange",
+ rctx->details.pdf.off,
+ rctx->details.pdf.docs,
+ &pdf_cb,
+ rctx);
+ if (NULL == rctx->tc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client requested PDF, but Typst is unavailable\n");
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_IMPLEMENTED,
+ TALER_EC_EXCHANGE_GENERIC_NO_TYPST_OR_PDFTK,
+ NULL);
+ }
+ GNUNET_CONTAINER_DLL_insert (rctx_head,
+ rctx_tail,
+ rctx);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+ GNUNET_assert (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-exchange-httpd_aml-attributes_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-aml-OFFICER_PUB-attributes-H_NORMALIZED_PAYTO.h
+ * @brief Handle /aml/$OFFICER_PUB/attributes/$H_PAYTO requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_ATTRIBUTES_H_NORMALIZED_PAYTO_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_ATTRIBUTES_H_NORMALIZED_PAYTO_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/attributes/$H_PAYTO" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be the state)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_attributes_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+/**
+ * Stop async running attribute GET requests.
+ */
+void
+TEH_handler_aml_attributes_get_cleanup (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-decisions.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-decisions.c
@@ -0,0 +1,205 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2024, 2025 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_get-aml-OFFICER_PUB-decisions.c
+ * @brief Return summary information about AML decisions
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h"
+#include "taler-exchange-httpd_get-metrics.h"
+
+/**
+ * Maximum number of records we return in one request.
+ */
+#define MAX_RECORDS 1024
+
+/**
+ * Return AML status.
+ *
+ * @param cls closure
+ * @param row_id current row in AML status table
+ * @param justification human-readable reason for the decision
+ * @param h_payto account for which the attribute data is stored
+ * @param decision_time when was the decision taken
+ * @param expiration_time when will the rules expire
+ * @param jproperties properties set for the account,
+ * NULL if no properties were set
+ * @param to_investigate true if AML staff should look at the account
+ * @param is_active true if this is the currently active decision about the account
+ * @param is_wallet true if the @a h_payto is for a wallet
+ * @param payto the payto URI of the account affected by the decision
+ * @param account_rules current active rules for the account
+ */
+static void
+record_cb (
+ void *cls,
+ uint64_t row_id,
+ const char *justification,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp decision_time,
+ struct GNUNET_TIME_Absolute expiration_time,
+ const json_t *jproperties,
+ bool to_investigate,
+ bool is_active,
+ bool is_wallet,
+ struct TALER_FullPayto payto,
+ const json_t *account_rules)
+{
+ json_t *records = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ records,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ TALER_JSON_pack_full_payto ("full_payto",
+ payto),
+ GNUNET_JSON_pack_bool ("is_wallet",
+ is_wallet),
+ GNUNET_JSON_pack_int64 ("rowid",
+ row_id),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("justification",
+ justification)),
+ GNUNET_JSON_pack_timestamp ("decision_time",
+ decision_time),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("properties",
+ (json_t *) jproperties)),
+ GNUNET_JSON_pack_object_incref ("limits",
+ (json_t *) account_rules),
+ GNUNET_JSON_pack_bool ("to_investigate",
+ to_investigate),
+ GNUNET_JSON_pack_bool ("is_active",
+ is_active)
+ )));
+}
+
+
+MHD_RESULT
+TEH_handler_aml_decisions_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ int64_t limit = -20;
+ uint64_t offset;
+ struct TALER_NormalizedPaytoHashP h_payto;
+ bool have_payto = false;
+ enum TALER_EXCHANGE_YesNoAll active_filter;
+ enum TALER_EXCHANGE_YesNoAll investigation_filter;
+
+ if (NULL != args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[0]);
+ }
+ TALER_MHD_parse_request_snumber (rc->connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (rc->connection,
+ "offset",
+ &offset);
+ if (offset > INT64_MAX)
+ {
+ GNUNET_break_op (0); /* broken client */
+ offset = INT64_MAX;
+ }
+ TALER_MHD_parse_request_arg_auto (rc->connection,
+ "h_payto",
+ &h_payto,
+ have_payto);
+ TALER_MHD_parse_request_yna (rc->connection,
+ "active",
+ TALER_EXCHANGE_YNA_ALL,
+ &active_filter);
+ TALER_MHD_parse_request_yna (rc->connection,
+ "investigation",
+ TALER_EXCHANGE_YNA_ALL,
+ &investigation_filter);
+ {
+ json_t *records;
+ enum GNUNET_DB_QueryStatus qs;
+
+ records = json_array ();
+ GNUNET_assert (NULL != records);
+ if (limit > MAX_RECORDS)
+ limit = MAX_RECORDS;
+ if (limit < -MAX_RECORDS)
+ limit = -MAX_RECORDS;
+ qs = TEH_plugin->select_aml_decisions (
+ TEH_plugin->cls,
+ have_payto
+ ? &h_payto
+ : NULL,
+ investigation_filter,
+ active_filter,
+ offset,
+ limit,
+ &record_cb,
+ records);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (records);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_aml_decisions");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ json_decref (records);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("records",
+ records));
+ }
+}
+
+
+/* end of taler-exchange-httpd_aml-decisions_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c
@@ -0,0 +1,173 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 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_get-aml-OFFICER_PUB-kyc-statistics-NAMES.c
+ * @brief Return summary information about KYC statistics
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h"
+#include "taler-exchange-httpd_get-metrics.h"
+
+/**
+ * Maximum number of statistics that can be requested in one go.
+ */
+#define MAX_STATS 64
+
+
+/**
+ * Function called with AML statistics (counters).
+ *
+ * @param cls JSON array to build
+ * @param name name of the counter
+ * @param cnt number of events for @a name in the query range
+ */
+static void
+add_stat (
+ void *cls,
+ const char *name,
+ uint64_t cnt)
+{
+ json_t *stats = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ stats,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("event",
+ name),
+ GNUNET_JSON_pack_uint64 ("counter",
+ cnt))));
+}
+
+
+MHD_RESULT
+TEH_handler_aml_kyc_statistics_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ struct GNUNET_TIME_Timestamp start_date = GNUNET_TIME_UNIT_ZERO_TS;
+ struct GNUNET_TIME_Timestamp end_date = GNUNET_TIME_UNIT_FOREVER_TS;
+ const char *all_names = args[0];
+ json_t *stats;
+ size_t max_names;
+
+ if ( (NULL == args[0]) ||
+ (NULL != args[1]) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ TALER_MHD_parse_request_timestamp (rc->connection,
+ "start_date",
+ &start_date);
+ TALER_MHD_parse_request_timestamp (rc->connection,
+ "end_date",
+ &end_date);
+ if (GNUNET_TIME_absolute_is_never (end_date.abs_time))
+ end_date = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_SECONDS));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking for stats on %s from [%llu,%llu)\n",
+ all_names,
+ (unsigned long long) start_date.abs_time.abs_value_us,
+ (unsigned long long) end_date.abs_time.abs_value_us);
+ /* Estimate number of names in 'all_names' */
+ max_names = 1;
+ for (size_t i = 0; '\0' != all_names[i]; i++)
+ if (' ' == all_names[i])
+ max_names++;
+ if (max_names > MAX_STATS)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_URI_TOO_LONG,
+ TALER_EC_GENERIC_URI_TOO_LONG,
+ rc->url);
+ }
+ stats = json_array ();
+ GNUNET_assert (NULL != stats);
+ {
+ char *buf;
+ const char *names[GNUNET_NZL (max_names)];
+ enum GNUNET_DB_QueryStatus qs;
+ size_t num_names = 0;
+
+ buf = GNUNET_strdup (all_names);
+ for (const char *tok = strtok (buf,
+ " ");
+ NULL != tok;
+ tok = strtok (NULL,
+ " "))
+ {
+ GNUNET_assert (num_names < max_names);
+ names[num_names++] = tok;
+ }
+ qs = TEH_plugin->select_aml_statistics (
+ TEH_plugin->cls,
+ num_names,
+ names,
+ start_date,
+ end_date,
+ &add_stat,
+ stats);
+ GNUNET_free (buf);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_aml_statistics");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("statistics",
+ stats));
+ }
+}
+
+
+/* end of taler-exchange-httpd_aml-statistics_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-aml-OFFICER_PUB-kyc-statistics-NAMES.h
+ * @brief Handle /aml/$OFFICER_PUB/kyc-statistics/$NAME requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_KYC_STATISTICS_NAMES_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_KYC_STATISTICS_NAMES_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/kyc-statistics/$NAME" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (name of the statistic)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_kyc_statistics_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.c
@@ -0,0 +1,180 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 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_get-aml-OFFICER_PUB-legitimizations.c
+ * @brief Return information about legitimization measures
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h"
+#include "taler-exchange-httpd_get-metrics.h"
+
+/**
+ * Maximum number of measures we return in one request.
+ */
+#define MAX_MEASURES 1024
+
+/**
+ * Return LEGITIMIZATION measure.
+ *
+ * @param cls closure
+ * @param h_payto hash of account the measure applies to
+ * @param start_time when was the process started
+ * @param jmeasures object of type ``LegitimizationMeasures``
+ * @param is_finished true if the measure was finished
+ * @param measure_serial_id row ID of the measure in the exchange table
+ */
+static void
+record_cb (
+ void *cls,
+ struct TALER_NormalizedPaytoHashP *h_payto,
+ struct GNUNET_TIME_Absolute start_time,
+ const json_t *jmeasures,
+ bool is_finished,
+ uint64_t measure_serial_id)
+{
+ json_t *measures = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ measures,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ h_payto),
+ GNUNET_JSON_pack_uint64 ("rowid",
+ measure_serial_id),
+ GNUNET_JSON_pack_timestamp ("start_time",
+ GNUNET_TIME_absolute_to_timestamp (
+ start_time)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_object_incref ("measures",
+ (json_t *) jmeasures)),
+ GNUNET_JSON_pack_bool ("is_finished",
+ is_finished)
+ )));
+}
+
+
+MHD_RESULT
+TEH_handler_aml_legitimization_measures_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ int64_t limit = -20;
+ uint64_t offset;
+ struct TALER_NormalizedPaytoHashP h_payto;
+ bool have_payto = false;
+ enum TALER_EXCHANGE_YesNoAll active_filter;
+
+ if (NULL != args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[0]);
+ }
+ TALER_MHD_parse_request_snumber (rc->connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (rc->connection,
+ "offset",
+ &offset);
+ if (offset > INT64_MAX)
+ {
+ GNUNET_break_op (0); /* broken client */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Received invalid offset %llu > %llu\n",
+ (unsigned long long) offset,
+ (unsigned long long) INT64_MAX);
+ offset = INT64_MAX;
+ }
+ TALER_MHD_parse_request_arg_auto (rc->connection,
+ "h_payto",
+ &h_payto,
+ have_payto);
+ TALER_MHD_parse_request_yna (rc->connection,
+ "active",
+ TALER_EXCHANGE_YNA_ALL,
+ &active_filter);
+ {
+ json_t *measures;
+ enum GNUNET_DB_QueryStatus qs;
+
+ measures = json_array ();
+ GNUNET_assert (NULL != measures);
+ if (limit > MAX_MEASURES)
+ limit = MAX_MEASURES;
+ if (limit < -MAX_MEASURES)
+ limit = -MAX_MEASURES;
+ qs = TEH_plugin->select_aml_measures (
+ TEH_plugin->cls,
+ have_payto
+ ? &h_payto
+ : NULL,
+ active_filter,
+ offset,
+ limit,
+ &record_cb,
+ measures);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (measures);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_aml_measures");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ json_decref (measures);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("measures",
+ measures));
+ }
+}
+
+
+/* end of taler-exchange-httpd_legitimization-measures_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-legitimizations.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-aml-OFFICER_PUB-legitimizations.h
+ * @brief Handle /aml/$OFFICER_PUB/legitimizations requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_LEGITIMIZATIONS_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_LEGITIMIZATIONS_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/legitimizations" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching measuress.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be the state)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_legitimization_measures_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-measures.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-measures.c
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 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_get-aml-OFFICER_PUB-measures.c
+ * @brief Return summary information about KYC measures
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-measures.h"
+
+
+MHD_RESULT
+TEH_handler_aml_measures_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ static json_t *roots;
+ static json_t *programs;
+ static json_t *checks;
+ static json_t *default_rules;
+
+ if (NULL == roots)
+ {
+ TALER_KYCLOGIC_get_measure_configuration (&roots,
+ &programs,
+ &checks,
+ &default_rules);
+ }
+ if (NULL != args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_incref ("default_rules",
+ default_rules),
+ GNUNET_JSON_pack_object_incref ("roots",
+ roots),
+ GNUNET_JSON_pack_object_incref ("programs",
+ programs),
+ GNUNET_JSON_pack_object_incref ("checks",
+ checks));
+}
+
+
+/* end of taler-exchange-httpd_aml-measures_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-measures.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-measures.h
@@ -0,0 +1,45 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-aml-OFFICER_PUB-measures.h
+ * @brief Handle /aml/$OFFICER_PUB/measures requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_MEASURES_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_MEASURES_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/measures" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be empty)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_measures_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.c b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.c
@@ -0,0 +1,258 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_get-aml-OFFICER_PUB-transfers.c
+ * @brief Return information about exchange wire transfers
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h"
+#include "taler-exchange-httpd_get-metrics.h"
+
+
+enum TransferType
+{
+ TT_CREDIT,
+ TT_DEBIT,
+ TT_KYCAUTH
+};
+
+
+/**
+ * Maximum number of transfers we return in one request.
+ */
+#define MAX_TRANSFERS 1024
+
+/**
+ * Return transfer data.
+ *
+ * @param cls closure
+ * @param row_id current row in AML status table
+ * @param payto_uri account involved with the wire transfer
+ * @param execution_time when was the transfer made
+ * @param amount wire amount of the transfer
+ */
+static void
+record_cb (
+ void *cls,
+ uint64_t row_id,
+ const char *payto_uri,
+ struct GNUNET_TIME_Absolute execution_time,
+ const struct TALER_Amount *amount)
+{
+ json_t *transfers = cls;
+
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ transfers,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_int64 ("rowid",
+ row_id),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ GNUNET_TIME_absolute_to_timestamp (
+ execution_time)),
+ TALER_JSON_pack_amount ("amount",
+ amount)
+ )));
+}
+
+
+/**
+ * Handle HTTP request by AML officer for transfer data.
+ *
+ * @param rc request context
+ * @param officer_pub the AML officer
+ * @param tt which type of transfer data to return
+ * @param args further arguments provided (should be empty)
+ * @return MHD status
+ */
+static MHD_RESULT
+aml_transfer_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ enum TransferType tt,
+ const char *const args[])
+{
+ int64_t limit = -20;
+ uint64_t offset;
+ struct TALER_Amount threshold;
+ struct TALER_NormalizedPaytoHashP h_payto;
+ bool have_payto = false;
+
+ if (NULL != args[0])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ args[0]);
+ }
+ TALER_MHD_parse_request_arg_auto (rc->connection,
+ "h_payto",
+ &h_payto,
+ have_payto);
+ TALER_MHD_parse_request_snumber (rc->connection,
+ "limit",
+ &limit);
+ if (limit > 0)
+ offset = 0;
+ else
+ offset = INT64_MAX;
+ TALER_MHD_parse_request_number (rc->connection,
+ "offset",
+ &offset);
+ if (offset > INT64_MAX)
+ {
+ GNUNET_break_op (0); /* broken client */
+ offset = INT64_MAX;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &threshold));
+ TALER_MHD_parse_request_amount (rc->connection,
+ "threshold",
+ &threshold);
+ {
+ json_t *transfers;
+ enum GNUNET_DB_QueryStatus qs;
+ const char *query;
+
+ transfers = json_array ();
+ GNUNET_assert (NULL != transfers);
+ if (limit > MAX_TRANSFERS)
+ limit = MAX_TRANSFERS;
+ if (limit < -MAX_TRANSFERS)
+ limit = -MAX_TRANSFERS;
+ switch (tt)
+ {
+ case TT_DEBIT:
+ qs = TEH_plugin->select_exchange_debit_transfers (
+ TEH_plugin->cls,
+ &threshold,
+ offset,
+ limit,
+ have_payto ? &h_payto : NULL,
+ &record_cb,
+ transfers);
+ query = "select_exchange_debit_transfers";
+ break;
+ case TT_CREDIT:
+ qs = TEH_plugin->select_exchange_credit_transfers (
+ TEH_plugin->cls,
+ &threshold,
+ offset,
+ limit,
+ have_payto ? &h_payto : NULL,
+ &record_cb,
+ transfers);
+ query = "select_exchange_credit_transfers";
+ break;
+ case TT_KYCAUTH:
+ qs = TEH_plugin->select_exchange_kycauth_transfers (
+ TEH_plugin->cls,
+ &threshold,
+ offset,
+ limit,
+ have_payto ? &h_payto : NULL,
+ &record_cb,
+ transfers);
+ query = "select_exchange_kycauth_transfers";
+ break;
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ json_decref (transfers);
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ query);
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ json_decref (transfers);
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("transfers",
+ transfers));
+ }
+}
+
+
+MHD_RESULT
+TEH_handler_aml_transfer_credit_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ return aml_transfer_get (rc,
+ officer_pub,
+ TT_CREDIT,
+ args);
+}
+
+
+MHD_RESULT
+TEH_handler_aml_transfer_kycauth_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ return aml_transfer_get (rc,
+ officer_pub,
+ TT_KYCAUTH,
+ args);
+}
+
+
+MHD_RESULT
+TEH_handler_aml_transfer_debit_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[])
+{
+ return aml_transfer_get (rc,
+ officer_pub,
+ TT_DEBIT,
+ args);
+}
+
+
+/* end of taler-exchange-httpd_aml-decisions_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h b/src/exchange/taler-exchange-httpd_get-aml-OFFICER_PUB-transfers.h
@@ -0,0 +1,79 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_get-aml-OFFICER_PUB-transfers.h
+ * @brief Handle /aml/$OFFICER_PUB/transfer-* requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_TRANSFERS_H
+#define TALER_EXCHANGE_HTTPD_GET_AML_OFFICER_PUB_TRANSFERS_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/transfer-credit" request. Parses the
+ * request details, checks the signatures and if appropriately authorized
+ * returns the matching wire transfers.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be empty)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_transfer_credit_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/transfer-kycauth" request. Parses the
+ * request details, checks the signatures and if appropriately authorized
+ * returns the matching wire transfers.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be empty)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_transfer_kycauth_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/transfer-debit" request. Parses the
+ * request details, checks the signatures and if appropriately authorized
+ * returns the matching wire transfers.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be empty)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_transfer_debit_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.c b/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.c
@@ -0,0 +1,763 @@
+/*
+ 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 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_get-coins-COIN_PUB-history.c
+ * @brief Handle GET /coins/$COIN_PUB/history requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-coins-COIN_PUB-history.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Add the headers we want to set for every response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+/**
+ * Compile the transaction history of a coin into a JSON object.
+ *
+ * @param coin_pub public key of the coin
+ * @param tl transaction history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+static json_t *
+compile_transaction_history (
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_EXCHANGEDB_TransactionList *tl)
+{
+ json_t *history;
+
+ history = json_array ();
+ if (NULL == history)
+ {
+ GNUNET_break (0); /* out of memory!? */
+ return NULL;
+ }
+ for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_TT_DEPOSIT:
+ {
+ const struct TALER_EXCHANGEDB_DepositListEntry *deposit =
+ pos->details.deposit;
+ struct TALER_MerchantWireHashP h_wire;
+
+ TALER_merchant_wire_signature_hash (deposit->receiver_wire_account,
+ &deposit->wire_salt,
+ &h_wire);
+#if ENABLE_SANITY_CHECKS
+ /* internal sanity check before we hand out a bogus sig... */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ &deposit->amount_with_fee,
+ &deposit->deposit_fee,
+ &h_wire,
+ &deposit->h_contract_terms,
+ deposit->no_wallet_data_hash
+ ? NULL
+ : &deposit->wallet_data_hash,
+ deposit->no_age_commitment
+ ? NULL
+ : &deposit->h_age_commitment,
+ &deposit->h_policy,
+ &deposit->h_denom_pub,
+ deposit->timestamp,
+ &deposit->merchant_pub,
+ deposit->refund_deadline,
+ coin_pub,
+ &deposit->csig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "DEPOSIT"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &deposit->amount_with_fee),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &deposit->deposit_fee),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &deposit->merchant_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ deposit->timestamp),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("refund_deadline",
+ deposit->refund_deadline)),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &deposit->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("h_wire",
+ &h_wire),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &deposit->h_denom_pub),
+ GNUNET_JSON_pack_allow_null (
+ deposit->has_policy
+ ? GNUNET_JSON_pack_data_auto ("h_policy",
+ &deposit->h_policy)
+ : GNUNET_JSON_pack_string (
+ "h_policy",
+ NULL)),
+ GNUNET_JSON_pack_allow_null (
+ deposit->no_wallet_data_hash
+ ? GNUNET_JSON_pack_string (
+ "wallet_data_hash",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("wallet_data_hash",
+ &deposit->wallet_data_hash)),
+ GNUNET_JSON_pack_allow_null (
+ deposit->no_age_commitment
+ ? GNUNET_JSON_pack_string (
+ "h_age_commitment",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ &deposit->h_age_commitment)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &deposit->csig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_MELT:
+ {
+ const struct TALER_EXCHANGEDB_MeltListEntry *melt =
+ pos->details.melt;
+ const struct TALER_AgeCommitmentHashP *phac;
+ const struct TALER_BlindingMasterSeedP *pbs;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ &melt->amount_with_fee,
+ &melt->melt_fee,
+ &melt->rc,
+ &melt->h_denom_pub,
+ &melt->h_age_commitment,
+ coin_pub,
+ &melt->coin_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ phac = (melt->no_age_commitment)
+ ? NULL
+ : &melt->h_age_commitment;
+ pbs = melt->no_blinding_seed
+ ? NULL
+ : &melt->blinding_seed;
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MELT"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &melt->amount_with_fee),
+ TALER_JSON_pack_amount ("melt_fee",
+ &melt->melt_fee),
+ GNUNET_JSON_pack_data_auto ("rc",
+ &melt->rc),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &melt->h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("refresh_seed",
+ &melt->refresh_seed),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("blinding_seed",
+ pbs)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &melt->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_RefundListEntry *refund =
+ pos->details.refund;
+ struct TALER_Amount value;
+
+#if ENABLE_SANITY_CHECKS
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (
+ coin_pub,
+ &refund->h_contract_terms,
+ refund->rtransaction_id,
+ &refund->refund_amount,
+ &refund->merchant_pub,
+ &refund->merchant_sig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+#endif
+ if (0 >
+ TALER_amount_subtract (&value,
+ &refund->refund_amount,
+ &refund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "REFUND"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &refund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &refund->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ &refund->merchant_pub),
+ GNUNET_JSON_pack_uint64 ("rtransaction_id",
+ refund->rtransaction_id),
+ GNUNET_JSON_pack_data_auto ("merchant_sig",
+ &refund->merchant_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH_RECEIVER:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.old_coin_recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ &pr->coin.coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key hash and
+ the denomination key's RSA signature over coin_pub, but as the
+ wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP-REFRESH-RECEIVER"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &pr->coin.coin_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW:
+ {
+ const struct TALER_EXCHANGEDB_RecoupListEntry *recoup =
+ pos->details.recoup;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ coin_pub,
+ &recoup->reserve_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP-WITHDRAW"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &recoup->coin_sig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &recoup->h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &recoup->coin_blind),
+ // FIXME-9828: spec says we should have h_commitment?
+ // FIXME-9828: spec says we should have coin_index?
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &recoup->reserve_pub),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_TT_RECOUP_REFRESH:
+ {
+ struct TALER_EXCHANGEDB_RecoupRefreshListEntry *pr =
+ pos->details.recoup_refresh;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_refresh_sign (
+ &TEH_keys_exchange_sign_,
+ pr->timestamp,
+ &pr->value,
+ coin_pub,
+ &pr->old_coin_pub,
+ &epub,
+ &esig))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ /* NOTE: we could also provide coin_pub's coin_sig, denomination key
+ hash and the denomination key's RSA signature over coin_pub, but as
+ the wallet should really already have this information (and cannot
+ check or do anything with it anyway if it doesn't), it seems
+ strictly unnecessary. */
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP-REFRESH"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &pr->value),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("old_coin_pub",
+ &pr->old_coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pr->coin_sig),
+ // FIXME-#9828: spec says to return h_denom_pub
+ GNUNET_JSON_pack_data_auto ("coin_blind",
+ &pr->coin_blind),
+ // FIXME-#9828: spec says to return h_commitment
+ // FIXME-#9828: spec says to return coin_index
+ // FIXME-#9828: spec says to return new_coin_blinding_secret
+ // FIXME-#9828: spec says to return new_coin_ev
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ pr->timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_DEPOSIT:
+ {
+ struct TALER_EXCHANGEDB_PurseDepositListEntry *pd
+ = pos->details.purse_deposit;
+ const struct TALER_AgeCommitmentHashP *phac = NULL;
+
+ if (! pd->no_age_commitment)
+ phac = &pd->h_age_commitment;
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-DEPOSIT"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &pd->amount),
+ GNUNET_JSON_pack_string ("exchange_base_url",
+ NULL == pd->exchange_base_url
+ ? TEH_base_url
+ : pd->exchange_base_url),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ phac)),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &pd->deposit_fee),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &pd->purse_pub),
+ GNUNET_JSON_pack_bool ("refunded",
+ pd->refunded),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &pd->coin_sig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &pd->h_denom_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+
+ case TALER_EXCHANGEDB_TT_PURSE_REFUND:
+ {
+ const struct TALER_EXCHANGEDB_PurseRefundListEntry *prefund =
+ pos->details.purse_refund;
+ struct TALER_Amount value;
+ enum TALER_ErrorCode ec;
+ struct TALER_ExchangePublicKeyP epub;
+ struct TALER_ExchangeSignatureP esig;
+
+ if (0 >
+ TALER_amount_subtract (&value,
+ &prefund->refund_amount,
+ &prefund->refund_fee))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ ec = TALER_exchange_online_purse_refund_sign (
+ &TEH_keys_exchange_sign_,
+ &value,
+ &prefund->refund_fee,
+ coin_pub,
+ &prefund->purse_pub,
+ &epub,
+ &esig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "PURSE-REFUND"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("amount",
+ &value),
+ TALER_JSON_pack_amount ("refund_fee",
+ &prefund->refund_fee),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &esig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &epub),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &prefund->purse_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_TT_RESERVE_OPEN:
+ {
+ struct TALER_EXCHANGEDB_ReserveOpenListEntry *role
+ = pos->details.reserve_open;
+
+ if (0 !=
+ json_array_append_new (
+ history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RESERVE-OPEN-DEPOSIT"),
+ GNUNET_JSON_pack_uint64 ("history_offset",
+ pos->coin_history_id),
+ TALER_JSON_pack_amount ("coin_contribution",
+ &role->coin_contribution),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &role->reserve_sig),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &role->coin_sig))))
+ {
+ GNUNET_break (0);
+ json_decref (history);
+ return NULL;
+ }
+ break;
+ }
+ }
+ }
+ return history;
+}
+
+
+MHD_RESULT
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub)
+{
+ struct TALER_EXCHANGEDB_TransactionList *tl = NULL;
+ uint64_t start_off = 0;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_Amount balance;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
+ /* Check signature */
+ {
+ struct TALER_CoinSpendSignatureP coin_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_COIN_HISTORY_SIGNATURE_HEADER,
+ &coin_sig,
+ required);
+ if (GNUNET_OK !=
+ TALER_wallet_coin_history_verify (start_off,
+ coin_pub,
+ &coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_COIN_HISTORY_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = start_off;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
+ }
+ else
+ {
+ etag_in = start_off;
+ }
+ }
+
+ /* Get history from DB between etag and now */
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls,
+ true,
+ coin_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &h_denom_pub,
+ &tl);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_coin_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_coin_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ }
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
+ {
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
+ }
+ if (NULL == tl)
+ {
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer_static (0,
+ "");
+ http_status = MHD_HTTP_NO_CONTENT;
+ }
+ else
+ {
+ /* 200: regular history */
+ json_t *history;
+
+ history = compile_transaction_history (coin_pub,
+ tl);
+ TEH_plugin->free_coin_transaction_list (TEH_plugin->cls,
+ tl);
+ tl = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ "Failed to compile coin history");
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+ http_status = MHD_HTTP_OK;
+ }
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_coins_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.h b/src/exchange/taler-exchange-httpd_get-coins-COIN_PUB-history.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-coins-COIN_PUB-history.h
+ * @brief Handle GET /coins/$COIN_PUB requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_COINS_COIN_PUB_HISTORY_H
+#define TALER_EXCHANGE_HTTPD_GET_COINS_COIN_PUB_HISTORY_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown reserves-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/coins/$COIN_PUB/history" request. Parses the
+ * given "coins_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then respond with the
+ * transaction history of the coin.
+ *
+ * @param rc request context
+ * @param coin_pub public key of the coin
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_coins_get (struct TEH_RequestContext *rc,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-config.c b/src/exchange/taler-exchange-httpd_get-config.c
@@ -0,0 +1,77 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-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 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_get-config.c
+ * @brief Handle /config requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-config.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include <jansson.h>
+
+
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ static struct MHD_Response *resp;
+
+ (void) args;
+ if (NULL == resp)
+ {
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal (
+ "wallet_balance_limit_without_kyc",
+ TALER_KYCLOGIC_get_wallet_thresholds ())),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("shopping_url",
+ TEH_shopping_url)),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_JSON_currency_specs_to_json (TEH_cspec)),
+ GNUNET_JSON_pack_string (
+ "currency",
+ TEH_currency),
+ GNUNET_JSON_pack_string (
+ "name",
+ "taler-exchange"),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "aml_spa_dialect",
+ TEH_aml_spa_dialect)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("shopping_url",
+ TEH_shopping_url)),
+ GNUNET_JSON_pack_string (
+ "implementation",
+ "urn:net:taler:specs:taler-exchange:c-reference"),
+ GNUNET_JSON_pack_string (
+ "version",
+ EXCHANGE_PROTOCOL_VERSION));
+ }
+ return MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+}
+
+
+/* end of taler-exchange-httpd_config.c */
diff --git a/src/exchange/taler-exchange-httpd_get-config.h b/src/exchange/taler-exchange-httpd_get-config.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ (C) 2025 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 EXCHANGEABILITY 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 taler-exchange-httpd_get-config.h
+ * @brief headers for /config handler
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_CONFIG_H
+#define TALER_EXCHANGE_HTTPD_GET_CONFIG_H
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Taler protocol version in the format CURRENT:REVISION:AGE
+ * as used by GNU libtool. See
+ * https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
+ *
+ * Please be very careful when updating and follow
+ * https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
+ * precisely. Note that this version has NOTHING to do with the
+ * release version, and the format is NOT the same that semantic
+ * versioning uses either.
+ *
+ * When changing this version, you likely want to also update
+ * #TALER_PROTOCOL_CURRENT and #TALER_PROTOCOL_AGE in
+ * exchange_api_handle.c!
+ *
+ * Returned via both /config and /keys endpoints.
+ */
+#define EXCHANGE_PROTOCOL_VERSION "34:0:0"
+
+
+/**
+ * Manages a /config call.
+ *
+ * @param rc context of the handler
+ * @param[in,out] args remaining arguments (ignored)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_config (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-contracts-CONTRACT_PUB.c b/src/exchange/taler-exchange-httpd_get-contracts-CONTRACT_PUB.c
@@ -0,0 +1,99 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_get-contracts-CONTRACT_PUB.c
+ * @brief Handle GET /contracts/$C_PUB requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_get-contracts-CONTRACT_PUB.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+MHD_RESULT
+TEH_handler_contracts_get (struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct TALER_ContractDiffiePublicP contract_pub;
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ void *econtract;
+ size_t econtract_size;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_PurseContractSignatureP econtract_sig;
+ MHD_RESULT res;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &contract_pub,
+ sizeof (contract_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_CONTRACTS_INVALID_CONTRACT_PUB,
+ args[0]);
+ }
+
+ qs = TEH_plugin->select_contract (TEH_plugin->cls,
+ &contract_pub,
+ &purse_pub,
+ &econtract_sig,
+ &econtract_size,
+ &econtract);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_contract");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_contract");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_CONTRACTS_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ res = TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &purse_pub),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract_sig),
+ GNUNET_JSON_pack_data_varsize ("econtract",
+ econtract,
+ econtract_size));
+ GNUNET_free (econtract);
+ return res;
+}
+
+
+/* end of taler-exchange-httpd_contract.c */
diff --git a/src/exchange/taler-exchange-httpd_get-contracts-CONTRACT_PUB.h b/src/exchange/taler-exchange-httpd_get-contracts-CONTRACT_PUB.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_get-contracts-CONTRACT_PUB.h
+ * @brief Handle /coins/$COIN_PUB/contract requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_CONTRACTS_CONTRACT_PUB_H
+#define TALER_EXCHANGE_HTTPD_GET_CONTRACTS_CONTRACT_PUB_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/contracts/$C_PUB" request. Returns the
+ * encrypted contract.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 1, first is the contract_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_contracts_get (struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-deposits.c b/src/exchange/taler-exchange-httpd_get-deposits.c
@@ -0,0 +1,536 @@
+/*
+ This file is part of TALER
+ Copyright (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 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_get-deposits.c
+ * @brief Handle wire deposit tracking-related requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_dbevents.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_util.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-deposits.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for #handle_wtid_data.
+ */
+struct DepositWtidContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct DepositWtidContext *prev;
+
+ /**
+ * Context for the request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Hash over the proposal data of the contract for which this deposit is made.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash over the wiring information of the merchant.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * The Merchant's public key. The deposit inquiry request is to be
+ * signed by the corresponding private key (using EdDSA).
+ */
+ struct TALER_MerchantPublicKeyP merchant;
+
+ /**
+ * Public key for KYC operations on the target bank
+ * account for the wire transfer. All zero if no
+ * public key is accepted yet. In that case, the
+ * client should use the @e merchant public key for
+ * the KYC auth wire transfer.
+ */
+ union TALER_AccountPublicKeyP account_pub;
+
+ /**
+ * The coin's public key. This is the value that must have been
+ * signed (blindly) by the Exchange.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Set by #handle_wtid data to the wire transfer ID.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Signature by the merchant.
+ */
+ struct TALER_MerchantSignatureP merchant_sig;
+
+ /**
+ * Set by #handle_wtid data to the coin's contribution to the wire transfer.
+ */
+ struct TALER_Amount coin_contribution;
+
+ /**
+ * Set by #handle_wtid data to the fee charged to the coin.
+ */
+ struct TALER_Amount coin_fee;
+
+ /**
+ * Set by #handle_wtid data to the wire transfer execution time.
+ */
+ struct GNUNET_TIME_Timestamp execution_time;
+
+ /**
+ * Timeout of the request, for long-polling.
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Set by #handle_wtid to the coin contribution to the transaction
+ * (that is, @e coin_contribution minus @e coin_fee).
+ */
+ struct TALER_Amount coin_delta;
+
+ /**
+ * KYC status information for the receiving account.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
+ * if we were woken up due to shutdown.
+ */
+ enum GNUNET_GenericReturnValue suspended;
+
+ /**
+ * What do we long-poll for? Defaults to
+ * #TALER_DGLPT_OK if not given.
+ */
+ enum TALER_DepositGetLongPollTarget lpt;
+};
+
+
+/**
+ * Head of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_head;
+
+/**
+ * Tail of DLL of suspended requests.
+ */
+static struct DepositWtidContext *dwc_tail;
+
+
+void
+TEH_deposits_get_cleanup ()
+{
+ struct DepositWtidContext *n;
+
+ for (struct DepositWtidContext *ctx = dwc_head;
+ NULL != ctx;
+ ctx = n)
+ {
+ n = ctx->next;
+ GNUNET_assert (GNUNET_YES == ctx->suspended);
+ ctx->suspended = GNUNET_SYSERR;
+ MHD_resume_connection (ctx->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
+ }
+}
+
+
+/**
+ * A merchant asked for details about a deposit. Provide
+ * them. Generates the 200 reply.
+ *
+ * @param ctx details to respond with
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_deposit_details (
+ const struct DepositWtidContext *ctx)
+{
+ struct MHD_Connection *connection = ctx->rc->connection;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_confirm_wire_sign (
+ &TEH_keys_exchange_sign_,
+ &ctx->h_wire,
+ &ctx->h_contract_terms,
+ &ctx->wtid,
+ &ctx->coin_pub,
+ ctx->execution_time,
+ &ctx->coin_delta,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &ctx->wtid),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ ctx->execution_time),
+ TALER_JSON_pack_amount ("coin_contribution",
+ &ctx->coin_delta),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct DepositWtidContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct DepositWtidContext *ctx = cls;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (GNUNET_YES != ctx->suspended)
+ return; /* might get multiple wake-up events */
+ GNUNET_CONTAINER_DLL_remove (dwc_head,
+ dwc_tail,
+ ctx);
+ GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming request handling\n");
+ TEH_check_invariants ();
+ ctx->suspended = GNUNET_NO;
+ MHD_resume_connection (ctx->rc->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * Lookup and return the wire transfer identifier.
+ *
+ * @param ctx context of the signed request to execute
+ * @return MHD result code
+ */
+static MHD_RESULT
+handle_track_transaction_request (
+ struct DepositWtidContext *ctx)
+{
+ struct MHD_Connection *connection = ctx->rc->connection;
+ enum GNUNET_DB_QueryStatus qs;
+ bool pending;
+ struct TALER_Amount fee;
+
+ qs = TEH_plugin->lookup_transfer_by_deposit (
+ TEH_plugin->cls,
+ &ctx->h_contract_terms,
+ &ctx->h_wire,
+ &ctx->coin_pub,
+ &ctx->merchant,
+ &pending,
+ &ctx->wtid,
+ &ctx->execution_time,
+ &ctx->coin_contribution,
+ &fee,
+ &ctx->kyc,
+ &ctx->account_pub);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_transfer_by_deposit");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
+ NULL);
+ }
+
+ if (0 >
+ TALER_amount_subtract (&ctx->coin_delta,
+ &ctx->coin_contribution,
+ &fee))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "wire fees exceed aggregate in database");
+ }
+ if (pending)
+ {
+ if (GNUNET_TIME_absolute_is_future (ctx->timeout))
+ {
+ bool do_suspend = false;
+ switch (ctx->lpt)
+ {
+ case TALER_DGLPT_NONE:
+ break;
+ case TALER_DGLPT_KYC_REQUIRED_OR_OK:
+ do_suspend = ctx->kyc.ok;
+ break;
+ case TALER_DGLPT_OK:
+ do_suspend = true;
+ break;
+ }
+ if (do_suspend)
+ {
+ GNUNET_assert (GNUNET_NO == ctx->suspended);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending request handling\n");
+ GNUNET_CONTAINER_DLL_insert (dwc_head,
+ dwc_tail,
+ ctx);
+ ctx->suspended = GNUNET_YES;
+ MHD_suspend_connection (connection);
+ return MHD_YES;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC required with row %llu\n",
+ (unsigned long long) ctx->kyc.requirement_row);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_ACCEPTED,
+ GNUNET_JSON_pack_allow_null (
+ (0 == ctx->kyc.requirement_row)
+ ? GNUNET_JSON_pack_string ("requirement_row",
+ NULL)
+ : GNUNET_JSON_pack_uint64 ("requirement_row",
+ ctx->kyc.requirement_row)),
+ GNUNET_JSON_pack_allow_null (
+ (GNUNET_is_zero (&ctx->account_pub))
+ ? GNUNET_JSON_pack_string ("account_pub",
+ NULL)
+ : GNUNET_JSON_pack_data_auto ("account_pub",
+ &ctx->account_pub)),
+ GNUNET_JSON_pack_bool ("kyc_ok",
+ ctx->kyc.ok),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ ctx->execution_time));
+ }
+ return reply_deposit_details (ctx);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+dwc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ GNUNET_assert (GNUNET_YES != ctx->suspended);
+ if (NULL != ctx->eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ ctx->eh);
+ ctx->eh = NULL;
+ }
+ GNUNET_free (ctx);
+}
+
+
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
+ const char *const args[4])
+{
+ struct DepositWtidContext *ctx = rc->rh_ctx;
+
+ if (NULL == ctx)
+ {
+ ctx = GNUNET_new (struct DepositWtidContext);
+ ctx->rc = rc;
+ ctx->lpt = TALER_DGLPT_OK; /* default */
+ rc->rh_ctx = ctx;
+ rc->rh_cleaner = &dwc_cleaner;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ctx->h_wire,
+ sizeof (ctx->h_wire)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
+ args[0]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[1],
+ strlen (args[1]),
+ &ctx->merchant,
+ sizeof (ctx->merchant)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
+ args[1]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[2],
+ strlen (args[2]),
+ &ctx->h_contract_terms,
+ sizeof (ctx->h_contract_terms)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
+ args[2]);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[3],
+ strlen (args[3]),
+ &ctx->coin_pub,
+ sizeof (ctx->coin_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
+ args[3]);
+ }
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "merchant_sig",
+ &ctx->merchant_sig);
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &ctx->timeout);
+ {
+ uint64_t num = 0;
+ int val;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "lpt",
+ &num);
+ val = (int) num;
+ if ( (val < 0) ||
+ (val > TALER_DGLPT_MAX) )
+ {
+ /* Protocol violation, but we can be graceful and
+ just ignore the long polling! */
+ GNUNET_break_op (0);
+ val = TALER_DGLPT_NONE;
+ }
+ ctx->lpt = (enum TALER_DepositGetLongPollTarget) val;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Long polling for target %d with timeout %s\n",
+ val,
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ ctx->timeout),
+ true));
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ {
+ if (GNUNET_OK !=
+ TALER_merchant_deposit_verify (&ctx->merchant,
+ &ctx->coin_pub,
+ &ctx->h_contract_terms,
+ &ctx->h_wire,
+ &ctx->merchant_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
+ NULL);
+ }
+ }
+ if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
+ (TALER_DGLPT_NONE != ctx->lpt) )
+ {
+ struct TALER_CoinDepositEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
+ .merchant_pub = ctx->merchant
+ };
+
+ ctx->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (ctx->timeout),
+ &rep.header,
+ &db_event_cb,
+ ctx);
+ GNUNET_break (NULL != ctx->eh);
+ }
+ }
+
+ return handle_track_transaction_request (ctx);
+}
+
+
+/* end of taler-exchange-httpd_deposits_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-deposits.h b/src/exchange/taler-exchange-httpd_get-deposits.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2017, 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_get-deposits.h
+ * @brief Handle wire transfer tracking-related requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_DEPOSITS_H
+#define TALER_EXCHANGE_HTTPD_GET_DEPOSITS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume long pollers on GET /deposits.
+ */
+void
+TEH_deposits_get_cleanup (void);
+
+
+/**
+ * Handle a "/deposits/$H_WIRE/$MERCHANT_PUB/$H_CONTRACT_TERMS/$COIN_PUB"
+ * request.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 4, contains:
+ * h_wire, merchant_pub, h_contract_terms and coin_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_deposits_get (struct TEH_RequestContext *rc,
+ const char *const args[4]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-keys.c b/src/exchange/taler-exchange-httpd_get-keys.c
@@ -0,0 +1,4428 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2025 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_get-keys.c
+ * @brief management of our various keys
+ * @author Christian Grothoff
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd.h"
+#include "taler-exchange-httpd_get-config.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler/taler_extensions.h"
+
+
+/**
+ * How many /keys request do we hold in suspension at
+ * most at any time?
+ */
+#define SKR_LIMIT 32
+
+
+/**
+ * When do we forcefully timeout a /keys request?
+ * Matches the 120s hard-coded into exchange_api_handle.c
+ */
+#define KEYS_TIMEOUT \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
+
+
+/**
+ * Information about a denomination on offer by the denomination helper.
+ */
+struct HelperDenomination
+{
+
+ /**
+ * When will the helper start to use this key for signing?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * For how long will the helper allow signing? 0 if
+ * the key was revoked or purged.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * Hash of the full denomination key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Signature over this key from the security module's key.
+ */
+ struct TALER_SecurityModuleSignatureP sm_sig;
+
+ /**
+ * The (full) public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Details depend on the @e denom_pub.cipher type.
+ */
+ union
+ {
+
+ /**
+ * Hash of the RSA key.
+ */
+ struct TALER_RsaPubHashP h_rsa;
+
+ /**
+ * Hash of the CS key.
+ */
+ struct TALER_CsPubHashP h_cs;
+
+ } h_details;
+
+ /**
+ * Name in configuration section for this denomination type.
+ */
+ char *section_name;
+
+
+};
+
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature
+{
+ /**
+ * We store the signatures in a DLL.
+ */
+ struct TEH_AuditorSignature *prev;
+
+ /**
+ * We store the signatures in a DLL.
+ */
+ struct TEH_AuditorSignature *next;
+
+ /**
+ * A signature from the auditor.
+ */
+ struct TALER_AuditorSignatureP asig;
+
+ /**
+ * Public key of the auditor.
+ */
+ struct TALER_AuditorPublicKeyP apub;
+
+};
+
+
+/**
+ * Information about a signing key on offer by the esign helper.
+ */
+struct HelperSignkey
+{
+ /**
+ * When will the helper start to use this key for signing?
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * For how long will the helper allow signing? 0 if
+ * the key was revoked or purged.
+ */
+ struct GNUNET_TIME_Relative validity_duration;
+
+ /**
+ * The public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Signature over this key from the security module's key.
+ */
+ struct TALER_SecurityModuleSignatureP sm_sig;
+
+};
+
+
+/**
+ * State associated with the crypto helpers / security modules. NOT updated
+ * when the #key_generation is updated (instead constantly kept in sync
+ * whenever #TEH_keys_get_state() is called).
+ */
+struct HelperState
+{
+
+ /**
+ * Handle for the esign/EdDSA helper.
+ */
+ struct TALER_CRYPTO_ExchangeSignHelper *esh;
+
+ /**
+ * Handle for the denom/RSA helper.
+ */
+ struct TALER_CRYPTO_RsaDenominationHelper *rsadh;
+
+ /**
+ * Handle for the denom/CS helper.
+ */
+ struct TALER_CRYPTO_CsDenominationHelper *csdh;
+
+ /**
+ * Map from H(denom_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *denom_keys;
+
+ /**
+ * Map from H(rsa_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *rsa_keys;
+
+ /**
+ * Map from H(cs_pub) to `struct HelperDenomination` entries.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *cs_keys;
+
+ /**
+ * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey`
+ * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
+ * an EdDSA public key.
+ */
+ struct GNUNET_CONTAINER_MultiPeerMap *esign_keys;
+
+};
+
+
+/**
+ * Information we track for the crypto helpers. Preserved
+ * when the @e key_generation changes, thus kept separate.
+ */
+static struct HelperState helpers;
+
+
+/**
+ * Entry in (sorted) array with possible pre-build responses for /keys.
+ * We keep pre-build responses for the various (valid) cherry-picking
+ * values around.
+ */
+struct KeysResponseData
+{
+
+ /**
+ * Response to return if the client supports (deflate) compression.
+ */
+ struct MHD_Response *response_compressed;
+
+ /**
+ * Response to return if the client does not support compression.
+ */
+ struct MHD_Response *response_uncompressed;
+
+ /**
+ * ETag for these responses.
+ */
+ char *etag;
+
+ /**
+ * Cherry-picking timestamp the client must have set for this
+ * response to be valid. 0 if this is the "full" response.
+ * The client's request must include this date or a higher one
+ * for this response to be applicable.
+ */
+ struct GNUNET_TIME_Timestamp cherry_pick_date;
+
+};
+
+
+/**
+ * @brief All information about an exchange online signing key (which is used to
+ * sign messages from the exchange).
+ */
+struct SigningKey
+{
+
+ /**
+ * The exchange's (online signing) public key.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Meta data about the signing key, such as validity periods.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+ /**
+ * The long-term offline master key's signature for this signing key.
+ * Signs over @e exchange_pub and @e meta.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+};
+
+struct TEH_KeyStateHandle
+{
+
+ /**
+ * Mapping from denomination keys to denomination key issue struct.
+ * Used to lookup the key by hash.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
+
+ /**
+ * Mapping from serial ID's to denomination key issue struct.
+ * Used to lookup the key by serial ID.
+ *
+ * FIXME: We need a 64-bit version of this in GNUNET.
+ */
+ struct GNUNET_CONTAINER_MultiHashMap32 *denomserial_map;
+
+ /**
+ * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey`
+ * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
+ * an EdDSA public key.
+ */
+ struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;
+
+ /**
+ * Head of DLL of our global fees.
+ */
+ struct TEH_GlobalFee *gf_head;
+
+ /**
+ * Tail of DLL of our global fees.
+ */
+ struct TEH_GlobalFee *gf_tail;
+
+ /**
+ * json array with the auditors of this exchange. Contains exactly
+ * the information needed for the "auditors" field of the /keys response.
+ */
+ json_t *auditors;
+
+ /**
+ * json array with the global fees of this exchange. Contains exactly
+ * the information needed for the "global_fees" field of the /keys response.
+ */
+ json_t *global_fees;
+
+ /**
+ * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of
+ * length @e krd_array_length;
+ */
+ struct KeysResponseData *krd_array;
+
+ /**
+ * Length of the @e krd_array.
+ */
+ unsigned int krd_array_length;
+
+ /**
+ * Cached reply for a GET /management/keys request. Used so we do not
+ * re-create the reply every time.
+ */
+ json_t *management_keys_reply;
+
+ /**
+ * For which (global) key_generation was this data structure created?
+ * Used to check when we are outdated and need to be re-generated.
+ */
+ uint64_t key_generation;
+
+ /**
+ * When did we initiate the key reloading?
+ */
+ struct GNUNET_TIME_Timestamp reload_time;
+
+ /**
+ * What is the period at which we rotate keys
+ * (signing or denomination keys)?
+ */
+ struct GNUNET_TIME_Relative rekey_frequency;
+
+ /**
+ * When does our online signing key expire and we
+ * thus need to re-generate this response?
+ */
+ struct GNUNET_TIME_Timestamp signature_expires;
+
+ /**
+ * True if #finish_keys_response() was not yet run and this key state
+ * is only suitable for the /management/keys API.
+ */
+ bool management_only;
+
+};
+
+
+/**
+ * Entry of /keys requests that are currently suspended because we are
+ * waiting for /keys to become ready.
+ */
+struct SuspendedKeysRequests
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct SuspendedKeysRequests *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct SuspendedKeysRequests *prev;
+
+ /**
+ * The suspended connection.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * When does this request timeout?
+ */
+ 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 part of the /keys response.
+ */
+struct WireStateHandle
+{
+
+ /**
+ * 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;
+
+ /**
+ * Is the wire data ready?
+ */
+ bool ready;
+
+};
+
+
+/**
+ * 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.
+ */
+static struct TEH_KeyStateHandle *key_state;
+
+/**
+ * Counter incremented whenever we have a reason to re-build the keys because
+ * something external changed. See #TEH_keys_get_state() and
+ * #TEH_keys_update_states() for uses of this variable.
+ */
+static uint64_t key_generation;
+
+/**
+ * Handler listening for wire updates by other exchange
+ * services.
+ */
+static struct GNUNET_DB_EventHandler *keys_eh;
+
+/**
+ * Head of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_head;
+
+/**
+ * Tail of DLL of suspended /keys requests.
+ */
+static struct SuspendedKeysRequests *skr_tail;
+
+/**
+ * Number of entries in the @e skr_head DLL.
+ */
+static unsigned int skr_size;
+
+/**
+ * Task to force timeouts on /keys requests.
+ */
+static struct GNUNET_SCHEDULER_Task *keys_tt;
+
+/**
+ * For how long should a signing key be legally retained?
+ * Configuration value.
+ */
+static struct GNUNET_TIME_Relative signkey_legal_duration;
+
+/**
+ * What type of asset are we dealing with here?
+ */
+static char *asset_type;
+
+/**
+ * RSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;
+
+/**
+ * CS security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub;
+
+/**
+ * EdDSA security module public key, all zero if not known.
+ */
+static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
+
+/**
+ * Are we shutting down?
+ */
+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);
+ }
+ 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);
+}
+
+
+/**
+ * Add information about a wire account to @a cls.
+ *
+ * @param cls a `json_t *` array 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 open_banking_gateway URL of an open banking gateway, NULL if there is none
+ * @param wire_transfer_gateway URL of a wire transfer gateway, NULL if there is none
+ * @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)
+ * @param bank_label label the wallet should use to display the account, can be NULL
+ * @param priority priority for ordering bank account labels
+ */
+static void
+add_wire_account (
+ void *cls,
+ const struct TALER_FullPayto payto_uri,
+ const char *conversion_url,
+ const char *open_banking_gateway,
+ const char *wire_transfer_gateway,
+ const json_t *debit_restrictions,
+ const json_t *credit_restrictions,
+ const struct TALER_MasterSignatureP *master_sig,
+ const char *bank_label,
+ int64_t priority)
+{
+ json_t *a = cls;
+
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ payto_uri,
+ conversion_url,
+ open_banking_gateway,
+ wire_transfer_gateway,
+ debit_restrictions,
+ credit_restrictions,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire account with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ if (0 !=
+ json_array_append_new (
+ a,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_full_payto (
+ "payto_uri",
+ payto_uri),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "conversion_url",
+ conversion_url)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "open_banking_gateway",
+ open_banking_gateway)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "wire_transfer_gateway",
+ wire_transfer_gateway)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "bank_label",
+ bank_label)),
+ GNUNET_JSON_pack_int64 (
+ "priority",
+ priority),
+ 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;
+
+ /**
+ * 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;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ ac->wire_method,
+ start_date,
+ end_date,
+ fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has wire fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ 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;
+ 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->ready = false;
+ return wsh;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Built wire data with %u accounts (%d)\n",
+ (unsigned int) json_array_size (wire_accounts_array),
+ (int) qs);
+ wire_fee_object = json_object ();
+ GNUNET_assert (NULL != wire_fee_object);
+ wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
+ {
+ 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)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "No wire method in `%s'\n",
+ payto_uri);
+ wsh->ready = false;
+ json_decref (wire_accounts_array);
+ json_decref (wire_fee_object);
+ return wsh;
+ }
+ if (NULL == json_object_get (wire_fee_object,
+ wire_method))
+ {
+ struct AddContext ac = {
+ .wire_method = wire_method,
+ .wsh = wsh,
+ .a = json_array ()
+ };
+
+ 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->ready = false;
+ return wsh;
+ }
+ if (0 != json_array_size (ac.a))
+ {
+ 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));
+ }
+ else
+ {
+ json_decref (ac.a);
+ }
+ }
+ GNUNET_free (wire_method);
+ }
+ }
+
+ wads = json_array (); /* #7271 */
+ GNUNET_assert (NULL != wads);
+ wsh->json_reply = GNUNET_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));
+ wsh->ready = true;
+ 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
+ */
+static 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;
+}
+
+
+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
+ */
+static void
+keys_timeout_cb (void *cls)
+{
+ struct SuspendedKeysRequests *skr;
+
+ (void) cls;
+ keys_tt = NULL;
+ while (NULL != (skr = skr_head))
+ {
+ if (GNUNET_TIME_absolute_is_future (skr->timeout))
+ break;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming /keys request due to timeout\n");
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ skr_size--;
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+ if (NULL == skr)
+ return;
+ keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+ &keys_timeout_cb,
+ NULL);
+}
+
+
+/**
+ * Suspend /keys request while we (hopefully) are waiting to be
+ * provisioned with key material.
+ *
+ * @param[in] connection to suspend
+ */
+static MHD_RESULT
+suspend_request (struct MHD_Connection *connection)
+{
+ struct SuspendedKeysRequests *skr;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending /keys request until key material changes\n");
+ if (terminating)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "Exchange terminating");
+ }
+ skr = GNUNET_new (struct SuspendedKeysRequests);
+ skr->connection = connection;
+ MHD_suspend_connection (connection);
+ GNUNET_CONTAINER_DLL_insert (skr_head,
+ skr_tail,
+ skr);
+ skr_size++;
+ skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT);
+ if (NULL == keys_tt)
+ {
+ keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
+ &keys_timeout_cb,
+ NULL);
+ }
+ while (skr_size > SKR_LIMIT)
+ {
+ skr = skr_tail;
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ skr_size--;
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+ return MHD_YES;
+}
+
+
+/**
+ * Called on each denomination key. Checks that the key still works.
+ *
+ * @param cls NULL
+ * @param hc denomination hash (unused)
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK
+ */
+static enum GNUNET_GenericReturnValue
+check_dk (void *cls,
+ const struct GNUNET_HashCode *hc,
+ void *value)
+{
+ struct TEH_DenominationKey *dk = value;
+
+ (void) cls;
+ (void) hc;
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check (
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key));
+ return GNUNET_OK;
+ case GNUNET_CRYPTO_BSA_CS:
+ /* nothing to do for GNUNET_CRYPTO_BSA_CS */
+ return GNUNET_OK;
+ }
+ GNUNET_assert (0);
+ return GNUNET_SYSERR;
+}
+
+
+void
+TEH_check_invariants ()
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ if (0 == TEH_check_invariants_flag)
+ return;
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ return;
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &check_dk,
+ NULL);
+}
+
+
+void
+TEH_resume_keys_requests (bool do_shutdown)
+{
+ struct SuspendedKeysRequests *skr;
+
+ if (do_shutdown)
+ terminating = true;
+ while (NULL != (skr = skr_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (skr_head,
+ skr_tail,
+ skr);
+ skr_size--;
+ MHD_resume_connection (skr->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_free (skr);
+ }
+ GNUNET_assert (0 == skr_size);
+}
+
+
+/**
+ * Clear memory for responses to "/keys" in @a ksh.
+ *
+ * @param[in,out] ksh key state to update
+ */
+static void
+clear_response_cache (struct TEH_KeyStateHandle *ksh)
+{
+ for (unsigned int i = 0; i<ksh->krd_array_length; i++)
+ {
+ struct KeysResponseData *krd = &ksh->krd_array[i];
+
+ MHD_destroy_response (krd->response_compressed);
+ MHD_destroy_response (krd->response_uncompressed);
+ GNUNET_free (krd->etag);
+ }
+ GNUNET_array_grow (ksh->krd_array,
+ ksh->krd_array_length,
+ 0);
+}
+
+
+/**
+ * Check that the given RSA security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &denom_rsa_sm_pub))
+ {
+ if (! GNUNET_is_zero (&denom_rsa_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our RSA security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Check that the given CS security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub RSA security module public key to check
+ */
+static void
+check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &denom_cs_sm_pub))
+ {
+ if (! GNUNET_is_zero (&denom_cs_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our CS security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Check that the given EdDSA security module's public key is the one
+ * we have pinned. If it does not match, we die hard.
+ *
+ * @param sm_pub EdDSA security module public key to check
+ */
+static void
+check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
+{
+ if (0 !=
+ GNUNET_memcmp (sm_pub,
+ &esign_sm_pub))
+ {
+ if (! GNUNET_is_zero (&esign_sm_pub))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Our EdDSA security module changed its key. This must not happen.\n");
+ GNUNET_assert (0);
+ }
+ esign_sm_pub = *sm_pub; /* TOFU ;-) */
+ }
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `denom_keys` map.
+ *
+ * @param cls the `struct HelperDenomination`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value the `struct HelperDenomination` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_denom_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct HelperDenomination *hd = value;
+
+ (void) cls;
+ (void) h_denom_pub;
+ TALER_denom_pub_free (&hd->denom_pub);
+ GNUNET_free (hd->section_name);
+ GNUNET_free (hd);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Helper function for #destroy_key_helpers to free all entries
+ * in the `esign_keys` map.
+ *
+ * @param cls the `struct HelperSignkey`
+ * @param pid unused, matches the exchange public key
+ * @param value the `struct HelperSignkey` to release
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+free_esign_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct HelperSignkey *hsk = value;
+
+ (void) cls;
+ (void) pid;
+ GNUNET_free (hsk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Destroy helper state. Does NOT call free() on @a hs, as that
+ * state is not separately allocated! Dual to #setup_key_helpers().
+ *
+ * @param[in] hs helper state to free, but NOT the @a hs pointer itself!
+ */
+static void
+destroy_key_helpers (struct HelperState *hs)
+{
+ GNUNET_CONTAINER_multihashmap_iterate (hs->denom_keys,
+ &free_denom_cb,
+ hs);
+ GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys);
+ hs->rsa_keys = NULL;
+ GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys);
+ hs->cs_keys = NULL;
+ GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys);
+ hs->denom_keys = NULL;
+ GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys,
+ &free_esign_cb,
+ hs);
+ GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys);
+ hs->esign_keys = NULL;
+ if (NULL != hs->rsadh)
+ {
+ TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh);
+ hs->rsadh = NULL;
+ }
+ if (NULL != hs->csdh)
+ {
+ TALER_CRYPTO_helper_cs_disconnect (hs->csdh);
+ hs->csdh = NULL;
+ }
+ if (NULL != hs->esh)
+ {
+ TALER_CRYPTO_helper_esign_disconnect (hs->esh);
+ hs->esh = NULL;
+ }
+}
+
+
+/**
+ * Looks up the AGE_RESTRICTED setting for a denomination in the config and
+ * returns the age restriction (mask) accordingly.
+ *
+ * @param section_name Section in the configuration for the particular
+ * denomination.
+ */
+static struct TALER_AgeMask
+load_age_mask (const char *section_name)
+{
+ static const struct TALER_AgeMask null_mask = {0};
+ enum GNUNET_GenericReturnValue ret;
+
+ if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
+ TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ return null_mask;
+
+ if (GNUNET_SYSERR ==
+ (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
+ section_name,
+ "AGE_RESTRICTED")))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "AGE_RESTRICTED",
+ "Value must be YES or NO\n");
+ return null_mask;
+ }
+
+ if (GNUNET_OK == ret)
+ {
+ if (! TEH_age_restriction_enabled)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "age restriction set in section %s, yet, age restriction is not enabled\n",
+ section_name);
+ return TEH_age_restriction_config.mask;
+ }
+
+
+ return null_mask;
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_rsa hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module
+ */
+static void
+helper_rsa_cb (
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_RsaPubHashP *h_rsa,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperDenomination *hd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "RSA helper announces key %s for denomination type %s with validity %s\n",
+ GNUNET_h2s (&h_rsa->hash),
+ section_name,
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ hd = GNUNET_CONTAINER_multihashmap_get (hs->rsa_keys,
+ &h_rsa->hash);
+ if (NULL != hd)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hd->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_denom_rsa_sm_pub (sm_pub);
+ hd = GNUNET_new (struct HelperDenomination);
+ hd->start_time = start_time;
+ hd->validity_duration = validity_duration;
+ hd->h_details.h_rsa = *h_rsa;
+ hd->sm_sig = *sm_sig;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key =
+ GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ /* load the age mask for the denomination, if applicable */
+ hd->denom_pub.age_mask = load_age_mask (section_name);
+ TALER_denom_pub_hash (&hd->denom_pub,
+ &hd->h_denom_pub);
+ hd->section_name = GNUNET_strdup (section_name);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->denom_keys,
+ &hd->h_denom_pub.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->rsa_keys,
+ &hd->h_details.h_rsa.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available CS keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param section_name name of the denomination type in the configuration;
+ * NULL if the key has been revoked or purged
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param h_cs hash of the @a denom_pub that is available (or was purged)
+ * @param bs_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module
+ */
+static void
+helper_cs_cb (
+ void *cls,
+ const char *section_name,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_CsPubHashP *h_cs,
+ struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperDenomination *hd;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "CS helper announces key %s for denomination type %s with validity %s\n",
+ GNUNET_h2s (&h_cs->hash),
+ section_name,
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys,
+ &h_cs->hash);
+ if (NULL != hd)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hd->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_denom_cs_sm_pub (sm_pub);
+ hd = GNUNET_new (struct HelperDenomination);
+ hd->start_time = start_time;
+ hd->validity_duration = validity_duration;
+ hd->h_details.h_cs = *h_cs;
+ hd->sm_sig = *sm_sig;
+ GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher);
+ hd->denom_pub.bsign_pub_key
+ = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
+ /* load the age mask for the denomination, if applicable */
+ hd->denom_pub.age_mask = load_age_mask (section_name);
+ TALER_denom_pub_hash (&hd->denom_pub,
+ &hd->h_denom_pub);
+ hd->section_name = GNUNET_strdup (section_name);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->denom_keys,
+ &hd->h_denom_pub.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (
+ hs->cs_keys,
+ &hd->h_details.h_cs.hash,
+ hd,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Function called with information about available keys for signing. Usually
+ * only called once per key upon connect. Also called again in case a key is
+ * being revoked, in that case with an @a end_time of zero.
+ *
+ * @param cls closure with the `struct HelperState *`
+ * @param start_time when does the key become available for signing;
+ * zero if the key has been revoked or purged
+ * @param validity_duration how long does the key remain available for signing;
+ * zero if the key has been revoked or purged
+ * @param exchange_pub the public key itself, NULL if the key was revoked or purged
+ * @param sm_pub public key of the security module, NULL if the key was revoked or purged
+ * @param sm_sig signature from the security module
+ */
+static void
+helper_esign_cb (
+ void *cls,
+ struct GNUNET_TIME_Timestamp start_time,
+ struct GNUNET_TIME_Relative validity_duration,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_SecurityModulePublicKeyP *sm_pub,
+ const struct TALER_SecurityModuleSignatureP *sm_sig)
+{
+ struct HelperState *hs = cls;
+ struct HelperSignkey *hsk;
+ struct GNUNET_PeerIdentity pid;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "EdDSA helper announces signing key %s with validity %s\n",
+ TALER_B2S (exchange_pub),
+ GNUNET_STRINGS_relative_time_to_string (validity_duration,
+ GNUNET_NO));
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ pid.public_key = exchange_pub->eddsa_pub;
+ hsk = GNUNET_CONTAINER_multipeermap_get (hs->esign_keys,
+ &pid);
+ if (NULL != hsk)
+ {
+ /* should be just an update (revocation!), so update existing entry */
+ hsk->validity_duration = validity_duration;
+ return;
+ }
+ GNUNET_assert (NULL != sm_pub);
+ check_esign_sm_pub (sm_pub);
+ hsk = GNUNET_new (struct HelperSignkey);
+ hsk->start_time = start_time;
+ hsk->validity_duration = validity_duration;
+ hsk->exchange_pub = *exchange_pub;
+ hsk->sm_sig = *sm_sig;
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multipeermap_put (
+ hs->esign_keys,
+ &pid,
+ hsk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Setup helper state.
+ *
+ * @param[out] hs helper state to initialize
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+setup_key_helpers (struct HelperState *hs)
+{
+ hs->denom_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->rsa_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->cs_keys
+ = GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_YES);
+ hs->esign_keys
+ = GNUNET_CONTAINER_multipeermap_create (32,
+ GNUNET_NO /* MUST BE NO! */);
+ hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_rsa_cb,
+ hs);
+ if (NULL == hs->rsadh)
+ return GNUNET_SYSERR;
+ hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_cs_cb,
+ hs);
+ if (NULL == hs->csdh)
+ return GNUNET_SYSERR;
+ hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
+ "taler-exchange",
+ &helper_esign_cb,
+ hs);
+ if (NULL == hs->esh)
+ return GNUNET_SYSERR;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Synchronize helper state. Polls the key helper for updates.
+ *
+ * @param[in,out] hs helper state to synchronize
+ */
+static void
+sync_key_helpers (struct HelperState *hs)
+{
+ TALER_CRYPTO_helper_rsa_poll (hs->rsadh);
+ TALER_CRYPTO_helper_cs_poll (hs->csdh);
+ TALER_CRYPTO_helper_esign_poll (hs->esh);
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param h_denom_pub hash of the denomination public key, unused
+ * @param value a `struct TEH_DenominationKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_denomination_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct TEH_DenominationKey *dk = value;
+ struct TEH_AuditorSignature *as;
+
+ (void) cls;
+ (void) h_denom_pub;
+ TALER_denom_pub_free (&dk->denom_pub);
+ while (NULL != (as = dk->as_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (dk->as_head,
+ dk->as_tail,
+ as);
+ GNUNET_free (as);
+ }
+ GNUNET_free (dk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free denomination key data.
+ *
+ * @param cls a `struct TEH_KeyStateHandle`, unused
+ * @param pid the online signing key (type-disguised), unused
+ * @param value a `struct SigningKey` to free
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+clear_signkey_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct SigningKey *sk = value;
+
+ (void) cls;
+ (void) pid;
+ GNUNET_free (sk);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Free resources associated with @a cls, possibly excluding
+ * the helper data.
+ *
+ * @param[in] ksh key state to release
+ */
+static void
+destroy_key_state (struct TEH_KeyStateHandle *ksh)
+{
+ struct TEH_GlobalFee *gf;
+
+ clear_response_cache (ksh);
+ while (NULL != (gf = ksh->gf_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ksh->gf_head,
+ ksh->gf_tail,
+ gf);
+ GNUNET_free (gf);
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &clear_denomination_cb,
+ ksh);
+ GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map);
+ GNUNET_CONTAINER_multihashmap32_destroy (ksh->denomserial_map);
+ GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+ &clear_signkey_cb,
+ ksh);
+ GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);
+ json_decref (ksh->auditors);
+ ksh->auditors = NULL;
+ json_decref (ksh->global_fees);
+ ksh->global_fees = NULL;
+ if (NULL != ksh->management_keys_reply)
+ {
+ json_decref (ksh->management_keys_reply);
+ ksh->management_keys_reply = NULL;
+ }
+ GNUNET_free (ksh);
+}
+
+
+/**
+ * Function called whenever another exchange process has updated
+ * the keys data in the database.
+ *
+ * @param cls NULL
+ * @param extra unused
+ * @param extra_size number of bytes in @a extra unused
+ */
+static void
+keys_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 /keys update event\n");
+ TEH_check_invariants ();
+ key_generation++;
+ TEH_resume_keys_requests (false);
+ TEH_check_invariants ();
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_init ()
+{
+ if (GNUNET_OK !=
+ setup_key_helpers (&helpers))
+ {
+ destroy_key_helpers (&helpers);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ "exchange",
+ "SIGNKEY_LEGAL_DURATION",
+ &signkey_legal_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "exchange",
+ "SIGNKEY_LEGAL_DURATION");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
+ "exchange",
+ "ASSET_TYPE",
+ &asset_type))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "exchange",
+ "ASSET_TYPE");
+ asset_type = GNUNET_strdup ("fiat");
+ }
+ {
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ keys_eh = TEH_plugin->event_listen (TEH_plugin->cls,
+ GNUNET_TIME_UNIT_FOREVER_REL,
+ &es,
+ &keys_update_event_cb,
+ NULL);
+ if (NULL == keys_eh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ {
+ 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;
+}
+
+
+/**
+ * Fully clean up our state.
+ */
+void
+TEH_keys_finished ()
+{
+ 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;
+ }
+ if (NULL != keys_tt)
+ {
+ GNUNET_SCHEDULER_cancel (keys_tt);
+ keys_tt = NULL;
+ }
+ if (NULL != key_state)
+ destroy_key_state (key_state);
+ if (NULL != keys_eh)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ keys_eh);
+ keys_eh = NULL;
+ }
+ destroy_key_helpers (&helpers);
+}
+
+
+/**
+ * Function called with information about the exchange's denomination keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param denom_pub public key of the denomination
+ * @param h_denom_pub hash of @a denom_pub
+ * @param meta meta data information about the denomination type (value, expirations, fees)
+ * @param master_sig master signature affirming the validity of this denomination
+ * @param recoup_possible true if the key was revoked and clients can currently recoup
+ * coins of this denomination
+ */
+static void
+denomination_info_cb (
+ void *cls,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig,
+ bool recoup_possible)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_DenominationKey *dk;
+
+ if (GNUNET_TIME_absolute_is_past (meta->expire_deposit.abs_time))
+ {
+ /* should have been filtered by DB query already! */
+ GNUNET_break (0);
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ h_denom_pub,
+ meta->start,
+ meta->expire_withdraw,
+ meta->expire_deposit,
+ meta->expire_legal,
+ &meta->value,
+ &meta->fees,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has denomination with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ denom_pub->bsign_pub_key->cipher);
+ if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) ||
+ GNUNET_TIME_absolute_is_zero (meta->expire_legal.abs_time) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database contains invalid denomination key %s\n",
+ GNUNET_h2s (&h_denom_pub->hash));
+ return;
+ }
+ dk = GNUNET_new (struct TEH_DenominationKey);
+ TALER_denom_pub_copy (&dk->denom_pub,
+ denom_pub);
+ dk->h_denom_pub = *h_denom_pub;
+ dk->meta = *meta;
+ dk->master_sig = *master_sig;
+ dk->recoup_possible = recoup_possible;
+ dk->denom_pub.age_mask = meta->age_mask;
+
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
+ &dk->h_denom_pub.hash,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ {
+ uint32_t serial32 = (uint32_t) dk->meta.serial;
+
+ GNUNET_assert (dk->meta.serial == (uint64_t) serial32);
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap32_put (ksh->denomserial_map,
+ serial32,
+ dk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+}
+
+
+/**
+ * Function called with information about the exchange's online signing keys.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param exchange_pub the public key
+ * @param meta meta data information about the denomination type (expirations)
+ * @param master_sig master signature affirming the validity of this denomination
+ */
+static void
+signkey_info_cb (
+ void *cls,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct SigningKey *sk;
+ struct GNUNET_PeerIdentity pid;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ exchange_pub,
+ meta->start,
+ meta->expire_sign,
+ meta->expire_legal,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has signing key with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ sk = GNUNET_new (struct SigningKey);
+ sk->exchange_pub = *exchange_pub;
+ sk->meta = *meta;
+ sk->master_sig = *master_sig;
+ pid.public_key = exchange_pub->eddsa_pub;
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multipeermap_put (ksh->signkey_map,
+ &pid,
+ sk,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+}
+
+
+/**
+ * Closure for #get_auditor_sigs.
+ */
+struct GetAuditorSigsContext
+{
+ /**
+ * Where to store the matching signatures.
+ */
+ json_t *denom_keys;
+
+ /**
+ * Public key of the auditor to match against.
+ */
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+};
+
+
+/**
+ * Extract the auditor signatures matching the auditor's public
+ * key from the @a value and generate the respective JSON.
+ *
+ * @param cls a `struct GetAuditorSigsContext`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+get_auditor_sigs (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct GetAuditorSigsContext *ctx = cls;
+ struct TEH_DenominationKey *dk = value;
+
+ for (struct TEH_AuditorSignature *as = dk->as_head;
+ NULL != as;
+ as = as->next)
+ {
+ if (0 !=
+ GNUNET_memcmp (ctx->auditor_pub,
+ &as->apub))
+ continue;
+ GNUNET_break (0 ==
+ json_array_append_new (
+ ctx->denom_keys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("denom_pub_h",
+ h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("auditor_sig",
+ &as->asig))));
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with information about the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of the auditor
+ * @param auditor_url URL of the REST API of the auditor
+ * @param auditor_name human readable official name of the auditor
+ */
+static void
+auditor_info_cb (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const char *auditor_url,
+ const char *auditor_name)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct GetAuditorSigsContext ctx;
+
+ ctx.denom_keys = json_array ();
+ GNUNET_assert (NULL != ctx.denom_keys);
+ ctx.auditor_pub = auditor_pub;
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &get_auditor_sigs,
+ &ctx);
+ GNUNET_break (0 ==
+ json_array_append_new (
+ ksh->auditors,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("auditor_name",
+ auditor_name),
+ GNUNET_JSON_pack_data_auto ("auditor_pub",
+ auditor_pub),
+ GNUNET_JSON_pack_string ("auditor_url",
+ auditor_url),
+ GNUNET_JSON_pack_array_steal ("denomination_keys",
+ ctx.denom_keys))));
+}
+
+
+/**
+ * Function called with information about the denominations
+ * audited by the exchange's auditors.
+ *
+ * @param cls closure with a `struct TEH_KeyStateHandle *`
+ * @param auditor_pub the public key of an auditor
+ * @param h_denom_pub hash of a denomination key audited by this auditor
+ * @param auditor_sig signature from the auditor affirming this
+ */
+static void
+auditor_denom_cb (
+ void *cls,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const struct TALER_AuditorSignatureP *auditor_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_DenominationKey *dk;
+ struct TEH_AuditorSignature *as;
+
+ dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+ &h_denom_pub->hash);
+ if (NULL == dk)
+ {
+ /* Odd, this should be impossible as per foreign key
+ constraint on 'auditor_denom_sigs'! Well, we can
+ safely continue anyway, so let's just log it. */
+ GNUNET_break (0);
+ return;
+ }
+ as = GNUNET_new (struct TEH_AuditorSignature);
+ as->asig = *auditor_sig;
+ as->apub = *auditor_pub;
+ GNUNET_CONTAINER_DLL_insert (dk->as_head,
+ dk->as_tail,
+ as);
+}
+
+
+/**
+ * Closure for #add_sign_key_cb.
+ */
+struct SignKeyCtx
+{
+ /**
+ * What is the current rotation frequency for signing keys. Updated.
+ */
+ struct GNUNET_TIME_Relative min_sk_frequency;
+
+ /**
+ * JSON array of signing keys (being created).
+ */
+ json_t *signkeys;
+};
+
+
+/**
+ * Function called for all signing keys, used to build up the
+ * respective JSON response.
+ *
+ * @param cls a `struct SignKeyCtx *` with the array to append keys to
+ * @param pid the exchange public key (in type disguise)
+ * @param value a `struct SigningKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_sign_key_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct SignKeyCtx *ctx = cls;
+ struct SigningKey *sk = value;
+
+ (void) pid;
+ if (GNUNET_TIME_absolute_is_future (sk->meta.expire_sign.abs_time))
+ {
+ ctx->min_sk_frequency =
+ GNUNET_TIME_relative_min (ctx->min_sk_frequency,
+ GNUNET_TIME_absolute_get_difference (
+ sk->meta.start.abs_time,
+ sk->meta.expire_sign.abs_time));
+ }
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ ctx->signkeys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ sk->meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ sk->meta.expire_sign),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ sk->meta.expire_legal),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ &sk->master_sig),
+ GNUNET_JSON_pack_data_auto ("key",
+ &sk->exchange_pub))));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_denom_key_cb.
+ */
+struct DenomKeyCtx
+{
+ /**
+ * Heap for sorting active denomination keys by start time.
+ */
+ struct GNUNET_CONTAINER_Heap *heap;
+
+ /**
+ * JSON array of revoked denomination keys.
+ */
+ json_t *recoup;
+
+ /**
+ * What is the minimum key rotation frequency of
+ * valid denomination keys?
+ */
+ struct GNUNET_TIME_Relative min_dk_frequency;
+};
+
+
+/**
+ * Function called for all denomination keys, used to build up the
+ * JSON list of *revoked* denomination keys and the
+ * heap of non-revoked denomination keys by timeout.
+ *
+ * @param cls a `struct DenomKeyCtx`
+ * @param h_denom_pub hash of the denomination key
+ * @param value a `struct TEH_DenominationKey`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_denom_key_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct DenomKeyCtx *dkc = cls;
+ struct TEH_DenominationKey *dk = value;
+
+ if (dk->recoup_possible)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ dkc->recoup,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ h_denom_pub))));
+ }
+ else
+ {
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ dkc->min_dk_frequency =
+ GNUNET_TIME_relative_min (dkc->min_dk_frequency,
+ GNUNET_TIME_absolute_get_difference (
+ dk->meta.start.abs_time,
+ dk->meta.expire_withdraw.abs_time));
+ }
+ (void) GNUNET_CONTAINER_heap_insert (dkc->heap,
+ dk,
+ dk->meta.start.abs_time.abs_value_us);
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Add the headers we want to set for every /keys response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+setup_general_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ char dat[128];
+
+ TALER_MHD_add_global_headers (response,
+ true);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "application/json"));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,must-revalidate,max-age=86400")
+ );
+ if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
+ {
+ 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);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration);
+ m = GNUNET_TIME_timestamp_min (we,
+ km);
+ TALER_MHD_get_date_string (m.abs_time,
+ dat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Setting /keys 'Expires' header to '%s' (rekey frequency is %s)\n",
+ dat,
+ GNUNET_TIME_relative2s (ksh->rekey_frequency,
+ false));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ ksh->signature_expires
+ = GNUNET_TIME_timestamp_min (m,
+ ksh->signature_expires);
+ }
+ /* Set cache control headers: our response varies depending on these headers */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_VARY,
+ MHD_HTTP_HEADER_ACCEPT_ENCODING));
+}
+
+
+/**
+ * Initialize @a krd using the given values for @a signkeys,
+ * @a recoup and @a denoms.
+ *
+ * @param[in,out] ksh key state handle we build @a krd for
+ * @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
+ * @param last_cherry_pick_date timestamp to use
+ * @param[in,out] signkeys list of sign keys to return
+ * @param[in,out] recoup list of revoked keys to return
+ * @param[in,out] grouped_denominations list of grouped denominations to return
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+create_krd (struct TEH_KeyStateHandle *ksh,
+ const struct GNUNET_HashCode *denom_keys_hash,
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date,
+ json_t *signkeys,
+ json_t *recoup,
+ json_t *grouped_denominations)
+{
+ struct KeysResponseData krd;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct WireStateHandle *wsh;
+ json_t *keys;
+
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
+ last_cherry_pick_date.abs_time));
+ GNUNET_assert (NULL != signkeys);
+ GNUNET_assert (NULL != recoup);
+ GNUNET_assert (NULL != grouped_denominations);
+ GNUNET_assert (NULL != ksh->auditors);
+ GNUNET_assert (NULL != TEH_currency);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Creating /keys at cherry pick date %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+
+ /* Sign hash over master signatures of all denomination keys until this time
+ (in reverse order). */
+ {
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec =
+ TALER_exchange_online_key_set_sign (
+ &TEH_keys_exchange_sign2_,
+ ksh,
+ last_cherry_pick_date,
+ denom_keys_hash,
+ &exchange_pub,
+ &exchange_sig)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not create key response data: cannot sign (%s)\n",
+ TALER_ErrorCode_get_hint (ec));
+ return GNUNET_SYSERR;
+ }
+ }
+
+ {
+ const struct SigningKey *sk;
+
+ sk = GNUNET_CONTAINER_multipeermap_get (
+ ksh->signkey_map,
+ (const struct GNUNET_PeerIdentity *) &exchange_pub);
+ ksh->signature_expires = GNUNET_TIME_timestamp_min (sk->meta.expire_sign,
+ 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),
+ GNUNET_JSON_pack_string ("base_url",
+ TEH_base_url),
+ GNUNET_JSON_pack_string ("currency",
+ TEH_currency),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "bank_compliance_language",
+ TEH_bank_compliance_language)),
+ GNUNET_JSON_pack_object_steal (
+ "currency_specification",
+ TALER_JSON_currency_specs_to_json (TEH_cspec)),
+ GNUNET_JSON_pack_array_incref (
+ "hard_limits",
+ TEH_hard_limits),
+ GNUNET_JSON_pack_array_incref (
+ "zero_limits",
+ TEH_zero_limits),
+ TALER_JSON_pack_amount ("stefan_abs",
+ &TEH_stefan_abs),
+ TALER_JSON_pack_amount ("stefan_log",
+ &TEH_stefan_log),
+ GNUNET_JSON_pack_double ("stefan_lin",
+ (double) TEH_stefan_lin),
+ GNUNET_JSON_pack_string ("asset_type",
+ asset_type),
+ GNUNET_JSON_pack_bool ("kyc_enabled",
+ GNUNET_YES == TEH_enable_kyc),
+ GNUNET_JSON_pack_bool ("disable_direct_deposit",
+ GNUNET_YES == TEH_disable_direct_deposit),
+ GNUNET_JSON_pack_bool ("rewards_allowed",
+ false),
+ GNUNET_JSON_pack_data_auto ("master_public_key",
+ &TEH_master_public_key),
+ GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
+ TEH_reserve_closing_delay),
+ GNUNET_JSON_pack_array_incref ("signkeys",
+ signkeys),
+ GNUNET_JSON_pack_array_incref ("recoup",
+ recoup),
+ 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",
+ ksh->auditors),
+ GNUNET_JSON_pack_array_incref ("global_fees",
+ ksh->global_fees),
+ GNUNET_JSON_pack_timestamp ("list_issue_date",
+ last_cherry_pick_date),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal (
+ "wallet_balance_limit_without_kyc",
+ TALER_KYCLOGIC_get_wallet_thresholds ())),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("shopping_url",
+ TEH_shopping_url)),
+ TALER_JSON_pack_amount ("tiny_amount",
+ &TEH_tiny_amount),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig));
+ GNUNET_assert (NULL != keys);
+ /* Signal support for the configured, enabled extensions. */
+ {
+ json_t *extensions = json_object ();
+ bool has_extensions = false;
+
+ GNUNET_assert (NULL != extensions);
+ /* Fill in the configurations of the enabled extensions */
+ for (const struct TALER_Extensions *iter = TALER_extensions_get_head ();
+ NULL != iter && NULL != iter->extension;
+ iter = iter->next)
+ {
+ const struct TALER_Extension *extension = iter->extension;
+ json_t *manifest;
+ int r;
+
+ /* skip if not enabled */
+ if (! extension->enabled)
+ continue;
+
+ /* flag our findings so far */
+ has_extensions = true;
+
+
+ manifest = extension->manifest (extension);
+ GNUNET_assert (manifest);
+
+ r = json_object_set_new (
+ extensions,
+ extension->name,
+ manifest);
+ GNUNET_assert (0 == r);
+ }
+
+ /* Update the keys object with the extensions and its signature */
+ if (has_extensions)
+ {
+ json_t *sig;
+ int r;
+
+ r = json_object_set_new (
+ keys,
+ "extensions",
+ extensions);
+ GNUNET_assert (0 == r);
+
+ /* Add the signature of the extensions, if it is not zero */
+ if (TEH_extensions_signed)
+ {
+ sig = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("extensions_sig",
+ &TEH_extensions_sig));
+
+ r = json_object_update (keys, sig);
+ GNUNET_assert (0 == r);
+ }
+ }
+ else
+ {
+ json_decref (extensions);
+ }
+ }
+
+
+ {
+ char *keys_json;
+ void *keys_jsonz;
+ size_t keys_jsonz_size;
+ int comp;
+ char etag[sizeof (struct GNUNET_HashCode) * 2];
+
+ /* Convert /keys response to UTF8-String */
+ keys_json = json_dumps (keys,
+ JSON_INDENT (2));
+ json_decref (keys);
+ GNUNET_assert (NULL != keys_json);
+
+ /* Keep copy for later compression... */
+ keys_jsonz = GNUNET_strdup (keys_json);
+ keys_jsonz_size = strlen (keys_json);
+
+ /* hash to compute etag */
+ {
+ struct GNUNET_HashCode ehash;
+ char *end;
+
+ GNUNET_CRYPTO_hash (keys_jsonz,
+ keys_jsonz_size,
+ &ehash);
+ end = GNUNET_STRINGS_data_to_string (&ehash,
+ sizeof (ehash),
+ etag,
+ sizeof (etag));
+ *end = '\0';
+ }
+
+ /* Create uncompressed response */
+ krd.response_uncompressed
+ = MHD_create_response_from_buffer (keys_jsonz_size,
+ keys_json,
+ MHD_RESPMEM_MUST_FREE);
+ GNUNET_assert (NULL != krd.response_uncompressed);
+ setup_general_response_headers (ksh,
+ krd.response_uncompressed);
+ /* Information is always public, revalidate after 1 day */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (krd.response_uncompressed,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ /* Also compute compressed version of /keys response */
+ comp = TALER_MHD_body_compress (&keys_jsonz,
+ &keys_jsonz_size);
+ krd.response_compressed
+ = MHD_create_response_from_buffer (keys_jsonz_size,
+ keys_jsonz,
+ MHD_RESPMEM_MUST_FREE);
+ GNUNET_assert (NULL != krd.response_compressed);
+ /* If the response is actually compressed, set the
+ respective header. */
+ GNUNET_assert ( (MHD_YES != comp) ||
+ (MHD_YES ==
+ MHD_add_response_header (krd.response_compressed,
+ MHD_HTTP_HEADER_CONTENT_ENCODING,
+ "deflate")) );
+ setup_general_response_headers (ksh,
+ krd.response_compressed);
+ /* Information is always public, revalidate after 1 day */
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (krd.response_compressed,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ krd.etag = GNUNET_strdup (etag);
+ }
+ krd.cherry_pick_date = last_cherry_pick_date;
+ GNUNET_array_append (ksh->krd_array,
+ ksh->krd_array_length,
+ krd);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Element in the `struct SignatureContext` array.
+ */
+struct SignatureElement
+{
+
+ /**
+ * Offset of the denomination in the group array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int offset;
+
+ /**
+ * Offset of the group in the denominations array,
+ * for sorting (2nd rank, ascending).
+ */
+ unsigned int group_offset;
+
+ /**
+ * Pointer to actual master signature to hash over.
+ */
+ struct TALER_MasterSignatureP master_sig;
+};
+
+/**
+ * Context for collecting the array of master signatures
+ * needed to verify the exchange_sig online signature.
+ */
+struct SignatureContext
+{
+ /**
+ * Array of signatures to hash over.
+ */
+ struct SignatureElement *elements;
+
+ /**
+ * Write offset in the @e elements array.
+ */
+ unsigned int elements_pos;
+
+ /**
+ * Allocated space for @e elements.
+ */
+ unsigned int elements_size;
+};
+
+
+/**
+ * Determine order to sort two elements by before
+ * we hash the master signatures. Used for
+ * sorting with qsort().
+ *
+ * @param a pointer to a `struct SignatureElement`
+ * @param b pointer to a `struct SignatureElement`
+ * @return 0 if equal, -1 if a < b, 1 if a > b.
+ */
+static int
+signature_context_sort_cb (const void *a,
+ const void *b)
+{
+ const struct SignatureElement *sa = a;
+ const struct SignatureElement *sb = b;
+
+ if (sa->group_offset < sb->group_offset)
+ return -1;
+ if (sa->group_offset > sb->group_offset)
+ return 1;
+ if (sa->offset < sb->offset)
+ return -1;
+ if (sa->offset > sb->offset)
+ return 1;
+ /* We should never have two disjoint elements
+ with same time and offset */
+ GNUNET_assert (sa == sb);
+ return 0;
+}
+
+
+/**
+ * Append a @a master_sig to the @a sig_ctx using the
+ * given attributes for (later) sorting.
+ *
+ * @param[in,out] sig_ctx signature context to update
+ * @param group_offset offset for the group
+ * @param offset offset for the entry
+ * @param master_sig master signature for the entry
+ */
+static void
+append_signature (struct SignatureContext *sig_ctx,
+ unsigned int group_offset,
+ unsigned int offset,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct SignatureElement *element;
+ unsigned int new_size;
+
+ if (sig_ctx->elements_pos == sig_ctx->elements_size)
+ {
+ if (0 == sig_ctx->elements_size)
+ new_size = 1024;
+ else
+ new_size = sig_ctx->elements_size * 2;
+ GNUNET_array_grow (sig_ctx->elements,
+ sig_ctx->elements_size,
+ new_size);
+ }
+ element = &sig_ctx->elements[sig_ctx->elements_pos++];
+ element->offset = offset;
+ element->group_offset = group_offset;
+ element->master_sig = *master_sig;
+}
+
+
+/**
+ *GroupData is the value we store for each group meta-data */
+struct GroupData
+{
+ /**
+ * The json blob with the group meta-data and list of denominations
+ */
+ json_t *json;
+
+ /**
+ * List of denominations for the group,
+ * included in @e json, do not free separately!
+ */
+ json_t *list;
+
+ /**
+ * Offset of the group in the final array.
+ */
+ unsigned int group_off;
+
+};
+
+
+/**
+ * Helper function called to clean up the group data
+ * in the denominations_by_group below.
+ *
+ * @param cls unused
+ * @param key unused
+ * @param value a `struct GroupData` to free
+ * @return #GNUNET_OK
+ */
+static int
+free_group (void *cls,
+ const struct GNUNET_HashCode *key,
+ void *value)
+{
+ struct GroupData *gd = value;
+
+ (void) cls;
+ (void) key;
+ GNUNET_free (gd);
+ return GNUNET_OK;
+}
+
+
+static void
+compute_msig_hash (struct SignatureContext *sig_ctx,
+ struct GNUNET_HashCode *hc)
+{
+ struct GNUNET_HashContext *hash_context;
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ qsort (sig_ctx->elements,
+ sig_ctx->elements_pos,
+ sizeof (struct SignatureElement),
+ &signature_context_sort_cb);
+ for (unsigned int i = 0; i<sig_ctx->elements_pos; i++)
+ {
+ struct SignatureElement *element = &sig_ctx->elements[i];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding %u,%u,%s\n",
+ element->group_offset,
+ element->offset,
+ TALER_B2S (&element->master_sig));
+ GNUNET_CRYPTO_hash_context_read (hash_context,
+ &element->master_sig,
+ sizeof (element->master_sig));
+ }
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ hc);
+}
+
+
+/**
+ * Update the "/keys" responses in @a ksh, computing the detailed replies.
+ *
+ * This function is to recompute all (including cherry-picked) responses we
+ * might want to return, based on the state already in @a ksh.
+ *
+ * @param[in,out] ksh state handle to update
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+finish_keys_response (struct TEH_KeyStateHandle *ksh)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ json_t *recoup;
+ struct SignKeyCtx sctx = {
+ .min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL
+ };
+ json_t *grouped_denominations = NULL;
+ struct GNUNET_TIME_Timestamp last_cherry_pick_date;
+ struct GNUNET_CONTAINER_Heap *heap;
+ struct SignatureContext sig_ctx = { 0 };
+ /* Remember if we have any denomination with age restriction */
+ bool has_age_restricted_denomination = false;
+ struct WireStateHandle *wsh;
+
+ wsh = get_wire_state ();
+ if (! wsh->ready)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ if (0 ==
+ json_array_size (json_object_get (wsh->json_reply,
+ "accounts")) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No wire accounts available. Refusing to generate /keys response.\n");
+ return GNUNET_NO;
+ }
+ sctx.signkeys = json_array ();
+ GNUNET_assert (NULL != sctx.signkeys);
+ recoup = json_array ();
+ GNUNET_assert (NULL != recoup);
+ grouped_denominations = json_array ();
+ GNUNET_assert (NULL != grouped_denominations);
+
+ GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
+ &add_sign_key_cb,
+ &sctx);
+ if (0 == json_array_size (sctx.signkeys))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No online signing keys available. Refusing to generate /keys response.\n");
+ ret = GNUNET_NO;
+ goto CLEANUP;
+ }
+ heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
+ {
+ struct DenomKeyCtx dkc = {
+ .recoup = recoup,
+ .heap = heap,
+ .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
+ };
+
+ GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
+ &add_denom_key_cb,
+ &dkc);
+ ksh->rekey_frequency
+ = GNUNET_TIME_relative_min (dkc.min_dk_frequency,
+ sctx.min_sk_frequency);
+ }
+
+ last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
+
+ {
+ struct TEH_DenominationKey *dk;
+ struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
+
+ denominations_by_group =
+ GNUNET_CONTAINER_multihashmap_create (1024,
+ GNUNET_NO /* NO, because keys are only on the stack */
+ );
+ /* heap = max heap, sorted by start time */
+ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+ {
+ if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
+ !=,
+ dk->meta.start) &&
+ (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
+ {
+ /*
+ * This is not the first entry in the heap (because last_cherry_pick_date !=
+ * GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
+ * start time. Therefore, we create a new entry in ksh.
+ */
+ struct GNUNET_HashCode hc;
+
+ compute_msig_hash (&sig_ctx,
+ &hc);
+ if (GNUNET_OK !=
+ create_krd (ksh,
+ &hc,
+ last_cherry_pick_date,
+ sctx.signkeys,
+ recoup,
+ grouped_denominations))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to generate key response data for %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ /* drain heap before destroying it */
+ while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
+ /* intentionally empty */;
+ GNUNET_CONTAINER_heap_destroy (heap);
+ goto CLEANUP;
+ }
+ }
+
+ last_cherry_pick_date = dk->meta.start;
+ /*
+ * Group the denominations by {cipher, value, fees, age_mask}.
+ *
+ * For each group we save the group meta-data and the list of
+ * denominations in this group as a json-blob in the multihashmap
+ * denominations_by_group.
+ */
+ {
+ struct GroupData *group;
+ json_t *entry;
+ struct GNUNET_HashCode key;
+ struct TALER_DenominationGroup meta = {
+ .cipher = dk->denom_pub.bsign_pub_key->cipher,
+ .value = dk->meta.value,
+ .fees = dk->meta.fees,
+ .age_mask = dk->meta.age_mask,
+ };
+
+ /* Search the group/JSON-blob for the key */
+ TALER_denomination_group_get_key (&meta,
+ &key);
+ group = GNUNET_CONTAINER_multihashmap_get (
+ denominations_by_group,
+ &key);
+ if (NULL == group)
+ {
+ /* There is no group for this meta-data yet, so we create a new group */
+ bool age_restricted = meta.age_mask.bits != 0;
+ const char *cipher;
+
+ group = GNUNET_new (struct GroupData);
+ switch (meta.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ cipher = age_restricted ? "RSA+age_restricted" : "RSA";
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ cipher = age_restricted ? "CS+age_restricted" : "CS";
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+ /* Create a new array for the denominations in this group */
+ group->list = json_array ();
+ GNUNET_assert (NULL != group->list);
+ group->json = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("cipher",
+ cipher),
+ GNUNET_JSON_pack_array_steal ("denoms",
+ group->list),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ TALER_JSON_pack_amount ("value",
+ &meta.value));
+ GNUNET_assert (NULL != group->json);
+ if (age_restricted)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_object_set_new (group->json,
+ "age_mask",
+ json_integer (
+ meta.age_mask.bits)));
+ /* Remember that we have found at least _one_ age restricted denomination */
+ has_age_restricted_denomination = true;
+ }
+ group->group_off
+ = json_array_size (grouped_denominations);
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ grouped_denominations,
+ group->json));
+ GNUNET_assert (
+ GNUNET_OK ==
+ GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
+ &key,
+ group,
+ GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
+ }
+
+ /* 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 (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 GNUNET_CRYPTO_BSA_RSA:
+ key_spec =
+ GNUNET_JSON_pack_rsa_public_key (
+ "rsa_pub",
+ dk->denom_pub.bsign_pub_key->details.rsa_public_key);
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ key_spec =
+ GNUNET_JSON_pack_data_varsize (
+ "cs_pub",
+ &dk->denom_pub.bsign_pub_key->details.cs_public_key,
+ sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
+ break;
+ default:
+ GNUNET_assert (false);
+ }
+
+ 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",
+ dk->meta.expire_withdraw),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ dk->meta.expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ dk->meta.expire_legal),
+ key_spec
+ );
+ GNUNET_assert (NULL != entry);
+ }
+
+ /* Build up the running hash of all master signatures of the
+ denominations */
+ append_signature (&sig_ctx,
+ group->group_off,
+ (unsigned int) json_array_size (group->list),
+ &dk->master_sig);
+ /* Finally, add the denomination to the list of denominations in this
+ group */
+ GNUNET_assert (json_is_array (group->list));
+ GNUNET_assert (0 ==
+ json_array_append_new (group->list,
+ entry));
+ }
+ } /* loop over heap ends */
+
+ GNUNET_CONTAINER_multihashmap_iterate (denominations_by_group,
+ &free_group,
+ NULL);
+ GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
+ }
+ GNUNET_CONTAINER_heap_destroy (heap);
+
+ if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
+ {
+ struct GNUNET_HashCode hc;
+
+ compute_msig_hash (&sig_ctx,
+ &hc);
+ if (GNUNET_OK !=
+ create_krd (ksh,
+ &hc,
+ last_cherry_pick_date,
+ sctx.signkeys,
+ recoup,
+ grouped_denominations))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed to generate key response data for %s\n",
+ GNUNET_TIME_timestamp2s (last_cherry_pick_date));
+ goto CLEANUP;
+ }
+ ksh->management_only = false;
+
+ /* Sanity check: Make sure that age restriction is enabled IFF at least
+ * one age restricted denomination exist */
+ if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is enabled, but NO denominations with age restriction found!\n");
+ goto CLEANUP;
+ }
+ else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Age restriction is NOT enabled, but denominations with age restriction found!\n")
+ ;
+ goto CLEANUP;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No denomination keys available. Refusing to generate /keys response.\n");
+ }
+ ret = GNUNET_OK;
+
+CLEANUP:
+ GNUNET_array_grow (sig_ctx.elements,
+ sig_ctx.elements_size,
+ 0);
+ json_decref (grouped_denominations);
+ if (NULL != sctx.signkeys)
+ json_decref (sctx.signkeys);
+ json_decref (recoup);
+ return ret;
+}
+
+
+/**
+ * Called with information about global fees.
+ *
+ * @param cls `struct TEH_KeyStateHandle *` we are building
+ * @param fees the global fees we charge
+ * @param purse_timeout when do purses time out
+ * @param history_expiration how long are account histories preserved
+ * @param purse_account_limit how many purses are free per account
+ * @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_GLOBAL_FEES)
+ */
+static void
+global_fee_info_cb (
+ void *cls,
+ const struct TALER_GlobalFeeSet *fees,
+ struct GNUNET_TIME_Relative purse_timeout,
+ struct GNUNET_TIME_Relative history_expiration,
+ uint32_t purse_account_limit,
+ struct GNUNET_TIME_Timestamp start_date,
+ struct GNUNET_TIME_Timestamp end_date,
+ const struct TALER_MasterSignatureP *master_sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ struct TEH_GlobalFee *gf;
+
+ if (GNUNET_OK !=
+ TALER_exchange_offline_global_fee_verify (
+ start_date,
+ end_date,
+ fees,
+ purse_timeout,
+ history_expiration,
+ purse_account_limit,
+ &TEH_master_public_key,
+ master_sig))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Database has global fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found global fees with %u purses\n",
+ purse_account_limit);
+ gf = GNUNET_new (struct TEH_GlobalFee);
+ gf->start_date = start_date;
+ gf->end_date = end_date;
+ gf->fees = *fees;
+ gf->purse_timeout = purse_timeout;
+ gf->history_expiration = history_expiration;
+ gf->purse_account_limit = purse_account_limit;
+ gf->master_sig = *master_sig;
+ GNUNET_CONTAINER_DLL_insert (ksh->gf_head,
+ ksh->gf_tail,
+ gf);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ ksh->global_fees,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_timestamp ("start_date",
+ start_date),
+ GNUNET_JSON_pack_timestamp ("end_date",
+ end_date),
+ TALER_JSON_PACK_GLOBAL_FEES (fees),
+ GNUNET_JSON_pack_time_rel ("history_expiration",
+ history_expiration),
+ GNUNET_JSON_pack_time_rel ("purse_timeout",
+ purse_timeout),
+ GNUNET_JSON_pack_uint64 ("purse_account_limit",
+ purse_account_limit),
+ GNUNET_JSON_pack_data_auto ("master_sig",
+ master_sig))));
+}
+
+
+/**
+ * Create a key state.
+ *
+ * @param management_only if we should NOT run 'finish_keys_response()'
+ * because we only need the state for the /management/keys API
+ * @return NULL on error (i.e. failed to access database)
+ */
+static struct TEH_KeyStateHandle *
+build_key_state (bool management_only)
+{
+ struct TEH_KeyStateHandle *ksh;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ksh = GNUNET_new (struct TEH_KeyStateHandle);
+ ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
+ ksh->reload_time = GNUNET_TIME_timestamp_get ();
+ /* We must use the key_generation from when we STARTED the process! */
+ ksh->key_generation = key_generation;
+ ksh->denomserial_map = GNUNET_CONTAINER_multihashmap32_create (1024);
+ ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
+ true);
+ ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
+ false /* MUST be false! */
+ );
+ ksh->auditors = json_array ();
+ GNUNET_assert (NULL != ksh->auditors);
+ /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
+ GNUNET_break (GNUNET_OK ==
+ TEH_plugin->preflight (TEH_plugin->cls));
+ if (NULL != ksh->global_fees)
+ json_decref (ksh->global_fees);
+ ksh->global_fees = json_array ();
+ qs = TEH_plugin->get_global_fees (TEH_plugin->cls,
+ &global_fee_info_cb,
+ ksh);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Loading global fees from DB: %d\n",
+ qs);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ destroy_key_state (ksh);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,
+ &denomination_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ destroy_key_state (ksh);
+ return NULL;
+ }
+ /* NOTE: ONLY fetches non-revoked AND master-signed signkeys! */
+ qs = TEH_plugin->iterate_active_signkeys (TEH_plugin->cls,
+ &signkey_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_auditor_denominations (TEH_plugin->cls,
+ &auditor_denom_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh);
+ return NULL;
+ }
+ qs = TEH_plugin->iterate_active_auditors (TEH_plugin->cls,
+ &auditor_info_cb,
+ ksh);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ destroy_key_state (ksh);
+ return NULL;
+ }
+
+ if (management_only)
+ {
+ ksh->management_only = true;
+ return ksh;
+ }
+
+ if (GNUNET_OK !=
+ finish_keys_response (ksh))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Could not finish /keys response (required data not configured yet)\n");
+ destroy_key_state (ksh);
+ return NULL;
+ }
+ TEH_resume_keys_requests (false);
+ return ksh;
+}
+
+
+void
+TEH_keys_update_states ()
+{
+ struct GNUNET_DB_EventHeaderP es = {
+ .size = htons (sizeof (es)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &es,
+ NULL,
+ 0);
+ key_generation++;
+ TEH_resume_keys_requests (false);
+}
+
+
+static struct TEH_KeyStateHandle *
+keys_get_state (bool management_only)
+{
+ struct TEH_KeyStateHandle *old_ksh;
+ struct TEH_KeyStateHandle *ksh;
+
+ old_ksh = key_state;
+ if (NULL == old_ksh)
+ {
+ ksh = build_key_state (management_only);
+ if (NULL == ksh)
+ return NULL;
+ key_state = ksh;
+ return ksh;
+ }
+ if ( (old_ksh->key_generation < key_generation) ||
+ (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
+ {
+ 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);
+ ksh = build_key_state (management_only);
+ key_state = ksh;
+ destroy_key_state (old_ksh);
+ return ksh;
+ }
+ sync_key_helpers (&helpers);
+ return old_ksh;
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void)
+{
+ return keys_get_state (true);
+}
+
+
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = keys_get_state (false);
+ if (NULL == ksh)
+ return NULL;
+
+ if (ksh->management_only)
+ {
+ if (GNUNET_OK !=
+ finish_keys_response (ksh))
+ return NULL;
+ }
+
+ return ksh;
+}
+
+
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+ struct TEH_KeyStateHandle *ksh,
+ struct GNUNET_TIME_Timestamp ts)
+{
+ for (const struct TEH_GlobalFee *gf = ksh->gf_head;
+ NULL != gf;
+ gf = gf->next)
+ {
+ if (GNUNET_TIME_timestamp_cmp (ts,
+ >=,
+ gf->start_date) &&
+ GNUNET_TIME_timestamp_cmp (ts,
+ <,
+ gf->end_date))
+ return gf;
+ }
+ return NULL;
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ *mret = TALER_MHD_reply_with_error (conn,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return NULL;
+ }
+
+ return TEH_keys_denomination_by_hash_from_state (ksh,
+ h_denom_pub,
+ conn,
+ mret);
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret)
+{
+ struct TEH_DenominationKey *dk;
+
+ dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
+ &h_denom_pub->hash);
+ if (NULL == dk)
+ {
+ if (NULL == conn)
+ return NULL;
+ *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
+ h_denom_pub);
+ return NULL;
+ }
+ return dk;
+}
+
+
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_serial_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ uint64_t denom_serial)
+{
+ struct TEH_DenominationKey *dk;
+ uint32_t serial32 = (uint32_t) denom_serial;
+
+ GNUNET_assert (denom_serial == (uint64_t) serial32);
+ dk = GNUNET_CONTAINER_multihashmap32_get (ksh->denomserial_map,
+ serial32);
+ return dk;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+ unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static csds_length])
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+ struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length];
+ struct TALER_CRYPTO_CsSignRequest csrs[csds_length];
+ struct TALER_BlindedDenominationSignature rs[csds_length];
+ struct TALER_BlindedDenominationSignature cs[csds_length];
+ unsigned int rsrs_pos = 0;
+ unsigned int csrs_pos = 0;
+ enum TALER_ErrorCode ec;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ for (unsigned int i = 0; i<csds_length; i++)
+ {
+ const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub;
+ const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ if (bp->blinded_message->cipher !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ switch (hd->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
+ rsrs[rsrs_pos].msg
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg;
+ rsrs[rsrs_pos].msg_size
+ = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size;
+ rsrs_pos++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
+ csrs[csrs_pos].blinded_planchet
+ = &bp->blinded_message->details.cs_blinded_message;
+ csrs_pos++;
+ break;
+ default:
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ }
+
+ if (0 != rsrs_pos)
+ {
+ memset (rs,
+ 0,
+ sizeof (rs));
+ }
+ if (0 != csrs_pos)
+ {
+ memset (cs,
+ 0,
+ sizeof (cs));
+ }
+ ec = TALER_EC_NONE;
+ if (0 != csrs_pos)
+ {
+ ec = TALER_CRYPTO_helper_cs_batch_sign (
+ helpers.csdh,
+ csrs_pos,
+ csrs,
+ for_melt,
+ (0 == rsrs_pos) ? bss : cs);
+ if (TALER_EC_NONE != ec)
+ {
+ for (unsigned int i = 0; i<csrs_pos; i++)
+ TALER_blinded_denom_sig_free (&cs[i]);
+ return ec;
+ }
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
+ }
+ if (0 != rsrs_pos)
+ {
+ ec = TALER_CRYPTO_helper_rsa_batch_sign (
+ helpers.rsadh,
+ rsrs_pos,
+ rsrs,
+ (0 == csrs_pos) ? bss : rs);
+ if (TALER_EC_NONE != ec)
+ {
+ for (unsigned int i = 0; i<csrs_pos; i++)
+ TALER_blinded_denom_sig_free (&cs[i]);
+ for (unsigned int i = 0; i<rsrs_pos; i++)
+ TALER_blinded_denom_sig_free (&rs[i]);
+ return ec;
+ }
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
+ }
+
+ if ( (0 != csrs_pos) &&
+ (0 != rsrs_pos) )
+ {
+ rsrs_pos = 0;
+ csrs_pos = 0;
+ for (unsigned int i = 0; i<csds_length; i++)
+ {
+ const struct TALER_BlindedPlanchet *bp = csds[i].bp;
+
+ switch (bp->blinded_message->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ bss[i] = rs[rsrs_pos++];
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ bss[i] = cs[csrs_pos++];
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+ }
+ }
+ return TALER_EC_NONE;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+ const struct TEH_CsDeriveData *cdd,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
+{
+ const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdd->nonce;
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ {
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+
+ {
+ struct TALER_CRYPTO_CsDeriveRequest cdr = {
+ .h_cs = &hd->h_details.h_cs,
+ .nonce = nonce
+ };
+ return TALER_CRYPTO_helper_cs_r_derive (helpers.csdh,
+ &cdr,
+ for_melt,
+ r_pub);
+ }
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub_simple (
+ unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length])
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+ struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length];
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ for (unsigned int i = 0; i<cdds_length; i++)
+ {
+ const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+ }
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ {
+ return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ cdrs[i].h_cs = &hd->h_details.h_cs;
+ cdrs[i].nonce = nonce;
+ }
+
+ return TALER_CRYPTO_helper_cs_r_batch_derive (helpers.csdh,
+ cdds_length,
+ cdrs,
+ for_melt,
+ r_pubs);
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+ const struct TEH_KeyStateHandle *ksh,
+ size_t num,
+ const struct TALER_DenominationHashP h_denom_pubs[static num],
+ const struct GNUNET_CRYPTO_CsSessionNonce nonces[static num],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static num],
+ size_t *err_idx)
+{
+ struct TALER_CRYPTO_CsDeriveRequest cdrs[num];
+
+ for (unsigned int i = 0; i<num; i++)
+ {
+ const struct TEH_DenominationKey *dk;
+ const struct HelperDenomination *hd;
+
+ *err_idx = i;
+
+ /* FIXME: right now we need both,
+ * TEH_DenominationKey and HelperDenomination,
+ * because only TEH_DenominationKey has .recoup_possible
+ */
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pubs[i].hash);
+ dk = TEH_keys_denomination_by_hash_from_state (ksh,
+ &h_denom_pubs[i],
+ NULL,
+ NULL);
+ if (NULL == hd)
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
+
+ GNUNET_assert (NULL != dk);
+
+ if (GNUNET_CRYPTO_BSA_CS !=
+ hd->denom_pub.bsign_pub_key->cipher)
+ return TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION
+ ;
+
+ if (GNUNET_TIME_absolute_is_future (hd->start_time.abs_time))
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE;
+
+ if (dk->recoup_possible)
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED;
+
+ if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
+ return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED;
+
+ cdrs[i].h_cs = &hd->h_details.h_cs;
+ cdrs[i].nonce = &nonces[i];
+ }
+
+ return TALER_CRYPTO_helper_cs_r_batch_derive (helpers.csdh,
+ num,
+ cdrs,
+ for_melt,
+ r_pubs);
+}
+
+
+void
+TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub)
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperDenomination *hd;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ switch (hd->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ TALER_CRYPTO_helper_rsa_revoke (helpers.rsadh,
+ &hd->h_details.h_rsa);
+ TEH_keys_update_states ();
+ return;
+ case GNUNET_CRYPTO_BSA_CS:
+ TALER_CRYPTO_helper_cs_revoke (helpers.csdh,
+ &hd->h_details.h_cs);
+ TEH_keys_update_states ();
+ return;
+ }
+ GNUNET_break (0);
+ return;
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+ const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ /* This *can* happen if the exchange's crypto helper is not running
+ or had some bad error. */
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Cannot sign request, no valid signing keys available.\n");
+ return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
+ }
+ return TEH_keys_exchange_sign2_ (ksh,
+ purpose,
+ pub,
+ sig);
+}
+
+
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+ void *cls,
+ const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig)
+{
+ struct TEH_KeyStateHandle *ksh = cls;
+ enum TALER_ErrorCode ec;
+
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA]++;
+ ec = TALER_CRYPTO_helper_esign_sign_ (helpers.esh,
+ purpose,
+ pub,
+ sig);
+ if (TALER_EC_NONE != ec)
+ return ec;
+ {
+ /* Here we check here that 'pub' is set to an exchange public key that is
+ actually signed by the master key! Otherwise, we happily continue to
+ use key material even if the offline signatures have not been made
+ yet! */
+ struct GNUNET_PeerIdentity pid;
+ struct SigningKey *sk;
+
+ pid.public_key = pub->eddsa_pub;
+ sk = GNUNET_CONTAINER_multipeermap_get (ksh->signkey_map,
+ &pid);
+ if (NULL == sk)
+ {
+ /* just to be safe, zero out the (valid) signature, as the key
+ should not or no longer be used */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot sign, offline key signatures are missing!\n");
+ memset (sig,
+ 0,
+ sizeof (*sig));
+ return TALER_EC_EXCHANGE_SIGNKEY_HELPER_OFFLINE_MISSING;
+ }
+ }
+ return ec;
+}
+
+
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub)
+{
+ struct TEH_KeyStateHandle *ksh;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ TALER_CRYPTO_helper_esign_revoke (helpers.esh,
+ exchange_pub);
+ TEH_keys_update_states ();
+}
+
+
+/**
+ * Comparator used for a binary search by cherry_pick_date for @a key in the
+ * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
+ *
+ * @param key pointer to a `struct GNUNET_TIME_Timestamp`
+ * @param value pointer to a `struct KeysResponseData` array entry
+ * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
+ */
+static int
+krd_search_comparator (const void *key,
+ const void *value)
+{
+ const struct GNUNET_TIME_Timestamp *kd = key;
+ const struct KeysResponseData *krd = value;
+
+ if (GNUNET_TIME_timestamp_cmp (*kd,
+ >,
+ krd->cherry_pick_date))
+ return -1;
+ if (GNUNET_TIME_timestamp_cmp (*kd,
+ <,
+ krd->cherry_pick_date))
+ return 1;
+ return 0;
+}
+
+
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ struct GNUNET_TIME_Timestamp last_issue_date;
+ const char *etag;
+
+ etag = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ (void) args;
+ {
+ const char *have_cherrypick;
+
+ have_cherrypick = MHD_lookup_connection_value (rc->connection,
+ MHD_GET_ARGUMENT_KIND,
+ "last_issue_date");
+ if (NULL != have_cherrypick)
+ {
+ unsigned long long cherrypickn;
+
+ if (1 !=
+ sscanf (have_cherrypick,
+ "%llu",
+ &cherrypickn))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ have_cherrypick);
+ }
+ /* The following multiplication may overflow; but this should not really
+ be a problem, as giving back 'older' data than what the client asks for
+ (given that the client asks for data in the distant future) is not
+ problematic */
+ last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn);
+ }
+ else
+ {
+ last_issue_date = GNUNET_TIME_UNIT_ZERO_TS;
+ }
+ }
+
+ {
+ struct TEH_KeyStateHandle *ksh;
+ const struct KeysResponseData *krd;
+
+ ksh = TEH_keys_get_state ();
+ if ( (NULL == ksh) ||
+ (0 == ksh->krd_array_length) )
+ {
+ if ( ( (SKR_LIMIT <= skr_size) &&
+ (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (rc->start_time),
+ >,
+ GNUNET_TIME_UNIT_SECONDS)) ) ||
+ TEH_suicide)
+ {
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ TEH_suicide
+ ? "server terminating"
+ : "too many connections suspended waiting on /keys");
+ }
+ return suspend_request (rc->connection);
+ }
+ krd = bsearch (&last_issue_date,
+ ksh->krd_array,
+ ksh->krd_array_length,
+ sizeof (struct KeysResponseData),
+ &krd_search_comparator);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Filtering /keys by cherry pick date %s found entry %u/%u\n",
+ GNUNET_TIME_timestamp2s (last_issue_date),
+ (unsigned int) (krd - ksh->krd_array),
+ ksh->krd_array_length);
+ if ( (NULL == krd) &&
+ (ksh->krd_array_length > 0) )
+ {
+ if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client provided invalid cherry picking timestamp %s, returning full response\n",
+ GNUNET_TIME_timestamp2s (last_issue_date));
+ krd = &ksh->krd_array[ksh->krd_array_length - 1];
+ }
+ if (NULL == krd)
+ {
+ /* Likely keys not ready *yet*.
+ Wait until they are. */
+ return suspend_request (rc->connection);
+ }
+ if ( (NULL != etag) &&
+ (0 == strcmp (etag,
+ krd->etag)) )
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ krd->etag,
+ &setup_general_response_headers,
+ ksh);
+
+ return MHD_queue_response (
+ rc->connection,
+ MHD_HTTP_OK,
+ (TALER_MHD_CT_DEFLATE ==
+ TALER_MHD_can_compress (rc->connection,
+ TALER_MHD_CT_DEFLATE))
+ ? krd->response_compressed
+ : krd->response_uncompressed);
+ }
+}
+
+
+/**
+ * Load extension data, like fees, expiration times (!) and age restriction
+ * flags for the denomination type configured in section @a section_name.
+ * Before calling this function, the `start` and `validity_duration` times must
+ * already be initialized in @a meta.
+ *
+ * @param section_name section in the configuration to use
+ * @param[in,out] meta denomination type data to complete
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+load_extension_data (const char *section_name,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct GNUNET_TIME_Relative deposit_duration;
+ struct GNUNET_TIME_Relative legal_duration;
+
+ GNUNET_assert (! GNUNET_TIME_absolute_is_zero (meta->start.abs_time)); /* caller bug */
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ section_name,
+ "DURATION_SPEND",
+ &deposit_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "DURATION_SPEND");
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
+ section_name,
+ "DURATION_LEGAL",
+ &legal_duration))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ section_name,
+ "DURATION_LEGAL");
+ return GNUNET_SYSERR;
+ }
+ meta->expire_deposit
+ = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_withdraw.abs_time,
+ deposit_duration));
+ meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_deposit.abs_time,
+ legal_duration));
+ if (GNUNET_OK !=
+ TALER_config_get_amount (TEH_cfg,
+ section_name,
+ "VALUE",
+ &meta->value))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "Need amount for option `%s' in section `%s'\n",
+ "VALUE",
+ section_name);
+ return GNUNET_SYSERR;
+ }
+ if (0 != strcasecmp (TEH_currency,
+ meta->value.currency))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Need denomination value in section `%s' to use currency `%s'\n",
+ section_name,
+ TEH_currency);
+ return GNUNET_SYSERR;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_denom_fees (TEH_cfg,
+ TEH_currency,
+ section_name,
+ &meta->fees))
+ return GNUNET_SYSERR;
+ meta->age_mask = load_age_mask (section_name);
+ return GNUNET_OK;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
+{
+ struct HelperDenomination *hd;
+ enum GNUNET_GenericReturnValue ok;
+
+ hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
+ &h_denom_pub->hash);
+ if (NULL == hd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Denomination %s not known\n",
+ GNUNET_h2s (&h_denom_pub->hash));
+ return GNUNET_NO;
+ }
+ meta->start = hd->start_time;
+ meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->start.abs_time,
+ hd->validity_duration));
+ ok = load_extension_data (hd->section_name,
+ meta);
+ if (GNUNET_OK == ok)
+ {
+ GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
+ hd->denom_pub.bsign_pub_key->cipher);
+ TALER_denom_pub_copy (denom_pub,
+ &hd->denom_pub);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "No fees for `%s', voiding key\n",
+ hd->section_name);
+ memset (denom_pub,
+ 0,
+ sizeof (*denom_pub));
+ }
+ return ok;
+}
+
+
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
+{
+ struct TEH_KeyStateHandle *ksh;
+ struct HelperSignkey *hsk;
+ struct GNUNET_PeerIdentity pid;
+
+ ksh = TEH_keys_get_state_for_management_only ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+
+ pid.public_key = exchange_pub->eddsa_pub;
+ hsk = GNUNET_CONTAINER_multipeermap_get (helpers.esign_keys,
+ &pid);
+ if (NULL == hsk)
+ {
+ GNUNET_break (0);
+ return GNUNET_NO;
+ }
+ meta->start = hsk->start_time;
+
+ meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->start.abs_time,
+ hsk->validity_duration));
+ meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta->expire_sign.abs_time,
+ signkey_legal_duration));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Closure for #add_future_denomkey_cb and #add_future_signkey_cb.
+ */
+struct FutureBuilderContext
+{
+ /**
+ * Our key state.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * Array of denomination keys.
+ */
+ json_t *denoms;
+
+ /**
+ * Array of signing keys.
+ */
+ json_t *signkeys;
+
+};
+
+
+/**
+ * Function called on all of our current and future denomination keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining denomination keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param h_denom_pub hash of the denomination public key
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_denomkey_cb (void *cls,
+ const struct GNUNET_HashCode *h_denom_pub,
+ void *value)
+{
+ struct FutureBuilderContext *fbc = cls;
+ struct HelperDenomination *hd = value;
+ struct TEH_DenominationKey *dk;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
+
+ dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
+ h_denom_pub);
+ if (NULL != dk)
+ return GNUNET_OK; /* skip: this key is already active! */
+ if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
+ return GNUNET_OK; /* this key already expired! */
+ meta.start = hd->start_time;
+ meta.expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (meta.start.abs_time,
+ hd->validity_duration));
+ if (GNUNET_OK !=
+ load_extension_data (hd->section_name,
+ &meta))
+ {
+ /* Woops, couldn't determine fee structure!? */
+ return GNUNET_OK;
+ }
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ fbc->denoms,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_amount ("value",
+ &meta.value),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ meta.start),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
+ meta.expire_withdraw),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
+ meta.expire_deposit),
+ GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
+ meta.expire_legal),
+ TALER_JSON_pack_denom_pub ("denom_pub",
+ &hd->denom_pub),
+ TALER_JSON_PACK_DENOM_FEES ("fee",
+ &meta.fees),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_sig",
+ &hd->sm_sig),
+ GNUNET_JSON_pack_string ("section_name",
+ hd->section_name))));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called on all of our current and future exchange signing keys
+ * known to the helper process. Filters out those that are current
+ * and adds the remaining signing keys (with their configuration
+ * data) to the JSON array.
+ *
+ * @param cls the `struct FutureBuilderContext *`
+ * @param pid actually the exchange public key (type disguised)
+ * @param value a `struct HelperDenomination`
+ * @return #GNUNET_OK (continue to iterate)
+ */
+static enum GNUNET_GenericReturnValue
+add_future_signkey_cb (void *cls,
+ const struct GNUNET_PeerIdentity *pid,
+ void *value)
+{
+ struct FutureBuilderContext *fbc = cls;
+ struct HelperSignkey *hsk = value;
+ struct SigningKey *sk;
+ struct GNUNET_TIME_Timestamp stamp_expire;
+ struct GNUNET_TIME_Timestamp legal_end;
+
+ sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map,
+ pid);
+ if (NULL != sk)
+ return GNUNET_OK; /* skip: this key is already active */
+ if (GNUNET_TIME_relative_is_zero (hsk->validity_duration))
+ return GNUNET_OK; /* this key already expired! */
+ stamp_expire = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (hsk->start_time.abs_time,
+ hsk->validity_duration));
+ legal_end = GNUNET_TIME_absolute_to_timestamp (
+ GNUNET_TIME_absolute_add (stamp_expire.abs_time,
+ signkey_legal_duration));
+ GNUNET_assert (0 ==
+ json_array_append_new (
+ fbc->signkeys,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("key",
+ &hsk->exchange_pub),
+ GNUNET_JSON_pack_timestamp ("stamp_start",
+ hsk->start_time),
+ GNUNET_JSON_pack_timestamp ("stamp_expire",
+ stamp_expire),
+ GNUNET_JSON_pack_timestamp ("stamp_end",
+ legal_end),
+ GNUNET_JSON_pack_data_auto ("signkey_secmod_sig",
+ &hsk->sm_sig))));
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ struct TEH_KeyStateHandle *ksh;
+ json_t *reply;
+
+ (void) rh;
+ ksh = TEH_keys_get_state_for_management_only ();
+ if (NULL == ksh)
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_SERVICE_UNAVAILABLE,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no key state");
+ }
+ sync_key_helpers (&helpers);
+ if (NULL == ksh->management_keys_reply)
+ {
+ struct FutureBuilderContext fbc = {
+ .ksh = ksh,
+ .denoms = json_array (),
+ .signkeys = json_array ()
+ };
+
+ GNUNET_assert (NULL != fbc.denoms);
+ GNUNET_assert (NULL != fbc.signkeys);
+ if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) &&
+ (GNUNET_is_zero (&denom_cs_sm_pub)) )
+ {
+ /* Either IPC failed, or neither helper had any denominations configured. */
+ json_decref (fbc.denoms);
+ json_decref (fbc.signkeys);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_GATEWAY,
+ TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
+ NULL);
+ }
+ if (GNUNET_is_zero (&esign_sm_pub))
+ {
+ json_decref (fbc.denoms);
+ json_decref (fbc.signkeys);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_GATEWAY,
+ TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
+ NULL);
+ }
+ GNUNET_CONTAINER_multihashmap_iterate (helpers.denom_keys,
+ &add_future_denomkey_cb,
+ &fbc);
+ GNUNET_CONTAINER_multipeermap_iterate (helpers.esign_keys,
+ &add_future_signkey_cb,
+ &fbc);
+ reply = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("future_denoms",
+ fbc.denoms),
+ GNUNET_JSON_pack_array_steal ("future_signkeys",
+ fbc.signkeys),
+ GNUNET_JSON_pack_data_auto ("master_pub",
+ &TEH_master_public_key),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
+ &denom_rsa_sm_pub),
+ GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key",
+ &denom_cs_sm_pub),
+ GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
+ &esign_sm_pub));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning GET /management/keys response:\n");
+ if (NULL == reply)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ GNUNET_assert (NULL == ksh->management_keys_reply);
+ ksh->management_keys_reply = reply;
+ }
+ else
+ {
+ reply = ksh->management_keys_reply;
+ }
+ return TALER_MHD_reply_json (connection,
+ reply,
+ MHD_HTTP_OK);
+}
+
+
+/* end of taler-exchange-httpd_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_get-keys.h b/src/exchange/taler-exchange-httpd_get-keys.h
@@ -0,0 +1,614 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-2022 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_get-keys.h
+ * @brief management of our various keys
+ * @defgroup crypto Cryptographic routines
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+#ifndef TALER_EXCHANGE_HTTPD_GET_KEYS_H
+#define TALER_EXCHANGE_HTTPD_GET_KEYS_H
+
+/**
+ * Signatures of an auditor over a denomination key of this exchange.
+ */
+struct TEH_AuditorSignature;
+
+
+/**
+ * @brief All information about a denomination key (which is used to
+ * sign coins into existence).
+ */
+struct TEH_DenominationKey
+{
+
+ /**
+ * Decoded denomination public key (the hash of it is in
+ * @e issue, but we sometimes need the full public key as well).
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+ /**
+ * Hash code of the denomination public key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Meta data about the type of the denomination, such as fees and validity
+ * periods.
+ */
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /**
+ * The long-term offline master key's signature for this denomination.
+ * Signs over @e h_denom_pub and @e meta.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * We store the auditor signatures for this denomination in a DLL.
+ */
+ struct TEH_AuditorSignature *as_head;
+
+ /**
+ * We store the auditor signatures for this denomination in a DLL.
+ */
+ struct TEH_AuditorSignature *as_tail;
+
+ /**
+ * Set to 'true' if this denomination has been revoked and recoup is
+ * thus supported right now.
+ */
+ bool recoup_possible;
+
+};
+
+
+/**
+ * Set of global fees (and options) for a time range.
+ */
+struct TEH_GlobalFee
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct TEH_GlobalFee *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct TEH_GlobalFee *prev;
+
+ /**
+ * Beginning of the validity period (inclusive).
+ */
+ struct GNUNET_TIME_Timestamp start_date;
+
+ /**
+ * End of the validity period (exclusive).
+ */
+ struct GNUNET_TIME_Timestamp end_date;
+
+ /**
+ * How long do unmerged purses stay around at most?
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * What is the longest history we return?
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Signature affirming these details.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Fee structure for operations that do not depend
+ * on a denomination or wire method.
+ */
+ struct TALER_GlobalFeeSet fees;
+
+ /**
+ * Number of free purses per account.
+ */
+ uint32_t purse_account_limit;
+};
+
+
+/**
+ * Snapshot of the (coin and signing) keys (including private keys) of
+ * the exchange. There can be multiple instances of this struct, as it is
+ * reference counted and only destroyed once the last user is done
+ * with it. The current instance is acquired using
+ * #TEH_KS_acquire(). Using this function increases the
+ * reference count. The contents of this structure (except for the
+ * reference counter) should be considered READ-ONLY until it is
+ * ultimately destroyed (as there can be many concurrent users).
+ */
+struct TEH_KeyStateHandle;
+
+
+/**
+ * Run internal invariant checks. For debugging.
+ */
+void
+TEH_check_invariants (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);
+
+
+/**
+ * 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_keys_get_state()).
+ */
+void
+TEH_wire_update_state (void);
+
+
+/**
+ * Return the current key state for this thread. Possibly re-builds the key
+ * state if we have reason to believe that something changed.
+ *
+ * The result is ONLY valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state()
+ * or #TEH_keys_exchange_sign().
+ *
+ * @return NULL on error
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state (void);
+
+/**
+ * Obtain the key state if we should NOT run finish_keys_response() because we
+ * only need the state for the /management/keys API
+ */
+struct TEH_KeyStateHandle *
+TEH_keys_get_state_for_management_only (void);
+
+/**
+ * Something changed in the database. Rebuild all key states. This function
+ * should be called if the exchange learns about a new signature from an
+ * auditor or our master key.
+ *
+ * (We do not do so immediately, but merely signal to all threads that they
+ * need to rebuild their key state upon the next call to
+ * #TEH_keys_get_state()).
+ */
+void
+TEH_keys_update_states (void);
+
+
+/**
+ * Look up global fee structure by @a ts.
+ *
+ * @param ksh key state state to look in
+ * @param ts timestamp to lookup global fees at
+ * @return the global fee details, or
+ * NULL if none are configured for @a ts
+ */
+const struct TEH_GlobalFee *
+TEH_keys_global_fee_by_time (
+ struct TEH_KeyStateHandle *ksh,
+ struct GNUNET_TIME_Timestamp ts);
+
+
+/**
+ * Look up the issue for a denom public key. Note that the result
+ * must only be used in this thread and only until another key or
+ * key state is resolved.
+ *
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn used to return status message if NULL is returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ * or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash (
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret);
+
+
+/**
+ * Look up the issue for a denom public key using a given @a ksh. This allows
+ * requesting multiple denominations with the same @a ksh which thus will
+ * remain valid until the next call to #TEH_keys_denomination_by_hash() or
+ * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state state to look in
+ * @param h_denom_pub hash of denomination public key
+ * @param[in,out] conn connection used to return status message if NULL is returned
+ * @param[out] mret set to the MHD status if NULL is returned
+ * @return the denomination key issue,
+ * or NULL if @a h_denom_pub could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_hash_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct MHD_Connection *conn,
+ MHD_RESULT *mret);
+
+
+/**
+ * Look up the issue for a denom public key using a given @a ksh. This allows
+ * requesting multiple denominations with the same @a ksh which thus will
+ * remain valid until the next call to #TEH_keys_denomination_by_hash() or
+ * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state state to look in
+ * @param denom_serial serial ID of the denomination in the table
+ * @return the denomination key issue,
+ * or NULL if @a denom_serial could not be found
+ */
+struct TEH_DenominationKey *
+TEH_keys_denomination_by_serial_from_state (
+ const struct TEH_KeyStateHandle *ksh,
+ uint64_t denom_serial);
+
+
+/**
+ * Information needed to create a blind signature.
+ */
+struct TEH_CoinSignData
+{
+ /**
+ * Hash of key to sign with.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Blinded planchet to sign over.
+ */
+ const struct TALER_BlindedPlanchet *bp;
+};
+
+
+/**
+ * Request to sign @a csds.
+ *
+ * @param csds array with data to blindly sign (and keys to sign with)
+ * @param csds_length length of @a csds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] bss array set to the blind signature on success; must be of length @a csds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_batch_sign (
+ unsigned int csds_length,
+ const struct TEH_CoinSignData csds[static csds_length],
+ bool for_melt,
+ struct TALER_BlindedDenominationSignature bss[static csds_length]);
+
+
+/**
+ * Information needed to derive the CS r_pub.
+ */
+struct TEH_CsDeriveData
+{
+ /**
+ * Hash of key to sign with.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Nonce to use.
+ */
+ const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
+};
+
+
+/**
+ * Request to derive CS @a r_pub using the denomination and nonce from @a cdd.
+ *
+ * @param cdd data to compute @a r_pub from
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pub where to write the result
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_r_pub (
+ const struct TEH_CsDeriveData *cdd,
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP *r_pub);
+
+
+/**
+ * Request to derive a bunch of CS @a r_pubs using the
+ * denominations and nonces from @a cdds.
+ *
+ * @param cdds array to compute @a r_pubs from
+ * @param cdds_length length of the @a cdds array
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pubs array where to write the result; must be of length @a cdds_length
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub_simple (
+ unsigned int cdds_length,
+ const struct TEH_CsDeriveData cdds[static cdds_length],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]);
+
+
+/**
+ * Request to derive a bunch of CS @a r_pubs using the
+ * denominations and nonces from @a cdds.
+ *
+ * @param ksh keys state to load the keys from
+ * @param num number of input elements
+ * @param h_denom_pubs array @a num of hashes of keys to sign with
+ * @param nonces array @a num of nonces to use
+ * @param for_melt true if this is for a melt operation
+ * @param[out] r_pubs array where to write the result; must be of length @a num
+ * @param[out] err_idx in case of error, the index into @e cdds that caused it
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_denomination_cs_batch_r_pub (
+ const struct TEH_KeyStateHandle *ksh,
+ size_t num,
+ const struct TALER_DenominationHashP h_denom_pubs[static num],
+ const struct GNUNET_CRYPTO_CsSessionNonce nonces[static num],
+ bool for_melt,
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static num],
+ size_t *err_idx);
+
+/**
+ * Revoke the public key associated with @a h_denom_pub.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param h_denom_pub hash of the public key to revoke
+ */
+void
+TEH_keys_denomination_revoke (
+ const struct TALER_DenominationHashP *h_denom_pub);
+
+
+/**
+ * Fully clean up keys subsystem.
+ */
+void
+TEH_keys_finished (void);
+
+
+/**
+ * Resumes all suspended /keys requests, we may now have key material
+ * (or are shutting down).
+ *
+ * @param do_shutdown are we shutting down?
+ */
+void
+TEH_resume_keys_requests (bool do_shutdown);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign_ (
+ const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * Sign the message in @a purpose with the exchange's signing key.
+ *
+ * The @a purpose data is the beginning of the data of which the signature is
+ * to be created. The `size` field in @a purpose must correctly indicate the
+ * number of bytes of the data structure, including its header. Use
+ * #TEH_keys_exchange_sign() instead of calling this function directly!
+ *
+ * @param cls key state state to look in
+ * @param purpose the message to sign
+ * @param[out] pub set to the current public signing key of the exchange
+ * @param[out] sig signature over purpose using current signing key
+ * @return #TALER_EC_NONE on success
+ */
+enum TALER_ErrorCode
+TEH_keys_exchange_sign2_ (
+ void *cls,
+ const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
+ struct TALER_ExchangePublicKeyP *pub,
+ struct TALER_ExchangeSignatureP *sig);
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign(ps,pub,sig) \
+ ({ \
+ /* check size is set correctly */ \
+ GNUNET_assert (htonl ((ps)->purpose.size) == \
+ sizeof (*ps)); \
+ /* check 'ps' begins with the purpose */ \
+ GNUNET_static_assert (((void*) (ps)) == \
+ ((void*) &(ps)->purpose)); \
+ TEH_keys_exchange_sign_ (&(ps)->purpose, \
+ pub, \
+ sig); \
+ })
+
+
+/**
+ * @ingroup crypto
+ * @brief EdDSA sign a given block.
+ *
+ * The @a ps data must be a fixed-size struct for which the signature is to be
+ * created. The `size` field in @a ps->purpose must correctly indicate the
+ * number of bytes of the data structure, including its header.
+ *
+ * This allows requesting multiple denominations with the same @a ksh which
+ * thus will remain valid until the next call to
+ * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state() or
+ * #TEH_keys_exchange_sign().
+ *
+ * @param ksh key state to use
+ * @param ps packed struct with what to sign, MUST begin with a purpose
+ * @param[out] pub where to store the public key to use for the signing
+ * @param[out] sig where to write the signature
+ * @return #TALER_EC_NONE on success
+ */
+#define TEH_keys_exchange_sign2(ksh,ps,pub,sig) \
+ ({ \
+ /* check size is set correctly */ \
+ GNUNET_assert (htonl ((ps)->purpose.size) == \
+ sizeof (*ps)); \
+ /* check 'ps' begins with the purpose */ \
+ GNUNET_static_assert (((void*) (ps)) == \
+ ((void*) &(ps)->purpose)); \
+ TEH_keys_exchange_sign2_ (ksh, \
+ &(ps)->purpose, \
+ pub, \
+ sig); \
+ })
+
+
+/**
+ * Revoke the given exchange's signing key.
+ * This function should be called AFTER the database was
+ * updated, as it also triggers #TEH_keys_update_states().
+ *
+ * Note that the actual revocation happens asynchronously and
+ * may thus fail silently. To verify that the revocation succeeded,
+ * clients must watch for the associated change to the key state.
+ *
+ * @param exchange_pub key to revoke
+ */
+void
+TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub);
+
+
+/**
+ * Function to call to handle requests to "/keys" by sending
+ * back our current key material.
+ *
+ * @param rc request context
+ * @param args array of additional options (must be empty for this function)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_get_handler (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+/**
+ * Function to call to handle requests to "/management/keys" by sending
+ * back our future key material.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Load fees and expiration times (!) for the denomination type configured for
+ * the denomination matching @a h_denom_pub.
+ *
+ * @param ksh key state to load fees from
+ * @param h_denom_pub hash of the denomination public key
+ * to use to derive the section name of the configuration to use
+ * @param[out] denom_pub set to the denomination public key (to be freed by caller!)
+ * @param[out] meta denomination type data to complete
+ * @return #GNUNET_OK on success,
+ * #GNUNET_NO if @a h_denom_pub is not known
+ * #GNUNET_SYSERR on hard errors
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ struct TALER_DenominationPublicKey *denom_pub,
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
+
+
+/**
+ * Load expiration times for the given onling signing key.
+ *
+ * @param exchange_pub the online signing key
+ * @param[out] meta set to meta data about the key
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
+ struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
+
+
+/**
+ * Initialize keys subsystem.
+ *
+ * @return #GNUNET_OK on success
+ */
+enum GNUNET_GenericReturnValue
+TEH_keys_init (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.c b/src/exchange/taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.c
@@ -0,0 +1,506 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-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 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_get-kyc-check-H_NORMALIZED_PAYTO.c
+ * @brief Handle request for generic KYC check.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h"
+#include "taler-exchange-httpd_post-kyc-wallet.h"
+#include "taler-exchange-httpd_responses.h"
+
+/**
+ * Reserve GET request that is long-polling.
+ */
+struct KycPoller
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Subscription for the database event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Account for which we perform the KYC check.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Signature by the account owner authorizing this
+ * operation.
+ */
+ union TALER_AccountSignatureP account_sig;
+
+ /**
+ * Public key from the account owner authorizing this
+ * operation. Optional, see @e have_pub.
+ */
+ union TALER_AccountPublicKeyP account_pub;
+
+ /**
+ * Generation of KYC rules already known to the client
+ * (when long-polling). Do not send these rules again.
+ */
+ uint64_t min_rule;
+
+ /**
+ * What are we long-polling for (if anything)?
+ */
+ enum TALER_EXCHANGE_KycLongPollTarget lpt;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+
+ /**
+ * True if we have an @e account_pub.
+ */
+ bool have_pub;
+
+};
+
+
+/**
+ * Head of list of requests in long polling.
+ */
+static struct KycPoller *kyp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct KycPoller *kyp_tail;
+
+
+void
+TEH_kyc_check_cleanup ()
+{
+ struct KycPoller *kyp;
+
+ while (NULL != (kyp = kyp_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ if (kyp->suspended)
+ {
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+kyp_cleanup (struct TEH_RequestContext *rc)
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+
+ GNUNET_assert (! kyp->suspended);
+ if (NULL != kyp->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening (KYP cleanup)\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ kyp->eh);
+ kyp->eh = NULL;
+ }
+ GNUNET_free (kyp);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TEH_RequestContext *rc = cls;
+ struct KycPoller *kyp = rc->rh_ctx;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (! kyp->suspended)
+ return; /* event triggered while main transaction
+ was still running, or got multiple wake-up events */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received KYC update event\n");
+ kyp->suspended = false;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ TEH_check_invariants ();
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming from long-polling on KYC status\n");
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_check (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+ json_t *jrules = NULL;
+ json_t *jlimits = NULL;
+ union TALER_AccountPublicKeyP reserve_pub;
+ struct TALER_AccountAccessTokenP access_token;
+ bool aml_review;
+ bool kyc_required;
+ bool access_ok = false;
+ enum GNUNET_GenericReturnValue is_wallet;
+ uint64_t rule_gen = 0;
+
+ if (NULL == kyp)
+ {
+ bool sig_required = true;
+
+ kyp = GNUNET_new (struct KycPoller);
+ kyp->connection = rc->connection;
+ rc->rh_ctx = kyp;
+ rc->rh_cleaner = &kyp_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &kyp->h_payto,
+ sizeof (kyp->h_payto)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
+ "h_payto");
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Checking KYC status for normalized payto hash %s\n",
+ args[0]);
+ TALER_MHD_parse_request_header_auto (
+ rc->connection,
+ TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
+ &kyp->account_sig,
+ sig_required);
+ TALER_MHD_parse_request_header_auto (
+ rc->connection,
+ TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
+ &kyp->account_pub,
+ kyp->have_pub);
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &kyp->timeout);
+ {
+ uint64_t num = 0;
+ int val;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "lpt",
+ &num);
+ val = (int) num;
+ if ( (val < 0) ||
+ (val > TALER_EXCHANGE_KLPT_MAX) )
+ {
+ /* Protocol violation, but we can be graceful and
+ just ignore the long polling! */
+ GNUNET_break_op (0);
+ val = TALER_EXCHANGE_KLPT_NONE;
+ }
+ kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Long polling for target %d with timeout %s\n",
+ val,
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ kyp->timeout),
+ true));
+ }
+ TALER_MHD_parse_request_number (rc->connection,
+ "min_rule",
+ &kyp->min_rule);
+ /* long polling needed? */
+ if (GNUNET_TIME_absolute_is_future (kyp->timeout))
+ {
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = kyp->h_payto
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening for KYC COMPLETED\n");
+ kyp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (kyp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rc);
+ }
+ } /* end initialization */
+
+ if (! TEH_enable_kyc)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC not enabled\n");
+ return TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ bool do_suspend;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking up KYC requirements for account %s (%s)\n",
+ TALER_B2S (&kyp->h_payto),
+ kyp->have_pub ? "with account-pub" : "legacy wallet");
+ qs = TEH_plugin->lookup_kyc_requirement_by_row (
+ TEH_plugin->cls,
+ &kyp->h_payto,
+ kyp->have_pub,
+ &kyp->account_pub,
+ &is_wallet,
+ &reserve_pub.reserve_pub,
+ &access_token,
+ &rule_gen,
+ &jrules,
+ &aml_review,
+ &kyc_required);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (
+ rc->connection,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "lookup_kyc_requirement_by_row");
+ }
+
+ do_suspend = false;
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* account unknown */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Account unknown!\n");
+ if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
+ (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
+ (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
+ {
+ do_suspend = true;
+ access_ok = true; /* for now */
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found rule %llu (client wants > %llu)\n",
+ (unsigned long long) rule_gen,
+ (unsigned long long) kyp->min_rule);
+ access_ok =
+ ( (! GNUNET_is_zero (&kyp->account_pub) &&
+ (GNUNET_OK ==
+ TALER_account_kyc_auth_verify (&kyp->account_pub,
+ &kyp->account_sig)) ) ||
+ (! GNUNET_is_zero (&reserve_pub) &&
+ (GNUNET_OK ==
+ TALER_account_kyc_auth_verify (&reserve_pub,
+ &kyp->account_sig)) ) );
+ if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
+ (rule_gen <= kyp->min_rule) )
+ {
+ switch (kyp->lpt)
+ {
+ case TALER_EXCHANGE_KLPT_NONE:
+ /* If KLPT is not given, just go for rule generation */
+ do_suspend = true;
+ break;
+ case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
+ if (! access_ok)
+ do_suspend = true;
+ break;
+ case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
+ if (! aml_review)
+ do_suspend = true;
+ break;
+ case TALER_EXCHANGE_KLPT_KYC_OK:
+ if (kyc_required)
+ do_suspend = true;
+ break;
+ }
+ }
+ }
+
+ if (do_suspend &&
+ (access_ok ||
+ (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on timeout (%s) for %d\n",
+ GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
+ kyp->timeout),
+ true),
+ (int) kyp->lpt);
+ GNUNET_assert (NULL != kyp->eh);
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning account unknown\n");
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
+ NULL);
+ }
+ }
+
+ if (! access_ok)
+ {
+ json_decref (jrules);
+ jrules = NULL;
+ if (GNUNET_is_zero (&kyp->account_pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
+ NULL);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning authorization failed\n");
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
+ GNUNET_JSON_pack_data_auto ("expected_account_pub",
+ &kyp->account_pub));
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC rules apply to %s:\n",
+ (GNUNET_SYSERR == is_wallet)
+ ? "unknown account type"
+ : ( (GNUNET_YES == is_wallet)
+ ? "wallet"
+ : "account"));
+ if (NULL != jrules)
+ json_dumpf (jrules,
+ stderr,
+ JSON_INDENT (2));
+
+ jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
+ is_wallet);
+ if (NULL == jlimits)
+ {
+ GNUNET_break_op (0);
+ json_decref (jrules);
+ jrules = NULL;
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "/kyc-check: rules_to_limits failed");
+ }
+ json_decref (jrules);
+ jrules = NULL;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC limits apply:\n");
+ json_dumpf (jlimits,
+ stderr,
+ JSON_INDENT (2));
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning KYC %s\n",
+ kyc_required ? "required" : "optional");
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ kyc_required
+ ? MHD_HTTP_ACCEPTED
+ : MHD_HTTP_OK,
+ GNUNET_JSON_pack_bool ("aml_review",
+ aml_review),
+ GNUNET_JSON_pack_uint64 ("rule_gen",
+ rule_gen),
+ GNUNET_JSON_pack_data_auto ("access_token",
+ &access_token),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal ("limits",
+ jlimits)));
+}
+
+
+/* end of taler-exchange-httpd_kyc-check.c */
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h b/src/exchange/taler-exchange-httpd_get-kyc-check-H_NORMALIZED_PAYTO.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-kyc-check-H_NORMALIZED_PAYTO.h
+ * @brief Handle /kyc-check requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_KYC_CHECK_H_NORMALIZED_PAYTO_H
+#define TALER_EXCHANGE_HTTPD_GET_KYC_CHECK_H_NORMALIZED_PAYTO_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/kyc-check" request. Checks the KYC
+ * status of the given account and returns it.
+ *
+ * @param rc details about the request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_check (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+/**
+ * Clean up long-polling KYC requests during shutdown.
+ */
+void
+TEH_kyc_check_cleanup (void);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.c b/src/exchange/taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.c
@@ -0,0 +1,825 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-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 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_get-kyc-info-ACCESS_TOKEN.c
+ * @brief Handle request for generic KYC info.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_common_kyc.h"
+
+
+/**
+ * Context for the GET /kyc-info request.
+ *
+ * Used for long-polling and other asynchronous waiting.
+ */
+struct KycPoller
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Subscription for the database event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Handle to async activity to get the latest legitimization
+ * rule set.
+ */
+ struct TALER_EXCHANGEDB_RuleUpdater *ru;
+
+ /**
+ * Current legitimization rule set, owned by callee. Will be NULL on error
+ * or for default rules. Will not contain skip rules and not be expired.
+ */
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
+
+ /**
+ * Handle for async KYC processing.
+ */
+ struct TEH_KycMeasureRunContext *kat;
+
+ /**
+ * Response to return, NULL if none yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Set to access token for a KYC process by the account,
+ * if @e have_token is true.
+ */
+ struct TALER_AccountAccessTokenP access_token;
+
+ /**
+ * Payto hash of the account matching @a access_token.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client. 0 for none
+ * (or malformed).
+ */
+ uint64_t etag_outcome_in;
+
+ /**
+ * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client. 0 for none
+ * (or malformed).
+ */
+ uint64_t etag_measure_in;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ *
+ */
+ uint64_t legitimization_measure_last_row;
+
+ /**
+ * Row in the legitimization outcomes table that @e lrs matches.
+ */
+ uint64_t legitimization_outcome_last_row;
+
+ /**
+ * HTTP status code to use with @e response.
+ */
+ unsigned int response_code;
+
+ /**
+ * #GNUNET_YES if our @e h_payto is for a wallet,
+ * #GNUNET_NO if it is for an account,
+ * #GNUNET_SYSERR if we do not know.
+ */
+ enum GNUNET_GenericReturnValue is_wallet;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+
+};
+
+
+/**
+ * Head of list of requests in long polling.
+ */
+static struct KycPoller *kyp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct KycPoller *kyp_tail;
+
+
+void
+TEH_kyc_info_cleanup ()
+{
+ struct KycPoller *kyp;
+
+ while (NULL != (kyp = kyp_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ if (kyp->suspended)
+ {
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+kyp_cleanup (struct TEH_RequestContext *rc)
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+
+ GNUNET_assert (! kyp->suspended);
+ if (NULL != kyp->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ kyp->eh);
+ kyp->eh = NULL;
+ }
+ if (NULL != kyp->ru)
+ {
+ TALER_EXCHANGEDB_update_rules_cancel (kyp->ru);
+ kyp->ru = NULL;
+ }
+ if (NULL != kyp->response)
+ {
+ MHD_destroy_response (kyp->response);
+ kyp->response = NULL;
+ }
+ if (NULL != kyp->lrs)
+ {
+ TALER_KYCLOGIC_rules_free (kyp->lrs);
+ kyp->lrs = NULL;
+ }
+ if (NULL != kyp->kat)
+ {
+ TEH_kyc_run_measure_cancel (kyp->kat);
+ kyp->kat = NULL;
+ }
+ GNUNET_free (kyp);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TEH_RequestContext *rc = cls;
+ struct KycPoller *kyp = rc->rh_ctx;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (! kyp->suspended)
+ return; /* event triggered while main transaction
+ was still running, or got multiple wake-up events */
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming from long-polling on KYC status\n");
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * Add the headers we want to set for every response.
+ *
+ * @param[in,out] response the response to modify
+ */
+static void
+add_nocache_header (struct MHD_Response *response)
+{
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+/**
+ * Resume processing the @a kyp request with the @a response.
+ *
+ * @param kyp request to resume and respond to
+ * @param http_status HTTP status for @a response
+ * @param response HTTP response to return
+ */
+static void
+resume_with_response (struct KycPoller *kyp,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ kyp->response_code = http_status;
+ kyp->response = response;
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Function called after a measure has been run.
+ *
+ * @param kyp request to fail with the error code
+ * @param ec error code or 0 on success
+ * @param hint detail error message or NULL on success / no info
+ */
+static void
+fail_with_ec (
+ struct KycPoller *kyp,
+ enum TALER_ErrorCode ec,
+ const char *hint)
+{
+ resume_with_response (kyp,
+ TALER_ErrorCode_get_http_status (ec),
+ TALER_MHD_make_error (ec,
+ hint));
+}
+
+
+/**
+ * Generate a reply with the KycProcessClientInformation from
+ * the LegitimizationMeasures.
+ *
+ * @param[in,out] kyp request to reply on
+ * @param legitimization_measure_row_id part of etag to set for the response
+ * @param legitimization_outcome_row_id part of etag to set for the response
+ * @param jmeasures a `LegitimizationMeasures` object to encode
+ * @param jvoluntary array of voluntary measures to encode, can be NULL
+ */
+static void
+resume_with_reply (struct KycPoller *kyp,
+ uint64_t legitimization_measure_row_id,
+ uint64_t legitimization_outcome_row_id,
+ const json_t *jmeasures,
+ const json_t *jvoluntary)
+{
+ const json_t *measures; /* array of MeasureInformation */
+ bool is_and_combinator = false;
+ bool verboten;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_bool ("is_and_combinator",
+ &is_and_combinator),
+ NULL),
+ GNUNET_JSON_spec_bool ("verboten",
+ &verboten),
+ GNUNET_JSON_spec_array_const ("measures",
+ &measures),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+ const char *ename;
+ unsigned int eline;
+ json_t *kris;
+ size_t i;
+ json_t *mi; /* a MeasureInformation object */
+
+ ret = GNUNET_JSON_parse (jmeasures,
+ spec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ fail_with_ec (kyp,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ ename);
+ return;
+ }
+ kris = json_array ();
+ GNUNET_assert (NULL != kris);
+ json_array_foreach ((json_t *) measures, i, mi)
+ {
+ const char *check_name;
+ const json_t *context = NULL;
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_string ("check_name",
+ &check_name),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("context",
+ &context),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *kri;
+
+ ret = GNUNET_JSON_parse (mi,
+ ispec,
+ &ename,
+ &eline);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ json_decref (kris);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ ename);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found required check `%s'\n",
+ check_name);
+ if (NULL != context)
+ {
+ json_dumpf (context,
+ stderr,
+ JSON_INDENT (2));
+ fprintf (stderr,
+ "\n");
+ }
+ /* Check if requirement is a duplicate, and in that case do
+ not return it */
+ {
+ bool duplicate = false;
+
+ for (size_t off = 0; off < i; off++)
+ {
+ json_t *have = json_array_get (measures,
+ off);
+ if ( (1 ==
+ json_equal (json_object_get (have,
+ "check_name"),
+ json_object_get (mi,
+ "check_name")) ) &&
+ (1 ==
+ json_equal (json_object_get (have,
+ "check_name"),
+ json_object_get (mi,
+ "check_name")) ) &&
+ (1 ==
+ json_equal (json_object_get (have,
+ "check_name"),
+ json_object_get (mi,
+ "check_name")) ) )
+ {
+ /* Duplicate requirement, do not return again */
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ {
+ continue;
+ }
+ }
+ kri = TALER_KYCLOGIC_measure_to_requirement (
+ check_name,
+ context,
+ &kyp->access_token,
+ i,
+ legitimization_measure_row_id);
+ if (NULL == kri)
+ {
+ GNUNET_break (0);
+ json_decref (kris);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "could not convert measure to requirement");
+ return;
+ }
+ GNUNET_assert (0 ==
+ json_array_append_new (kris,
+ kri));
+ }
+
+ {
+ char etags[128];
+ struct MHD_Response *resp;
+
+ GNUNET_snprintf (etags,
+ sizeof (etags),
+ "\"%llu-%llu\"",
+ (unsigned long long) legitimization_measure_row_id,
+ (unsigned long long) legitimization_outcome_row_id);
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_array_steal ("requirements",
+ kris),
+ GNUNET_JSON_pack_bool ("is_and_combinator",
+ is_and_combinator),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_incref (
+ "voluntary_measures",
+ (json_t *) jvoluntary)));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etags));
+ add_nocache_header (resp);
+ resume_with_response (kyp,
+ MHD_HTTP_OK,
+ resp);
+ }
+}
+
+
+/**
+ * Function called with the current rule set.
+ *
+ * @param cls closure with a `struct KycPoller *`
+ * @param rur includes legitimziation rule set that applies to the account
+ * (owned by callee, callee must free the lrs!)
+ */
+static void
+current_rules_cb (
+ void *cls,
+ struct TALER_EXCHANGEDB_RuleUpdaterResult *rur)
+{
+ struct KycPoller *kyp = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ uint64_t legitimization_measure_last_row;
+ json_t *jmeasures;
+ json_t *vmeasures;
+
+ kyp->ru = NULL;
+ if (TALER_EC_NONE != rur->ec)
+ {
+ /* Rollback should not be needed, just to be sure */
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (kyp,
+ rur->ec,
+ rur->hint);
+ return;
+ }
+ GNUNET_assert (NULL == kyp->lrs);
+ kyp->lrs
+ = rur->lrs;
+ kyp->legitimization_outcome_last_row
+ = rur->legitimization_outcome_last_row;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "LRS is for the account uses %s\n",
+ NULL == kyp->lrs
+ ? "default rules"
+ : "custom rules");
+
+ /* Check if there is an unfinished legitimization measure */
+ qs = TEH_plugin->lookup_kyc_status_by_token (
+ TEH_plugin->cls,
+ &kyp->access_token,
+ &legitimization_measure_last_row,
+ &jmeasures);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "lookup_kyc_status_by_token returned %d\n",
+ (int) qs);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_kyc_status_by_token");
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ jmeasures
+ = TALER_KYCLOGIC_zero_measures (kyp->lrs,
+ kyp->is_wallet);
+ if (NULL == jmeasures)
+ {
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (qs < 0)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc-info");
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No KYC requirement open\n");
+ resume_with_response (kyp,
+ MHD_HTTP_OK,
+ TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_array_steal (
+ "voluntary_measures",
+ TALER_KYCLOGIC_voluntary_measures (
+ kyp->lrs))
+ )));
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Making applicable zero-measures for %s under current rules active\n",
+ (GNUNET_SYSERR == kyp->is_wallet)
+ ? "unknown account type"
+ : ( (GNUNET_YES == kyp->is_wallet)
+ ? "wallets"
+ : "accounts"));
+ json_dumpf (jmeasures,
+ stderr,
+ JSON_INDENT (2));
+ qs = TEH_plugin->insert_active_legitimization_measure (
+ TEH_plugin->cls,
+ &kyp->access_token,
+ jmeasures,
+ &legitimization_measure_last_row);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (kyp,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_active_legitimization_measure");
+ return;
+ }
+ }
+ if ( (legitimization_measure_last_row == kyp->etag_measure_in) &&
+ (kyp->legitimization_outcome_last_row == kyp->etag_outcome_in) &&
+ GNUNET_TIME_absolute_is_future (kyp->timeout) )
+ {
+ /* Note: in practice this commit should do nothing, but we cannot
+ trust that the client provided correct etags, and so we must
+ commit anyway just in case the client lied about the etags. */
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (qs < 0)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc-info");
+ return;
+ }
+ if (NULL != kyp->lrs)
+ {
+ TALER_KYCLOGIC_rules_free (kyp->lrs);
+ kyp->lrs = NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending HTTP request on timeout (%s)\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ kyp->timeout),
+ true));
+ GNUNET_assert (NULL != kyp->eh);
+ GNUNET_break (kyp->suspended);
+ return;
+ }
+ qs = TEH_plugin->commit (TEH_plugin->cls);
+ if (qs < 0)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ fail_with_ec (
+ kyp,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "kyc-info");
+ return;
+ }
+ if ( (legitimization_measure_last_row ==
+ kyp->etag_measure_in) &&
+ (kyp->legitimization_outcome_last_row ==
+ kyp->etag_outcome_in) )
+ {
+ char etags[128];
+ struct MHD_Response *resp;
+
+ GNUNET_snprintf (etags,
+ sizeof (etags),
+ "\"%llu-%llu\"",
+ (unsigned long long) legitimization_measure_last_row,
+ (unsigned long long) kyp->legitimization_outcome_last_row);
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ add_nocache_header (resp);
+ TALER_MHD_add_global_headers (resp,
+ false);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etags));
+ resume_with_response (kyp,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ json_decref (jmeasures);
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Generating success reply to kyc-info query\n");
+ vmeasures = TALER_KYCLOGIC_voluntary_measures (kyp->lrs);
+ resume_with_reply (kyp,
+ legitimization_measure_last_row,
+ kyp->legitimization_outcome_last_row,
+ jmeasures,
+ vmeasures);
+ json_decref (vmeasures);
+ json_decref (jmeasures);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_info (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+ enum GNUNET_DB_QueryStatus qs;
+
+ if (NULL == kyp)
+ {
+ bool bis_wallet;
+
+ kyp = GNUNET_new (struct KycPoller);
+ kyp->connection = rc->connection;
+ rc->rh_ctx = kyp;
+ rc->rh_cleaner = &kyp_cleanup;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ args[0],
+ strlen (args[0]),
+ &kyp->access_token,
+ sizeof (kyp->access_token)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "access token");
+ }
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &kyp->timeout);
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (
+ rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev1;
+ unsigned long long ev2;
+
+ if (2 != sscanf (etags,
+ "\"%llu-%llu\"%c",
+ &ev1,
+ &ev2,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `%s' header `%s'\n",
+ MHD_HTTP_HEADER_IF_NONE_MATCH,
+ etags);
+ }
+ else
+ {
+ kyp->etag_measure_in = (uint64_t) ev1;
+ kyp->etag_outcome_in = (uint64_t) ev2;
+ }
+ }
+ } /* etag */
+
+ /* Check access token */
+ kyp->is_wallet = GNUNET_SYSERR;
+ qs = TEH_plugin->lookup_h_payto_by_access_token (
+ TEH_plugin->cls,
+ &kyp->access_token,
+ &kyp->h_payto,
+ &bis_wallet);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (
+ rc->connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_h_payto_by_access_token");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_KYC_INFO_AUTHORIZATION_FAILED));
+ }
+ kyp->is_wallet = (bis_wallet) ? GNUNET_YES : GNUNET_NO;
+
+ if (GNUNET_TIME_absolute_is_future (kyp->timeout))
+ {
+ struct TALER_KycCompletedEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
+ .h_payto = kyp->h_payto
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening\n");
+ kyp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (kyp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rc);
+ }
+ } /* end of one-time initialization */
+
+ if (NULL != kyp->response)
+ {
+ return MHD_queue_response (rc->connection,
+ kyp->response_code,
+ kyp->response);
+ }
+
+ kyp->ru = TALER_EXCHANGEDB_update_rules (TEH_plugin,
+ &TEH_attribute_key,
+ &kyp->h_payto,
+ kyp->is_wallet,
+ ¤t_rules_cb,
+ kyp);
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+
+}
+
+
+/* end of taler-exchange-httpd_kyc-info.c */
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.h b/src/exchange/taler-exchange-httpd_get-kyc-info-ACCESS_TOKEN.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-kyc-info-ACCESS_TOKEN.h
+ * @brief Handle /kyc-info requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_KYC_INFO_ACCESS_TOKEN_H
+#define TALER_EXCHANGE_HTTPD_GET_KYC_INFO_ACCESS_TOKEN_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown kyc-info subsystem. Resumes all suspended long-polling clients
+ * and cleans up data structures.
+ */
+void
+TEH_kyc_info_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-info" request.
+ *
+ * @param rc request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_info (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.c b/src/exchange/taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.c
@@ -0,0 +1,641 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-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_get-kyc-proof-PROVIDER_NAME.c
+ * @brief Handle request for proof for KYC check.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_attributes.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_templating_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Context for the proof.
+ */
+struct KycProofContext
+{
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycProofContext *next;
+
+ /**
+ * Kept in a DLL while suspended.
+ */
+ struct KycProofContext *prev;
+
+ /**
+ * Details about the connection we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Proof logic to run.
+ */
+ struct TALER_KYCLOGIC_Plugin *logic;
+
+ /**
+ * Configuration for @a logic.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Asynchronous operation with the proof system.
+ */
+ struct TALER_KYCLOGIC_ProofHandle *ph;
+
+ /**
+ * KYC AML trigger operation.
+ */
+ struct TEH_KycMeasureRunContext *kat;
+
+ /**
+ * Process information about the user for the plugin from the database, can
+ * be NULL.
+ */
+ char *provider_user_id;
+
+ /**
+ * Process information about the legitimization process for the plugin from the
+ * database, can be NULL.
+ */
+ char *provider_legitimization_id;
+
+ /**
+ * Hash of payment target URI this is about.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * Final HTTP response to return.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Final HTTP response code to return.
+ */
+ unsigned int response_code;
+
+ /**
+ * HTTP response from the KYC provider plugin.
+ */
+ struct MHD_Response *proof_response;
+
+ /**
+ * HTTP response code from the KYC provider plugin.
+ */
+ unsigned int proof_response_code;
+
+ /**
+ * Provider configuration section name of the logic we are running.
+ */
+ const char *provider_name;
+
+ /**
+ * Row in the database for this legitimization operation.
+ */
+ uint64_t process_row;
+
+ /**
+ * True if we are suspended,
+ */
+ bool suspended;
+
+ /**
+ * True if @e h_payto is for a wallet.
+ */
+ bool is_wallet;
+
+};
+
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycProofContext *kpc_head;
+
+/**
+ * Contexts are kept in a DLL while suspended.
+ */
+static struct KycProofContext *kpc_tail;
+
+
+/**
+ * Generate HTML error for @a connection using @a template.
+ *
+ * @param connection HTTP client connection
+ * @param template template to expand
+ * @param[in,out] http_status HTTP status of the response
+ * @param ec Taler error code to return
+ * @param message extended message to return
+ * @return MHD response object
+ */
+static struct MHD_Response *
+make_html_error (struct MHD_Connection *connection,
+ const char *template,
+ unsigned int *http_status,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response = NULL;
+ json_t *body;
+ enum GNUNET_GenericReturnValue ret;
+
+ body = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("message",
+ message)),
+ TALER_JSON_pack_ec (
+ ec));
+ ret = TALER_TEMPLATING_build (connection,
+ http_status,
+ template,
+ NULL,
+ NULL,
+ body,
+ &response);
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_SYSERR != ret)
+ GNUNET_break (MHD_NO !=
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CONTENT_TYPE,
+ "text/html"));
+
+ json_decref (body);
+ return response;
+}
+
+
+/**
+ * Resume processing the @a kpc request.
+ *
+ * @param kpc request to resume
+ */
+static void
+kpc_resume (struct KycProofContext *kpc)
+{
+ GNUNET_assert (GNUNET_YES == kpc->suspended);
+ kpc->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_resume_connection (kpc->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+void
+TEH_kyc_proof_cleanup (void)
+{
+ struct KycProofContext *kpc;
+
+ while (NULL != (kpc = kpc_head))
+ {
+ if (NULL != kpc->ph)
+ {
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
+ }
+ kpc_resume (kpc);
+ }
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param ec error code or 0 on success
+ * @param detail error message or NULL on success / no info
+ */
+static void
+proof_finish (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ struct KycProofContext *kpc = cls;
+
+ kpc->kat = NULL;
+ if (TALER_EC_NONE != ec)
+ {
+ kpc->response_code = TALER_ErrorCode_get_http_status (ec);
+ GNUNET_break (5 != kpc->response_code / 100);
+ GNUNET_assert (kpc->response_code != UINT_MAX);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Templating error response for %d and HTTP status %u (%s)\n",
+ (int) ec,
+ kpc->response_code,
+ detail);
+ kpc->response = make_html_error (
+ kpc->rc->connection,
+ "kyc-proof-internal-error",
+ &kpc->response_code,
+ ec,
+ detail);
+ }
+ else
+ {
+ GNUNET_assert (NULL != kpc->proof_response);
+ kpc->response_code = kpc->proof_response_code;
+ kpc->response = kpc->proof_response;
+ kpc->proof_response = NULL;
+ kpc->proof_response_code = 0;
+ }
+ GNUNET_assert (NULL != kpc->response);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming with response %p and status %u\n",
+ kpc->response,
+ kpc->response_code);
+ kpc_resume (kpc);
+}
+
+
+/**
+ * Respond with an HTML message on the given @a rc.
+ *
+ * @param[in,out] rc request to respond to
+ * @param http_status HTTP status code to use
+ * @param template template to fill in
+ * @param ec error code to use for the template
+ * @param message additional message to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+respond_html_ec (struct TEH_RequestContext *rc,
+ unsigned int http_status,
+ const char *template,
+ enum TALER_ErrorCode ec,
+ const char *message)
+{
+ struct MHD_Response *response;
+ MHD_RESULT res;
+
+ response = make_html_error (rc->connection,
+ template,
+ &http_status,
+ ec,
+ message);
+ res = MHD_queue_response (rc->connection,
+ http_status,
+ response);
+ MHD_destroy_response (response);
+ return res;
+}
+
+
+/**
+ * Function called with the result of a proof check operation.
+ *
+ * Note that the "decref" for the @a response
+ * will be done by the callee and MUST NOT be done by the plugin.
+ *
+ * @param cls closure
+ * @param status KYC status
+ * @param provider_name name of the provider
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param expiration until when is the KYC check valid
+ * @param attributes user attributes returned by the provider
+ * @param http_status HTTP status code of @a response
+ * @param[in] response to return to the HTTP client
+ */
+static void
+proof_cb (
+ void *cls,
+ enum TALER_KYCLOGIC_KycStatus status,
+ const char *provider_name,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ struct GNUNET_TIME_Absolute expiration,
+ const json_t *attributes,
+ unsigned int http_status,
+ struct MHD_Response *response)
+{
+ struct KycProofContext *kpc = cls;
+ struct TEH_RequestContext *rc = kpc->rc;
+ struct GNUNET_AsyncScopeSave old_scope;
+ enum GNUNET_DB_QueryStatus qs;
+
+ kpc->ph = NULL;
+ kpc->proof_response = response;
+ kpc->proof_response_code = http_status;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ switch (status)
+ {
+ case TALER_KYCLOGIC_STATUS_SUCCESS:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process #%llu succeeded with KYC provider\n",
+ (unsigned long long) kpc->process_row);
+ qs = TEH_kyc_store_attributes (
+ kpc->process_row,
+ &kpc->h_payto,
+ provider_name,
+ provider_user_id,
+ provider_legitimization_id,
+ expiration,
+ attributes);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ proof_finish (kpc,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "kyc_store_attributes");
+ break;
+ }
+
+ kpc->kat = TEH_kyc_run_measure_for_attributes (
+ &rc->async_scope_id,
+ kpc->process_row,
+ &kpc->h_payto,
+ kpc->is_wallet,
+ &proof_finish,
+ kpc);
+ if (NULL == kpc->kat)
+ {
+ GNUNET_break_op (0);
+ proof_finish (kpc,
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ NULL);
+ }
+ break;
+ case TALER_KYCLOGIC_STATUS_FAILED:
+ case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
+ case TALER_KYCLOGIC_STATUS_USER_ABORTED:
+ case TALER_KYCLOGIC_STATUS_ABORTED:
+ GNUNET_assert (NULL == kpc->kat);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC process %s/%s (Row #%llu) failed: %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ status);
+ if (5 == http_status / 100)
+ {
+ char *msg;
+
+ /* OAuth2 server had a problem, do NOT log this as a KYC failure */
+ GNUNET_break (0);
+ GNUNET_asprintf (&msg,
+ "Failure by KYC provider (HTTP status %u)\n",
+ http_status);
+ proof_finish (
+ kpc,
+ TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
+ msg);
+ GNUNET_free (msg);
+ break;
+ }
+ if (! TEH_kyc_failed (
+ kpc->process_row,
+ &kpc->h_payto,
+ kpc->provider_name,
+ provider_user_id,
+ provider_legitimization_id,
+ TALER_KYCLOGIC_status2s (status),
+ TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
+ {
+ GNUNET_break (0);
+ proof_finish (
+ kpc,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "TEH_kyc_failed");
+ break;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "KYC process #%llu failed with status %d\n",
+ (unsigned long long) kpc->process_row,
+ status);
+ proof_finish (kpc,
+ TALER_EC_NONE,
+ NULL);
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC status of %s/%s (Row #%llu) is %d\n",
+ provider_user_id,
+ provider_legitimization_id,
+ (unsigned long long) kpc->process_row,
+ (int) status);
+ break;
+ }
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context
+ */
+static void
+clean_kpc (struct TEH_RequestContext *rc)
+{
+ struct KycProofContext *kpc = rc->rh_ctx;
+
+ if (NULL != kpc->ph)
+ {
+ kpc->logic->proof_cancel (kpc->ph);
+ kpc->ph = NULL;
+ }
+ if (NULL != kpc->kat)
+ {
+ TEH_kyc_run_measure_cancel (kpc->kat);
+ kpc->kat = NULL;
+ }
+ if (NULL != kpc->response)
+ {
+ MHD_destroy_response (kpc->response);
+ kpc->response = NULL;
+ }
+ if (NULL != kpc->proof_response)
+ {
+ MHD_destroy_response (kpc->proof_response);
+ kpc->proof_response = NULL;
+ }
+ GNUNET_free (kpc->provider_user_id);
+ GNUNET_free (kpc->provider_legitimization_id);
+ GNUNET_free (kpc);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_proof (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct KycProofContext *kpc = rc->rh_ctx;
+ const char *provider_name_or_logic = args[0];
+
+ if (NULL == kpc)
+ {
+ /* first time */
+ if (NULL == provider_name_or_logic)
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-endpoint-unknown",
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ "'/kyc-proof/$PROVIDER_NAME?state=$H_PAYTO' required");
+ }
+ kpc = GNUNET_new (struct KycProofContext);
+ kpc->rc = rc;
+ rc->rh_ctx = kpc;
+ rc->rh_cleaner = &clean_kpc;
+ TALER_MHD_parse_request_arg_auto_t (rc->connection,
+ "state",
+ &kpc->h_payto);
+ if (GNUNET_OK !=
+ TALER_KYCLOGIC_lookup_logic (
+ provider_name_or_logic,
+ &kpc->logic,
+ &kpc->pd,
+ &kpc->provider_name))
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
+ provider_name_or_logic);
+ }
+ if (NULL != kpc->provider_name)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute expiration;
+
+ if (0 != strcmp (provider_name_or_logic,
+ kpc->provider_name))
+ {
+ GNUNET_break_op (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_BAD_REQUEST,
+ "kyc-proof-bad-request",
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "PROVIDER_NAME");
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Looking for KYC process at %s\n",
+ kpc->provider_name);
+ qs = TEH_plugin->lookup_kyc_process_by_account (
+ TEH_plugin->cls,
+ kpc->provider_name,
+ &kpc->h_payto,
+ &kpc->process_row,
+ &expiration,
+ &kpc->provider_user_id,
+ &kpc->provider_legitimization_id,
+ &kpc->is_wallet);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_kyc_process_by_account");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break_op (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_NOT_FOUND,
+ "kyc-proof-target-unknown",
+ TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
+ kpc->provider_name);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if (GNUNET_TIME_absolute_is_future (expiration))
+ {
+ /* KYC not required */
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_OK,
+ "kyc-proof-already-done",
+ TALER_EC_NONE,
+ NULL);
+ }
+ }
+ kpc->ph = kpc->logic->proof (
+ kpc->logic->cls,
+ kpc->pd,
+ rc->connection,
+ &kpc->h_payto,
+ kpc->process_row,
+ kpc->provider_user_id,
+ kpc->provider_legitimization_id,
+ &proof_cb,
+ kpc);
+ if (NULL == kpc->ph)
+ {
+ GNUNET_break (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "could not start proof with KYC logic");
+ }
+
+
+ kpc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kpc_head,
+ kpc_tail,
+ kpc);
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+
+ if (NULL == kpc->response)
+ {
+ GNUNET_break (0);
+ return respond_html_ec (
+ rc,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ "kyc-proof-internal-error",
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "handler resumed without response");
+ }
+
+ /* return response from KYC logic */
+ return MHD_queue_response (rc->connection,
+ kpc->response_code,
+ kpc->response);
+}
+
+
+/* end of taler-exchange-httpd_kyc-proof.c */
diff --git a/src/exchange/taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.h b/src/exchange/taler-exchange-httpd_get-kyc-proof-PROVIDER_NAME.h
@@ -0,0 +1,49 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_get-kyc-proof-PROVIDER_NAME.h
+ * @brief Handle /kyc-proof requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_KYC_PROOF_PROVIDER_NAME_H
+#define TALER_EXCHANGE_HTTPD_GET_KYC_PROOF_PROVIDER_NAME_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown kyc-proof subsystem. Resumes all suspended long-polling clients
+ * and cleans up data structures.
+ */
+void
+TEH_kyc_proof_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-proof" request.
+ *
+ * @param rc request to handle
+ * @param args one argument with the legitimization_uuid
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_proof (
+ struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-metrics.c b/src/exchange/taler-exchange-httpd_get-metrics.c
@@ -0,0 +1,160 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2015-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_get-metrics.c
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_json_lib.h>
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-metrics.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include <jansson.h>
+
+
+unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_withdraw_num_coins;
+
+unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+
+MHD_RESULT
+TEH_handler_metrics (struct TEH_RequestContext *rc,
+ const char *const args[])
+{
+ char *reply;
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ (void) args;
+ GNUNET_asprintf (&reply,
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_serialization_failures "
+ " number of database serialization errors by type\n"
+ "# TYPE taler_exchange_serialization_failures counter\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_received_requests "
+ " number of received requests by type\n"
+ "# TYPE taler_exchange_received_requests counter\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_received_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#if NOT_YET_IMPLEMENTED
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+ "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
+#endif
+ "# HELP taler_exchange_num_signatures "
+ " number of signatures created by cipher\n"
+ "# TYPE taler_exchange_num_signatures counter\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_num_signature_verifications "
+ " number of signatures verified by cipher\n"
+ "# TYPE taler_exchange_num_signature_verifications counter\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_num_keyexchanges "
+ " number of key exchanges done by cipher\n"
+ "# TYPE taler_exchange_num_keyexchanges counter\n"
+ "taler_exchange_num_keyexchanges{type=\"%s\"} %llu\n"
+ "# HELP taler_exchange_withdraw_num_coins "
+ " number of coins withdrawn in a withdraw request\n"
+ "# TYPE taler_exchange_withdraw_num_coins counter\n"
+ "taler_exchange_withdraw_num_coins{} %llu\n",
+ "deposit",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT],
+ "refresh-reveal",
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL],
+ "other",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_OTHER],
+ "deposit",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_conflict[TEH_MT_REQUEST_MELT],
+ "other",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_OTHER],
+ "deposit",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_DEPOSIT],
+ "withdraw",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_WITHDRAW],
+ "melt",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_MELT],
+ "withdraw",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW],
+#if NOT_YET_IMPLEMENTED
+ "deposit",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT],
+ "melt",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT],
+ "kyc-upload",
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_KYC_UPLOAD],
+#endif
+ "rsa",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA],
+ "cs",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS],
+ "eddsa",
+ TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA],
+ "rsa",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA],
+ "cs",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS],
+ "eddsa",
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA],
+ "ecdh",
+ TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH],
+ TEH_METRICS_withdraw_num_coins);
+ resp = MHD_create_response_from_buffer (strlen (reply),
+ reply,
+ MHD_RESPMEM_MUST_FREE);
+ ret = MHD_queue_response (rc->connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_metrics.c */
diff --git a/src/exchange/taler-exchange-httpd_get-metrics.h b/src/exchange/taler-exchange-httpd_get-metrics.h
@@ -0,0 +1,132 @@
+/*
+ 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_get-metrics.h
+ * @brief Handle /metrics requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_METRICS_H
+#define TALER_EXCHANGE_HTTPD_GET_METRICS_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Request types for which we collect metrics.
+ */
+enum TEH_MetricTypeRequest
+{
+ TEH_MT_REQUEST_OTHER = 0,
+ TEH_MT_REQUEST_DEPOSIT = 1,
+ TEH_MT_REQUEST_WITHDRAW = 2,
+ TEH_MT_REQUEST_MELT = 3,
+ TEH_MT_REQUEST_PURSE_CREATE = 4,
+ TEH_MT_REQUEST_PURSE_MERGE = 5,
+ TEH_MT_REQUEST_RESERVE_PURSE = 6,
+ TEH_MT_REQUEST_PURSE_DEPOSIT = 7,
+ TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 8,
+ TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 9,
+ TEH_MT_REQUEST_IDEMPOTENT_MELT = 10,
+ TEH_MT_REQUEST_BATCH_DEPOSIT = 11,
+ TEH_MT_REQUEST_POLICY_FULFILLMENT = 12,
+ TEH_MT_REQUEST_KYC_UPLOAD = 13,
+ TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */
+};
+
+/**
+ * Success types for which we collect metrics.
+ */
+enum TEH_MetricTypeSuccess
+{
+ TEH_MT_SUCCESS_DEPOSIT = 0,
+ TEH_MT_SUCCESS_WITHDRAW = 1,
+ TEH_MT_SUCCESS_MELT = 2,
+ TEH_MT_SUCCESS_REFRESH_REVEAL = 3,
+ TEH_MT_SUCCESS_WITHDRAW_REVEAL = 4,
+ TEH_MT_SUCCESS_COUNT = 5 /* MUST BE LAST! */
+};
+
+/**
+ * Cipher types for which we collect signature metrics.
+ */
+enum TEH_MetricTypeSignature
+{
+ TEH_MT_SIGNATURE_RSA = 0,
+ TEH_MT_SIGNATURE_CS = 1,
+ TEH_MT_SIGNATURE_EDDSA = 2,
+ TEH_MT_SIGNATURE_COUNT = 3
+};
+
+/**
+ * Cipher types for which we collect key exchange metrics.
+ */
+enum TEH_MetricTypeKeyX
+{
+ TEH_MT_KEYX_ECDH = 0,
+ TEH_MT_KEYX_COUNT = 1
+};
+
+/**
+ * Number of requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of successful requests handled of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
+
+/**
+ * Number of coins withdrawn in a withdraw request
+ */
+extern unsigned long long TEH_METRICS_withdraw_num_coins;
+
+/**
+ * Number of serialization errors encountered when
+ * handling requests of the respective type.
+ */
+extern unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
+
+/**
+ * Number of signatures created by the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of signatures verified by the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
+
+/**
+ * Number of key exchanges done with the respective cipher.
+ */
+extern unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
+
+/**
+ * Handle a "/metrics" 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_metrics (struct TEH_RequestContext *rc,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-purses-PURSE_PUB-merge.c b/src/exchange/taler-exchange-httpd_get-purses-PURSE_PUB-merge.c
@@ -0,0 +1,445 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_get-purses-PURSE_PUB-merge.c
+ * @brief Handle GET /purses/$PID/$TARGET requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-purses-PURSE_PUB-merge.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Information about an ongoing /purses GET operation.
+ */
+struct GetContext
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct GetContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct GetContext *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Subscription for the database event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * Subscription for refund event we are
+ * waiting for.
+ */
+ struct GNUNET_DB_EventHandler *ehr;
+
+ /**
+ * Public key of our purse.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * When does this purse expire?
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When was this purse merged?
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * How much is the purse (supposed) to be worth?
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * How much was deposited into the purse so far?
+ */
+ struct TALER_Amount deposited;
+
+ /**
+ * Hash over the contract of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * true to wait for merge, false to wait for deposit.
+ */
+ bool wait_for_merge;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+};
+
+
+/**
+ * Head of DLL of suspended GET requests.
+ */
+static struct GetContext *gc_head;
+
+/**
+ * Tail of DLL of suspended GET requests.
+ */
+static struct GetContext *gc_tail;
+
+
+void
+TEH_purses_get_cleanup ()
+{
+ struct GetContext *gc;
+
+ while (NULL != (gc = gc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ if (gc->suspended)
+ {
+ gc->suspended = false;
+ MHD_resume_connection (gc->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct GetContext` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+gc_cleanup (struct TEH_RequestContext *rc)
+{
+ struct GetContext *gc = rc->rh_ctx;
+
+ GNUNET_assert (! gc->suspended);
+ if (NULL != gc->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ gc->eh);
+ gc->eh = NULL;
+ }
+ if (NULL != gc->ehr)
+ {
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ gc->ehr);
+ gc->ehr = NULL;
+ }
+ GNUNET_free (gc);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct TEH_RequestContext *rc = cls;
+ struct GetContext *gc = rc->rh_ctx;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (NULL == gc)
+ return; /* event triggered while main transaction
+ was still running */
+ if (! gc->suspended)
+ return; /* might get multiple wake-up events */
+ gc->suspended = false;
+ GNUNET_async_scope_enter (&rc->async_scope_id,
+ &old_scope);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Waking up on %p - %p - %s\n",
+ rc,
+ gc,
+ gc->suspended ? "suspended" : "active");
+ TEH_check_invariants ();
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ MHD_resume_connection (gc->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_purses_get (struct TEH_RequestContext *rc,
+ const char *const args[2])
+{
+ struct GetContext *gc = rc->rh_ctx;
+ bool purse_deleted;
+ bool purse_refunded;
+ MHD_RESULT res;
+
+ if (NULL == gc)
+ {
+ gc = GNUNET_new (struct GetContext);
+ rc->rh_ctx = gc;
+ rc->rh_cleaner = &gc_cleanup;
+ gc->connection = rc->connection;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &gc->purse_pub,
+ sizeof (gc->purse_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
+ args[0]);
+ }
+ if (0 == strcmp (args[1],
+ "merge"))
+ gc->wait_for_merge = true;
+ else if (0 == strcmp (args[1],
+ "deposit"))
+ gc->wait_for_merge = false;
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
+ args[1]);
+ }
+
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &gc->timeout);
+ if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
+ (NULL == gc->eh) )
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (
+ gc->wait_for_merge
+ ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
+ : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = gc->purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening on purse %s (%s)\n",
+ TALER_B2S (&gc->purse_pub),
+ gc->wait_for_merge
+ ? "waiting for merge"
+ : "waiting for deposit");
+ gc->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (gc->timeout),
+ &rep.header,
+ &db_event_cb,
+ rc);
+ if (NULL == gc->eh)
+ {
+ GNUNET_break (0);
+ gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
+ }
+ else
+ {
+ struct GNUNET_DB_EventHeaderP repr = {
+ .size = htons (sizeof (repr)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED),
+ };
+
+ gc->ehr = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (gc->timeout),
+ &repr,
+ &db_event_cb,
+ rc);
+ }
+ }
+ } /* end first-time initialization */
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp create_timestamp;
+
+ qs = TEH_plugin->select_purse (TEH_plugin->cls,
+ &gc->purse_pub,
+ &create_timestamp,
+ &gc->purse_expiration,
+ &gc->amount,
+ &gc->deposited,
+ &gc->h_contract,
+ &gc->merge_timestamp,
+ &purse_deleted,
+ &purse_refunded);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "select_purse %s returned %d (%s)\n",
+ args[0],
+ (int) qs,
+ GNUNET_TIME_timestamp2s (gc->merge_timestamp));
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_purse");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_purse");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ }
+ if (purse_refunded ||
+ purse_deleted)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse refunded or deleted\n");
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_GONE,
+ purse_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED
+ ,
+ GNUNET_TIME_timestamp2s (
+ gc->purse_expiration));
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Deposited amount is %s (%d/%d/%d)\n",
+ TALER_amount2s (&gc->deposited),
+ GNUNET_TIME_absolute_is_future (gc->timeout),
+ GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time),
+ (0 <
+ TALER_amount_cmp (&gc->amount,
+ &gc->deposited)));
+ if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
+ ( ((gc->wait_for_merge) &&
+ GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
+ ((! gc->wait_for_merge) &&
+ (0 <
+ TALER_amount_cmp (&gc->amount,
+ &gc->deposited))) ) )
+ {
+ gc->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (gc_head,
+ gc_tail,
+ gc);
+ MHD_suspend_connection (gc->connection);
+ return MHD_YES;
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ struct TALER_ExchangeSignatureP exchange_sig;
+ enum TALER_ErrorCode ec;
+
+ if (GNUNET_TIME_timestamp_cmp (dt,
+ >,
+ gc->purse_expiration))
+ dt = gc->purse_expiration;
+ if (0 <
+ TALER_amount_cmp (&gc->amount,
+ &gc->deposited))
+ {
+ /* amount > deposited: not yet fully paid */
+ dt = GNUNET_TIME_UNIT_ZERO_TS;
+ }
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_status_sign (
+ &TEH_keys_exchange_sign_,
+ gc->merge_timestamp,
+ dt,
+ &gc->deposited,
+ &exchange_pub,
+ &exchange_sig)))
+ {
+ res = TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ }
+ else
+ {
+ /* Make sure merge_timestamp is omitted if not yet merged */
+ if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time))
+ gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS;
+ res = TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("balance",
+ &gc->deposited),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ gc->purse_expiration),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ gc->merge_timestamp)),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_timestamp ("deposit_timestamp",
+ dt))
+ );
+ }
+ }
+ return res;
+}
+
+
+/* end of taler-exchange-httpd_purses_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-purses-PURSE_PUB-merge.h b/src/exchange/taler-exchange-httpd_get-purses-PURSE_PUB-merge.h
@@ -0,0 +1,51 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_get-purses-PURSE_PUB-merge.h
+ * @brief Handle /purses/$PURSE_PUB/$TARGET GET requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_PURSES_PURSE_PUB_MERGE_H
+#define TALER_EXCHANGE_HTTPD_GET_PURSES_PURSE_PUB_MERGE_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown purses-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_purses_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/purses/$PID/$TARGET" request. Parses the
+ * given "purse_pub" in @a args (which should contain the
+ * EdDSA public key of a purse) and then respond with the
+ * status of the purse.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 2, the purse_pub and a target)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_get (struct TEH_RequestContext *rc,
+ const char *const args[2]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-history.c b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-history.c
@@ -0,0 +1,633 @@
+/*
+ This file is part of TALER
+ Copyright (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 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_get-reserves-RESERVE_PUB-history.c
+ * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Compile the history of a reserve into a JSON object.
+ *
+ * @param rh reserve history to JSON-ify
+ * @return json representation of the @a rh, NULL on error
+ */
+static json_t *
+compile_reserve_history (
+ const struct TALER_EXCHANGEDB_ReserveHistory *rh)
+{
+ json_t *json_history;
+
+ json_history = json_array ();
+ GNUNET_assert (NULL != json_history);
+ for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
+ NULL != pos;
+ pos = pos->next)
+ {
+ switch (pos->type)
+ {
+ case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
+ {
+ const struct TALER_EXCHANGEDB_BankTransfer *bank =
+ pos->details.bank;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CREDIT"),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ bank->execution_date),
+ TALER_JSON_pack_full_payto ("sender_account_url",
+ bank->sender_account_details),
+ GNUNET_JSON_pack_uint64 ("wire_reference",
+ bank->wire_reference),
+ TALER_JSON_pack_amount ("amount",
+ &bank->amount))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ break;
+ }
+ case TALER_EXCHANGEDB_RO_WITHDRAW_COINS:
+ {
+ const struct TALER_EXCHANGEDB_Withdraw *withdraw
+ = pos->details.withdraw;
+ struct TALER_Amount withdraw_fee;
+ struct TEH_KeyStateHandle *ksh;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (
+ TEH_currency,
+ &withdraw_fee));
+
+ /*
+ * We need to calculate the fees for the withdraw.
+ * Therefore, we need to get access to the key state.
+ */
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+
+ for (size_t i = 0; i < withdraw->num_coins; i++)
+ {
+ /* Find the denomination and accumulate the fee */
+ {
+ struct TEH_DenominationKey *dk;
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ &withdraw->denom_pub_hashes[i],
+ NULL,
+ NULL);
+
+ if (NULL == dk)
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+
+ if (0 > TALER_amount_add (&withdraw_fee,
+ &withdraw_fee,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ }
+
+ /* Prepare the entry for the history */
+ {
+ json_t *j_entry = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string (
+ "type",
+ "WITHDRAW"),
+ GNUNET_JSON_pack_data_auto (
+ "reserve_sig",
+ &withdraw->reserve_sig),
+ GNUNET_JSON_pack_data_auto (
+ "planchets_h",
+ &withdraw->planchets_h),
+ GNUNET_JSON_pack_uint64 (
+ "num_coins",
+ withdraw->num_coins),
+ TALER_JSON_pack_array_of_data (
+ "denom_pub_hashes",
+ withdraw->num_coins,
+ withdraw->denom_pub_hashes,
+ sizeof(withdraw->denom_pub_hashes[0])),
+ TALER_JSON_pack_amount (
+ "withdraw_fee",
+ &withdraw_fee),
+ TALER_JSON_pack_amount (
+ "amount",
+ &withdraw->amount_with_fee)
+ );
+
+ if (! withdraw->no_blinding_seed)
+ {
+ if (0!=
+ json_object_update_new (
+ j_entry,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto (
+ "blinding_seed",
+ &withdraw->blinding_seed))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ if (0!=
+ json_object_update_new (
+ j_entry,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_array_of_data (
+ "cs_r_values",
+ withdraw->num_cs_r_values,
+ withdraw->cs_r_values,
+ sizeof(withdraw->cs_r_values[0])))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ if (withdraw->age_proof_required)
+ {
+ if (0 !=
+ json_object_update_new (
+ j_entry,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_uint64 (
+ "noreveal_index",
+ withdraw->noreveal_index),
+ GNUNET_JSON_pack_data_auto (
+ "selected_h",
+ &withdraw->selected_h),
+ GNUNET_JSON_pack_uint64 (
+ "max_age",
+ withdraw->max_age))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ j_entry))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ }
+
+ break;
+ case TALER_EXCHANGEDB_RO_RECOUP_COIN:
+ {
+ const struct TALER_EXCHANGEDB_Recoup *recoup
+ = pos->details.recoup;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_confirm_recoup_sign (
+ &TEH_keys_exchange_sign_,
+ recoup->timestamp,
+ &recoup->value,
+ &recoup->coin.coin_pub,
+ &recoup->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "RECOUP"),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ recoup->timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &recoup->value),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &recoup->coin.coin_pub))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
+ {
+ const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
+ pos->details.closing;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ if (TALER_EC_NONE !=
+ TALER_exchange_online_reserve_closed_sign (
+ &TEH_keys_exchange_sign_,
+ closing->execution_date,
+ &closing->amount,
+ &closing->closing_fee,
+ closing->receiver_account_details,
+ &closing->wtid,
+ &pos->details.closing->reserve_pub,
+ &pub,
+ &sig))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSING"),
+ TALER_JSON_pack_full_payto ("receiver_account_details",
+ closing->receiver_account_details),
+ GNUNET_JSON_pack_data_auto ("wtid",
+ &closing->wtid),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_timestamp ("timestamp",
+ closing->execution_date),
+ TALER_JSON_pack_amount ("amount",
+ &closing->amount),
+ TALER_JSON_pack_amount ("closing_fee",
+ &closing->closing_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_PURSE_MERGE:
+ {
+ const struct TALER_EXCHANGEDB_PurseMerge *merge =
+ pos->details.merge;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "MERGE"),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &merge->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge->merge_pub),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ merge->min_age),
+ GNUNET_JSON_pack_uint64 ("flags",
+ merge->flags),
+ GNUNET_JSON_pack_data_auto ("purse_pub",
+ &merge->purse_pub),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &merge->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge->merge_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ merge->purse_expiration),
+ TALER_JSON_pack_amount ("purse_fee",
+ &merge->purse_fee),
+ TALER_JSON_pack_amount ("amount",
+ &merge->amount_with_fee),
+ GNUNET_JSON_pack_bool ("merged",
+ merge->merged))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_HistoryRequest *history =
+ pos->details.history;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "HISTORY"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &history->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ history->request_timestamp),
+ TALER_JSON_pack_amount ("amount",
+ &history->history_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_OpenRequest *orq =
+ pos->details.open_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "OPEN"),
+ GNUNET_JSON_pack_uint64 ("requested_min_purses",
+ orq->purse_limit),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &orq->reserve_sig),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ orq->request_timestamp),
+ GNUNET_JSON_pack_timestamp ("requested_expiration",
+ orq->reserve_expiration),
+ TALER_JSON_pack_amount ("open_fee",
+ &orq->open_fee))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+
+ case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
+ {
+ const struct TALER_EXCHANGEDB_CloseRequest *crq =
+ pos->details.close_request;
+
+ if (0 !=
+ json_array_append_new (
+ json_history,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "CLOSE"),
+ GNUNET_JSON_pack_data_auto ("reserve_sig",
+ &crq->reserve_sig),
+ GNUNET_is_zero (&crq->target_account_h_payto)
+ ? GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("h_payto",
+ NULL))
+ : GNUNET_JSON_pack_data_auto ("h_payto",
+ &crq->target_account_h_payto),
+ GNUNET_JSON_pack_timestamp ("request_timestamp",
+ crq->request_timestamp))))
+ {
+ GNUNET_break (0);
+ json_decref (json_history);
+ return NULL;
+ }
+ }
+ break;
+ }
+ }
+ return json_history;
+}
+
+
+/**
+ * Add the headers we want to set for every /keys response.
+ *
+ * @param cls the key state to use
+ * @param[in,out] response the response to modify
+ */
+static void
+add_response_headers (void *cls,
+ struct MHD_Response *response)
+{
+ (void) cls;
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "no-cache"));
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
+ uint64_t start_off = 0;
+ struct TALER_Amount balance;
+ uint64_t etag_in;
+ uint64_t etag_out;
+ char etagp[24];
+ struct MHD_Response *resp;
+ unsigned int http_status;
+
+ TALER_MHD_parse_request_number (rc->connection,
+ "start",
+ &start_off);
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ bool required = true;
+
+ TALER_MHD_parse_request_header_auto (rc->connection,
+ TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
+ &reserve_sig,
+ required);
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_history_verify (start_off,
+ reserve_pub,
+ &reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+
+ /* Get etag */
+ {
+ const char *etags;
+
+ etags = MHD_lookup_connection_value (rc->connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != etags)
+ {
+ char dummy;
+ unsigned long long ev;
+
+ if (1 != sscanf (etags,
+ "\"%llu\"%c",
+ &ev,
+ &dummy))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Client send malformed `If-None-Match' header `%s'\n",
+ etags);
+ etag_in = 0;
+ }
+ else
+ {
+ etag_in = (uint64_t) ev;
+ }
+ }
+ else
+ {
+ etag_in = start_off;
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
+ reserve_pub,
+ start_off,
+ etag_in,
+ &etag_out,
+ &balance,
+ &rh);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_history");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_history");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* Handled below */
+ break;
+ }
+ }
+
+ GNUNET_snprintf (etagp,
+ sizeof (etagp),
+ "\"%llu\"",
+ (unsigned long long) etag_out);
+ if (etag_in == etag_out)
+ {
+ TEH_plugin->free_reserve_history (TEH_plugin->cls,
+ rh);
+ return TEH_RESPONSE_reply_not_modified (rc->connection,
+ etagp,
+ &add_response_headers,
+ NULL);
+ }
+ if (NULL == rh)
+ {
+ /* 204: empty history */
+ resp = MHD_create_response_from_buffer_static (0,
+ "");
+ http_status = MHD_HTTP_NO_CONTENT;
+ }
+ else
+ {
+ json_t *history;
+
+ http_status = MHD_HTTP_OK;
+ history = compile_reserve_history (rh);
+ TEH_plugin->free_reserve_history (TEH_plugin->cls,
+ rh);
+ rh = NULL;
+ if (NULL == history)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ resp = TALER_MHD_MAKE_JSON_PACK (
+ TALER_JSON_pack_amount ("balance",
+ &balance),
+ GNUNET_JSON_pack_array_steal ("history",
+ history));
+ }
+ add_response_headers (NULL,
+ resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etagp));
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (rc->connection,
+ http_status,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_history.c */
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB-history.h
@@ -0,0 +1,43 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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_get-reserves-RESERVE_PUB-history.h
+ * @brief Handle /reserves/$RESERVE_PUB/history requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_HISTORY_H
+#define TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_HISTORY_H
+
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/reserves/$RID/history" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_history (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB.c b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB.c
@@ -0,0 +1,278 @@
+/*
+ This file is part of TALER
+ Copyright (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 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_get-reserves-RESERVE_PUB.c
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-reserves-RESERVE_PUB.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Reserve GET request that is long-polling.
+ */
+struct ReservePoller
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePoller *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Subscription for the database event we are waiting for.
+ */
+ struct GNUNET_DB_EventHandler *eh;
+
+ /**
+ * When will this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Balance of the reserve, set in the callback.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Last origin account of the reserve, NULL if only
+ * P2P payments were made.
+ */
+ struct TALER_FullPayto origin_account;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+
+};
+
+
+/**
+ * Head of list of requests in long polling.
+ */
+static struct ReservePoller *rp_head;
+
+/**
+ * Tail of list of requests in long polling.
+ */
+static struct ReservePoller *rp_tail;
+
+
+void
+TEH_reserves_get_cleanup ()
+{
+ for (struct ReservePoller *rp = rp_head;
+ NULL != rp;
+ rp = rp->next)
+ {
+ if (rp->suspended)
+ {
+ rp->suspended = false;
+ MHD_resume_connection (rp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+rp_cleanup (struct TEH_RequestContext *rc)
+{
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ GNUNET_assert (! rp->suspended);
+ if (NULL != rp->eh)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
+ TEH_plugin->event_listen_cancel (TEH_plugin->cls,
+ rp->eh);
+ rp->eh = NULL;
+ }
+ GNUNET_CONTAINER_DLL_remove (rp_head,
+ rp_tail,
+ rp);
+ GNUNET_free (rp->origin_account.full_payto);
+ GNUNET_free (rp);
+}
+
+
+/**
+ * Function called on events received from Postgres.
+ * Wakes up long pollers.
+ *
+ * @param cls the `struct TEH_RequestContext *`
+ * @param extra additional event data provided
+ * @param extra_size number of bytes in @a extra
+ */
+static void
+db_event_cb (void *cls,
+ const void *extra,
+ size_t extra_size)
+{
+ struct ReservePoller *rp = cls;
+ struct GNUNET_AsyncScopeSave old_scope;
+
+ (void) extra;
+ (void) extra_size;
+ if (! rp->suspended)
+ return; /* might get multiple wake-up events */
+ GNUNET_async_scope_enter (&rp->rc->async_scope_id,
+ &old_scope);
+ TEH_check_invariants ();
+ rp->suspended = false;
+ MHD_resume_connection (rp->connection);
+ TALER_MHD_daemon_trigger ();
+ TEH_check_invariants ();
+ GNUNET_async_scope_restore (&old_scope);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub)
+{
+ struct ReservePoller *rp = rc->rh_ctx;
+
+ if (NULL == rp)
+ {
+ rp = GNUNET_new (struct ReservePoller);
+ rp->connection = rc->connection;
+ rp->rc = rc;
+ rc->rh_ctx = rp;
+ rc->rh_cleaner = &rp_cleanup;
+ GNUNET_CONTAINER_DLL_insert (rp_head,
+ rp_tail,
+ rp);
+ rp->reserve_pub = *reserve_pub;
+ TALER_MHD_parse_request_timeout (rc->connection,
+ &rp->timeout);
+ }
+
+ if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
+ (NULL == rp->eh) )
+ {
+ struct TALER_ReserveEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
+ .reserve_pub = rp->reserve_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting DB event listening until %s\n",
+ GNUNET_TIME_absolute2s (rp->timeout));
+ rp->eh = TEH_plugin->event_listen (
+ TEH_plugin->cls,
+ GNUNET_TIME_absolute_get_remaining (rp->timeout),
+ &rep.header,
+ &db_event_cb,
+ rp);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_free (rp->origin_account.full_payto);
+ qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
+ &rp->reserve_pub,
+ &rp->balance,
+ &rp->origin_account);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0); /* single-shot query should never have soft-errors */
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SOFT_FAILURE,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_balance");
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got reserve balance of %s\n",
+ TALER_amount2s (&rp->balance));
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string (
+ "last_origin",
+ rp->origin_account.full_payto)),
+ TALER_JSON_pack_amount ("balance",
+ &rp->balance));
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (! GNUNET_TIME_absolute_is_future (rp->timeout))
+ {
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Long-polling on reserve for %s\n",
+ GNUNET_STRINGS_relative_time_to_string (
+ GNUNET_TIME_absolute_get_remaining (rp->timeout),
+ true));
+ rp->suspended = true;
+ MHD_suspend_connection (rc->connection);
+ return MHD_YES;
+ }
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/* end of taler-exchange-httpd_reserves_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB.h b/src/exchange/taler-exchange-httpd_get-reserves-RESERVE_PUB.h
@@ -0,0 +1,54 @@
+/*
+ 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 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_get-reserves-RESERVE_PUB.h
+ * @brief Handle /reserves/$RESERVE_PUB GET requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_H
+#define TALER_EXCHANGE_HTTPD_GET_RESERVES_RESERVE_PUB_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Shutdown reserves-get subsystem. Resumes all
+ * suspended long-polling clients and cleans up
+ * data structures.
+ */
+void
+TEH_reserves_get_cleanup (void);
+
+
+/**
+ * Handle a GET "/reserves/" request. Parses the
+ * given "reserve_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then respond with the
+ * status of the reserve.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c b/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_get-reserves-attest-RESERVE_PUB.c
+ * @brief Handle GET /reserves/$RESERVE_PUB/attest requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for #reserve_attest_transaction.
+ */
+struct ReserveAttestContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the payto URI of this reserve.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * Available attributes.
+ */
+ json_t *attributes;
+
+};
+
+
+/**
+ * Function called with information about all applicable
+ * legitimization processes for the given user.
+ *
+ * @param cls our `struct ReserveAttestContext *`
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_name provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ */
+static void
+kyc_process_cb (void *cls,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const char *provider_name,
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
+{
+ struct ReserveAttestContext *rsc = cls;
+ json_t *attrs;
+ json_t *val;
+ const char *name;
+
+ if (GNUNET_TIME_absolute_is_past (
+ expiration_time.abs_time))
+ return;
+ attrs = TALER_CRYPTO_kyc_attributes_decrypt (
+ &TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ json_object_foreach (attrs, name, val)
+ {
+ bool duplicate = false;
+ size_t idx;
+ json_t *str;
+
+ json_array_foreach (rsc->attributes, idx, str)
+ {
+ if (0 == strcmp (json_string_value (str),
+ name))
+ {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate)
+ continue;
+ GNUNET_assert (0 ==
+ json_array_append_new (rsc->attributes,
+ json_string (name)));
+ }
+ json_decref (attrs);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_get_attest (
+ struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct ReserveAttestContext rsc = {
+ .attributes = NULL
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ args[0],
+ strlen (args[0]),
+ &rsc.reserve_pub,
+ sizeof (rsc.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ {
+ struct TALER_NormalizedPayto payto_uri;
+
+ payto_uri
+ = TALER_reserve_make_payto (TEH_base_url,
+ &rsc.reserve_pub);
+ TALER_normalized_payto_hash (payto_uri,
+ &rsc.h_payto);
+ GNUNET_free (payto_uri.normalized_payto);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ rsc.attributes = json_array ();
+ GNUNET_assert (NULL != rsc.attributes);
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
+ &rsc.h_payto,
+ &kyc_process_cb,
+ &rsc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ json_decref (rsc.attributes);
+ rsc.attributes = NULL;
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_kyc_attributes");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ json_decref (rsc.attributes);
+ rsc.attributes = NULL;
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_kyc_attributes");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break_op (0);
+ json_decref (rsc.attributes);
+ rsc.attributes = NULL;
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("details",
+ rsc.attributes));
+}
+
+
+/* end of taler-exchange-httpd_reserves_get_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h b/src/exchange/taler-exchange-httpd_get-reserves-attest-RESERVE_PUB.h
@@ -0,0 +1,44 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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_get-reserves-attest-RESERVE_PUB.h
+ * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_RESERVES_ATTEST_RESERVE_PUB_H
+#define TALER_EXCHANGE_HTTPD_GET_RESERVES_ATTEST_RESERVE_PUB_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/reserves/$RID/attest" request. Parses the
+ * given "reserve_pub" in @a args (which should contain the
+ * EdDSA public key of a reserve) and then responds with the
+ * available attestations for the reserve.
+ *
+ * @param rc request context
+ * @param args array of additional options (length: 1, just the reserve_pub)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_get-transfers-WTID.c b/src/exchange/taler-exchange-httpd_get-transfers-WTID.c
@@ -0,0 +1,641 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2018, 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_get-transfers-WTID.c
+ * @brief Handle wire transfer(s) GET requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_get-transfers-WTID.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+
+
+/**
+ * Information about one of the transactions that was
+ * aggregated, to be returned in the /transfers response.
+ */
+struct AggregatedDepositDetail
+{
+
+ /**
+ * We keep deposit details in a DLL.
+ */
+ struct AggregatedDepositDetail *next;
+
+ /**
+ * We keep deposit details in a DLL.
+ */
+ struct AggregatedDepositDetail *prev;
+
+ /**
+ * Hash of the contract terms.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Coin's public key of the deposited coin.
+ */
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+
+ /**
+ * Total value of the coin in the deposit (after
+ * refunds).
+ */
+ struct TALER_Amount deposit_value;
+
+ /**
+ * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds).
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Total amount refunded for this coin.
+ */
+ struct TALER_Amount refund_total;
+};
+
+
+/**
+ * A merchant asked for transaction details about a wire transfer.
+ * Provide them. Generates the 200 reply.
+ *
+ * @param connection connection to the client
+ * @param total total amount that was transferred
+ * @param merchant_pub public key of the merchant
+ * @param payto_uri destination account
+ * @param wire_fee wire fee that was charged
+ * @param exec_time execution time of the wire transfer
+ * @param wdd_head linked list with details about the combined deposits
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_transfer_details (struct MHD_Connection *connection,
+ const struct TALER_Amount *total,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_FullPayto payto_uri,
+ const struct TALER_Amount *wire_fee,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct AggregatedDepositDetail *wdd_head)
+{
+ json_t *deposits;
+ struct GNUNET_HashContext *hash_context;
+ struct GNUNET_HashCode h_details;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ struct TALER_FullPaytoHashP h_payto;
+
+ deposits = json_array ();
+ GNUNET_assert (NULL != deposits);
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+ for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
+ NULL != wdd_pos;
+ wdd_pos = wdd_pos->next)
+ {
+ TALER_exchange_online_wire_deposit_append (hash_context,
+ &wdd_pos->h_contract_terms,
+ exec_time,
+ &wdd_pos->coin_pub,
+ &wdd_pos->deposit_value,
+ &wdd_pos->deposit_fee);
+ if (0 !=
+ json_array_append_new (
+ deposits,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &wdd_pos->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &wdd_pos->coin_pub),
+
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("refund_total",
+ TALER_amount_is_zero (
+ &wdd_pos->refund_total)
+ ? NULL
+ : &wdd_pos->refund_total)),
+ TALER_JSON_pack_amount ("deposit_value",
+ &wdd_pos->deposit_value),
+ TALER_JSON_pack_amount ("deposit_fee",
+ &wdd_pos->deposit_fee))))
+ {
+ json_decref (deposits);
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ "json_array_append_new() failed");
+ }
+ }
+ GNUNET_CRYPTO_hash_context_finish (hash_context,
+ &h_details);
+ {
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_wire_deposit_sign (
+ &TEH_keys_exchange_sign_,
+ total,
+ wire_fee,
+ merchant_pub,
+ payto_uri,
+ &h_details,
+ &pub,
+ &sig)))
+ {
+ json_decref (deposits);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ }
+
+ TALER_full_payto_hash (payto_uri,
+ &h_payto);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total",
+ total),
+ TALER_JSON_pack_amount ("wire_fee",
+ wire_fee),
+ GNUNET_JSON_pack_data_auto ("merchant_pub",
+ merchant_pub),
+ GNUNET_JSON_pack_data_auto ("h_payto",
+ &h_payto),
+ GNUNET_JSON_pack_timestamp ("execution_time",
+ exec_time),
+ GNUNET_JSON_pack_array_steal ("deposits",
+ deposits),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Closure for #handle_transaction_data.
+ */
+struct WtidTransactionContext
+{
+
+ /**
+ * Identifier of the wire transfer to track.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Total amount of the wire transfer, as calculated by
+ * summing up the individual amounts. To be rounded down
+ * to calculate the real transfer amount at the end.
+ * Only valid if @e is_valid is #GNUNET_YES.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Public key of the merchant, only valid if @e is_valid
+ * is #GNUNET_YES.
+ */
+ struct TALER_MerchantPublicKeyP merchant_pub;
+
+ /**
+ * Wire fees applicable at @e exec_time.
+ */
+ struct TALER_WireFeeSet fees;
+
+ /**
+ * Execution time of the wire transfer
+ */
+ struct GNUNET_TIME_Timestamp exec_time;
+
+ /**
+ * Head of DLL with deposit details for transfers GET response.
+ */
+ struct AggregatedDepositDetail *wdd_head;
+
+ /**
+ * Tail of DLL with deposit details for transfers GET response.
+ */
+ struct AggregatedDepositDetail *wdd_tail;
+
+ /**
+ * Where were the funds wired?
+ */
+ struct TALER_FullPayto payto_uri;
+
+ /**
+ * JSON array with details about the individual deposits.
+ */
+ json_t *deposits;
+
+ /**
+ * Initially #GNUNET_NO, if we found no deposits so far. Set to
+ * #GNUNET_YES if we got transaction data, and the database replies
+ * remained consistent with respect to @e merchant_pub and @e h_wire
+ * (as they should). Set to #GNUNET_SYSERR if we encountered an
+ * internal error.
+ */
+ enum GNUNET_GenericReturnValue is_valid;
+
+};
+
+
+/**
+ * Callback that totals up the applicable refunds.
+ *
+ * @param cls a `struct TALER_Amount` where we keep the total
+ * @param amount_with_fee amount being refunded
+ */
+static enum GNUNET_GenericReturnValue
+add_refunds (void *cls,
+ const struct TALER_Amount *amount_with_fee)
+
+{
+ struct TALER_Amount *total = cls;
+
+ if (0 >
+ TALER_amount_add (total,
+ total,
+ amount_with_fee))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Function called with the results of the lookup of the individual deposits
+ * that were aggregated for the given wire transfer.
+ *
+ * @param cls our context for transmission
+ * @param rowid which row in the DB is the information from (for diagnostics), ignored
+ * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
+ * @param account_payto_uri where the funds were sent
+ * @param h_payto hash over @a account_payto_uri as it is in the DB
+ * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
+ * @param h_contract_terms which proposal was this payment about
+ * @param denom_pub denomination public key of the @a coin_pub (ignored)
+ * @param coin_pub which public key was this payment about
+ * @param deposit_value amount contributed by this coin in total (including fee)
+ * @param deposit_fee deposit fee charged by exchange for this coin
+ */
+static void
+handle_deposit_data (
+ void *cls,
+ uint64_t rowid,
+ const struct TALER_MerchantPublicKeyP *merchant_pub,
+ const struct TALER_FullPayto account_payto_uri,
+ const struct TALER_FullPaytoHashP *h_payto,
+ struct GNUNET_TIME_Timestamp exec_time,
+ const struct TALER_PrivateContractHashP *h_contract_terms,
+ const struct TALER_DenominationPublicKey *denom_pub,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_Amount *deposit_value,
+ const struct TALER_Amount *deposit_fee)
+{
+ struct WtidTransactionContext *ctx = cls;
+ struct TALER_Amount total_refunds;
+ struct TALER_Amount dval;
+ struct TALER_Amount dfee;
+ enum GNUNET_DB_QueryStatus qs;
+
+ (void) rowid;
+ (void) denom_pub;
+ (void) h_payto;
+ if (GNUNET_SYSERR == ctx->is_valid)
+ return;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &total_refunds));
+ qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls,
+ coin_pub,
+ merchant_pub,
+ h_contract_terms,
+ &add_refunds,
+ &total_refunds);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (1 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* Refunds exceeded total deposit? not OK! */
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (0 ==
+ TALER_amount_cmp (&total_refunds,
+ deposit_value))
+ {
+ /* total_refunds == deposit_value;
+ in this case, the total contributed to the
+ wire transfer is zero (as are fees) */
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dval));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (deposit_value->currency,
+ &dfee));
+
+ }
+ else
+ {
+ /* Compute deposit value by subtracting refunds */
+ GNUNET_assert (0 <
+ TALER_amount_subtract (&dval,
+ deposit_value,
+ &total_refunds));
+ if (-1 ==
+ TALER_amount_cmp (&dval,
+ deposit_fee))
+ {
+ /* dval < deposit_fee, so after refunds less than
+ the deposit fee remains; reduce deposit fee to
+ the remaining value of the coin */
+ dfee = dval;
+ }
+ else
+ {
+ /* Partial refund, deposit fee remains */
+ dfee = *deposit_fee;
+ }
+ }
+
+ if (GNUNET_NO == ctx->is_valid)
+ {
+ /* First one we encounter, setup general information in 'ctx' */
+ ctx->merchant_pub = *merchant_pub;
+ ctx->payto_uri.full_payto = GNUNET_strdup (account_payto_uri.full_payto);
+ ctx->exec_time = exec_time;
+ ctx->is_valid = GNUNET_YES;
+ if (0 >
+ TALER_amount_subtract (&ctx->total,
+ &dval,
+ &dfee))
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ }
+ else
+ {
+ struct TALER_Amount delta;
+
+ /* Subsequent data, check general information matches that in 'ctx';
+ (it should, otherwise the deposits should not have been aggregated) */
+ if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
+ merchant_pub)) ||
+ (0 != TALER_full_payto_cmp (account_payto_uri,
+ ctx->payto_uri)) )
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (0 >
+ TALER_amount_subtract (&delta,
+ &dval,
+ &dfee))
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ if (0 >
+ TALER_amount_add (&ctx->total,
+ &ctx->total,
+ &delta))
+ {
+ GNUNET_break (0);
+ ctx->is_valid = GNUNET_SYSERR;
+ return;
+ }
+ }
+
+ {
+ struct AggregatedDepositDetail *wdd;
+
+ wdd = GNUNET_new (struct AggregatedDepositDetail);
+ wdd->deposit_value = dval;
+ wdd->deposit_fee = dfee;
+ wdd->refund_total = total_refunds;
+ wdd->h_contract_terms = *h_contract_terms;
+ wdd->coin_pub = *coin_pub;
+ GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
+ ctx->wdd_tail,
+ wdd);
+ }
+}
+
+
+/**
+ * Free data structure reachable from @a ctx, but not @a ctx itself.
+ *
+ * @param ctx context to free
+ */
+static void
+free_ctx (struct WtidTransactionContext *ctx)
+{
+ struct AggregatedDepositDetail *wdd;
+
+ while (NULL != (wdd = ctx->wdd_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (ctx->wdd_head,
+ ctx->wdd_tail,
+ wdd);
+ GNUNET_free (wdd);
+ }
+ GNUNET_free (ctx->payto_uri.full_payto);
+}
+
+
+/**
+ * Execute a "/transfers" GET operation. Returns the deposit details of the
+ * deposits that were aggregated to create the given wire transfer.
+ *
+ * If it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+get_transfer_deposits (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct WtidTransactionContext *ctx = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp wire_fee_start_date;
+ struct GNUNET_TIME_Timestamp wire_fee_end_date;
+ struct TALER_MasterSignatureP wire_fee_master_sig;
+
+ /* resetting to NULL/0 in case transaction was repeated after
+ serialization failure */
+ free_ctx (ctx);
+ qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
+ &ctx->wtid,
+ &handle_deposit_data,
+ ctx);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "wire transfer");
+ }
+ return qs;
+ }
+ if (GNUNET_SYSERR == ctx->is_valid)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "wire history malformed");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_NO == ctx->is_valid)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ {
+ char *wire_method;
+ uint64_t rowid;
+
+ wire_method = TALER_payto_get_method (ctx->payto_uri.full_payto);
+ if (NULL == wire_method)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "payto:// without wire method encountered");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
+ wire_method,
+ ctx->exec_time,
+ &rowid,
+ &wire_fee_start_date,
+ &wire_fee_end_date,
+ &ctx->fees,
+ &wire_fee_master_sig);
+ GNUNET_free (wire_method);
+ }
+ if (0 >= qs)
+ {
+ if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
+ (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
+ NULL);
+ }
+ return qs;
+ }
+ if (0 >
+ TALER_amount_subtract (&ctx->total,
+ &ctx->total,
+ &ctx->fees.wire))
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+MHD_RESULT
+TEH_handler_transfers_get (struct TEH_RequestContext *rc,
+ const char *const args[1])
+{
+ struct WtidTransactionContext ctx;
+ MHD_RESULT mhd_ret;
+
+ memset (&ctx,
+ 0,
+ sizeof (ctx));
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &ctx.wtid,
+ sizeof (ctx.wtid)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED,
+ args[0]);
+ }
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "run transfers GET",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &get_transfer_deposits,
+ &ctx))
+ {
+ free_ctx (&ctx);
+ return mhd_ret;
+ }
+ mhd_ret = reply_transfer_details (rc->connection,
+ &ctx.total,
+ &ctx.merchant_pub,
+ ctx.payto_uri,
+ &ctx.fees.wire,
+ ctx.exec_time,
+ ctx.wdd_head);
+ free_ctx (&ctx);
+ return mhd_ret;
+}
+
+
+/* end of taler-exchange-httpd_transfers_get.c */
diff --git a/src/exchange/taler-exchange-httpd_get-transfers-WTID.h b/src/exchange/taler-exchange-httpd_get-transfers-WTID.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2017, 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_get-transfers-WTID.h
+ * @brief Handle wire transfer tracking-related requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_GET_TRANSFERS_WTID_H
+#define TALER_EXCHANGE_HTTPD_GET_TRANSFERS_WTID_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a GET "/transfers/$WTID" request.
+ *
+ * @param rc request context of the handler
+ * @param args array of additional options (length: 1, just the wtid)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_transfers_get (struct TEH_RequestContext *rc,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_keys.c b/src/exchange/taler-exchange-httpd_keys.c
@@ -1,4428 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020-2025 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_keys.c
- * @brief management of our various keys
- * @author Christian Grothoff
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd.h"
-#include "taler-exchange-httpd_config.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler/taler_extensions.h"
-
-
-/**
- * How many /keys request do we hold in suspension at
- * most at any time?
- */
-#define SKR_LIMIT 32
-
-
-/**
- * When do we forcefully timeout a /keys request?
- * Matches the 120s hard-coded into exchange_api_handle.c
- */
-#define KEYS_TIMEOUT \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
-
-
-/**
- * Information about a denomination on offer by the denomination helper.
- */
-struct HelperDenomination
-{
-
- /**
- * When will the helper start to use this key for signing?
- */
- struct GNUNET_TIME_Timestamp start_time;
-
- /**
- * For how long will the helper allow signing? 0 if
- * the key was revoked or purged.
- */
- struct GNUNET_TIME_Relative validity_duration;
-
- /**
- * Hash of the full denomination key.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Signature over this key from the security module's key.
- */
- struct TALER_SecurityModuleSignatureP sm_sig;
-
- /**
- * The (full) public key.
- */
- struct TALER_DenominationPublicKey denom_pub;
-
- /**
- * Details depend on the @e denom_pub.cipher type.
- */
- union
- {
-
- /**
- * Hash of the RSA key.
- */
- struct TALER_RsaPubHashP h_rsa;
-
- /**
- * Hash of the CS key.
- */
- struct TALER_CsPubHashP h_cs;
-
- } h_details;
-
- /**
- * Name in configuration section for this denomination type.
- */
- char *section_name;
-
-
-};
-
-
-/**
- * Signatures of an auditor over a denomination key of this exchange.
- */
-struct TEH_AuditorSignature
-{
- /**
- * We store the signatures in a DLL.
- */
- struct TEH_AuditorSignature *prev;
-
- /**
- * We store the signatures in a DLL.
- */
- struct TEH_AuditorSignature *next;
-
- /**
- * A signature from the auditor.
- */
- struct TALER_AuditorSignatureP asig;
-
- /**
- * Public key of the auditor.
- */
- struct TALER_AuditorPublicKeyP apub;
-
-};
-
-
-/**
- * Information about a signing key on offer by the esign helper.
- */
-struct HelperSignkey
-{
- /**
- * When will the helper start to use this key for signing?
- */
- struct GNUNET_TIME_Timestamp start_time;
-
- /**
- * For how long will the helper allow signing? 0 if
- * the key was revoked or purged.
- */
- struct GNUNET_TIME_Relative validity_duration;
-
- /**
- * The public key.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Signature over this key from the security module's key.
- */
- struct TALER_SecurityModuleSignatureP sm_sig;
-
-};
-
-
-/**
- * State associated with the crypto helpers / security modules. NOT updated
- * when the #key_generation is updated (instead constantly kept in sync
- * whenever #TEH_keys_get_state() is called).
- */
-struct HelperState
-{
-
- /**
- * Handle for the esign/EdDSA helper.
- */
- struct TALER_CRYPTO_ExchangeSignHelper *esh;
-
- /**
- * Handle for the denom/RSA helper.
- */
- struct TALER_CRYPTO_RsaDenominationHelper *rsadh;
-
- /**
- * Handle for the denom/CS helper.
- */
- struct TALER_CRYPTO_CsDenominationHelper *csdh;
-
- /**
- * Map from H(denom_pub) to `struct HelperDenomination` entries.
- */
- struct GNUNET_CONTAINER_MultiHashMap *denom_keys;
-
- /**
- * Map from H(rsa_pub) to `struct HelperDenomination` entries.
- */
- struct GNUNET_CONTAINER_MultiHashMap *rsa_keys;
-
- /**
- * Map from H(cs_pub) to `struct HelperDenomination` entries.
- */
- struct GNUNET_CONTAINER_MultiHashMap *cs_keys;
-
- /**
- * Map from `struct TALER_ExchangePublicKey` to `struct HelperSignkey`
- * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
- * an EdDSA public key.
- */
- struct GNUNET_CONTAINER_MultiPeerMap *esign_keys;
-
-};
-
-
-/**
- * Information we track for the crypto helpers. Preserved
- * when the @e key_generation changes, thus kept separate.
- */
-static struct HelperState helpers;
-
-
-/**
- * Entry in (sorted) array with possible pre-build responses for /keys.
- * We keep pre-build responses for the various (valid) cherry-picking
- * values around.
- */
-struct KeysResponseData
-{
-
- /**
- * Response to return if the client supports (deflate) compression.
- */
- struct MHD_Response *response_compressed;
-
- /**
- * Response to return if the client does not support compression.
- */
- struct MHD_Response *response_uncompressed;
-
- /**
- * ETag for these responses.
- */
- char *etag;
-
- /**
- * Cherry-picking timestamp the client must have set for this
- * response to be valid. 0 if this is the "full" response.
- * The client's request must include this date or a higher one
- * for this response to be applicable.
- */
- struct GNUNET_TIME_Timestamp cherry_pick_date;
-
-};
-
-
-/**
- * @brief All information about an exchange online signing key (which is used to
- * sign messages from the exchange).
- */
-struct SigningKey
-{
-
- /**
- * The exchange's (online signing) public key.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Meta data about the signing key, such as validity periods.
- */
- struct TALER_EXCHANGEDB_SignkeyMetaData meta;
-
- /**
- * The long-term offline master key's signature for this signing key.
- * Signs over @e exchange_pub and @e meta.
- */
- struct TALER_MasterSignatureP master_sig;
-
-};
-
-struct TEH_KeyStateHandle
-{
-
- /**
- * Mapping from denomination keys to denomination key issue struct.
- * Used to lookup the key by hash.
- */
- struct GNUNET_CONTAINER_MultiHashMap *denomkey_map;
-
- /**
- * Mapping from serial ID's to denomination key issue struct.
- * Used to lookup the key by serial ID.
- *
- * FIXME: We need a 64-bit version of this in GNUNET.
- */
- struct GNUNET_CONTAINER_MultiHashMap32 *denomserial_map;
-
- /**
- * Map from `struct TALER_ExchangePublicKey` to `struct SigningKey`
- * entries. Based on the fact that a `struct GNUNET_PeerIdentity` is also
- * an EdDSA public key.
- */
- struct GNUNET_CONTAINER_MultiPeerMap *signkey_map;
-
- /**
- * Head of DLL of our global fees.
- */
- struct TEH_GlobalFee *gf_head;
-
- /**
- * Tail of DLL of our global fees.
- */
- struct TEH_GlobalFee *gf_tail;
-
- /**
- * json array with the auditors of this exchange. Contains exactly
- * the information needed for the "auditors" field of the /keys response.
- */
- json_t *auditors;
-
- /**
- * json array with the global fees of this exchange. Contains exactly
- * the information needed for the "global_fees" field of the /keys response.
- */
- json_t *global_fees;
-
- /**
- * Sorted array of responses to /keys (MUST be sorted by cherry-picking date) of
- * length @e krd_array_length;
- */
- struct KeysResponseData *krd_array;
-
- /**
- * Length of the @e krd_array.
- */
- unsigned int krd_array_length;
-
- /**
- * Cached reply for a GET /management/keys request. Used so we do not
- * re-create the reply every time.
- */
- json_t *management_keys_reply;
-
- /**
- * For which (global) key_generation was this data structure created?
- * Used to check when we are outdated and need to be re-generated.
- */
- uint64_t key_generation;
-
- /**
- * When did we initiate the key reloading?
- */
- struct GNUNET_TIME_Timestamp reload_time;
-
- /**
- * What is the period at which we rotate keys
- * (signing or denomination keys)?
- */
- struct GNUNET_TIME_Relative rekey_frequency;
-
- /**
- * When does our online signing key expire and we
- * thus need to re-generate this response?
- */
- struct GNUNET_TIME_Timestamp signature_expires;
-
- /**
- * True if #finish_keys_response() was not yet run and this key state
- * is only suitable for the /management/keys API.
- */
- bool management_only;
-
-};
-
-
-/**
- * Entry of /keys requests that are currently suspended because we are
- * waiting for /keys to become ready.
- */
-struct SuspendedKeysRequests
-{
- /**
- * Kept in a DLL.
- */
- struct SuspendedKeysRequests *next;
-
- /**
- * Kept in a DLL.
- */
- struct SuspendedKeysRequests *prev;
-
- /**
- * The suspended connection.
- */
- struct MHD_Connection *connection;
-
- /**
- * When does this request timeout?
- */
- 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 part of the /keys response.
- */
-struct WireStateHandle
-{
-
- /**
- * 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;
-
- /**
- * Is the wire data ready?
- */
- bool ready;
-
-};
-
-
-/**
- * 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.
- */
-static struct TEH_KeyStateHandle *key_state;
-
-/**
- * Counter incremented whenever we have a reason to re-build the keys because
- * something external changed. See #TEH_keys_get_state() and
- * #TEH_keys_update_states() for uses of this variable.
- */
-static uint64_t key_generation;
-
-/**
- * Handler listening for wire updates by other exchange
- * services.
- */
-static struct GNUNET_DB_EventHandler *keys_eh;
-
-/**
- * Head of DLL of suspended /keys requests.
- */
-static struct SuspendedKeysRequests *skr_head;
-
-/**
- * Tail of DLL of suspended /keys requests.
- */
-static struct SuspendedKeysRequests *skr_tail;
-
-/**
- * Number of entries in the @e skr_head DLL.
- */
-static unsigned int skr_size;
-
-/**
- * Task to force timeouts on /keys requests.
- */
-static struct GNUNET_SCHEDULER_Task *keys_tt;
-
-/**
- * For how long should a signing key be legally retained?
- * Configuration value.
- */
-static struct GNUNET_TIME_Relative signkey_legal_duration;
-
-/**
- * What type of asset are we dealing with here?
- */
-static char *asset_type;
-
-/**
- * RSA security module public key, all zero if not known.
- */
-static struct TALER_SecurityModulePublicKeyP denom_rsa_sm_pub;
-
-/**
- * CS security module public key, all zero if not known.
- */
-static struct TALER_SecurityModulePublicKeyP denom_cs_sm_pub;
-
-/**
- * EdDSA security module public key, all zero if not known.
- */
-static struct TALER_SecurityModulePublicKeyP esign_sm_pub;
-
-/**
- * Are we shutting down?
- */
-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);
- }
- 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);
-}
-
-
-/**
- * Add information about a wire account to @a cls.
- *
- * @param cls a `json_t *` array 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 open_banking_gateway URL of an open banking gateway, NULL if there is none
- * @param wire_transfer_gateway URL of a wire transfer gateway, NULL if there is none
- * @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)
- * @param bank_label label the wallet should use to display the account, can be NULL
- * @param priority priority for ordering bank account labels
- */
-static void
-add_wire_account (
- void *cls,
- const struct TALER_FullPayto payto_uri,
- const char *conversion_url,
- const char *open_banking_gateway,
- const char *wire_transfer_gateway,
- const json_t *debit_restrictions,
- const json_t *credit_restrictions,
- const struct TALER_MasterSignatureP *master_sig,
- const char *bank_label,
- int64_t priority)
-{
- json_t *a = cls;
-
- if (GNUNET_OK !=
- TALER_exchange_wire_signature_check (
- payto_uri,
- conversion_url,
- open_banking_gateway,
- wire_transfer_gateway,
- debit_restrictions,
- credit_restrictions,
- &TEH_master_public_key,
- master_sig))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database has wire account with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
- return;
- }
- if (0 !=
- json_array_append_new (
- a,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_full_payto (
- "payto_uri",
- payto_uri),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "conversion_url",
- conversion_url)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "open_banking_gateway",
- open_banking_gateway)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "wire_transfer_gateway",
- wire_transfer_gateway)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "bank_label",
- bank_label)),
- GNUNET_JSON_pack_int64 (
- "priority",
- priority),
- 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;
-
- /**
- * 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;
-
- if (GNUNET_OK !=
- TALER_exchange_offline_wire_fee_verify (
- ac->wire_method,
- start_date,
- end_date,
- fees,
- &TEH_master_public_key,
- master_sig))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database has wire fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
- return;
- }
- 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;
- 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->ready = false;
- return wsh;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Built wire data with %u accounts (%d)\n",
- (unsigned int) json_array_size (wire_accounts_array),
- (int) qs);
- wire_fee_object = json_object ();
- GNUNET_assert (NULL != wire_fee_object);
- wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
- {
- 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)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "No wire method in `%s'\n",
- payto_uri);
- wsh->ready = false;
- json_decref (wire_accounts_array);
- json_decref (wire_fee_object);
- return wsh;
- }
- if (NULL == json_object_get (wire_fee_object,
- wire_method))
- {
- struct AddContext ac = {
- .wire_method = wire_method,
- .wsh = wsh,
- .a = json_array ()
- };
-
- 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->ready = false;
- return wsh;
- }
- if (0 != json_array_size (ac.a))
- {
- 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));
- }
- else
- {
- json_decref (ac.a);
- }
- }
- GNUNET_free (wire_method);
- }
- }
-
- wads = json_array (); /* #7271 */
- GNUNET_assert (NULL != wads);
- wsh->json_reply = GNUNET_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));
- wsh->ready = true;
- 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
- */
-static 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;
-}
-
-
-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
- */
-static void
-keys_timeout_cb (void *cls)
-{
- struct SuspendedKeysRequests *skr;
-
- (void) cls;
- keys_tt = NULL;
- while (NULL != (skr = skr_head))
- {
- if (GNUNET_TIME_absolute_is_future (skr->timeout))
- break;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming /keys request due to timeout\n");
- GNUNET_CONTAINER_DLL_remove (skr_head,
- skr_tail,
- skr);
- skr_size--;
- MHD_resume_connection (skr->connection);
- TALER_MHD_daemon_trigger ();
- GNUNET_free (skr);
- }
- if (NULL == skr)
- return;
- keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
- &keys_timeout_cb,
- NULL);
-}
-
-
-/**
- * Suspend /keys request while we (hopefully) are waiting to be
- * provisioned with key material.
- *
- * @param[in] connection to suspend
- */
-static MHD_RESULT
-suspend_request (struct MHD_Connection *connection)
-{
- struct SuspendedKeysRequests *skr;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending /keys request until key material changes\n");
- if (terminating)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- "Exchange terminating");
- }
- skr = GNUNET_new (struct SuspendedKeysRequests);
- skr->connection = connection;
- MHD_suspend_connection (connection);
- GNUNET_CONTAINER_DLL_insert (skr_head,
- skr_tail,
- skr);
- skr_size++;
- skr->timeout = GNUNET_TIME_relative_to_absolute (KEYS_TIMEOUT);
- if (NULL == keys_tt)
- {
- keys_tt = GNUNET_SCHEDULER_add_at (skr->timeout,
- &keys_timeout_cb,
- NULL);
- }
- while (skr_size > SKR_LIMIT)
- {
- skr = skr_tail;
- GNUNET_CONTAINER_DLL_remove (skr_head,
- skr_tail,
- skr);
- skr_size--;
- MHD_resume_connection (skr->connection);
- TALER_MHD_daemon_trigger ();
- GNUNET_free (skr);
- }
- return MHD_YES;
-}
-
-
-/**
- * Called on each denomination key. Checks that the key still works.
- *
- * @param cls NULL
- * @param hc denomination hash (unused)
- * @param value a `struct TEH_DenominationKey`
- * @return #GNUNET_OK
- */
-static enum GNUNET_GenericReturnValue
-check_dk (void *cls,
- const struct GNUNET_HashCode *hc,
- void *value)
-{
- struct TEH_DenominationKey *dk = value;
-
- (void) cls;
- (void) hc;
- switch (dk->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- break;
- case GNUNET_CRYPTO_BSA_RSA:
- GNUNET_assert (GNUNET_CRYPTO_rsa_public_key_check (
- dk->denom_pub.bsign_pub_key->details.rsa_public_key));
- return GNUNET_OK;
- case GNUNET_CRYPTO_BSA_CS:
- /* nothing to do for GNUNET_CRYPTO_BSA_CS */
- return GNUNET_OK;
- }
- GNUNET_assert (0);
- return GNUNET_SYSERR;
-}
-
-
-void
-TEH_check_invariants ()
-{
- struct TEH_KeyStateHandle *ksh;
-
- if (0 == TEH_check_invariants_flag)
- return;
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- return;
- GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
- &check_dk,
- NULL);
-}
-
-
-void
-TEH_resume_keys_requests (bool do_shutdown)
-{
- struct SuspendedKeysRequests *skr;
-
- if (do_shutdown)
- terminating = true;
- while (NULL != (skr = skr_head))
- {
- GNUNET_CONTAINER_DLL_remove (skr_head,
- skr_tail,
- skr);
- skr_size--;
- MHD_resume_connection (skr->connection);
- TALER_MHD_daemon_trigger ();
- GNUNET_free (skr);
- }
- GNUNET_assert (0 == skr_size);
-}
-
-
-/**
- * Clear memory for responses to "/keys" in @a ksh.
- *
- * @param[in,out] ksh key state to update
- */
-static void
-clear_response_cache (struct TEH_KeyStateHandle *ksh)
-{
- for (unsigned int i = 0; i<ksh->krd_array_length; i++)
- {
- struct KeysResponseData *krd = &ksh->krd_array[i];
-
- MHD_destroy_response (krd->response_compressed);
- MHD_destroy_response (krd->response_uncompressed);
- GNUNET_free (krd->etag);
- }
- GNUNET_array_grow (ksh->krd_array,
- ksh->krd_array_length,
- 0);
-}
-
-
-/**
- * Check that the given RSA security module's public key is the one
- * we have pinned. If it does not match, we die hard.
- *
- * @param sm_pub RSA security module public key to check
- */
-static void
-check_denom_rsa_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
-{
- if (0 !=
- GNUNET_memcmp (sm_pub,
- &denom_rsa_sm_pub))
- {
- if (! GNUNET_is_zero (&denom_rsa_sm_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Our RSA security module changed its key. This must not happen.\n");
- GNUNET_assert (0);
- }
- denom_rsa_sm_pub = *sm_pub; /* TOFU ;-) */
- }
-}
-
-
-/**
- * Check that the given CS security module's public key is the one
- * we have pinned. If it does not match, we die hard.
- *
- * @param sm_pub RSA security module public key to check
- */
-static void
-check_denom_cs_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
-{
- if (0 !=
- GNUNET_memcmp (sm_pub,
- &denom_cs_sm_pub))
- {
- if (! GNUNET_is_zero (&denom_cs_sm_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Our CS security module changed its key. This must not happen.\n");
- GNUNET_assert (0);
- }
- denom_cs_sm_pub = *sm_pub; /* TOFU ;-) */
- }
-}
-
-
-/**
- * Check that the given EdDSA security module's public key is the one
- * we have pinned. If it does not match, we die hard.
- *
- * @param sm_pub EdDSA security module public key to check
- */
-static void
-check_esign_sm_pub (const struct TALER_SecurityModulePublicKeyP *sm_pub)
-{
- if (0 !=
- GNUNET_memcmp (sm_pub,
- &esign_sm_pub))
- {
- if (! GNUNET_is_zero (&esign_sm_pub))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Our EdDSA security module changed its key. This must not happen.\n");
- GNUNET_assert (0);
- }
- esign_sm_pub = *sm_pub; /* TOFU ;-) */
- }
-}
-
-
-/**
- * Helper function for #destroy_key_helpers to free all entries
- * in the `denom_keys` map.
- *
- * @param cls the `struct HelperDenomination`
- * @param h_denom_pub hash of the denomination public key
- * @param value the `struct HelperDenomination` to release
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-free_denom_cb (void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- void *value)
-{
- struct HelperDenomination *hd = value;
-
- (void) cls;
- (void) h_denom_pub;
- TALER_denom_pub_free (&hd->denom_pub);
- GNUNET_free (hd->section_name);
- GNUNET_free (hd);
- return GNUNET_OK;
-}
-
-
-/**
- * Helper function for #destroy_key_helpers to free all entries
- * in the `esign_keys` map.
- *
- * @param cls the `struct HelperSignkey`
- * @param pid unused, matches the exchange public key
- * @param value the `struct HelperSignkey` to release
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-free_esign_cb (void *cls,
- const struct GNUNET_PeerIdentity *pid,
- void *value)
-{
- struct HelperSignkey *hsk = value;
-
- (void) cls;
- (void) pid;
- GNUNET_free (hsk);
- return GNUNET_OK;
-}
-
-
-/**
- * Destroy helper state. Does NOT call free() on @a hs, as that
- * state is not separately allocated! Dual to #setup_key_helpers().
- *
- * @param[in] hs helper state to free, but NOT the @a hs pointer itself!
- */
-static void
-destroy_key_helpers (struct HelperState *hs)
-{
- GNUNET_CONTAINER_multihashmap_iterate (hs->denom_keys,
- &free_denom_cb,
- hs);
- GNUNET_CONTAINER_multihashmap_destroy (hs->rsa_keys);
- hs->rsa_keys = NULL;
- GNUNET_CONTAINER_multihashmap_destroy (hs->cs_keys);
- hs->cs_keys = NULL;
- GNUNET_CONTAINER_multihashmap_destroy (hs->denom_keys);
- hs->denom_keys = NULL;
- GNUNET_CONTAINER_multipeermap_iterate (hs->esign_keys,
- &free_esign_cb,
- hs);
- GNUNET_CONTAINER_multipeermap_destroy (hs->esign_keys);
- hs->esign_keys = NULL;
- if (NULL != hs->rsadh)
- {
- TALER_CRYPTO_helper_rsa_disconnect (hs->rsadh);
- hs->rsadh = NULL;
- }
- if (NULL != hs->csdh)
- {
- TALER_CRYPTO_helper_cs_disconnect (hs->csdh);
- hs->csdh = NULL;
- }
- if (NULL != hs->esh)
- {
- TALER_CRYPTO_helper_esign_disconnect (hs->esh);
- hs->esh = NULL;
- }
-}
-
-
-/**
- * Looks up the AGE_RESTRICTED setting for a denomination in the config and
- * returns the age restriction (mask) accordingly.
- *
- * @param section_name Section in the configuration for the particular
- * denomination.
- */
-static struct TALER_AgeMask
-load_age_mask (const char *section_name)
-{
- static const struct TALER_AgeMask null_mask = {0};
- enum GNUNET_GenericReturnValue ret;
-
- if (GNUNET_OK != (GNUNET_CONFIGURATION_have_value (
- TEH_cfg,
- section_name,
- "AGE_RESTRICTED")))
- return null_mask;
-
- if (GNUNET_SYSERR ==
- (ret = GNUNET_CONFIGURATION_get_value_yesno (TEH_cfg,
- section_name,
- "AGE_RESTRICTED")))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- section_name,
- "AGE_RESTRICTED",
- "Value must be YES or NO\n");
- return null_mask;
- }
-
- if (GNUNET_OK == ret)
- {
- if (! TEH_age_restriction_enabled)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "age restriction set in section %s, yet, age restriction is not enabled\n",
- section_name);
- return TEH_age_restriction_config.mask;
- }
-
-
- return null_mask;
-}
-
-
-/**
- * Function called with information about available keys for signing. Usually
- * only called once per key upon connect. Also called again in case a key is
- * being revoked, in that case with an @a end_time of zero.
- *
- * @param cls closure with the `struct HelperState *`
- * @param section_name name of the denomination type in the configuration;
- * NULL if the key has been revoked or purged
- * @param start_time when does the key become available for signing;
- * zero if the key has been revoked or purged
- * @param validity_duration how long does the key remain available for signing;
- * zero if the key has been revoked or purged
- * @param h_rsa hash of the @a denom_pub that is available (or was purged)
- * @param bs_pub the public key itself, NULL if the key was revoked or purged
- * @param sm_pub public key of the security module, NULL if the key was revoked or purged
- * @param sm_sig signature from the security module
- */
-static void
-helper_rsa_cb (
- void *cls,
- const char *section_name,
- struct GNUNET_TIME_Timestamp start_time,
- struct GNUNET_TIME_Relative validity_duration,
- const struct TALER_RsaPubHashP *h_rsa,
- struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
- const struct TALER_SecurityModulePublicKeyP *sm_pub,
- const struct TALER_SecurityModuleSignatureP *sm_sig)
-{
- struct HelperState *hs = cls;
- struct HelperDenomination *hd;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "RSA helper announces key %s for denomination type %s with validity %s\n",
- GNUNET_h2s (&h_rsa->hash),
- section_name,
- GNUNET_STRINGS_relative_time_to_string (validity_duration,
- GNUNET_NO));
- key_generation++;
- TEH_resume_keys_requests (false);
- hd = GNUNET_CONTAINER_multihashmap_get (hs->rsa_keys,
- &h_rsa->hash);
- if (NULL != hd)
- {
- /* should be just an update (revocation!), so update existing entry */
- hd->validity_duration = validity_duration;
- return;
- }
- GNUNET_assert (NULL != sm_pub);
- check_denom_rsa_sm_pub (sm_pub);
- hd = GNUNET_new (struct HelperDenomination);
- hd->start_time = start_time;
- hd->validity_duration = validity_duration;
- hd->h_details.h_rsa = *h_rsa;
- hd->sm_sig = *sm_sig;
- GNUNET_assert (GNUNET_CRYPTO_BSA_RSA == bs_pub->cipher);
- hd->denom_pub.bsign_pub_key =
- GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
- /* load the age mask for the denomination, if applicable */
- hd->denom_pub.age_mask = load_age_mask (section_name);
- TALER_denom_pub_hash (&hd->denom_pub,
- &hd->h_denom_pub);
- hd->section_name = GNUNET_strdup (section_name);
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (
- hs->denom_keys,
- &hd->h_denom_pub.hash,
- hd,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (
- hs->rsa_keys,
- &hd->h_details.h_rsa.hash,
- hd,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-}
-
-
-/**
- * Function called with information about available CS keys for signing. Usually
- * only called once per key upon connect. Also called again in case a key is
- * being revoked, in that case with an @a end_time of zero.
- *
- * @param cls closure with the `struct HelperState *`
- * @param section_name name of the denomination type in the configuration;
- * NULL if the key has been revoked or purged
- * @param start_time when does the key become available for signing;
- * zero if the key has been revoked or purged
- * @param validity_duration how long does the key remain available for signing;
- * zero if the key has been revoked or purged
- * @param h_cs hash of the @a denom_pub that is available (or was purged)
- * @param bs_pub the public key itself, NULL if the key was revoked or purged
- * @param sm_pub public key of the security module, NULL if the key was revoked or purged
- * @param sm_sig signature from the security module
- */
-static void
-helper_cs_cb (
- void *cls,
- const char *section_name,
- struct GNUNET_TIME_Timestamp start_time,
- struct GNUNET_TIME_Relative validity_duration,
- const struct TALER_CsPubHashP *h_cs,
- struct GNUNET_CRYPTO_BlindSignPublicKey *bs_pub,
- const struct TALER_SecurityModulePublicKeyP *sm_pub,
- const struct TALER_SecurityModuleSignatureP *sm_sig)
-{
- struct HelperState *hs = cls;
- struct HelperDenomination *hd;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "CS helper announces key %s for denomination type %s with validity %s\n",
- GNUNET_h2s (&h_cs->hash),
- section_name,
- GNUNET_STRINGS_relative_time_to_string (validity_duration,
- GNUNET_NO));
- key_generation++;
- TEH_resume_keys_requests (false);
- hd = GNUNET_CONTAINER_multihashmap_get (hs->cs_keys,
- &h_cs->hash);
- if (NULL != hd)
- {
- /* should be just an update (revocation!), so update existing entry */
- hd->validity_duration = validity_duration;
- return;
- }
- GNUNET_assert (NULL != sm_pub);
- check_denom_cs_sm_pub (sm_pub);
- hd = GNUNET_new (struct HelperDenomination);
- hd->start_time = start_time;
- hd->validity_duration = validity_duration;
- hd->h_details.h_cs = *h_cs;
- hd->sm_sig = *sm_sig;
- GNUNET_assert (GNUNET_CRYPTO_BSA_CS == bs_pub->cipher);
- hd->denom_pub.bsign_pub_key
- = GNUNET_CRYPTO_bsign_pub_incref (bs_pub);
- /* load the age mask for the denomination, if applicable */
- hd->denom_pub.age_mask = load_age_mask (section_name);
- TALER_denom_pub_hash (&hd->denom_pub,
- &hd->h_denom_pub);
- hd->section_name = GNUNET_strdup (section_name);
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (
- hs->denom_keys,
- &hd->h_denom_pub.hash,
- hd,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (
- hs->cs_keys,
- &hd->h_details.h_cs.hash,
- hd,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-}
-
-
-/**
- * Function called with information about available keys for signing. Usually
- * only called once per key upon connect. Also called again in case a key is
- * being revoked, in that case with an @a end_time of zero.
- *
- * @param cls closure with the `struct HelperState *`
- * @param start_time when does the key become available for signing;
- * zero if the key has been revoked or purged
- * @param validity_duration how long does the key remain available for signing;
- * zero if the key has been revoked or purged
- * @param exchange_pub the public key itself, NULL if the key was revoked or purged
- * @param sm_pub public key of the security module, NULL if the key was revoked or purged
- * @param sm_sig signature from the security module
- */
-static void
-helper_esign_cb (
- void *cls,
- struct GNUNET_TIME_Timestamp start_time,
- struct GNUNET_TIME_Relative validity_duration,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_SecurityModulePublicKeyP *sm_pub,
- const struct TALER_SecurityModuleSignatureP *sm_sig)
-{
- struct HelperState *hs = cls;
- struct HelperSignkey *hsk;
- struct GNUNET_PeerIdentity pid;
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "EdDSA helper announces signing key %s with validity %s\n",
- TALER_B2S (exchange_pub),
- GNUNET_STRINGS_relative_time_to_string (validity_duration,
- GNUNET_NO));
- key_generation++;
- TEH_resume_keys_requests (false);
- pid.public_key = exchange_pub->eddsa_pub;
- hsk = GNUNET_CONTAINER_multipeermap_get (hs->esign_keys,
- &pid);
- if (NULL != hsk)
- {
- /* should be just an update (revocation!), so update existing entry */
- hsk->validity_duration = validity_duration;
- return;
- }
- GNUNET_assert (NULL != sm_pub);
- check_esign_sm_pub (sm_pub);
- hsk = GNUNET_new (struct HelperSignkey);
- hsk->start_time = start_time;
- hsk->validity_duration = validity_duration;
- hsk->exchange_pub = *exchange_pub;
- hsk->sm_sig = *sm_sig;
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multipeermap_put (
- hs->esign_keys,
- &pid,
- hsk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-}
-
-
-/**
- * Setup helper state.
- *
- * @param[out] hs helper state to initialize
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-setup_key_helpers (struct HelperState *hs)
-{
- hs->denom_keys
- = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
- hs->rsa_keys
- = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
- hs->cs_keys
- = GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_YES);
- hs->esign_keys
- = GNUNET_CONTAINER_multipeermap_create (32,
- GNUNET_NO /* MUST BE NO! */);
- hs->rsadh = TALER_CRYPTO_helper_rsa_connect (TEH_cfg,
- "taler-exchange",
- &helper_rsa_cb,
- hs);
- if (NULL == hs->rsadh)
- return GNUNET_SYSERR;
- hs->csdh = TALER_CRYPTO_helper_cs_connect (TEH_cfg,
- "taler-exchange",
- &helper_cs_cb,
- hs);
- if (NULL == hs->csdh)
- return GNUNET_SYSERR;
- hs->esh = TALER_CRYPTO_helper_esign_connect (TEH_cfg,
- "taler-exchange",
- &helper_esign_cb,
- hs);
- if (NULL == hs->esh)
- return GNUNET_SYSERR;
- return GNUNET_OK;
-}
-
-
-/**
- * Synchronize helper state. Polls the key helper for updates.
- *
- * @param[in,out] hs helper state to synchronize
- */
-static void
-sync_key_helpers (struct HelperState *hs)
-{
- TALER_CRYPTO_helper_rsa_poll (hs->rsadh);
- TALER_CRYPTO_helper_cs_poll (hs->csdh);
- TALER_CRYPTO_helper_esign_poll (hs->esh);
-}
-
-
-/**
- * Free denomination key data.
- *
- * @param cls a `struct TEH_KeyStateHandle`, unused
- * @param h_denom_pub hash of the denomination public key, unused
- * @param value a `struct TEH_DenominationKey` to free
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-clear_denomination_cb (void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- void *value)
-{
- struct TEH_DenominationKey *dk = value;
- struct TEH_AuditorSignature *as;
-
- (void) cls;
- (void) h_denom_pub;
- TALER_denom_pub_free (&dk->denom_pub);
- while (NULL != (as = dk->as_head))
- {
- GNUNET_CONTAINER_DLL_remove (dk->as_head,
- dk->as_tail,
- as);
- GNUNET_free (as);
- }
- GNUNET_free (dk);
- return GNUNET_OK;
-}
-
-
-/**
- * Free denomination key data.
- *
- * @param cls a `struct TEH_KeyStateHandle`, unused
- * @param pid the online signing key (type-disguised), unused
- * @param value a `struct SigningKey` to free
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-clear_signkey_cb (void *cls,
- const struct GNUNET_PeerIdentity *pid,
- void *value)
-{
- struct SigningKey *sk = value;
-
- (void) cls;
- (void) pid;
- GNUNET_free (sk);
- return GNUNET_OK;
-}
-
-
-/**
- * Free resources associated with @a cls, possibly excluding
- * the helper data.
- *
- * @param[in] ksh key state to release
- */
-static void
-destroy_key_state (struct TEH_KeyStateHandle *ksh)
-{
- struct TEH_GlobalFee *gf;
-
- clear_response_cache (ksh);
- while (NULL != (gf = ksh->gf_head))
- {
- GNUNET_CONTAINER_DLL_remove (ksh->gf_head,
- ksh->gf_tail,
- gf);
- GNUNET_free (gf);
- }
- GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
- &clear_denomination_cb,
- ksh);
- GNUNET_CONTAINER_multihashmap_destroy (ksh->denomkey_map);
- GNUNET_CONTAINER_multihashmap32_destroy (ksh->denomserial_map);
- GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
- &clear_signkey_cb,
- ksh);
- GNUNET_CONTAINER_multipeermap_destroy (ksh->signkey_map);
- json_decref (ksh->auditors);
- ksh->auditors = NULL;
- json_decref (ksh->global_fees);
- ksh->global_fees = NULL;
- if (NULL != ksh->management_keys_reply)
- {
- json_decref (ksh->management_keys_reply);
- ksh->management_keys_reply = NULL;
- }
- GNUNET_free (ksh);
-}
-
-
-/**
- * Function called whenever another exchange process has updated
- * the keys data in the database.
- *
- * @param cls NULL
- * @param extra unused
- * @param extra_size number of bytes in @a extra unused
- */
-static void
-keys_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 /keys update event\n");
- TEH_check_invariants ();
- key_generation++;
- TEH_resume_keys_requests (false);
- TEH_check_invariants ();
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_keys_init ()
-{
- if (GNUNET_OK !=
- setup_key_helpers (&helpers))
- {
- destroy_key_helpers (&helpers);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- "exchange",
- "SIGNKEY_LEGAL_DURATION",
- &signkey_legal_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- "exchange",
- "SIGNKEY_LEGAL_DURATION");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
- "exchange",
- "ASSET_TYPE",
- &asset_type))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
- "exchange",
- "ASSET_TYPE");
- asset_type = GNUNET_strdup ("fiat");
- }
- {
- struct GNUNET_DB_EventHeaderP es = {
- .size = htons (sizeof (es)),
- .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
- };
-
- keys_eh = TEH_plugin->event_listen (TEH_plugin->cls,
- GNUNET_TIME_UNIT_FOREVER_REL,
- &es,
- &keys_update_event_cb,
- NULL);
- if (NULL == keys_eh)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- }
- {
- 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;
-}
-
-
-/**
- * Fully clean up our state.
- */
-void
-TEH_keys_finished ()
-{
- 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;
- }
- if (NULL != keys_tt)
- {
- GNUNET_SCHEDULER_cancel (keys_tt);
- keys_tt = NULL;
- }
- if (NULL != key_state)
- destroy_key_state (key_state);
- if (NULL != keys_eh)
- {
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- keys_eh);
- keys_eh = NULL;
- }
- destroy_key_helpers (&helpers);
-}
-
-
-/**
- * Function called with information about the exchange's denomination keys.
- *
- * @param cls closure with a `struct TEH_KeyStateHandle *`
- * @param denom_pub public key of the denomination
- * @param h_denom_pub hash of @a denom_pub
- * @param meta meta data information about the denomination type (value, expirations, fees)
- * @param master_sig master signature affirming the validity of this denomination
- * @param recoup_possible true if the key was revoked and clients can currently recoup
- * coins of this denomination
- */
-static void
-denomination_info_cb (
- void *cls,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_DenominationHashP *h_denom_pub,
- const struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta,
- const struct TALER_MasterSignatureP *master_sig,
- bool recoup_possible)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- struct TEH_DenominationKey *dk;
-
- if (GNUNET_TIME_absolute_is_past (meta->expire_deposit.abs_time))
- {
- /* should have been filtered by DB query already! */
- GNUNET_break (0);
- return;
- }
- if (GNUNET_OK !=
- TALER_exchange_offline_denom_validity_verify (
- h_denom_pub,
- meta->start,
- meta->expire_withdraw,
- meta->expire_deposit,
- meta->expire_legal,
- &meta->value,
- &meta->fees,
- &TEH_master_public_key,
- master_sig))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database has denomination with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
- return;
- }
-
- GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
- denom_pub->bsign_pub_key->cipher);
- if (GNUNET_TIME_absolute_is_zero (meta->start.abs_time) ||
- GNUNET_TIME_absolute_is_zero (meta->expire_withdraw.abs_time) ||
- GNUNET_TIME_absolute_is_zero (meta->expire_deposit.abs_time) ||
- GNUNET_TIME_absolute_is_zero (meta->expire_legal.abs_time) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database contains invalid denomination key %s\n",
- GNUNET_h2s (&h_denom_pub->hash));
- return;
- }
- dk = GNUNET_new (struct TEH_DenominationKey);
- TALER_denom_pub_copy (&dk->denom_pub,
- denom_pub);
- dk->h_denom_pub = *h_denom_pub;
- dk->meta = *meta;
- dk->master_sig = *master_sig;
- dk->recoup_possible = recoup_possible;
- dk->denom_pub.age_mask = meta->age_mask;
-
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (ksh->denomkey_map,
- &dk->h_denom_pub.hash,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- {
- uint32_t serial32 = (uint32_t) dk->meta.serial;
-
- GNUNET_assert (dk->meta.serial == (uint64_t) serial32);
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap32_put (ksh->denomserial_map,
- serial32,
- dk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
-}
-
-
-/**
- * Function called with information about the exchange's online signing keys.
- *
- * @param cls closure with a `struct TEH_KeyStateHandle *`
- * @param exchange_pub the public key
- * @param meta meta data information about the denomination type (expirations)
- * @param master_sig master signature affirming the validity of this denomination
- */
-static void
-signkey_info_cb (
- void *cls,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const struct TALER_EXCHANGEDB_SignkeyMetaData *meta,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- struct SigningKey *sk;
- struct GNUNET_PeerIdentity pid;
-
- if (GNUNET_OK !=
- TALER_exchange_offline_signkey_validity_verify (
- exchange_pub,
- meta->start,
- meta->expire_sign,
- meta->expire_legal,
- &TEH_master_public_key,
- master_sig))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database has signing key with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
- return;
- }
- sk = GNUNET_new (struct SigningKey);
- sk->exchange_pub = *exchange_pub;
- sk->meta = *meta;
- sk->master_sig = *master_sig;
- pid.public_key = exchange_pub->eddsa_pub;
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multipeermap_put (ksh->signkey_map,
- &pid,
- sk,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
-}
-
-
-/**
- * Closure for #get_auditor_sigs.
- */
-struct GetAuditorSigsContext
-{
- /**
- * Where to store the matching signatures.
- */
- json_t *denom_keys;
-
- /**
- * Public key of the auditor to match against.
- */
- const struct TALER_AuditorPublicKeyP *auditor_pub;
-};
-
-
-/**
- * Extract the auditor signatures matching the auditor's public
- * key from the @a value and generate the respective JSON.
- *
- * @param cls a `struct GetAuditorSigsContext`
- * @param h_denom_pub hash of the denomination public key
- * @param value a `struct TEH_DenominationKey`
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-get_auditor_sigs (void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- void *value)
-{
- struct GetAuditorSigsContext *ctx = cls;
- struct TEH_DenominationKey *dk = value;
-
- for (struct TEH_AuditorSignature *as = dk->as_head;
- NULL != as;
- as = as->next)
- {
- if (0 !=
- GNUNET_memcmp (ctx->auditor_pub,
- &as->apub))
- continue;
- GNUNET_break (0 ==
- json_array_append_new (
- ctx->denom_keys,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("denom_pub_h",
- h_denom_pub),
- GNUNET_JSON_pack_data_auto ("auditor_sig",
- &as->asig))));
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with information about the exchange's auditors.
- *
- * @param cls closure with a `struct TEH_KeyStateHandle *`
- * @param auditor_pub the public key of the auditor
- * @param auditor_url URL of the REST API of the auditor
- * @param auditor_name human readable official name of the auditor
- */
-static void
-auditor_info_cb (
- void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const char *auditor_url,
- const char *auditor_name)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- struct GetAuditorSigsContext ctx;
-
- ctx.denom_keys = json_array ();
- GNUNET_assert (NULL != ctx.denom_keys);
- ctx.auditor_pub = auditor_pub;
- GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
- &get_auditor_sigs,
- &ctx);
- GNUNET_break (0 ==
- json_array_append_new (
- ksh->auditors,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("auditor_name",
- auditor_name),
- GNUNET_JSON_pack_data_auto ("auditor_pub",
- auditor_pub),
- GNUNET_JSON_pack_string ("auditor_url",
- auditor_url),
- GNUNET_JSON_pack_array_steal ("denomination_keys",
- ctx.denom_keys))));
-}
-
-
-/**
- * Function called with information about the denominations
- * audited by the exchange's auditors.
- *
- * @param cls closure with a `struct TEH_KeyStateHandle *`
- * @param auditor_pub the public key of an auditor
- * @param h_denom_pub hash of a denomination key audited by this auditor
- * @param auditor_sig signature from the auditor affirming this
- */
-static void
-auditor_denom_cb (
- void *cls,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const struct TALER_DenominationHashP *h_denom_pub,
- const struct TALER_AuditorSignatureP *auditor_sig)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- struct TEH_DenominationKey *dk;
- struct TEH_AuditorSignature *as;
-
- dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
- &h_denom_pub->hash);
- if (NULL == dk)
- {
- /* Odd, this should be impossible as per foreign key
- constraint on 'auditor_denom_sigs'! Well, we can
- safely continue anyway, so let's just log it. */
- GNUNET_break (0);
- return;
- }
- as = GNUNET_new (struct TEH_AuditorSignature);
- as->asig = *auditor_sig;
- as->apub = *auditor_pub;
- GNUNET_CONTAINER_DLL_insert (dk->as_head,
- dk->as_tail,
- as);
-}
-
-
-/**
- * Closure for #add_sign_key_cb.
- */
-struct SignKeyCtx
-{
- /**
- * What is the current rotation frequency for signing keys. Updated.
- */
- struct GNUNET_TIME_Relative min_sk_frequency;
-
- /**
- * JSON array of signing keys (being created).
- */
- json_t *signkeys;
-};
-
-
-/**
- * Function called for all signing keys, used to build up the
- * respective JSON response.
- *
- * @param cls a `struct SignKeyCtx *` with the array to append keys to
- * @param pid the exchange public key (in type disguise)
- * @param value a `struct SigningKey`
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-add_sign_key_cb (void *cls,
- const struct GNUNET_PeerIdentity *pid,
- void *value)
-{
- struct SignKeyCtx *ctx = cls;
- struct SigningKey *sk = value;
-
- (void) pid;
- if (GNUNET_TIME_absolute_is_future (sk->meta.expire_sign.abs_time))
- {
- ctx->min_sk_frequency =
- GNUNET_TIME_relative_min (ctx->min_sk_frequency,
- GNUNET_TIME_absolute_get_difference (
- sk->meta.start.abs_time,
- sk->meta.expire_sign.abs_time));
- }
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- ctx->signkeys,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("stamp_start",
- sk->meta.start),
- GNUNET_JSON_pack_timestamp ("stamp_expire",
- sk->meta.expire_sign),
- GNUNET_JSON_pack_timestamp ("stamp_end",
- sk->meta.expire_legal),
- GNUNET_JSON_pack_data_auto ("master_sig",
- &sk->master_sig),
- GNUNET_JSON_pack_data_auto ("key",
- &sk->exchange_pub))));
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for #add_denom_key_cb.
- */
-struct DenomKeyCtx
-{
- /**
- * Heap for sorting active denomination keys by start time.
- */
- struct GNUNET_CONTAINER_Heap *heap;
-
- /**
- * JSON array of revoked denomination keys.
- */
- json_t *recoup;
-
- /**
- * What is the minimum key rotation frequency of
- * valid denomination keys?
- */
- struct GNUNET_TIME_Relative min_dk_frequency;
-};
-
-
-/**
- * Function called for all denomination keys, used to build up the
- * JSON list of *revoked* denomination keys and the
- * heap of non-revoked denomination keys by timeout.
- *
- * @param cls a `struct DenomKeyCtx`
- * @param h_denom_pub hash of the denomination key
- * @param value a `struct TEH_DenominationKey`
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-add_denom_key_cb (void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- void *value)
-{
- struct DenomKeyCtx *dkc = cls;
- struct TEH_DenominationKey *dk = value;
-
- if (dk->recoup_possible)
- {
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- dkc->recoup,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- h_denom_pub))));
- }
- else
- {
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- dkc->min_dk_frequency =
- GNUNET_TIME_relative_min (dkc->min_dk_frequency,
- GNUNET_TIME_absolute_get_difference (
- dk->meta.start.abs_time,
- dk->meta.expire_withdraw.abs_time));
- }
- (void) GNUNET_CONTAINER_heap_insert (dkc->heap,
- dk,
- dk->meta.start.abs_time.abs_value_us);
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Add the headers we want to set for every /keys response.
- *
- * @param cls the key state to use
- * @param[in,out] response the response to modify
- */
-static void
-setup_general_response_headers (void *cls,
- struct MHD_Response *response)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- char dat[128];
-
- TALER_MHD_add_global_headers (response,
- true);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "application/json"));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "public,must-revalidate,max-age=86400")
- );
- if (! GNUNET_TIME_relative_is_zero (ksh->rekey_frequency))
- {
- 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);
- /* Round up to next full day to ensure the expiration
- time does not become a fingerprint! */
- a = GNUNET_TIME_absolute_round_down (a,
- GNUNET_TIME_UNIT_DAYS);
- a = GNUNET_TIME_absolute_add (a,
- GNUNET_TIME_UNIT_DAYS);
- km = GNUNET_TIME_absolute_to_timestamp (a);
- we = GNUNET_TIME_absolute_to_timestamp (wire_state->cache_expiration);
- m = GNUNET_TIME_timestamp_min (we,
- km);
- TALER_MHD_get_date_string (m.abs_time,
- dat);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Setting /keys 'Expires' header to '%s' (rekey frequency is %s)\n",
- dat,
- GNUNET_TIME_relative2s (ksh->rekey_frequency,
- false));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_EXPIRES,
- dat));
- ksh->signature_expires
- = GNUNET_TIME_timestamp_min (m,
- ksh->signature_expires);
- }
- /* Set cache control headers: our response varies depending on these headers */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_VARY,
- MHD_HTTP_HEADER_ACCEPT_ENCODING));
-}
-
-
-/**
- * Initialize @a krd using the given values for @a signkeys,
- * @a recoup and @a denoms.
- *
- * @param[in,out] ksh key state handle we build @a krd for
- * @param[in] denom_keys_hash hash over all the denomination keys in @a denoms
- * @param last_cherry_pick_date timestamp to use
- * @param[in,out] signkeys list of sign keys to return
- * @param[in,out] recoup list of revoked keys to return
- * @param[in,out] grouped_denominations list of grouped denominations to return
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-create_krd (struct TEH_KeyStateHandle *ksh,
- const struct GNUNET_HashCode *denom_keys_hash,
- struct GNUNET_TIME_Timestamp last_cherry_pick_date,
- json_t *signkeys,
- json_t *recoup,
- json_t *grouped_denominations)
-{
- struct KeysResponseData krd;
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- struct WireStateHandle *wsh;
- json_t *keys;
-
- wsh = get_wire_state ();
- if (! wsh->ready)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (
- last_cherry_pick_date.abs_time));
- GNUNET_assert (NULL != signkeys);
- GNUNET_assert (NULL != recoup);
- GNUNET_assert (NULL != grouped_denominations);
- GNUNET_assert (NULL != ksh->auditors);
- GNUNET_assert (NULL != TEH_currency);
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Creating /keys at cherry pick date %s\n",
- GNUNET_TIME_timestamp2s (last_cherry_pick_date));
-
- /* Sign hash over master signatures of all denomination keys until this time
- (in reverse order). */
- {
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec =
- TALER_exchange_online_key_set_sign (
- &TEH_keys_exchange_sign2_,
- ksh,
- last_cherry_pick_date,
- denom_keys_hash,
- &exchange_pub,
- &exchange_sig)))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Could not create key response data: cannot sign (%s)\n",
- TALER_ErrorCode_get_hint (ec));
- return GNUNET_SYSERR;
- }
- }
-
- {
- const struct SigningKey *sk;
-
- sk = GNUNET_CONTAINER_multipeermap_get (
- ksh->signkey_map,
- (const struct GNUNET_PeerIdentity *) &exchange_pub);
- ksh->signature_expires = GNUNET_TIME_timestamp_min (sk->meta.expire_sign,
- 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),
- GNUNET_JSON_pack_string ("base_url",
- TEH_base_url),
- GNUNET_JSON_pack_string ("currency",
- TEH_currency),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "bank_compliance_language",
- TEH_bank_compliance_language)),
- GNUNET_JSON_pack_object_steal (
- "currency_specification",
- TALER_JSON_currency_specs_to_json (TEH_cspec)),
- GNUNET_JSON_pack_array_incref (
- "hard_limits",
- TEH_hard_limits),
- GNUNET_JSON_pack_array_incref (
- "zero_limits",
- TEH_zero_limits),
- TALER_JSON_pack_amount ("stefan_abs",
- &TEH_stefan_abs),
- TALER_JSON_pack_amount ("stefan_log",
- &TEH_stefan_log),
- GNUNET_JSON_pack_double ("stefan_lin",
- (double) TEH_stefan_lin),
- GNUNET_JSON_pack_string ("asset_type",
- asset_type),
- GNUNET_JSON_pack_bool ("kyc_enabled",
- GNUNET_YES == TEH_enable_kyc),
- GNUNET_JSON_pack_bool ("disable_direct_deposit",
- GNUNET_YES == TEH_disable_direct_deposit),
- GNUNET_JSON_pack_bool ("rewards_allowed",
- false),
- GNUNET_JSON_pack_data_auto ("master_public_key",
- &TEH_master_public_key),
- GNUNET_JSON_pack_time_rel ("reserve_closing_delay",
- TEH_reserve_closing_delay),
- GNUNET_JSON_pack_array_incref ("signkeys",
- signkeys),
- GNUNET_JSON_pack_array_incref ("recoup",
- recoup),
- 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",
- ksh->auditors),
- GNUNET_JSON_pack_array_incref ("global_fees",
- ksh->global_fees),
- GNUNET_JSON_pack_timestamp ("list_issue_date",
- last_cherry_pick_date),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal (
- "wallet_balance_limit_without_kyc",
- TALER_KYCLOGIC_get_wallet_thresholds ())),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("shopping_url",
- TEH_shopping_url)),
- TALER_JSON_pack_amount ("tiny_amount",
- &TEH_tiny_amount),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &exchange_sig));
- GNUNET_assert (NULL != keys);
- /* Signal support for the configured, enabled extensions. */
- {
- json_t *extensions = json_object ();
- bool has_extensions = false;
-
- GNUNET_assert (NULL != extensions);
- /* Fill in the configurations of the enabled extensions */
- for (const struct TALER_Extensions *iter = TALER_extensions_get_head ();
- NULL != iter && NULL != iter->extension;
- iter = iter->next)
- {
- const struct TALER_Extension *extension = iter->extension;
- json_t *manifest;
- int r;
-
- /* skip if not enabled */
- if (! extension->enabled)
- continue;
-
- /* flag our findings so far */
- has_extensions = true;
-
-
- manifest = extension->manifest (extension);
- GNUNET_assert (manifest);
-
- r = json_object_set_new (
- extensions,
- extension->name,
- manifest);
- GNUNET_assert (0 == r);
- }
-
- /* Update the keys object with the extensions and its signature */
- if (has_extensions)
- {
- json_t *sig;
- int r;
-
- r = json_object_set_new (
- keys,
- "extensions",
- extensions);
- GNUNET_assert (0 == r);
-
- /* Add the signature of the extensions, if it is not zero */
- if (TEH_extensions_signed)
- {
- sig = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("extensions_sig",
- &TEH_extensions_sig));
-
- r = json_object_update (keys, sig);
- GNUNET_assert (0 == r);
- }
- }
- else
- {
- json_decref (extensions);
- }
- }
-
-
- {
- char *keys_json;
- void *keys_jsonz;
- size_t keys_jsonz_size;
- int comp;
- char etag[sizeof (struct GNUNET_HashCode) * 2];
-
- /* Convert /keys response to UTF8-String */
- keys_json = json_dumps (keys,
- JSON_INDENT (2));
- json_decref (keys);
- GNUNET_assert (NULL != keys_json);
-
- /* Keep copy for later compression... */
- keys_jsonz = GNUNET_strdup (keys_json);
- keys_jsonz_size = strlen (keys_json);
-
- /* hash to compute etag */
- {
- struct GNUNET_HashCode ehash;
- char *end;
-
- GNUNET_CRYPTO_hash (keys_jsonz,
- keys_jsonz_size,
- &ehash);
- end = GNUNET_STRINGS_data_to_string (&ehash,
- sizeof (ehash),
- etag,
- sizeof (etag));
- *end = '\0';
- }
-
- /* Create uncompressed response */
- krd.response_uncompressed
- = MHD_create_response_from_buffer (keys_jsonz_size,
- keys_json,
- MHD_RESPMEM_MUST_FREE);
- GNUNET_assert (NULL != krd.response_uncompressed);
- setup_general_response_headers (ksh,
- krd.response_uncompressed);
- /* Information is always public, revalidate after 1 day */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (krd.response_uncompressed,
- MHD_HTTP_HEADER_ETAG,
- etag));
- /* Also compute compressed version of /keys response */
- comp = TALER_MHD_body_compress (&keys_jsonz,
- &keys_jsonz_size);
- krd.response_compressed
- = MHD_create_response_from_buffer (keys_jsonz_size,
- keys_jsonz,
- MHD_RESPMEM_MUST_FREE);
- GNUNET_assert (NULL != krd.response_compressed);
- /* If the response is actually compressed, set the
- respective header. */
- GNUNET_assert ( (MHD_YES != comp) ||
- (MHD_YES ==
- MHD_add_response_header (krd.response_compressed,
- MHD_HTTP_HEADER_CONTENT_ENCODING,
- "deflate")) );
- setup_general_response_headers (ksh,
- krd.response_compressed);
- /* Information is always public, revalidate after 1 day */
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (krd.response_compressed,
- MHD_HTTP_HEADER_ETAG,
- etag));
- krd.etag = GNUNET_strdup (etag);
- }
- krd.cherry_pick_date = last_cherry_pick_date;
- GNUNET_array_append (ksh->krd_array,
- ksh->krd_array_length,
- krd);
- return GNUNET_OK;
-}
-
-
-/**
- * Element in the `struct SignatureContext` array.
- */
-struct SignatureElement
-{
-
- /**
- * Offset of the denomination in the group array,
- * for sorting (2nd rank, ascending).
- */
- unsigned int offset;
-
- /**
- * Offset of the group in the denominations array,
- * for sorting (2nd rank, ascending).
- */
- unsigned int group_offset;
-
- /**
- * Pointer to actual master signature to hash over.
- */
- struct TALER_MasterSignatureP master_sig;
-};
-
-/**
- * Context for collecting the array of master signatures
- * needed to verify the exchange_sig online signature.
- */
-struct SignatureContext
-{
- /**
- * Array of signatures to hash over.
- */
- struct SignatureElement *elements;
-
- /**
- * Write offset in the @e elements array.
- */
- unsigned int elements_pos;
-
- /**
- * Allocated space for @e elements.
- */
- unsigned int elements_size;
-};
-
-
-/**
- * Determine order to sort two elements by before
- * we hash the master signatures. Used for
- * sorting with qsort().
- *
- * @param a pointer to a `struct SignatureElement`
- * @param b pointer to a `struct SignatureElement`
- * @return 0 if equal, -1 if a < b, 1 if a > b.
- */
-static int
-signature_context_sort_cb (const void *a,
- const void *b)
-{
- const struct SignatureElement *sa = a;
- const struct SignatureElement *sb = b;
-
- if (sa->group_offset < sb->group_offset)
- return -1;
- if (sa->group_offset > sb->group_offset)
- return 1;
- if (sa->offset < sb->offset)
- return -1;
- if (sa->offset > sb->offset)
- return 1;
- /* We should never have two disjoint elements
- with same time and offset */
- GNUNET_assert (sa == sb);
- return 0;
-}
-
-
-/**
- * Append a @a master_sig to the @a sig_ctx using the
- * given attributes for (later) sorting.
- *
- * @param[in,out] sig_ctx signature context to update
- * @param group_offset offset for the group
- * @param offset offset for the entry
- * @param master_sig master signature for the entry
- */
-static void
-append_signature (struct SignatureContext *sig_ctx,
- unsigned int group_offset,
- unsigned int offset,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct SignatureElement *element;
- unsigned int new_size;
-
- if (sig_ctx->elements_pos == sig_ctx->elements_size)
- {
- if (0 == sig_ctx->elements_size)
- new_size = 1024;
- else
- new_size = sig_ctx->elements_size * 2;
- GNUNET_array_grow (sig_ctx->elements,
- sig_ctx->elements_size,
- new_size);
- }
- element = &sig_ctx->elements[sig_ctx->elements_pos++];
- element->offset = offset;
- element->group_offset = group_offset;
- element->master_sig = *master_sig;
-}
-
-
-/**
- *GroupData is the value we store for each group meta-data */
-struct GroupData
-{
- /**
- * The json blob with the group meta-data and list of denominations
- */
- json_t *json;
-
- /**
- * List of denominations for the group,
- * included in @e json, do not free separately!
- */
- json_t *list;
-
- /**
- * Offset of the group in the final array.
- */
- unsigned int group_off;
-
-};
-
-
-/**
- * Helper function called to clean up the group data
- * in the denominations_by_group below.
- *
- * @param cls unused
- * @param key unused
- * @param value a `struct GroupData` to free
- * @return #GNUNET_OK
- */
-static int
-free_group (void *cls,
- const struct GNUNET_HashCode *key,
- void *value)
-{
- struct GroupData *gd = value;
-
- (void) cls;
- (void) key;
- GNUNET_free (gd);
- return GNUNET_OK;
-}
-
-
-static void
-compute_msig_hash (struct SignatureContext *sig_ctx,
- struct GNUNET_HashCode *hc)
-{
- struct GNUNET_HashContext *hash_context;
-
- hash_context = GNUNET_CRYPTO_hash_context_start ();
- qsort (sig_ctx->elements,
- sig_ctx->elements_pos,
- sizeof (struct SignatureElement),
- &signature_context_sort_cb);
- for (unsigned int i = 0; i<sig_ctx->elements_pos; i++)
- {
- struct SignatureElement *element = &sig_ctx->elements[i];
-
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Adding %u,%u,%s\n",
- element->group_offset,
- element->offset,
- TALER_B2S (&element->master_sig));
- GNUNET_CRYPTO_hash_context_read (hash_context,
- &element->master_sig,
- sizeof (element->master_sig));
- }
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- hc);
-}
-
-
-/**
- * Update the "/keys" responses in @a ksh, computing the detailed replies.
- *
- * This function is to recompute all (including cherry-picked) responses we
- * might want to return, based on the state already in @a ksh.
- *
- * @param[in,out] ksh state handle to update
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-finish_keys_response (struct TEH_KeyStateHandle *ksh)
-{
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
- json_t *recoup;
- struct SignKeyCtx sctx = {
- .min_sk_frequency = GNUNET_TIME_UNIT_FOREVER_REL
- };
- json_t *grouped_denominations = NULL;
- struct GNUNET_TIME_Timestamp last_cherry_pick_date;
- struct GNUNET_CONTAINER_Heap *heap;
- struct SignatureContext sig_ctx = { 0 };
- /* Remember if we have any denomination with age restriction */
- bool has_age_restricted_denomination = false;
- struct WireStateHandle *wsh;
-
- wsh = get_wire_state ();
- if (! wsh->ready)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- if (0 ==
- json_array_size (json_object_get (wsh->json_reply,
- "accounts")) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No wire accounts available. Refusing to generate /keys response.\n");
- return GNUNET_NO;
- }
- sctx.signkeys = json_array ();
- GNUNET_assert (NULL != sctx.signkeys);
- recoup = json_array ();
- GNUNET_assert (NULL != recoup);
- grouped_denominations = json_array ();
- GNUNET_assert (NULL != grouped_denominations);
-
- GNUNET_CONTAINER_multipeermap_iterate (ksh->signkey_map,
- &add_sign_key_cb,
- &sctx);
- if (0 == json_array_size (sctx.signkeys))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No online signing keys available. Refusing to generate /keys response.\n");
- ret = GNUNET_NO;
- goto CLEANUP;
- }
- heap = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MAX);
- {
- struct DenomKeyCtx dkc = {
- .recoup = recoup,
- .heap = heap,
- .min_dk_frequency = GNUNET_TIME_UNIT_FOREVER_REL,
- };
-
- GNUNET_CONTAINER_multihashmap_iterate (ksh->denomkey_map,
- &add_denom_key_cb,
- &dkc);
- ksh->rekey_frequency
- = GNUNET_TIME_relative_min (dkc.min_dk_frequency,
- sctx.min_sk_frequency);
- }
-
- last_cherry_pick_date = GNUNET_TIME_UNIT_ZERO_TS;
-
- {
- struct TEH_DenominationKey *dk;
- struct GNUNET_CONTAINER_MultiHashMap *denominations_by_group;
-
- denominations_by_group =
- GNUNET_CONTAINER_multihashmap_create (1024,
- GNUNET_NO /* NO, because keys are only on the stack */
- );
- /* heap = max heap, sorted by start time */
- while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
- {
- if (GNUNET_TIME_timestamp_cmp (last_cherry_pick_date,
- !=,
- dk->meta.start) &&
- (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time)) )
- {
- /*
- * This is not the first entry in the heap (because last_cherry_pick_date !=
- * GNUNET_TIME_UNIT_ZERO_TS) and the previous entry had a different
- * start time. Therefore, we create a new entry in ksh.
- */
- struct GNUNET_HashCode hc;
-
- compute_msig_hash (&sig_ctx,
- &hc);
- if (GNUNET_OK !=
- create_krd (ksh,
- &hc,
- last_cherry_pick_date,
- sctx.signkeys,
- recoup,
- grouped_denominations))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cherry_pick_date));
- /* drain heap before destroying it */
- while (NULL != (dk = GNUNET_CONTAINER_heap_remove_root (heap)))
- /* intentionally empty */;
- GNUNET_CONTAINER_heap_destroy (heap);
- goto CLEANUP;
- }
- }
-
- last_cherry_pick_date = dk->meta.start;
- /*
- * Group the denominations by {cipher, value, fees, age_mask}.
- *
- * For each group we save the group meta-data and the list of
- * denominations in this group as a json-blob in the multihashmap
- * denominations_by_group.
- */
- {
- struct GroupData *group;
- json_t *entry;
- struct GNUNET_HashCode key;
- struct TALER_DenominationGroup meta = {
- .cipher = dk->denom_pub.bsign_pub_key->cipher,
- .value = dk->meta.value,
- .fees = dk->meta.fees,
- .age_mask = dk->meta.age_mask,
- };
-
- /* Search the group/JSON-blob for the key */
- TALER_denomination_group_get_key (&meta,
- &key);
- group = GNUNET_CONTAINER_multihashmap_get (
- denominations_by_group,
- &key);
- if (NULL == group)
- {
- /* There is no group for this meta-data yet, so we create a new group */
- bool age_restricted = meta.age_mask.bits != 0;
- const char *cipher;
-
- group = GNUNET_new (struct GroupData);
- switch (meta.cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- cipher = age_restricted ? "RSA+age_restricted" : "RSA";
- break;
- case GNUNET_CRYPTO_BSA_CS:
- cipher = age_restricted ? "CS+age_restricted" : "CS";
- break;
- default:
- GNUNET_assert (false);
- }
- /* Create a new array for the denominations in this group */
- group->list = json_array ();
- GNUNET_assert (NULL != group->list);
- group->json = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("cipher",
- cipher),
- GNUNET_JSON_pack_array_steal ("denoms",
- group->list),
- TALER_JSON_PACK_DENOM_FEES ("fee",
- &meta.fees),
- TALER_JSON_pack_amount ("value",
- &meta.value));
- GNUNET_assert (NULL != group->json);
- if (age_restricted)
- {
- GNUNET_assert (
- 0 ==
- json_object_set_new (group->json,
- "age_mask",
- json_integer (
- meta.age_mask.bits)));
- /* Remember that we have found at least _one_ age restricted denomination */
- has_age_restricted_denomination = true;
- }
- group->group_off
- = json_array_size (grouped_denominations);
- GNUNET_assert (0 ==
- json_array_append_new (
- grouped_denominations,
- group->json));
- GNUNET_assert (
- GNUNET_OK ==
- GNUNET_CONTAINER_multihashmap_put (denominations_by_group,
- &key,
- group,
- GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
- }
-
- /* 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 (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 GNUNET_CRYPTO_BSA_RSA:
- key_spec =
- GNUNET_JSON_pack_rsa_public_key (
- "rsa_pub",
- dk->denom_pub.bsign_pub_key->details.rsa_public_key);
- break;
- case GNUNET_CRYPTO_BSA_CS:
- key_spec =
- GNUNET_JSON_pack_data_varsize (
- "cs_pub",
- &dk->denom_pub.bsign_pub_key->details.cs_public_key,
- sizeof (dk->denom_pub.bsign_pub_key->details.cs_public_key));
- break;
- default:
- GNUNET_assert (false);
- }
-
- 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",
- dk->meta.expire_withdraw),
- GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
- dk->meta.expire_deposit),
- GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
- dk->meta.expire_legal),
- key_spec
- );
- GNUNET_assert (NULL != entry);
- }
-
- /* Build up the running hash of all master signatures of the
- denominations */
- append_signature (&sig_ctx,
- group->group_off,
- (unsigned int) json_array_size (group->list),
- &dk->master_sig);
- /* Finally, add the denomination to the list of denominations in this
- group */
- GNUNET_assert (json_is_array (group->list));
- GNUNET_assert (0 ==
- json_array_append_new (group->list,
- entry));
- }
- } /* loop over heap ends */
-
- GNUNET_CONTAINER_multihashmap_iterate (denominations_by_group,
- &free_group,
- NULL);
- GNUNET_CONTAINER_multihashmap_destroy (denominations_by_group);
- }
- GNUNET_CONTAINER_heap_destroy (heap);
-
- if (! GNUNET_TIME_absolute_is_zero (last_cherry_pick_date.abs_time))
- {
- struct GNUNET_HashCode hc;
-
- compute_msig_hash (&sig_ctx,
- &hc);
- if (GNUNET_OK !=
- create_krd (ksh,
- &hc,
- last_cherry_pick_date,
- sctx.signkeys,
- recoup,
- grouped_denominations))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Failed to generate key response data for %s\n",
- GNUNET_TIME_timestamp2s (last_cherry_pick_date));
- goto CLEANUP;
- }
- ksh->management_only = false;
-
- /* Sanity check: Make sure that age restriction is enabled IFF at least
- * one age restricted denomination exist */
- if (! has_age_restricted_denomination && TEH_age_restriction_enabled)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Age restriction is enabled, but NO denominations with age restriction found!\n");
- goto CLEANUP;
- }
- else if (has_age_restricted_denomination && ! TEH_age_restriction_enabled)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Age restriction is NOT enabled, but denominations with age restriction found!\n")
- ;
- goto CLEANUP;
- }
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No denomination keys available. Refusing to generate /keys response.\n");
- }
- ret = GNUNET_OK;
-
-CLEANUP:
- GNUNET_array_grow (sig_ctx.elements,
- sig_ctx.elements_size,
- 0);
- json_decref (grouped_denominations);
- if (NULL != sctx.signkeys)
- json_decref (sctx.signkeys);
- json_decref (recoup);
- return ret;
-}
-
-
-/**
- * Called with information about global fees.
- *
- * @param cls `struct TEH_KeyStateHandle *` we are building
- * @param fees the global fees we charge
- * @param purse_timeout when do purses time out
- * @param history_expiration how long are account histories preserved
- * @param purse_account_limit how many purses are free per account
- * @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_GLOBAL_FEES)
- */
-static void
-global_fee_info_cb (
- void *cls,
- const struct TALER_GlobalFeeSet *fees,
- struct GNUNET_TIME_Relative purse_timeout,
- struct GNUNET_TIME_Relative history_expiration,
- uint32_t purse_account_limit,
- struct GNUNET_TIME_Timestamp start_date,
- struct GNUNET_TIME_Timestamp end_date,
- const struct TALER_MasterSignatureP *master_sig)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- struct TEH_GlobalFee *gf;
-
- if (GNUNET_OK !=
- TALER_exchange_offline_global_fee_verify (
- start_date,
- end_date,
- fees,
- purse_timeout,
- history_expiration,
- purse_account_limit,
- &TEH_master_public_key,
- master_sig))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Database has global fee with invalid signature. Skipping entry. Did the exchange offline public key change?\n");
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found global fees with %u purses\n",
- purse_account_limit);
- gf = GNUNET_new (struct TEH_GlobalFee);
- gf->start_date = start_date;
- gf->end_date = end_date;
- gf->fees = *fees;
- gf->purse_timeout = purse_timeout;
- gf->history_expiration = history_expiration;
- gf->purse_account_limit = purse_account_limit;
- gf->master_sig = *master_sig;
- GNUNET_CONTAINER_DLL_insert (ksh->gf_head,
- ksh->gf_tail,
- gf);
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- ksh->global_fees,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_timestamp ("start_date",
- start_date),
- GNUNET_JSON_pack_timestamp ("end_date",
- end_date),
- TALER_JSON_PACK_GLOBAL_FEES (fees),
- GNUNET_JSON_pack_time_rel ("history_expiration",
- history_expiration),
- GNUNET_JSON_pack_time_rel ("purse_timeout",
- purse_timeout),
- GNUNET_JSON_pack_uint64 ("purse_account_limit",
- purse_account_limit),
- GNUNET_JSON_pack_data_auto ("master_sig",
- master_sig))));
-}
-
-
-/**
- * Create a key state.
- *
- * @param management_only if we should NOT run 'finish_keys_response()'
- * because we only need the state for the /management/keys API
- * @return NULL on error (i.e. failed to access database)
- */
-static struct TEH_KeyStateHandle *
-build_key_state (bool management_only)
-{
- struct TEH_KeyStateHandle *ksh;
- enum GNUNET_DB_QueryStatus qs;
-
- ksh = GNUNET_new (struct TEH_KeyStateHandle);
- ksh->signature_expires = GNUNET_TIME_UNIT_FOREVER_TS;
- ksh->reload_time = GNUNET_TIME_timestamp_get ();
- /* We must use the key_generation from when we STARTED the process! */
- ksh->key_generation = key_generation;
- ksh->denomserial_map = GNUNET_CONTAINER_multihashmap32_create (1024);
- ksh->denomkey_map = GNUNET_CONTAINER_multihashmap_create (1024,
- true);
- ksh->signkey_map = GNUNET_CONTAINER_multipeermap_create (32,
- false /* MUST be false! */
- );
- ksh->auditors = json_array ();
- GNUNET_assert (NULL != ksh->auditors);
- /* NOTE: fetches master-signed signkeys, but ALSO those that were revoked! */
- GNUNET_break (GNUNET_OK ==
- TEH_plugin->preflight (TEH_plugin->cls));
- if (NULL != ksh->global_fees)
- json_decref (ksh->global_fees);
- ksh->global_fees = json_array ();
- qs = TEH_plugin->get_global_fees (TEH_plugin->cls,
- &global_fee_info_cb,
- ksh);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Loading global fees from DB: %d\n",
- qs);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- destroy_key_state (ksh);
- return NULL;
- }
- qs = TEH_plugin->iterate_denominations (TEH_plugin->cls,
- &denomination_info_cb,
- ksh);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- destroy_key_state (ksh);
- return NULL;
- }
- /* NOTE: ONLY fetches non-revoked AND master-signed signkeys! */
- qs = TEH_plugin->iterate_active_signkeys (TEH_plugin->cls,
- &signkey_info_cb,
- ksh);
- if (qs < 0)
- {
- GNUNET_break (0);
- destroy_key_state (ksh);
- return NULL;
- }
- qs = TEH_plugin->iterate_auditor_denominations (TEH_plugin->cls,
- &auditor_denom_cb,
- ksh);
- if (qs < 0)
- {
- GNUNET_break (0);
- destroy_key_state (ksh);
- return NULL;
- }
- qs = TEH_plugin->iterate_active_auditors (TEH_plugin->cls,
- &auditor_info_cb,
- ksh);
- if (qs < 0)
- {
- GNUNET_break (0);
- destroy_key_state (ksh);
- return NULL;
- }
-
- if (management_only)
- {
- ksh->management_only = true;
- return ksh;
- }
-
- if (GNUNET_OK !=
- finish_keys_response (ksh))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Could not finish /keys response (required data not configured yet)\n");
- destroy_key_state (ksh);
- return NULL;
- }
- TEH_resume_keys_requests (false);
- return ksh;
-}
-
-
-void
-TEH_keys_update_states ()
-{
- struct GNUNET_DB_EventHeaderP es = {
- .size = htons (sizeof (es)),
- .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
- };
-
- TEH_plugin->event_notify (TEH_plugin->cls,
- &es,
- NULL,
- 0);
- key_generation++;
- TEH_resume_keys_requests (false);
-}
-
-
-static struct TEH_KeyStateHandle *
-keys_get_state (bool management_only)
-{
- struct TEH_KeyStateHandle *old_ksh;
- struct TEH_KeyStateHandle *ksh;
-
- old_ksh = key_state;
- if (NULL == old_ksh)
- {
- ksh = build_key_state (management_only);
- if (NULL == ksh)
- return NULL;
- key_state = ksh;
- return ksh;
- }
- if ( (old_ksh->key_generation < key_generation) ||
- (GNUNET_TIME_absolute_is_past (old_ksh->signature_expires.abs_time)) )
- {
- 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);
- ksh = build_key_state (management_only);
- key_state = ksh;
- destroy_key_state (old_ksh);
- return ksh;
- }
- sync_key_helpers (&helpers);
- return old_ksh;
-}
-
-
-struct TEH_KeyStateHandle *
-TEH_keys_get_state_for_management_only (void)
-{
- return keys_get_state (true);
-}
-
-
-struct TEH_KeyStateHandle *
-TEH_keys_get_state (void)
-{
- struct TEH_KeyStateHandle *ksh;
-
- ksh = keys_get_state (false);
- if (NULL == ksh)
- return NULL;
-
- if (ksh->management_only)
- {
- if (GNUNET_OK !=
- finish_keys_response (ksh))
- return NULL;
- }
-
- return ksh;
-}
-
-
-const struct TEH_GlobalFee *
-TEH_keys_global_fee_by_time (
- struct TEH_KeyStateHandle *ksh,
- struct GNUNET_TIME_Timestamp ts)
-{
- for (const struct TEH_GlobalFee *gf = ksh->gf_head;
- NULL != gf;
- gf = gf->next)
- {
- if (GNUNET_TIME_timestamp_cmp (ts,
- >=,
- gf->start_date) &&
- GNUNET_TIME_timestamp_cmp (ts,
- <,
- gf->end_date))
- return gf;
- }
- return NULL;
-}
-
-
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash (
- const struct TALER_DenominationHashP *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret)
-{
- struct TEH_KeyStateHandle *ksh;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- *mret = TALER_MHD_reply_with_error (conn,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return NULL;
- }
-
- return TEH_keys_denomination_by_hash_from_state (ksh,
- h_denom_pub,
- conn,
- mret);
-}
-
-
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash_from_state (
- const struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret)
-{
- struct TEH_DenominationKey *dk;
-
- dk = GNUNET_CONTAINER_multihashmap_get (ksh->denomkey_map,
- &h_denom_pub->hash);
- if (NULL == dk)
- {
- if (NULL == conn)
- return NULL;
- *mret = TEH_RESPONSE_reply_unknown_denom_pub_hash (conn,
- h_denom_pub);
- return NULL;
- }
- return dk;
-}
-
-
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_serial_from_state (
- const struct TEH_KeyStateHandle *ksh,
- uint64_t denom_serial)
-{
- struct TEH_DenominationKey *dk;
- uint32_t serial32 = (uint32_t) denom_serial;
-
- GNUNET_assert (denom_serial == (uint64_t) serial32);
- dk = GNUNET_CONTAINER_multihashmap32_get (ksh->denomserial_map,
- serial32);
- return dk;
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_denomination_batch_sign (
- unsigned int csds_length,
- const struct TEH_CoinSignData csds[static csds_length],
- bool for_melt,
- struct TALER_BlindedDenominationSignature bss[static csds_length])
-{
- struct TEH_KeyStateHandle *ksh;
- struct HelperDenomination *hd;
- struct TALER_CRYPTO_RsaSignRequest rsrs[csds_length];
- struct TALER_CRYPTO_CsSignRequest csrs[csds_length];
- struct TALER_BlindedDenominationSignature rs[csds_length];
- struct TALER_BlindedDenominationSignature cs[csds_length];
- unsigned int rsrs_pos = 0;
- unsigned int csrs_pos = 0;
- enum TALER_ErrorCode ec;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- for (unsigned int i = 0; i<csds_length; i++)
- {
- const struct TALER_DenominationHashP *h_denom_pub = csds[i].h_denom_pub;
- const struct TALER_BlindedPlanchet *bp = csds[i].bp;
-
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- if (bp->blinded_message->cipher !=
- hd->denom_pub.bsign_pub_key->cipher)
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- switch (hd->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- rsrs[rsrs_pos].h_rsa = &hd->h_details.h_rsa;
- rsrs[rsrs_pos].msg
- = bp->blinded_message->details.rsa_blinded_message.blinded_msg;
- rsrs[rsrs_pos].msg_size
- = bp->blinded_message->details.rsa_blinded_message.blinded_msg_size;
- rsrs_pos++;
- break;
- case GNUNET_CRYPTO_BSA_CS:
- csrs[csrs_pos].h_cs = &hd->h_details.h_cs;
- csrs[csrs_pos].blinded_planchet
- = &bp->blinded_message->details.cs_blinded_message;
- csrs_pos++;
- break;
- default:
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
- }
-
- if (0 != rsrs_pos)
- {
- memset (rs,
- 0,
- sizeof (rs));
- }
- if (0 != csrs_pos)
- {
- memset (cs,
- 0,
- sizeof (cs));
- }
- ec = TALER_EC_NONE;
- if (0 != csrs_pos)
- {
- ec = TALER_CRYPTO_helper_cs_batch_sign (
- helpers.csdh,
- csrs_pos,
- csrs,
- for_melt,
- (0 == rsrs_pos) ? bss : cs);
- if (TALER_EC_NONE != ec)
- {
- for (unsigned int i = 0; i<csrs_pos; i++)
- TALER_blinded_denom_sig_free (&cs[i]);
- return ec;
- }
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS] += csrs_pos;
- }
- if (0 != rsrs_pos)
- {
- ec = TALER_CRYPTO_helper_rsa_batch_sign (
- helpers.rsadh,
- rsrs_pos,
- rsrs,
- (0 == csrs_pos) ? bss : rs);
- if (TALER_EC_NONE != ec)
- {
- for (unsigned int i = 0; i<csrs_pos; i++)
- TALER_blinded_denom_sig_free (&cs[i]);
- for (unsigned int i = 0; i<rsrs_pos; i++)
- TALER_blinded_denom_sig_free (&rs[i]);
- return ec;
- }
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA] += rsrs_pos;
- }
-
- if ( (0 != csrs_pos) &&
- (0 != rsrs_pos) )
- {
- rsrs_pos = 0;
- csrs_pos = 0;
- for (unsigned int i = 0; i<csds_length; i++)
- {
- const struct TALER_BlindedPlanchet *bp = csds[i].bp;
-
- switch (bp->blinded_message->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- bss[i] = rs[rsrs_pos++];
- break;
- case GNUNET_CRYPTO_BSA_CS:
- bss[i] = cs[csrs_pos++];
- break;
- default:
- GNUNET_assert (0);
- }
- }
- }
- return TALER_EC_NONE;
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_r_pub (
- const struct TEH_CsDeriveData *cdd,
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP *r_pub)
-{
- const struct TALER_DenominationHashP *h_denom_pub = cdd->h_denom_pub;
- const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdd->nonce;
- struct TEH_KeyStateHandle *ksh;
- struct HelperDenomination *hd;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- }
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- {
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- }
- if (GNUNET_CRYPTO_BSA_CS !=
- hd->denom_pub.bsign_pub_key->cipher)
- {
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
-
- {
- struct TALER_CRYPTO_CsDeriveRequest cdr = {
- .h_cs = &hd->h_details.h_cs,
- .nonce = nonce
- };
- return TALER_CRYPTO_helper_cs_r_derive (helpers.csdh,
- &cdr,
- for_melt,
- r_pub);
- }
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_batch_r_pub_simple (
- unsigned int cdds_length,
- const struct TEH_CsDeriveData cdds[static cdds_length],
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length])
-{
- struct TEH_KeyStateHandle *ksh;
- struct HelperDenomination *hd;
- struct TALER_CRYPTO_CsDeriveRequest cdrs[cdds_length];
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- }
- for (unsigned int i = 0; i<cdds_length; i++)
- {
- const struct TALER_DenominationHashP *h_denom_pub = cdds[i].h_denom_pub;
- const struct GNUNET_CRYPTO_CsSessionNonce *nonce = cdds[i].nonce;
-
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- {
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
- }
- if (GNUNET_CRYPTO_BSA_CS !=
- hd->denom_pub.bsign_pub_key->cipher)
- {
- return TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
- cdrs[i].h_cs = &hd->h_details.h_cs;
- cdrs[i].nonce = nonce;
- }
-
- return TALER_CRYPTO_helper_cs_r_batch_derive (helpers.csdh,
- cdds_length,
- cdrs,
- for_melt,
- r_pubs);
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_batch_r_pub (
- const struct TEH_KeyStateHandle *ksh,
- size_t num,
- const struct TALER_DenominationHashP h_denom_pubs[static num],
- const struct GNUNET_CRYPTO_CsSessionNonce nonces[static num],
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static num],
- size_t *err_idx)
-{
- struct TALER_CRYPTO_CsDeriveRequest cdrs[num];
-
- for (unsigned int i = 0; i<num; i++)
- {
- const struct TEH_DenominationKey *dk;
- const struct HelperDenomination *hd;
-
- *err_idx = i;
-
- /* FIXME: right now we need both,
- * TEH_DenominationKey and HelperDenomination,
- * because only TEH_DenominationKey has .recoup_possible
- */
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pubs[i].hash);
- dk = TEH_keys_denomination_by_hash_from_state (ksh,
- &h_denom_pubs[i],
- NULL,
- NULL);
- if (NULL == hd)
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN;
-
- GNUNET_assert (NULL != dk);
-
- if (GNUNET_CRYPTO_BSA_CS !=
- hd->denom_pub.bsign_pub_key->cipher)
- return TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION
- ;
-
- if (GNUNET_TIME_absolute_is_future (hd->start_time.abs_time))
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE;
-
- if (dk->recoup_possible)
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED;
-
- if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
- return TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED;
-
- cdrs[i].h_cs = &hd->h_details.h_cs;
- cdrs[i].nonce = &nonces[i];
- }
-
- return TALER_CRYPTO_helper_cs_r_batch_derive (helpers.csdh,
- num,
- cdrs,
- for_melt,
- r_pubs);
-}
-
-
-void
-TEH_keys_denomination_revoke (const struct TALER_DenominationHashP *h_denom_pub)
-{
- struct TEH_KeyStateHandle *ksh;
- struct HelperDenomination *hd;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- GNUNET_break (0);
- return;
- }
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- {
- GNUNET_break (0);
- return;
- }
- switch (hd->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- break;
- case GNUNET_CRYPTO_BSA_RSA:
- TALER_CRYPTO_helper_rsa_revoke (helpers.rsadh,
- &hd->h_details.h_rsa);
- TEH_keys_update_states ();
- return;
- case GNUNET_CRYPTO_BSA_CS:
- TALER_CRYPTO_helper_cs_revoke (helpers.csdh,
- &hd->h_details.h_cs);
- TEH_keys_update_states ();
- return;
- }
- GNUNET_break (0);
- return;
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_exchange_sign_ (
- const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig)
-{
- struct TEH_KeyStateHandle *ksh;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- /* This *can* happen if the exchange's crypto helper is not running
- or had some bad error. */
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Cannot sign request, no valid signing keys available.\n");
- return TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING;
- }
- return TEH_keys_exchange_sign2_ (ksh,
- purpose,
- pub,
- sig);
-}
-
-
-enum TALER_ErrorCode
-TEH_keys_exchange_sign2_ (
- void *cls,
- const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig)
-{
- struct TEH_KeyStateHandle *ksh = cls;
- enum TALER_ErrorCode ec;
-
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA]++;
- ec = TALER_CRYPTO_helper_esign_sign_ (helpers.esh,
- purpose,
- pub,
- sig);
- if (TALER_EC_NONE != ec)
- return ec;
- {
- /* Here we check here that 'pub' is set to an exchange public key that is
- actually signed by the master key! Otherwise, we happily continue to
- use key material even if the offline signatures have not been made
- yet! */
- struct GNUNET_PeerIdentity pid;
- struct SigningKey *sk;
-
- pid.public_key = pub->eddsa_pub;
- sk = GNUNET_CONTAINER_multipeermap_get (ksh->signkey_map,
- &pid);
- if (NULL == sk)
- {
- /* just to be safe, zero out the (valid) signature, as the key
- should not or no longer be used */
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot sign, offline key signatures are missing!\n");
- memset (sig,
- 0,
- sizeof (*sig));
- return TALER_EC_EXCHANGE_SIGNKEY_HELPER_OFFLINE_MISSING;
- }
- }
- return ec;
-}
-
-
-void
-TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub)
-{
- struct TEH_KeyStateHandle *ksh;
-
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- GNUNET_break (0);
- return;
- }
- TALER_CRYPTO_helper_esign_revoke (helpers.esh,
- exchange_pub);
- TEH_keys_update_states ();
-}
-
-
-/**
- * Comparator used for a binary search by cherry_pick_date for @a key in the
- * `struct KeysResponseData` array. See libc's qsort() and bsearch() functions.
- *
- * @param key pointer to a `struct GNUNET_TIME_Timestamp`
- * @param value pointer to a `struct KeysResponseData` array entry
- * @return 0 if time matches, -1 if key is smaller, 1 if key is larger
- */
-static int
-krd_search_comparator (const void *key,
- const void *value)
-{
- const struct GNUNET_TIME_Timestamp *kd = key;
- const struct KeysResponseData *krd = value;
-
- if (GNUNET_TIME_timestamp_cmp (*kd,
- >,
- krd->cherry_pick_date))
- return -1;
- if (GNUNET_TIME_timestamp_cmp (*kd,
- <,
- krd->cherry_pick_date))
- return 1;
- return 0;
-}
-
-
-MHD_RESULT
-TEH_keys_get_handler (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- struct GNUNET_TIME_Timestamp last_issue_date;
- const char *etag;
-
- etag = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- (void) args;
- {
- const char *have_cherrypick;
-
- have_cherrypick = MHD_lookup_connection_value (rc->connection,
- MHD_GET_ARGUMENT_KIND,
- "last_issue_date");
- if (NULL != have_cherrypick)
- {
- unsigned long long cherrypickn;
-
- if (1 !=
- sscanf (have_cherrypick,
- "%llu",
- &cherrypickn))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- have_cherrypick);
- }
- /* The following multiplication may overflow; but this should not really
- be a problem, as giving back 'older' data than what the client asks for
- (given that the client asks for data in the distant future) is not
- problematic */
- last_issue_date = GNUNET_TIME_timestamp_from_s (cherrypickn);
- }
- else
- {
- last_issue_date = GNUNET_TIME_UNIT_ZERO_TS;
- }
- }
-
- {
- struct TEH_KeyStateHandle *ksh;
- const struct KeysResponseData *krd;
-
- ksh = TEH_keys_get_state ();
- if ( (NULL == ksh) ||
- (0 == ksh->krd_array_length) )
- {
- if ( ( (SKR_LIMIT <= skr_size) &&
- (GNUNET_TIME_relative_cmp (
- GNUNET_TIME_absolute_get_duration (rc->start_time),
- >,
- GNUNET_TIME_UNIT_SECONDS)) ) ||
- TEH_suicide)
- {
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- TEH_suicide
- ? "server terminating"
- : "too many connections suspended waiting on /keys");
- }
- return suspend_request (rc->connection);
- }
- krd = bsearch (&last_issue_date,
- ksh->krd_array,
- ksh->krd_array_length,
- sizeof (struct KeysResponseData),
- &krd_search_comparator);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Filtering /keys by cherry pick date %s found entry %u/%u\n",
- GNUNET_TIME_timestamp2s (last_issue_date),
- (unsigned int) (krd - ksh->krd_array),
- ksh->krd_array_length);
- if ( (NULL == krd) &&
- (ksh->krd_array_length > 0) )
- {
- if (! GNUNET_TIME_absolute_is_zero (last_issue_date.abs_time))
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client provided invalid cherry picking timestamp %s, returning full response\n",
- GNUNET_TIME_timestamp2s (last_issue_date));
- krd = &ksh->krd_array[ksh->krd_array_length - 1];
- }
- if (NULL == krd)
- {
- /* Likely keys not ready *yet*.
- Wait until they are. */
- return suspend_request (rc->connection);
- }
- if ( (NULL != etag) &&
- (0 == strcmp (etag,
- krd->etag)) )
- return TEH_RESPONSE_reply_not_modified (rc->connection,
- krd->etag,
- &setup_general_response_headers,
- ksh);
-
- return MHD_queue_response (
- rc->connection,
- MHD_HTTP_OK,
- (TALER_MHD_CT_DEFLATE ==
- TALER_MHD_can_compress (rc->connection,
- TALER_MHD_CT_DEFLATE))
- ? krd->response_compressed
- : krd->response_uncompressed);
- }
-}
-
-
-/**
- * Load extension data, like fees, expiration times (!) and age restriction
- * flags for the denomination type configured in section @a section_name.
- * Before calling this function, the `start` and `validity_duration` times must
- * already be initialized in @a meta.
- *
- * @param section_name section in the configuration to use
- * @param[in,out] meta denomination type data to complete
- * @return #GNUNET_OK on success
- */
-static enum GNUNET_GenericReturnValue
-load_extension_data (const char *section_name,
- struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
-{
- struct GNUNET_TIME_Relative deposit_duration;
- struct GNUNET_TIME_Relative legal_duration;
-
- GNUNET_assert (! GNUNET_TIME_absolute_is_zero (meta->start.abs_time)); /* caller bug */
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- section_name,
- "DURATION_SPEND",
- &deposit_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section_name,
- "DURATION_SPEND");
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
- section_name,
- "DURATION_LEGAL",
- &legal_duration))
- {
- GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
- section_name,
- "DURATION_LEGAL");
- return GNUNET_SYSERR;
- }
- meta->expire_deposit
- = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta->expire_withdraw.abs_time,
- deposit_duration));
- meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta->expire_deposit.abs_time,
- legal_duration));
- if (GNUNET_OK !=
- TALER_config_get_amount (TEH_cfg,
- section_name,
- "VALUE",
- &meta->value))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "Need amount for option `%s' in section `%s'\n",
- "VALUE",
- section_name);
- return GNUNET_SYSERR;
- }
- if (0 != strcasecmp (TEH_currency,
- meta->value.currency))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Need denomination value in section `%s' to use currency `%s'\n",
- section_name,
- TEH_currency);
- return GNUNET_SYSERR;
- }
- if (GNUNET_OK !=
- TALER_config_get_denom_fees (TEH_cfg,
- TEH_currency,
- section_name,
- &meta->fees))
- return GNUNET_SYSERR;
- meta->age_mask = load_age_mask (section_name);
- return GNUNET_OK;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *h_denom_pub,
- struct TALER_DenominationPublicKey *denom_pub,
- struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta)
-{
- struct HelperDenomination *hd;
- enum GNUNET_GenericReturnValue ok;
-
- hd = GNUNET_CONTAINER_multihashmap_get (helpers.denom_keys,
- &h_denom_pub->hash);
- if (NULL == hd)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Denomination %s not known\n",
- GNUNET_h2s (&h_denom_pub->hash));
- return GNUNET_NO;
- }
- meta->start = hd->start_time;
- meta->expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta->start.abs_time,
- hd->validity_duration));
- ok = load_extension_data (hd->section_name,
- meta);
- if (GNUNET_OK == ok)
- {
- GNUNET_assert (GNUNET_CRYPTO_BSA_INVALID !=
- hd->denom_pub.bsign_pub_key->cipher);
- TALER_denom_pub_copy (denom_pub,
- &hd->denom_pub);
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "No fees for `%s', voiding key\n",
- hd->section_name);
- memset (denom_pub,
- 0,
- sizeof (*denom_pub));
- }
- return ok;
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct TALER_EXCHANGEDB_SignkeyMetaData *meta)
-{
- struct TEH_KeyStateHandle *ksh;
- struct HelperSignkey *hsk;
- struct GNUNET_PeerIdentity pid;
-
- ksh = TEH_keys_get_state_for_management_only ();
- if (NULL == ksh)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
-
- pid.public_key = exchange_pub->eddsa_pub;
- hsk = GNUNET_CONTAINER_multipeermap_get (helpers.esign_keys,
- &pid);
- if (NULL == hsk)
- {
- GNUNET_break (0);
- return GNUNET_NO;
- }
- meta->start = hsk->start_time;
-
- meta->expire_sign = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta->start.abs_time,
- hsk->validity_duration));
- meta->expire_legal = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta->expire_sign.abs_time,
- signkey_legal_duration));
- return GNUNET_OK;
-}
-
-
-/**
- * Closure for #add_future_denomkey_cb and #add_future_signkey_cb.
- */
-struct FutureBuilderContext
-{
- /**
- * Our key state.
- */
- struct TEH_KeyStateHandle *ksh;
-
- /**
- * Array of denomination keys.
- */
- json_t *denoms;
-
- /**
- * Array of signing keys.
- */
- json_t *signkeys;
-
-};
-
-
-/**
- * Function called on all of our current and future denomination keys
- * known to the helper process. Filters out those that are current
- * and adds the remaining denomination keys (with their configuration
- * data) to the JSON array.
- *
- * @param cls the `struct FutureBuilderContext *`
- * @param h_denom_pub hash of the denomination public key
- * @param value a `struct HelperDenomination`
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-add_future_denomkey_cb (void *cls,
- const struct GNUNET_HashCode *h_denom_pub,
- void *value)
-{
- struct FutureBuilderContext *fbc = cls;
- struct HelperDenomination *hd = value;
- struct TEH_DenominationKey *dk;
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta = {0};
-
- dk = GNUNET_CONTAINER_multihashmap_get (fbc->ksh->denomkey_map,
- h_denom_pub);
- if (NULL != dk)
- return GNUNET_OK; /* skip: this key is already active! */
- if (GNUNET_TIME_relative_is_zero (hd->validity_duration))
- return GNUNET_OK; /* this key already expired! */
- meta.start = hd->start_time;
- meta.expire_withdraw = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (meta.start.abs_time,
- hd->validity_duration));
- if (GNUNET_OK !=
- load_extension_data (hd->section_name,
- &meta))
- {
- /* Woops, couldn't determine fee structure!? */
- return GNUNET_OK;
- }
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- fbc->denoms,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_amount ("value",
- &meta.value),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- meta.start),
- GNUNET_JSON_pack_timestamp ("stamp_expire_withdraw",
- meta.expire_withdraw),
- GNUNET_JSON_pack_timestamp ("stamp_expire_deposit",
- meta.expire_deposit),
- GNUNET_JSON_pack_timestamp ("stamp_expire_legal",
- meta.expire_legal),
- TALER_JSON_pack_denom_pub ("denom_pub",
- &hd->denom_pub),
- TALER_JSON_PACK_DENOM_FEES ("fee",
- &meta.fees),
- GNUNET_JSON_pack_data_auto ("denom_secmod_sig",
- &hd->sm_sig),
- GNUNET_JSON_pack_string ("section_name",
- hd->section_name))));
- return GNUNET_OK;
-}
-
-
-/**
- * Function called on all of our current and future exchange signing keys
- * known to the helper process. Filters out those that are current
- * and adds the remaining signing keys (with their configuration
- * data) to the JSON array.
- *
- * @param cls the `struct FutureBuilderContext *`
- * @param pid actually the exchange public key (type disguised)
- * @param value a `struct HelperDenomination`
- * @return #GNUNET_OK (continue to iterate)
- */
-static enum GNUNET_GenericReturnValue
-add_future_signkey_cb (void *cls,
- const struct GNUNET_PeerIdentity *pid,
- void *value)
-{
- struct FutureBuilderContext *fbc = cls;
- struct HelperSignkey *hsk = value;
- struct SigningKey *sk;
- struct GNUNET_TIME_Timestamp stamp_expire;
- struct GNUNET_TIME_Timestamp legal_end;
-
- sk = GNUNET_CONTAINER_multipeermap_get (fbc->ksh->signkey_map,
- pid);
- if (NULL != sk)
- return GNUNET_OK; /* skip: this key is already active */
- if (GNUNET_TIME_relative_is_zero (hsk->validity_duration))
- return GNUNET_OK; /* this key already expired! */
- stamp_expire = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (hsk->start_time.abs_time,
- hsk->validity_duration));
- legal_end = GNUNET_TIME_absolute_to_timestamp (
- GNUNET_TIME_absolute_add (stamp_expire.abs_time,
- signkey_legal_duration));
- GNUNET_assert (0 ==
- json_array_append_new (
- fbc->signkeys,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("key",
- &hsk->exchange_pub),
- GNUNET_JSON_pack_timestamp ("stamp_start",
- hsk->start_time),
- GNUNET_JSON_pack_timestamp ("stamp_expire",
- stamp_expire),
- GNUNET_JSON_pack_timestamp ("stamp_end",
- legal_end),
- GNUNET_JSON_pack_data_auto ("signkey_secmod_sig",
- &hsk->sm_sig))));
- return GNUNET_OK;
-}
-
-
-MHD_RESULT
-TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection)
-{
- struct TEH_KeyStateHandle *ksh;
- json_t *reply;
-
- (void) rh;
- ksh = TEH_keys_get_state_for_management_only ();
- if (NULL == ksh)
- {
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_SERVICE_UNAVAILABLE,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- "no key state");
- }
- sync_key_helpers (&helpers);
- if (NULL == ksh->management_keys_reply)
- {
- struct FutureBuilderContext fbc = {
- .ksh = ksh,
- .denoms = json_array (),
- .signkeys = json_array ()
- };
-
- GNUNET_assert (NULL != fbc.denoms);
- GNUNET_assert (NULL != fbc.signkeys);
- if ( (GNUNET_is_zero (&denom_rsa_sm_pub)) &&
- (GNUNET_is_zero (&denom_cs_sm_pub)) )
- {
- /* Either IPC failed, or neither helper had any denominations configured. */
- json_decref (fbc.denoms);
- json_decref (fbc.signkeys);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_EXCHANGE_DENOMINATION_HELPER_UNAVAILABLE,
- NULL);
- }
- if (GNUNET_is_zero (&esign_sm_pub))
- {
- json_decref (fbc.denoms);
- json_decref (fbc.signkeys);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_GATEWAY,
- TALER_EC_EXCHANGE_SIGNKEY_HELPER_UNAVAILABLE,
- NULL);
- }
- GNUNET_CONTAINER_multihashmap_iterate (helpers.denom_keys,
- &add_future_denomkey_cb,
- &fbc);
- GNUNET_CONTAINER_multipeermap_iterate (helpers.esign_keys,
- &add_future_signkey_cb,
- &fbc);
- reply = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("future_denoms",
- fbc.denoms),
- GNUNET_JSON_pack_array_steal ("future_signkeys",
- fbc.signkeys),
- GNUNET_JSON_pack_data_auto ("master_pub",
- &TEH_master_public_key),
- GNUNET_JSON_pack_data_auto ("denom_secmod_public_key",
- &denom_rsa_sm_pub),
- GNUNET_JSON_pack_data_auto ("denom_secmod_cs_public_key",
- &denom_cs_sm_pub),
- GNUNET_JSON_pack_data_auto ("signkey_secmod_public_key",
- &esign_sm_pub));
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning GET /management/keys response:\n");
- if (NULL == reply)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- }
- GNUNET_assert (NULL == ksh->management_keys_reply);
- ksh->management_keys_reply = reply;
- }
- else
- {
- reply = ksh->management_keys_reply;
- }
- return TALER_MHD_reply_json (connection,
- reply,
- MHD_HTTP_OK);
-}
-
-
-/* end of taler-exchange-httpd_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_keys.h b/src/exchange/taler-exchange-httpd_keys.h
@@ -1,614 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020-2022 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_keys.h
- * @brief management of our various keys
- * @defgroup crypto Cryptographic routines
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-#ifndef TALER_EXCHANGE_HTTPD_KEYS_H
-#define TALER_EXCHANGE_HTTPD_KEYS_H
-
-/**
- * Signatures of an auditor over a denomination key of this exchange.
- */
-struct TEH_AuditorSignature;
-
-
-/**
- * @brief All information about a denomination key (which is used to
- * sign coins into existence).
- */
-struct TEH_DenominationKey
-{
-
- /**
- * Decoded denomination public key (the hash of it is in
- * @e issue, but we sometimes need the full public key as well).
- */
- struct TALER_DenominationPublicKey denom_pub;
-
- /**
- * Hash code of the denomination public key.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Meta data about the type of the denomination, such as fees and validity
- * periods.
- */
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
-
- /**
- * The long-term offline master key's signature for this denomination.
- * Signs over @e h_denom_pub and @e meta.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * We store the auditor signatures for this denomination in a DLL.
- */
- struct TEH_AuditorSignature *as_head;
-
- /**
- * We store the auditor signatures for this denomination in a DLL.
- */
- struct TEH_AuditorSignature *as_tail;
-
- /**
- * Set to 'true' if this denomination has been revoked and recoup is
- * thus supported right now.
- */
- bool recoup_possible;
-
-};
-
-
-/**
- * Set of global fees (and options) for a time range.
- */
-struct TEH_GlobalFee
-{
- /**
- * Kept in a DLL.
- */
- struct TEH_GlobalFee *next;
-
- /**
- * Kept in a DLL.
- */
- struct TEH_GlobalFee *prev;
-
- /**
- * Beginning of the validity period (inclusive).
- */
- struct GNUNET_TIME_Timestamp start_date;
-
- /**
- * End of the validity period (exclusive).
- */
- struct GNUNET_TIME_Timestamp end_date;
-
- /**
- * How long do unmerged purses stay around at most?
- */
- struct GNUNET_TIME_Relative purse_timeout;
-
- /**
- * What is the longest history we return?
- */
- struct GNUNET_TIME_Relative history_expiration;
-
- /**
- * Signature affirming these details.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Fee structure for operations that do not depend
- * on a denomination or wire method.
- */
- struct TALER_GlobalFeeSet fees;
-
- /**
- * Number of free purses per account.
- */
- uint32_t purse_account_limit;
-};
-
-
-/**
- * Snapshot of the (coin and signing) keys (including private keys) of
- * the exchange. There can be multiple instances of this struct, as it is
- * reference counted and only destroyed once the last user is done
- * with it. The current instance is acquired using
- * #TEH_KS_acquire(). Using this function increases the
- * reference count. The contents of this structure (except for the
- * reference counter) should be considered READ-ONLY until it is
- * ultimately destroyed (as there can be many concurrent users).
- */
-struct TEH_KeyStateHandle;
-
-
-/**
- * Run internal invariant checks. For debugging.
- */
-void
-TEH_check_invariants (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);
-
-
-/**
- * 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_keys_get_state()).
- */
-void
-TEH_wire_update_state (void);
-
-
-/**
- * Return the current key state for this thread. Possibly re-builds the key
- * state if we have reason to believe that something changed.
- *
- * The result is ONLY valid until the next call to
- * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state()
- * or #TEH_keys_exchange_sign().
- *
- * @return NULL on error
- */
-struct TEH_KeyStateHandle *
-TEH_keys_get_state (void);
-
-/**
- * Obtain the key state if we should NOT run finish_keys_response() because we
- * only need the state for the /management/keys API
- */
-struct TEH_KeyStateHandle *
-TEH_keys_get_state_for_management_only (void);
-
-/**
- * Something changed in the database. Rebuild all key states. This function
- * should be called if the exchange learns about a new signature from an
- * auditor or our master key.
- *
- * (We do not do so immediately, but merely signal to all threads that they
- * need to rebuild their key state upon the next call to
- * #TEH_keys_get_state()).
- */
-void
-TEH_keys_update_states (void);
-
-
-/**
- * Look up global fee structure by @a ts.
- *
- * @param ksh key state state to look in
- * @param ts timestamp to lookup global fees at
- * @return the global fee details, or
- * NULL if none are configured for @a ts
- */
-const struct TEH_GlobalFee *
-TEH_keys_global_fee_by_time (
- struct TEH_KeyStateHandle *ksh,
- struct GNUNET_TIME_Timestamp ts);
-
-
-/**
- * Look up the issue for a denom public key. Note that the result
- * must only be used in this thread and only until another key or
- * key state is resolved.
- *
- * @param h_denom_pub hash of denomination public key
- * @param[in,out] conn used to return status message if NULL is returned
- * @param[out] mret set to the MHD status if NULL is returned
- * @return the denomination key issue,
- * or NULL if @a h_denom_pub could not be found
- */
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash (
- const struct TALER_DenominationHashP *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret);
-
-
-/**
- * Look up the issue for a denom public key using a given @a ksh. This allows
- * requesting multiple denominations with the same @a ksh which thus will
- * remain valid until the next call to #TEH_keys_denomination_by_hash() or
- * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
- *
- * @param ksh key state state to look in
- * @param h_denom_pub hash of denomination public key
- * @param[in,out] conn connection used to return status message if NULL is returned
- * @param[out] mret set to the MHD status if NULL is returned
- * @return the denomination key issue,
- * or NULL if @a h_denom_pub could not be found
- */
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_hash_from_state (
- const struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *h_denom_pub,
- struct MHD_Connection *conn,
- MHD_RESULT *mret);
-
-
-/**
- * Look up the issue for a denom public key using a given @a ksh. This allows
- * requesting multiple denominations with the same @a ksh which thus will
- * remain valid until the next call to #TEH_keys_denomination_by_hash() or
- * #TEH_keys_get_state() or #TEH_keys_exchange_sign().
- *
- * @param ksh key state state to look in
- * @param denom_serial serial ID of the denomination in the table
- * @return the denomination key issue,
- * or NULL if @a denom_serial could not be found
- */
-struct TEH_DenominationKey *
-TEH_keys_denomination_by_serial_from_state (
- const struct TEH_KeyStateHandle *ksh,
- uint64_t denom_serial);
-
-
-/**
- * Information needed to create a blind signature.
- */
-struct TEH_CoinSignData
-{
- /**
- * Hash of key to sign with.
- */
- const struct TALER_DenominationHashP *h_denom_pub;
-
- /**
- * Blinded planchet to sign over.
- */
- const struct TALER_BlindedPlanchet *bp;
-};
-
-
-/**
- * Request to sign @a csds.
- *
- * @param csds array with data to blindly sign (and keys to sign with)
- * @param csds_length length of @a csds array
- * @param for_melt true if this is for a melt operation
- * @param[out] bss array set to the blind signature on success; must be of length @a csds_length
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_denomination_batch_sign (
- unsigned int csds_length,
- const struct TEH_CoinSignData csds[static csds_length],
- bool for_melt,
- struct TALER_BlindedDenominationSignature bss[static csds_length]);
-
-
-/**
- * Information needed to derive the CS r_pub.
- */
-struct TEH_CsDeriveData
-{
- /**
- * Hash of key to sign with.
- */
- const struct TALER_DenominationHashP *h_denom_pub;
-
- /**
- * Nonce to use.
- */
- const struct GNUNET_CRYPTO_CsSessionNonce *nonce;
-};
-
-
-/**
- * Request to derive CS @a r_pub using the denomination and nonce from @a cdd.
- *
- * @param cdd data to compute @a r_pub from
- * @param for_melt true if this is for a melt operation
- * @param[out] r_pub where to write the result
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_r_pub (
- const struct TEH_CsDeriveData *cdd,
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP *r_pub);
-
-
-/**
- * Request to derive a bunch of CS @a r_pubs using the
- * denominations and nonces from @a cdds.
- *
- * @param cdds array to compute @a r_pubs from
- * @param cdds_length length of the @a cdds array
- * @param for_melt true if this is for a melt operation
- * @param[out] r_pubs array where to write the result; must be of length @a cdds_length
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_batch_r_pub_simple (
- unsigned int cdds_length,
- const struct TEH_CsDeriveData cdds[static cdds_length],
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static cdds_length]);
-
-
-/**
- * Request to derive a bunch of CS @a r_pubs using the
- * denominations and nonces from @a cdds.
- *
- * @param ksh keys state to load the keys from
- * @param num number of input elements
- * @param h_denom_pubs array @a num of hashes of keys to sign with
- * @param nonces array @a num of nonces to use
- * @param for_melt true if this is for a melt operation
- * @param[out] r_pubs array where to write the result; must be of length @a num
- * @param[out] err_idx in case of error, the index into @e cdds that caused it
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_denomination_cs_batch_r_pub (
- const struct TEH_KeyStateHandle *ksh,
- size_t num,
- const struct TALER_DenominationHashP h_denom_pubs[static num],
- const struct GNUNET_CRYPTO_CsSessionNonce nonces[static num],
- bool for_melt,
- struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[static num],
- size_t *err_idx);
-
-/**
- * Revoke the public key associated with @a h_denom_pub.
- * This function should be called AFTER the database was
- * updated, as it also triggers #TEH_keys_update_states().
- *
- * Note that the actual revocation happens asynchronously and
- * may thus fail silently. To verify that the revocation succeeded,
- * clients must watch for the associated change to the key state.
- *
- * @param h_denom_pub hash of the public key to revoke
- */
-void
-TEH_keys_denomination_revoke (
- const struct TALER_DenominationHashP *h_denom_pub);
-
-
-/**
- * Fully clean up keys subsystem.
- */
-void
-TEH_keys_finished (void);
-
-
-/**
- * Resumes all suspended /keys requests, we may now have key material
- * (or are shutting down).
- *
- * @param do_shutdown are we shutting down?
- */
-void
-TEH_resume_keys_requests (bool do_shutdown);
-
-
-/**
- * Sign the message in @a purpose with the exchange's signing key.
- *
- * The @a purpose data is the beginning of the data of which the signature is
- * to be created. The `size` field in @a purpose must correctly indicate the
- * number of bytes of the data structure, including its header. Use
- * #TEH_keys_exchange_sign() instead of calling this function directly!
- *
- * @param purpose the message to sign
- * @param[out] pub set to the current public signing key of the exchange
- * @param[out] sig signature over purpose using current signing key
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_exchange_sign_ (
- const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig);
-
-
-/**
- * Sign the message in @a purpose with the exchange's signing key.
- *
- * The @a purpose data is the beginning of the data of which the signature is
- * to be created. The `size` field in @a purpose must correctly indicate the
- * number of bytes of the data structure, including its header. Use
- * #TEH_keys_exchange_sign() instead of calling this function directly!
- *
- * @param cls key state state to look in
- * @param purpose the message to sign
- * @param[out] pub set to the current public signing key of the exchange
- * @param[out] sig signature over purpose using current signing key
- * @return #TALER_EC_NONE on success
- */
-enum TALER_ErrorCode
-TEH_keys_exchange_sign2_ (
- void *cls,
- const struct GNUNET_CRYPTO_SignaturePurpose *purpose,
- struct TALER_ExchangePublicKeyP *pub,
- struct TALER_ExchangeSignatureP *sig);
-
-
-/**
- * @ingroup crypto
- * @brief EdDSA sign a given block.
- *
- * The @a ps data must be a fixed-size struct for which the signature is to be
- * created. The `size` field in @a ps->purpose must correctly indicate the
- * number of bytes of the data structure, including its header.
- *
- * @param ps packed struct with what to sign, MUST begin with a purpose
- * @param[out] pub where to store the public key to use for the signing
- * @param[out] sig where to write the signature
- * @return #TALER_EC_NONE on success
- */
-#define TEH_keys_exchange_sign(ps,pub,sig) \
- ({ \
- /* check size is set correctly */ \
- GNUNET_assert (htonl ((ps)->purpose.size) == \
- sizeof (*ps)); \
- /* check 'ps' begins with the purpose */ \
- GNUNET_static_assert (((void*) (ps)) == \
- ((void*) &(ps)->purpose)); \
- TEH_keys_exchange_sign_ (&(ps)->purpose, \
- pub, \
- sig); \
- })
-
-
-/**
- * @ingroup crypto
- * @brief EdDSA sign a given block.
- *
- * The @a ps data must be a fixed-size struct for which the signature is to be
- * created. The `size` field in @a ps->purpose must correctly indicate the
- * number of bytes of the data structure, including its header.
- *
- * This allows requesting multiple denominations with the same @a ksh which
- * thus will remain valid until the next call to
- * #TEH_keys_denomination_by_hash() or #TEH_keys_get_state() or
- * #TEH_keys_exchange_sign().
- *
- * @param ksh key state to use
- * @param ps packed struct with what to sign, MUST begin with a purpose
- * @param[out] pub where to store the public key to use for the signing
- * @param[out] sig where to write the signature
- * @return #TALER_EC_NONE on success
- */
-#define TEH_keys_exchange_sign2(ksh,ps,pub,sig) \
- ({ \
- /* check size is set correctly */ \
- GNUNET_assert (htonl ((ps)->purpose.size) == \
- sizeof (*ps)); \
- /* check 'ps' begins with the purpose */ \
- GNUNET_static_assert (((void*) (ps)) == \
- ((void*) &(ps)->purpose)); \
- TEH_keys_exchange_sign2_ (ksh, \
- &(ps)->purpose, \
- pub, \
- sig); \
- })
-
-
-/**
- * Revoke the given exchange's signing key.
- * This function should be called AFTER the database was
- * updated, as it also triggers #TEH_keys_update_states().
- *
- * Note that the actual revocation happens asynchronously and
- * may thus fail silently. To verify that the revocation succeeded,
- * clients must watch for the associated change to the key state.
- *
- * @param exchange_pub key to revoke
- */
-void
-TEH_keys_exchange_revoke (const struct TALER_ExchangePublicKeyP *exchange_pub);
-
-
-/**
- * Function to call to handle requests to "/keys" by sending
- * back our current key material.
- *
- * @param rc request context
- * @param args array of additional options (must be empty for this function)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_keys_get_handler (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-/**
- * Function to call to handle requests to "/management/keys" by sending
- * back our future key material.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @return MHD result code
- */
-MHD_RESULT
-TEH_keys_management_get_keys_handler (const struct TEH_RequestHandler *rh,
- struct MHD_Connection *connection);
-
-
-/**
- * Load fees and expiration times (!) for the denomination type configured for
- * the denomination matching @a h_denom_pub.
- *
- * @param ksh key state to load fees from
- * @param h_denom_pub hash of the denomination public key
- * to use to derive the section name of the configuration to use
- * @param[out] denom_pub set to the denomination public key (to be freed by caller!)
- * @param[out] meta denomination type data to complete
- * @return #GNUNET_OK on success,
- * #GNUNET_NO if @a h_denom_pub is not known
- * #GNUNET_SYSERR on hard errors
- */
-enum GNUNET_GenericReturnValue
-TEH_keys_load_fees (struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *h_denom_pub,
- struct TALER_DenominationPublicKey *denom_pub,
- struct TALER_EXCHANGEDB_DenominationKeyMetaData *meta);
-
-
-/**
- * Load expiration times for the given onling signing key.
- *
- * @param exchange_pub the online signing key
- * @param[out] meta set to meta data about the key
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_keys_get_timing (const struct TALER_ExchangePublicKeyP *exchange_pub,
- struct TALER_EXCHANGEDB_SignkeyMetaData *meta);
-
-
-/**
- * Initialize keys subsystem.
- *
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_keys_init (void);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.c b/src/exchange/taler-exchange-httpd_kyc-check.c
@@ -1,506 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021-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 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_kyc-check.c
- * @brief Handle request for generic KYC check.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_kyc-check.h"
-#include "taler-exchange-httpd_kyc-wallet.h"
-#include "taler-exchange-httpd_responses.h"
-
-/**
- * Reserve GET request that is long-polling.
- */
-struct KycPoller
-{
- /**
- * Kept in a DLL.
- */
- struct KycPoller *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycPoller *prev;
-
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Subscription for the database event we are
- * waiting for.
- */
- struct GNUNET_DB_EventHandler *eh;
-
- /**
- * Account for which we perform the KYC check.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * When will this request time out?
- */
- struct GNUNET_TIME_Absolute timeout;
-
- /**
- * Signature by the account owner authorizing this
- * operation.
- */
- union TALER_AccountSignatureP account_sig;
-
- /**
- * Public key from the account owner authorizing this
- * operation. Optional, see @e have_pub.
- */
- union TALER_AccountPublicKeyP account_pub;
-
- /**
- * Generation of KYC rules already known to the client
- * (when long-polling). Do not send these rules again.
- */
- uint64_t min_rule;
-
- /**
- * What are we long-polling for (if anything)?
- */
- enum TALER_EXCHANGE_KycLongPollTarget lpt;
-
- /**
- * True if we are still suspended.
- */
- bool suspended;
-
- /**
- * True if we have an @e account_pub.
- */
- bool have_pub;
-
-};
-
-
-/**
- * Head of list of requests in long polling.
- */
-static struct KycPoller *kyp_head;
-
-/**
- * Tail of list of requests in long polling.
- */
-static struct KycPoller *kyp_tail;
-
-
-void
-TEH_kyc_check_cleanup ()
-{
- struct KycPoller *kyp;
-
- while (NULL != (kyp = kyp_head))
- {
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- if (kyp->suspended)
- {
- kyp->suspended = false;
- MHD_resume_connection (kyp->connection);
- }
- }
-}
-
-
-/**
- * Function called once a connection is done to
- * clean up the `struct ReservePoller` state.
- *
- * @param rc context to clean up for
- */
-static void
-kyp_cleanup (struct TEH_RequestContext *rc)
-{
- struct KycPoller *kyp = rc->rh_ctx;
-
- GNUNET_assert (! kyp->suspended);
- if (NULL != kyp->eh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening (KYP cleanup)\n");
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- kyp->eh);
- kyp->eh = NULL;
- }
- GNUNET_free (kyp);
-}
-
-
-/**
- * Function called on events received from Postgres.
- * Wakes up long pollers.
- *
- * @param cls the `struct TEH_RequestContext *`
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-db_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- struct TEH_RequestContext *rc = cls;
- struct KycPoller *kyp = rc->rh_ctx;
- struct GNUNET_AsyncScopeSave old_scope;
-
- (void) extra;
- (void) extra_size;
- if (! kyp->suspended)
- return; /* event triggered while main transaction
- was still running, or got multiple wake-up events */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received KYC update event\n");
- kyp->suspended = false;
- GNUNET_async_scope_enter (&rc->async_scope_id,
- &old_scope);
- TEH_check_invariants ();
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming from long-polling on KYC status\n");
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- MHD_resume_connection (kyp->connection);
- TALER_MHD_daemon_trigger ();
- TEH_check_invariants ();
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_check (
- struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct KycPoller *kyp = rc->rh_ctx;
- json_t *jrules = NULL;
- json_t *jlimits = NULL;
- union TALER_AccountPublicKeyP reserve_pub;
- struct TALER_AccountAccessTokenP access_token;
- bool aml_review;
- bool kyc_required;
- bool access_ok = false;
- enum GNUNET_GenericReturnValue is_wallet;
- uint64_t rule_gen = 0;
-
- if (NULL == kyp)
- {
- bool sig_required = true;
-
- kyp = GNUNET_new (struct KycPoller);
- kyp->connection = rc->connection;
- rc->rh_ctx = kyp;
- rc->rh_cleaner = &kyp_cleanup;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &kyp->h_payto,
- sizeof (kyp->h_payto)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PATH_SEGMENT_MALFORMED,
- "h_payto");
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Checking KYC status for normalized payto hash %s\n",
- args[0]);
- TALER_MHD_parse_request_header_auto (
- rc->connection,
- TALER_HTTP_HEADER_ACCOUNT_OWNER_SIGNATURE,
- &kyp->account_sig,
- sig_required);
- TALER_MHD_parse_request_header_auto (
- rc->connection,
- TALER_HTTP_HEADER_ACCOUNT_OWNER_PUBKEY,
- &kyp->account_pub,
- kyp->have_pub);
- TALER_MHD_parse_request_timeout (rc->connection,
- &kyp->timeout);
- {
- uint64_t num = 0;
- int val;
-
- TALER_MHD_parse_request_number (rc->connection,
- "lpt",
- &num);
- val = (int) num;
- if ( (val < 0) ||
- (val > TALER_EXCHANGE_KLPT_MAX) )
- {
- /* Protocol violation, but we can be graceful and
- just ignore the long polling! */
- GNUNET_break_op (0);
- val = TALER_EXCHANGE_KLPT_NONE;
- }
- kyp->lpt = (enum TALER_EXCHANGE_KycLongPollTarget) val;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long polling for target %d with timeout %s\n",
- val,
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- kyp->timeout),
- true));
- }
- TALER_MHD_parse_request_number (rc->connection,
- "min_rule",
- &kyp->min_rule);
- /* long polling needed? */
- if (GNUNET_TIME_absolute_is_future (kyp->timeout))
- {
- struct TALER_KycCompletedEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
- .h_payto = kyp->h_payto
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening for KYC COMPLETED\n");
- kyp->eh = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (kyp->timeout),
- &rep.header,
- &db_event_cb,
- rc);
- }
- } /* end initialization */
-
- if (! TEH_enable_kyc)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC not enabled\n");
- return TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
- bool do_suspend;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Looking up KYC requirements for account %s (%s)\n",
- TALER_B2S (&kyp->h_payto),
- kyp->have_pub ? "with account-pub" : "legacy wallet");
- qs = TEH_plugin->lookup_kyc_requirement_by_row (
- TEH_plugin->cls,
- &kyp->h_payto,
- kyp->have_pub,
- &kyp->account_pub,
- &is_wallet,
- &reserve_pub.reserve_pub,
- &access_token,
- &rule_gen,
- &jrules,
- &aml_review,
- &kyc_required);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (
- rc->connection,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "lookup_kyc_requirement_by_row");
- }
-
- do_suspend = false;
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- /* account unknown */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Account unknown!\n");
- if ( (TALER_EXCHANGE_KLPT_NONE != kyp->lpt) &&
- (TALER_EXCHANGE_KLPT_KYC_OK != kyp->lpt) &&
- (GNUNET_TIME_absolute_is_future (kyp->timeout)) )
- {
- do_suspend = true;
- access_ok = true; /* for now */
- }
- }
- else
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found rule %llu (client wants > %llu)\n",
- (unsigned long long) rule_gen,
- (unsigned long long) kyp->min_rule);
- access_ok =
- ( (! GNUNET_is_zero (&kyp->account_pub) &&
- (GNUNET_OK ==
- TALER_account_kyc_auth_verify (&kyp->account_pub,
- &kyp->account_sig)) ) ||
- (! GNUNET_is_zero (&reserve_pub) &&
- (GNUNET_OK ==
- TALER_account_kyc_auth_verify (&reserve_pub,
- &kyp->account_sig)) ) );
- if (GNUNET_TIME_absolute_is_future (kyp->timeout) &&
- (rule_gen <= kyp->min_rule) )
- {
- switch (kyp->lpt)
- {
- case TALER_EXCHANGE_KLPT_NONE:
- /* If KLPT is not given, just go for rule generation */
- do_suspend = true;
- break;
- case TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER:
- if (! access_ok)
- do_suspend = true;
- break;
- case TALER_EXCHANGE_KLPT_INVESTIGATION_DONE:
- if (! aml_review)
- do_suspend = true;
- break;
- case TALER_EXCHANGE_KLPT_KYC_OK:
- if (kyc_required)
- do_suspend = true;
- break;
- }
- }
- }
-
- if (do_suspend &&
- (access_ok ||
- (TALER_EXCHANGE_KLPT_KYC_AUTH_TRANSFER == kyp->lpt) ) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending HTTP request on timeout (%s) for %d\n",
- GNUNET_TIME_relative2s (GNUNET_TIME_absolute_get_remaining (
- kyp->timeout),
- true),
- (int) kyp->lpt);
- GNUNET_assert (NULL != kyp->eh);
- kyp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (kyp_head,
- kyp_tail,
- kyp);
- MHD_suspend_connection (kyp->connection);
- return MHD_YES;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning account unknown\n");
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
- NULL);
- }
- }
-
- if (! access_ok)
- {
- json_decref (jrules);
- jrules = NULL;
- if (GNUNET_is_zero (&kyp->account_pub))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_KEY_UNKNOWN,
- NULL);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning authorization failed\n");
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_KYC_CHECK_AUTHORIZATION_FAILED),
- GNUNET_JSON_pack_data_auto ("expected_account_pub",
- &kyp->account_pub));
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC rules apply to %s:\n",
- (GNUNET_SYSERR == is_wallet)
- ? "unknown account type"
- : ( (GNUNET_YES == is_wallet)
- ? "wallet"
- : "account"));
- if (NULL != jrules)
- json_dumpf (jrules,
- stderr,
- JSON_INDENT (2));
-
- jlimits = TALER_KYCLOGIC_rules_to_limits (jrules,
- is_wallet);
- if (NULL == jlimits)
- {
- GNUNET_break_op (0);
- json_decref (jrules);
- jrules = NULL;
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "/kyc-check: rules_to_limits failed");
- }
- json_decref (jrules);
- jrules = NULL;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC limits apply:\n");
- json_dumpf (jlimits,
- stderr,
- JSON_INDENT (2));
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Returning KYC %s\n",
- kyc_required ? "required" : "optional");
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- kyc_required
- ? MHD_HTTP_ACCEPTED
- : MHD_HTTP_OK,
- GNUNET_JSON_pack_bool ("aml_review",
- aml_review),
- GNUNET_JSON_pack_uint64 ("rule_gen",
- rule_gen),
- GNUNET_JSON_pack_data_auto ("access_token",
- &access_token),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal ("limits",
- jlimits)));
-}
-
-
-/* end of taler-exchange-httpd_kyc-check.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-check.h b/src/exchange/taler-exchange-httpd_kyc-check.h
@@ -1,49 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_kyc-check.h
- * @brief Handle /kyc-check requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_CHECK_H
-#define TALER_EXCHANGE_HTTPD_KYC_CHECK_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/kyc-check" request. Checks the KYC
- * status of the given account and returns it.
- *
- * @param rc details about the request to handle
- * @param args one argument with the legitimization_uuid
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_check (
- struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-/**
- * Clean up long-polling KYC requests during shutdown.
- */
-void
-TEH_kyc_check_cleanup (void);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-info.c b/src/exchange/taler-exchange-httpd_kyc-info.c
@@ -1,825 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021-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 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_kyc-info.c
- * @brief Handle request for generic KYC info.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_kyc-info.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_common_kyc.h"
-
-
-/**
- * Context for the GET /kyc-info request.
- *
- * Used for long-polling and other asynchronous waiting.
- */
-struct KycPoller
-{
- /**
- * Kept in a DLL.
- */
- struct KycPoller *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycPoller *prev;
-
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Subscription for the database event we are
- * waiting for.
- */
- struct GNUNET_DB_EventHandler *eh;
-
- /**
- * Handle to async activity to get the latest legitimization
- * rule set.
- */
- struct TALER_EXCHANGEDB_RuleUpdater *ru;
-
- /**
- * Current legitimization rule set, owned by callee. Will be NULL on error
- * or for default rules. Will not contain skip rules and not be expired.
- */
- struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs;
-
- /**
- * Handle for async KYC processing.
- */
- struct TEH_KycMeasureRunContext *kat;
-
- /**
- * Response to return, NULL if none yet.
- */
- struct MHD_Response *response;
-
- /**
- * Set to access token for a KYC process by the account,
- * if @e have_token is true.
- */
- struct TALER_AccountAccessTokenP access_token;
-
- /**
- * Payto hash of the account matching @a access_token.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client. 0 for none
- * (or malformed).
- */
- uint64_t etag_outcome_in;
-
- /**
- * #MHD_HTTP_HEADER_IF_NONE_MATCH Etag value sent by the client. 0 for none
- * (or malformed).
- */
- uint64_t etag_measure_in;
-
- /**
- * When will this request time out?
- */
- struct GNUNET_TIME_Absolute timeout;
-
- /**
- *
- */
- uint64_t legitimization_measure_last_row;
-
- /**
- * Row in the legitimization outcomes table that @e lrs matches.
- */
- uint64_t legitimization_outcome_last_row;
-
- /**
- * HTTP status code to use with @e response.
- */
- unsigned int response_code;
-
- /**
- * #GNUNET_YES if our @e h_payto is for a wallet,
- * #GNUNET_NO if it is for an account,
- * #GNUNET_SYSERR if we do not know.
- */
- enum GNUNET_GenericReturnValue is_wallet;
-
- /**
- * True if we are still suspended.
- */
- bool suspended;
-
-};
-
-
-/**
- * Head of list of requests in long polling.
- */
-static struct KycPoller *kyp_head;
-
-/**
- * Tail of list of requests in long polling.
- */
-static struct KycPoller *kyp_tail;
-
-
-void
-TEH_kyc_info_cleanup ()
-{
- struct KycPoller *kyp;
-
- while (NULL != (kyp = kyp_head))
- {
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- if (kyp->suspended)
- {
- kyp->suspended = false;
- MHD_resume_connection (kyp->connection);
- }
- }
-}
-
-
-/**
- * Function called once a connection is done to
- * clean up the `struct ReservePoller` state.
- *
- * @param rc context to clean up for
- */
-static void
-kyp_cleanup (struct TEH_RequestContext *rc)
-{
- struct KycPoller *kyp = rc->rh_ctx;
-
- GNUNET_assert (! kyp->suspended);
- if (NULL != kyp->eh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening\n");
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- kyp->eh);
- kyp->eh = NULL;
- }
- if (NULL != kyp->ru)
- {
- TALER_EXCHANGEDB_update_rules_cancel (kyp->ru);
- kyp->ru = NULL;
- }
- if (NULL != kyp->response)
- {
- MHD_destroy_response (kyp->response);
- kyp->response = NULL;
- }
- if (NULL != kyp->lrs)
- {
- TALER_KYCLOGIC_rules_free (kyp->lrs);
- kyp->lrs = NULL;
- }
- if (NULL != kyp->kat)
- {
- TEH_kyc_run_measure_cancel (kyp->kat);
- kyp->kat = NULL;
- }
- GNUNET_free (kyp);
-}
-
-
-/**
- * Function called on events received from Postgres.
- * Wakes up long pollers.
- *
- * @param cls the `struct TEH_RequestContext *`
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-db_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- struct TEH_RequestContext *rc = cls;
- struct KycPoller *kyp = rc->rh_ctx;
- struct GNUNET_AsyncScopeSave old_scope;
-
- (void) extra;
- (void) extra_size;
- if (! kyp->suspended)
- return; /* event triggered while main transaction
- was still running, or got multiple wake-up events */
- GNUNET_async_scope_enter (&rc->async_scope_id,
- &old_scope);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming from long-polling on KYC status\n");
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- kyp->suspended = false;
- MHD_resume_connection (kyp->connection);
- TALER_MHD_daemon_trigger ();
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-/**
- * Add the headers we want to set for every response.
- *
- * @param[in,out] response the response to modify
- */
-static void
-add_nocache_header (struct MHD_Response *response)
-{
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "no-cache"));
-}
-
-
-/**
- * Resume processing the @a kyp request with the @a response.
- *
- * @param kyp request to resume and respond to
- * @param http_status HTTP status for @a response
- * @param response HTTP response to return
- */
-static void
-resume_with_response (struct KycPoller *kyp,
- unsigned int http_status,
- struct MHD_Response *response)
-{
- kyp->response_code = http_status;
- kyp->response = response;
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- kyp->suspended = false;
- MHD_resume_connection (kyp->connection);
- TALER_MHD_daemon_trigger ();
-}
-
-
-/**
- * Function called after a measure has been run.
- *
- * @param kyp request to fail with the error code
- * @param ec error code or 0 on success
- * @param hint detail error message or NULL on success / no info
- */
-static void
-fail_with_ec (
- struct KycPoller *kyp,
- enum TALER_ErrorCode ec,
- const char *hint)
-{
- resume_with_response (kyp,
- TALER_ErrorCode_get_http_status (ec),
- TALER_MHD_make_error (ec,
- hint));
-}
-
-
-/**
- * Generate a reply with the KycProcessClientInformation from
- * the LegitimizationMeasures.
- *
- * @param[in,out] kyp request to reply on
- * @param legitimization_measure_row_id part of etag to set for the response
- * @param legitimization_outcome_row_id part of etag to set for the response
- * @param jmeasures a `LegitimizationMeasures` object to encode
- * @param jvoluntary array of voluntary measures to encode, can be NULL
- */
-static void
-resume_with_reply (struct KycPoller *kyp,
- uint64_t legitimization_measure_row_id,
- uint64_t legitimization_outcome_row_id,
- const json_t *jmeasures,
- const json_t *jvoluntary)
-{
- const json_t *measures; /* array of MeasureInformation */
- bool is_and_combinator = false;
- bool verboten;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_bool ("is_and_combinator",
- &is_and_combinator),
- NULL),
- GNUNET_JSON_spec_bool ("verboten",
- &verboten),
- GNUNET_JSON_spec_array_const ("measures",
- &measures),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue ret;
- const char *ename;
- unsigned int eline;
- json_t *kris;
- size_t i;
- json_t *mi; /* a MeasureInformation object */
-
- ret = GNUNET_JSON_parse (jmeasures,
- spec,
- &ename,
- &eline);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- fail_with_ec (kyp,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- ename);
- return;
- }
- kris = json_array ();
- GNUNET_assert (NULL != kris);
- json_array_foreach ((json_t *) measures, i, mi)
- {
- const char *check_name;
- const json_t *context = NULL;
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_string ("check_name",
- &check_name),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_object_const ("context",
- &context),
- NULL),
- GNUNET_JSON_spec_end ()
- };
- json_t *kri;
-
- ret = GNUNET_JSON_parse (mi,
- ispec,
- &ename,
- &eline);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- json_decref (kris);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- ename);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found required check `%s'\n",
- check_name);
- if (NULL != context)
- {
- json_dumpf (context,
- stderr,
- JSON_INDENT (2));
- fprintf (stderr,
- "\n");
- }
- /* Check if requirement is a duplicate, and in that case do
- not return it */
- {
- bool duplicate = false;
-
- for (size_t off = 0; off < i; off++)
- {
- json_t *have = json_array_get (measures,
- off);
- if ( (1 ==
- json_equal (json_object_get (have,
- "check_name"),
- json_object_get (mi,
- "check_name")) ) &&
- (1 ==
- json_equal (json_object_get (have,
- "check_name"),
- json_object_get (mi,
- "check_name")) ) &&
- (1 ==
- json_equal (json_object_get (have,
- "check_name"),
- json_object_get (mi,
- "check_name")) ) )
- {
- /* Duplicate requirement, do not return again */
- duplicate = true;
- break;
- }
- }
- if (duplicate)
- {
- continue;
- }
- }
- kri = TALER_KYCLOGIC_measure_to_requirement (
- check_name,
- context,
- &kyp->access_token,
- i,
- legitimization_measure_row_id);
- if (NULL == kri)
- {
- GNUNET_break (0);
- json_decref (kris);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "could not convert measure to requirement");
- return;
- }
- GNUNET_assert (0 ==
- json_array_append_new (kris,
- kri));
- }
-
- {
- char etags[128];
- struct MHD_Response *resp;
-
- GNUNET_snprintf (etags,
- sizeof (etags),
- "\"%llu-%llu\"",
- (unsigned long long) legitimization_measure_row_id,
- (unsigned long long) legitimization_outcome_row_id);
- resp = TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_array_steal ("requirements",
- kris),
- GNUNET_JSON_pack_bool ("is_and_combinator",
- is_and_combinator),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_incref (
- "voluntary_measures",
- (json_t *) jvoluntary)));
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- etags));
- add_nocache_header (resp);
- resume_with_response (kyp,
- MHD_HTTP_OK,
- resp);
- }
-}
-
-
-/**
- * Function called with the current rule set.
- *
- * @param cls closure with a `struct KycPoller *`
- * @param rur includes legitimziation rule set that applies to the account
- * (owned by callee, callee must free the lrs!)
- */
-static void
-current_rules_cb (
- void *cls,
- struct TALER_EXCHANGEDB_RuleUpdaterResult *rur)
-{
- struct KycPoller *kyp = cls;
- enum GNUNET_DB_QueryStatus qs;
- uint64_t legitimization_measure_last_row;
- json_t *jmeasures;
- json_t *vmeasures;
-
- kyp->ru = NULL;
- if (TALER_EC_NONE != rur->ec)
- {
- /* Rollback should not be needed, just to be sure */
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (kyp,
- rur->ec,
- rur->hint);
- return;
- }
- GNUNET_assert (NULL == kyp->lrs);
- kyp->lrs
- = rur->lrs;
- kyp->legitimization_outcome_last_row
- = rur->legitimization_outcome_last_row;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "LRS is for the account uses %s\n",
- NULL == kyp->lrs
- ? "default rules"
- : "custom rules");
-
- /* Check if there is an unfinished legitimization measure */
- qs = TEH_plugin->lookup_kyc_status_by_token (
- TEH_plugin->cls,
- &kyp->access_token,
- &legitimization_measure_last_row,
- &jmeasures);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "lookup_kyc_status_by_token returned %d\n",
- (int) qs);
- if (qs < 0)
- {
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_kyc_status_by_token");
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- jmeasures
- = TALER_KYCLOGIC_zero_measures (kyp->lrs,
- kyp->is_wallet);
- if (NULL == jmeasures)
- {
- qs = TEH_plugin->commit (TEH_plugin->cls);
- if (qs < 0)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- "kyc-info");
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "No KYC requirement open\n");
- resume_with_response (kyp,
- MHD_HTTP_OK,
- TALER_MHD_MAKE_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_array_steal (
- "voluntary_measures",
- TALER_KYCLOGIC_voluntary_measures (
- kyp->lrs))
- )));
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Making applicable zero-measures for %s under current rules active\n",
- (GNUNET_SYSERR == kyp->is_wallet)
- ? "unknown account type"
- : ( (GNUNET_YES == kyp->is_wallet)
- ? "wallets"
- : "accounts"));
- json_dumpf (jmeasures,
- stderr,
- JSON_INDENT (2));
- qs = TEH_plugin->insert_active_legitimization_measure (
- TEH_plugin->cls,
- &kyp->access_token,
- jmeasures,
- &legitimization_measure_last_row);
- if (qs < 0)
- {
- GNUNET_break (0);
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (kyp,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_active_legitimization_measure");
- return;
- }
- }
- if ( (legitimization_measure_last_row == kyp->etag_measure_in) &&
- (kyp->legitimization_outcome_last_row == kyp->etag_outcome_in) &&
- GNUNET_TIME_absolute_is_future (kyp->timeout) )
- {
- /* Note: in practice this commit should do nothing, but we cannot
- trust that the client provided correct etags, and so we must
- commit anyway just in case the client lied about the etags. */
- qs = TEH_plugin->commit (TEH_plugin->cls);
- if (qs < 0)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- "kyc-info");
- return;
- }
- if (NULL != kyp->lrs)
- {
- TALER_KYCLOGIC_rules_free (kyp->lrs);
- kyp->lrs = NULL;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Suspending HTTP request on timeout (%s)\n",
- GNUNET_TIME_relative2s (
- GNUNET_TIME_absolute_get_remaining (
- kyp->timeout),
- true));
- GNUNET_assert (NULL != kyp->eh);
- GNUNET_break (kyp->suspended);
- return;
- }
- qs = TEH_plugin->commit (TEH_plugin->cls);
- if (qs < 0)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- fail_with_ec (
- kyp,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- "kyc-info");
- return;
- }
- if ( (legitimization_measure_last_row ==
- kyp->etag_measure_in) &&
- (kyp->legitimization_outcome_last_row ==
- kyp->etag_outcome_in) )
- {
- char etags[128];
- struct MHD_Response *resp;
-
- GNUNET_snprintf (etags,
- sizeof (etags),
- "\"%llu-%llu\"",
- (unsigned long long) legitimization_measure_last_row,
- (unsigned long long) kyp->legitimization_outcome_last_row);
- resp = MHD_create_response_from_buffer (0,
- NULL,
- MHD_RESPMEM_PERSISTENT);
- add_nocache_header (resp);
- TALER_MHD_add_global_headers (resp,
- false);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- etags));
- resume_with_response (kyp,
- MHD_HTTP_NOT_MODIFIED,
- resp);
- json_decref (jmeasures);
- return;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Generating success reply to kyc-info query\n");
- vmeasures = TALER_KYCLOGIC_voluntary_measures (kyp->lrs);
- resume_with_reply (kyp,
- legitimization_measure_last_row,
- kyp->legitimization_outcome_last_row,
- jmeasures,
- vmeasures);
- json_decref (vmeasures);
- json_decref (jmeasures);
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_info (
- struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct KycPoller *kyp = rc->rh_ctx;
- enum GNUNET_DB_QueryStatus qs;
-
- if (NULL == kyp)
- {
- bool bis_wallet;
-
- kyp = GNUNET_new (struct KycPoller);
- kyp->connection = rc->connection;
- rc->rh_ctx = kyp;
- rc->rh_cleaner = &kyp_cleanup;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (
- args[0],
- strlen (args[0]),
- &kyp->access_token,
- sizeof (kyp->access_token)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "access token");
- }
- TALER_MHD_parse_request_timeout (rc->connection,
- &kyp->timeout);
-
- /* Get etag */
- {
- const char *etags;
-
- etags = MHD_lookup_connection_value (
- rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- if (NULL != etags)
- {
- char dummy;
- unsigned long long ev1;
- unsigned long long ev2;
-
- if (2 != sscanf (etags,
- "\"%llu-%llu\"%c",
- &ev1,
- &ev2,
- &dummy))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client send malformed `%s' header `%s'\n",
- MHD_HTTP_HEADER_IF_NONE_MATCH,
- etags);
- }
- else
- {
- kyp->etag_measure_in = (uint64_t) ev1;
- kyp->etag_outcome_in = (uint64_t) ev2;
- }
- }
- } /* etag */
-
- /* Check access token */
- kyp->is_wallet = GNUNET_SYSERR;
- qs = TEH_plugin->lookup_h_payto_by_access_token (
- TEH_plugin->cls,
- &kyp->access_token,
- &kyp->h_payto,
- &bis_wallet);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (
- rc->connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_h_payto_by_access_token");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break_op (0);
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_KYC_INFO_AUTHORIZATION_FAILED));
- }
- kyp->is_wallet = (bis_wallet) ? GNUNET_YES : GNUNET_NO;
-
- if (GNUNET_TIME_absolute_is_future (kyp->timeout))
- {
- struct TALER_KycCompletedEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_KYC_COMPLETED),
- .h_payto = kyp->h_payto
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening\n");
- kyp->eh = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (kyp->timeout),
- &rep.header,
- &db_event_cb,
- rc);
- }
- } /* end of one-time initialization */
-
- if (NULL != kyp->response)
- {
- return MHD_queue_response (rc->connection,
- kyp->response_code,
- kyp->response);
- }
-
- kyp->ru = TALER_EXCHANGEDB_update_rules (TEH_plugin,
- &TEH_attribute_key,
- &kyp->h_payto,
- kyp->is_wallet,
- ¤t_rules_cb,
- kyp);
- kyp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (kyp_head,
- kyp_tail,
- kyp);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
-
-}
-
-
-/* end of taler-exchange-httpd_kyc-info.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-info.h b/src/exchange/taler-exchange-httpd_kyc-info.h
@@ -1,49 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_kyc-info.h
- * @brief Handle /kyc-info requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_INFO_H
-#define TALER_EXCHANGE_HTTPD_KYC_INFO_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Shutdown kyc-info subsystem. Resumes all suspended long-polling clients
- * and cleans up data structures.
- */
-void
-TEH_kyc_info_cleanup (void);
-
-
-/**
- * Handle a "/kyc-info" request.
- *
- * @param rc request to handle
- * @param args one argument with the legitimization_uuid
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_info (
- struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.c b/src/exchange/taler-exchange-httpd_kyc-proof.c
@@ -1,641 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021-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_kyc-proof.c
- * @brief Handle request for proof for KYC check.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_attributes.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_templating_lib.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_kyc-proof.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Context for the proof.
- */
-struct KycProofContext
-{
-
- /**
- * Kept in a DLL while suspended.
- */
- struct KycProofContext *next;
-
- /**
- * Kept in a DLL while suspended.
- */
- struct KycProofContext *prev;
-
- /**
- * Details about the connection we are processing.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Proof logic to run.
- */
- struct TALER_KYCLOGIC_Plugin *logic;
-
- /**
- * Configuration for @a logic.
- */
- struct TALER_KYCLOGIC_ProviderDetails *pd;
-
- /**
- * Asynchronous operation with the proof system.
- */
- struct TALER_KYCLOGIC_ProofHandle *ph;
-
- /**
- * KYC AML trigger operation.
- */
- struct TEH_KycMeasureRunContext *kat;
-
- /**
- * Process information about the user for the plugin from the database, can
- * be NULL.
- */
- char *provider_user_id;
-
- /**
- * Process information about the legitimization process for the plugin from the
- * database, can be NULL.
- */
- char *provider_legitimization_id;
-
- /**
- * Hash of payment target URI this is about.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * Final HTTP response to return.
- */
- struct MHD_Response *response;
-
- /**
- * Final HTTP response code to return.
- */
- unsigned int response_code;
-
- /**
- * HTTP response from the KYC provider plugin.
- */
- struct MHD_Response *proof_response;
-
- /**
- * HTTP response code from the KYC provider plugin.
- */
- unsigned int proof_response_code;
-
- /**
- * Provider configuration section name of the logic we are running.
- */
- const char *provider_name;
-
- /**
- * Row in the database for this legitimization operation.
- */
- uint64_t process_row;
-
- /**
- * True if we are suspended,
- */
- bool suspended;
-
- /**
- * True if @e h_payto is for a wallet.
- */
- bool is_wallet;
-
-};
-
-
-/**
- * Contexts are kept in a DLL while suspended.
- */
-static struct KycProofContext *kpc_head;
-
-/**
- * Contexts are kept in a DLL while suspended.
- */
-static struct KycProofContext *kpc_tail;
-
-
-/**
- * Generate HTML error for @a connection using @a template.
- *
- * @param connection HTTP client connection
- * @param template template to expand
- * @param[in,out] http_status HTTP status of the response
- * @param ec Taler error code to return
- * @param message extended message to return
- * @return MHD response object
- */
-static struct MHD_Response *
-make_html_error (struct MHD_Connection *connection,
- const char *template,
- unsigned int *http_status,
- enum TALER_ErrorCode ec,
- const char *message)
-{
- struct MHD_Response *response = NULL;
- json_t *body;
- enum GNUNET_GenericReturnValue ret;
-
- body = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("message",
- message)),
- TALER_JSON_pack_ec (
- ec));
- ret = TALER_TEMPLATING_build (connection,
- http_status,
- template,
- NULL,
- NULL,
- body,
- &response);
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_SYSERR != ret)
- GNUNET_break (MHD_NO !=
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CONTENT_TYPE,
- "text/html"));
-
- json_decref (body);
- return response;
-}
-
-
-/**
- * Resume processing the @a kpc request.
- *
- * @param kpc request to resume
- */
-static void
-kpc_resume (struct KycProofContext *kpc)
-{
- GNUNET_assert (GNUNET_YES == kpc->suspended);
- kpc->suspended = false;
- GNUNET_CONTAINER_DLL_remove (kpc_head,
- kpc_tail,
- kpc);
- MHD_resume_connection (kpc->rc->connection);
- TALER_MHD_daemon_trigger ();
-}
-
-
-void
-TEH_kyc_proof_cleanup (void)
-{
- struct KycProofContext *kpc;
-
- while (NULL != (kpc = kpc_head))
- {
- if (NULL != kpc->ph)
- {
- kpc->logic->proof_cancel (kpc->ph);
- kpc->ph = NULL;
- }
- kpc_resume (kpc);
- }
-}
-
-
-/**
- * Function called after the KYC-AML trigger is done.
- *
- * @param cls closure
- * @param ec error code or 0 on success
- * @param detail error message or NULL on success / no info
- */
-static void
-proof_finish (
- void *cls,
- enum TALER_ErrorCode ec,
- const char *detail)
-{
- struct KycProofContext *kpc = cls;
-
- kpc->kat = NULL;
- if (TALER_EC_NONE != ec)
- {
- kpc->response_code = TALER_ErrorCode_get_http_status (ec);
- GNUNET_break (5 != kpc->response_code / 100);
- GNUNET_assert (kpc->response_code != UINT_MAX);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Templating error response for %d and HTTP status %u (%s)\n",
- (int) ec,
- kpc->response_code,
- detail);
- kpc->response = make_html_error (
- kpc->rc->connection,
- "kyc-proof-internal-error",
- &kpc->response_code,
- ec,
- detail);
- }
- else
- {
- GNUNET_assert (NULL != kpc->proof_response);
- kpc->response_code = kpc->proof_response_code;
- kpc->response = kpc->proof_response;
- kpc->proof_response = NULL;
- kpc->proof_response_code = 0;
- }
- GNUNET_assert (NULL != kpc->response);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Resuming with response %p and status %u\n",
- kpc->response,
- kpc->response_code);
- kpc_resume (kpc);
-}
-
-
-/**
- * Respond with an HTML message on the given @a rc.
- *
- * @param[in,out] rc request to respond to
- * @param http_status HTTP status code to use
- * @param template template to fill in
- * @param ec error code to use for the template
- * @param message additional message to return
- * @return MHD result code
- */
-static MHD_RESULT
-respond_html_ec (struct TEH_RequestContext *rc,
- unsigned int http_status,
- const char *template,
- enum TALER_ErrorCode ec,
- const char *message)
-{
- struct MHD_Response *response;
- MHD_RESULT res;
-
- response = make_html_error (rc->connection,
- template,
- &http_status,
- ec,
- message);
- res = MHD_queue_response (rc->connection,
- http_status,
- response);
- MHD_destroy_response (response);
- return res;
-}
-
-
-/**
- * Function called with the result of a proof check operation.
- *
- * Note that the "decref" for the @a response
- * will be done by the callee and MUST NOT be done by the plugin.
- *
- * @param cls closure
- * @param status KYC status
- * @param provider_name name of the provider
- * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
- * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
- * @param expiration until when is the KYC check valid
- * @param attributes user attributes returned by the provider
- * @param http_status HTTP status code of @a response
- * @param[in] response to return to the HTTP client
- */
-static void
-proof_cb (
- void *cls,
- enum TALER_KYCLOGIC_KycStatus status,
- const char *provider_name,
- const char *provider_user_id,
- const char *provider_legitimization_id,
- struct GNUNET_TIME_Absolute expiration,
- const json_t *attributes,
- unsigned int http_status,
- struct MHD_Response *response)
-{
- struct KycProofContext *kpc = cls;
- struct TEH_RequestContext *rc = kpc->rc;
- struct GNUNET_AsyncScopeSave old_scope;
- enum GNUNET_DB_QueryStatus qs;
-
- kpc->ph = NULL;
- kpc->proof_response = response;
- kpc->proof_response_code = http_status;
- GNUNET_async_scope_enter (&rc->async_scope_id,
- &old_scope);
- switch (status)
- {
- case TALER_KYCLOGIC_STATUS_SUCCESS:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC process #%llu succeeded with KYC provider\n",
- (unsigned long long) kpc->process_row);
- qs = TEH_kyc_store_attributes (
- kpc->process_row,
- &kpc->h_payto,
- provider_name,
- provider_user_id,
- provider_legitimization_id,
- expiration,
- attributes);
- if (0 >= qs)
- {
- GNUNET_break (0);
- proof_finish (kpc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "kyc_store_attributes");
- break;
- }
-
- kpc->kat = TEH_kyc_run_measure_for_attributes (
- &rc->async_scope_id,
- kpc->process_row,
- &kpc->h_payto,
- kpc->is_wallet,
- &proof_finish,
- kpc);
- if (NULL == kpc->kat)
- {
- GNUNET_break_op (0);
- proof_finish (kpc,
- TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
- NULL);
- }
- break;
- case TALER_KYCLOGIC_STATUS_FAILED:
- case TALER_KYCLOGIC_STATUS_PROVIDER_FAILED:
- case TALER_KYCLOGIC_STATUS_USER_ABORTED:
- case TALER_KYCLOGIC_STATUS_ABORTED:
- GNUNET_assert (NULL == kpc->kat);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC process %s/%s (Row #%llu) failed: %d\n",
- provider_user_id,
- provider_legitimization_id,
- (unsigned long long) kpc->process_row,
- status);
- if (5 == http_status / 100)
- {
- char *msg;
-
- /* OAuth2 server had a problem, do NOT log this as a KYC failure */
- GNUNET_break (0);
- GNUNET_asprintf (&msg,
- "Failure by KYC provider (HTTP status %u)\n",
- http_status);
- proof_finish (
- kpc,
- TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY,
- msg);
- GNUNET_free (msg);
- break;
- }
- if (! TEH_kyc_failed (
- kpc->process_row,
- &kpc->h_payto,
- kpc->provider_name,
- provider_user_id,
- provider_legitimization_id,
- TALER_KYCLOGIC_status2s (status),
- TALER_EC_EXCHANGE_GENERIC_KYC_FAILED))
- {
- GNUNET_break (0);
- proof_finish (
- kpc,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "TEH_kyc_failed");
- break;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "KYC process #%llu failed with status %d\n",
- (unsigned long long) kpc->process_row,
- status);
- proof_finish (kpc,
- TALER_EC_NONE,
- NULL);
- break;
- default:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC status of %s/%s (Row #%llu) is %d\n",
- provider_user_id,
- provider_legitimization_id,
- (unsigned long long) kpc->process_row,
- (int) status);
- break;
- }
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-/**
- * Function called to clean up a context.
- *
- * @param rc request context
- */
-static void
-clean_kpc (struct TEH_RequestContext *rc)
-{
- struct KycProofContext *kpc = rc->rh_ctx;
-
- if (NULL != kpc->ph)
- {
- kpc->logic->proof_cancel (kpc->ph);
- kpc->ph = NULL;
- }
- if (NULL != kpc->kat)
- {
- TEH_kyc_run_measure_cancel (kpc->kat);
- kpc->kat = NULL;
- }
- if (NULL != kpc->response)
- {
- MHD_destroy_response (kpc->response);
- kpc->response = NULL;
- }
- if (NULL != kpc->proof_response)
- {
- MHD_destroy_response (kpc->proof_response);
- kpc->proof_response = NULL;
- }
- GNUNET_free (kpc->provider_user_id);
- GNUNET_free (kpc->provider_legitimization_id);
- GNUNET_free (kpc);
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_proof (
- struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct KycProofContext *kpc = rc->rh_ctx;
- const char *provider_name_or_logic = args[0];
-
- if (NULL == kpc)
- {
- /* first time */
- if (NULL == provider_name_or_logic)
- {
- GNUNET_break_op (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_NOT_FOUND,
- "kyc-proof-endpoint-unknown",
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- "'/kyc-proof/$PROVIDER_NAME?state=$H_PAYTO' required");
- }
- kpc = GNUNET_new (struct KycProofContext);
- kpc->rc = rc;
- rc->rh_ctx = kpc;
- rc->rh_cleaner = &clean_kpc;
- TALER_MHD_parse_request_arg_auto_t (rc->connection,
- "state",
- &kpc->h_payto);
- if (GNUNET_OK !=
- TALER_KYCLOGIC_lookup_logic (
- provider_name_or_logic,
- &kpc->logic,
- &kpc->pd,
- &kpc->provider_name))
- {
- GNUNET_break_op (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_NOT_FOUND,
- "kyc-proof-target-unknown",
- TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_UNKNOWN,
- provider_name_or_logic);
- }
- if (NULL != kpc->provider_name)
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute expiration;
-
- if (0 != strcmp (provider_name_or_logic,
- kpc->provider_name))
- {
- GNUNET_break_op (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_BAD_REQUEST,
- "kyc-proof-bad-request",
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "PROVIDER_NAME");
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Looking for KYC process at %s\n",
- kpc->provider_name);
- qs = TEH_plugin->lookup_kyc_process_by_account (
- TEH_plugin->cls,
- kpc->provider_name,
- &kpc->h_payto,
- &kpc->process_row,
- &expiration,
- &kpc->provider_user_id,
- &kpc->provider_legitimization_id,
- &kpc->is_wallet);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "kyc-proof-internal-error",
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_kyc_process_by_account");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break_op (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_NOT_FOUND,
- "kyc-proof-target-unknown",
- TALER_EC_EXCHANGE_KYC_PROOF_REQUEST_UNKNOWN,
- kpc->provider_name);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- if (GNUNET_TIME_absolute_is_future (expiration))
- {
- /* KYC not required */
- return respond_html_ec (
- rc,
- MHD_HTTP_OK,
- "kyc-proof-already-done",
- TALER_EC_NONE,
- NULL);
- }
- }
- kpc->ph = kpc->logic->proof (
- kpc->logic->cls,
- kpc->pd,
- rc->connection,
- &kpc->h_payto,
- kpc->process_row,
- kpc->provider_user_id,
- kpc->provider_legitimization_id,
- &proof_cb,
- kpc);
- if (NULL == kpc->ph)
- {
- GNUNET_break (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "kyc-proof-internal-error",
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "could not start proof with KYC logic");
- }
-
-
- kpc->suspended = true;
- GNUNET_CONTAINER_DLL_insert (kpc_head,
- kpc_tail,
- kpc);
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
- }
-
- if (NULL == kpc->response)
- {
- GNUNET_break (0);
- return respond_html_ec (
- rc,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- "kyc-proof-internal-error",
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "handler resumed without response");
- }
-
- /* return response from KYC logic */
- return MHD_queue_response (rc->connection,
- kpc->response_code,
- kpc->response);
-}
-
-
-/* end of taler-exchange-httpd_kyc-proof.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-proof.h b/src/exchange/taler-exchange-httpd_kyc-proof.h
@@ -1,49 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_kyc-proof.h
- * @brief Handle /kyc-proof requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_PROOF_H
-#define TALER_EXCHANGE_HTTPD_KYC_PROOF_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Shutdown kyc-proof subsystem. Resumes all suspended long-polling clients
- * and cleans up data structures.
- */
-void
-TEH_kyc_proof_cleanup (void);
-
-
-/**
- * Handle a "/kyc-proof" request.
- *
- * @param rc request to handle
- * @param args one argument with the legitimization_uuid
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_proof (
- struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-start.c b/src/exchange/taler-exchange-httpd_kyc-start.c
@@ -1,537 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021-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 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_kyc-start.c
- * @brief Handle request for starting a KYC process with an external provider.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_kyc-start.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * POST request in asynchronous processing.
- */
-struct KycPoller
-{
-
- /**
- * Access token for the KYC data of the account.
- */
- struct TALER_AccountAccessTokenP access_token;
-
- /**
- * Authorization hash for the selected measure.
- */
- struct TALER_KycMeasureAuthorizationHashP shv;
-
- /**
- * Hash of the payto:// URI we are starting to the KYC for.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * Kept in a DLL.
- */
- struct KycPoller *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycPoller *prev;
-
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Logic for @e ih
- */
- struct TALER_KYCLOGIC_Plugin *ih_logic;
-
- /**
- * Handle to asynchronously running KYC initiation
- * request.
- */
- struct TALER_KYCLOGIC_InitiateHandle *ih;
-
- /**
- * Set of applicable KYC measures.
- */
- json_t *jmeasures;
-
- /**
- * Where to redirect the user to start the KYC process.
- */
- char *redirect_url;
-
- /**
- * Set to the name of the KYC provider.
- */
- const char *provider_name;
-
- /**
- * Set to error details, on error (@ec not TALER_EC_NONE).
- */
- char *hint;
-
- /**
- * Row of the requirement being started.
- */
- unsigned long long legitimization_measure_serial_id;
-
- /**
- * Row of KYC process being initiated.
- */
- uint64_t process_row;
-
- /**
- * Index of the measure this upload is for.
- */
- unsigned int measure_index;
-
- /**
- * Set to error encountered with KYC logic, if any.
- */
- enum TALER_ErrorCode ec;
-
- /**
- * True if we are still suspended.
- */
- bool suspended;
-
- /**
- * True if @e h_payto is for a wallet
- */
- bool is_wallet;
-
-};
-
-
-/**
- * Head of list of requests in asynchronous processing.
- */
-static struct KycPoller *kyp_head;
-
-/**
- * Tail of list of requests in asynchronous processing.
- */
-static struct KycPoller *kyp_tail;
-
-
-void
-TEH_kyc_start_cleanup ()
-{
- struct KycPoller *kyp;
-
- while (NULL != (kyp = kyp_head))
- {
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- if (NULL != kyp->ih)
- {
- kyp->ih_logic->initiate_cancel (kyp->ih);
- kyp->ih = NULL;
- }
- if (kyp->suspended)
- {
- kyp->suspended = false;
- MHD_resume_connection (kyp->connection);
- }
- }
-}
-
-
-/**
- * Function called once a connection is done to
- * clean up the `struct ReservePoller` state.
- *
- * @param rc context to clean up for
- */
-static void
-kyp_cleanup (struct TEH_RequestContext *rc)
-{
- struct KycPoller *kyp = rc->rh_ctx;
-
- GNUNET_assert (! kyp->suspended);
- if (NULL != kyp->ih)
- {
- kyp->ih_logic->initiate_cancel (kyp->ih);
- kyp->ih = NULL;
- }
- GNUNET_free (kyp->redirect_url);
- GNUNET_free (kyp->hint);
- json_decref (kyp->jmeasures);
- GNUNET_free (kyp);
-}
-
-
-/**
- * Function called with the result of a KYC initiation
- * operation.
- *
- * @param cls closure with our `struct KycPoller *`
- * @param ec #TALER_EC_NONE on success
- * @param redirect_url set to where to redirect the user on success, NULL on failure
- * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
- * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
- * @param error_msg_hint set to additional details to return to user, NULL on success
- */
-static void
-initiate_cb (
- void *cls,
- enum TALER_ErrorCode ec,
- const char *redirect_url,
- const char *provider_user_id,
- const char *provider_legitimization_id,
- const char *error_msg_hint)
-{
- struct KycPoller *kyp = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- kyp->ih = NULL;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "KYC initiation `%s' completed with ec=%d (%s)\n",
- provider_legitimization_id,
- ec,
- (TALER_EC_NONE == ec)
- ? redirect_url
- : error_msg_hint);
- kyp->ec = ec;
- if (TALER_EC_NONE == ec)
- {
- kyp->redirect_url = GNUNET_strdup (redirect_url);
- }
- else
- {
- kyp->hint = GNUNET_strdup (error_msg_hint);
- }
- qs = TEH_plugin->update_kyc_process_by_row (
- TEH_plugin->cls,
- kyp->process_row,
- kyp->provider_name,
- &kyp->h_payto,
- provider_user_id,
- provider_legitimization_id,
- redirect_url,
- GNUNET_TIME_UNIT_ZERO_ABS,
- ec,
- error_msg_hint,
- TALER_EC_NONE != ec);
- if (qs <= 0)
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "KYC requirement update failed for %s with status %d at %s:%u\n",
- TALER_B2S (&kyp->h_payto),
- qs,
- __FILE__,
- __LINE__);
- GNUNET_assert (kyp->suspended);
- kyp->suspended = false;
- GNUNET_CONTAINER_DLL_remove (kyp_head,
- kyp_tail,
- kyp);
- MHD_resume_connection (kyp->connection);
- TALER_MHD_daemon_trigger ();
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_start (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1])
-{
- struct KycPoller *kyp = rc->rh_ctx;
-
- (void) root;
- if (NULL == kyp)
- {
- const char *id = args[0];
- enum GNUNET_DB_QueryStatus qs;
- const struct TALER_KYCLOGIC_KycProvider *provider;
- struct TALER_KYCLOGIC_ProviderDetails *pd;
- bool is_finished;
- const json_t *context;
-
- kyp = GNUNET_new (struct KycPoller);
- kyp->connection = rc->connection;
- rc->rh_ctx = kyp;
- rc->rh_cleaner = &kyp_cleanup;
-
- {
- char dummy;
- const char *slash;
-
- slash = strchr (id, '-');
- if (NULL == slash)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (id,
- slash - id,
- &kyp->shv,
- sizeof (kyp->shv)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "Authorization hash in ID is malformed");
- }
- if (2 !=
- sscanf (slash + 1,
- "%u-%llu%c",
- &kyp->measure_index,
- &kyp->legitimization_measure_serial_id,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "ID is malformed");
- }
- }
- qs = TEH_plugin->lookup_pending_legitimization (
- TEH_plugin->cls,
- kyp->legitimization_measure_serial_id,
- &kyp->access_token,
- &kyp->h_payto,
- &kyp->jmeasures,
- &is_finished,
- &kyp->is_wallet);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
- return TALER_MHD_reply_with_ec (
- rc->connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_pending_legitimization");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- if (is_finished)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
- rc->url);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Found pending legitimization %llu with measures\n",
- (unsigned long long) kyp->legitimization_measure_serial_id);
- json_dumpf (kyp->jmeasures,
- stderr,
- JSON_INDENT (2));
- fprintf (stderr,
- "\n");
-
- {
- struct TALER_KycMeasureAuthorizationHashP shv2;
-
- TALER_kyc_measure_authorization_hash (
- &kyp->access_token,
- kyp->legitimization_measure_serial_id,
- kyp->measure_index,
- &shv2);
- if (0 !=
- GNUNET_memcmp (&kyp->shv,
- &shv2))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- }
-
- {
- const char *check_name;
- const char *prog_name;
-
- kyp->ec = TALER_KYCLOGIC_select_measure (
- kyp->jmeasures,
- kyp->measure_index,
- &check_name,
- &prog_name,
- &context);
- if (TALER_EC_NONE != kyp->ec)
- {
- /* return EC in next call to this function */
- GNUNET_break_op (0);
- kyp->hint
- = GNUNET_strdup ("TALER_KYCLOGIC_select_measure");
- return MHD_YES;
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Selected measure is %llu with check `%s'\n",
- (unsigned long long) kyp->measure_index,
- check_name);
- if (NULL != context)
- {
- json_dumpf (context,
- stderr,
- JSON_INDENT (2));
- fprintf (stderr,
- "\n");
- }
-
- provider = TALER_KYCLOGIC_check_to_provider (
- check_name);
- if (NULL == provider)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK,
- check_name);
- }
- }
-
- TALER_KYCLOGIC_provider_to_logic (
- provider,
- &kyp->ih_logic,
- &pd,
- &kyp->provider_name);
-
- /* FIXME-#9419: the next two DB interactions should be ONE
- transaction */
- /* Check if we already initiated this process */
- qs = TEH_plugin->get_pending_kyc_requirement_process (
- TEH_plugin->cls,
- &kyp->h_payto,
- kyp->provider_name,
- &kyp->redirect_url);
- if (qs < 0)
- {
- GNUNET_break (0);
- /* Simple query, never should be a soft error. */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_pending_kyc_requirement_process");
- }
- if (NULL != kyp->redirect_url)
- return MHD_YES; /* success, return the redirect URL
- (in next call to this function) */
-
- /* set up new requirement process */
- qs = TEH_plugin->insert_kyc_requirement_process (
- TEH_plugin->cls,
- &kyp->h_payto,
- kyp->measure_index,
- kyp->legitimization_measure_serial_id,
- kyp->provider_name,
- NULL, /* provider_account_id */
- NULL, /* provider_legitimziation_id */
- &kyp->process_row);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_kyc_requirement_process");
- }
-
- kyp->ih = kyp->ih_logic->initiate (
- kyp->ih_logic->cls,
- pd,
- &kyp->h_payto,
- kyp->process_row,
- context,
- &initiate_cb,
- kyp);
- if (NULL == kyp->ih)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
- "initiate KYC process");
- }
- kyp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (kyp_head,
- kyp_tail,
- kyp);
- MHD_suspend_connection (kyp->connection);
- return MHD_YES;
- }
-
- if ( (TALER_EC_NONE != kyp->ec) ||
- (NULL == kyp->redirect_url) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "KYC process failed to start with error %d (%s)\n",
- (int) kyp->ec,
- kyp->hint);
- if (TALER_EC_NONE == kyp->ec)
- {
- GNUNET_break (0);
- kyp->ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
- }
- return TALER_MHD_reply_with_ec (rc->connection,
- kyp->ec,
- kyp->hint);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("redirect_url",
- kyp->redirect_url));
-}
-
-
-/* end of taler-exchange-httpd_kyc-start.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-start.h b/src/exchange/taler-exchange-httpd_kyc-start.h
@@ -1,50 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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 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_kyc-start.h
- * @brief Handle /kyc-start requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_START_H
-#define TALER_EXCHANGE_HTTPD_KYC_START_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume suspended requests for /kyc-start.
- */
-void
-TEH_kyc_start_cleanup (void);
-
-
-/**
- * Handle a "/kyc-start" request.
- *
- * @param rc request to handle
- * @param root uploaded JSON data (empty by current API)
- * @param args array with the ID
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_start (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-upload.c b/src/exchange/taler-exchange-httpd_kyc-upload.c
@@ -1,567 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_kyc-upload.c
- * @brief Handle /kyc-upload/$ID request
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_kyc-upload.h"
-
-#define MAX_RETRIES 3
-
-/**
- * Context used for processing the KYC upload req
- */
-struct UploadContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct UploadContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct UploadContext *prev;
-
- /**
- * Access token for the KYC data of the account.
- */
- struct TALER_AccountAccessTokenP access_token;
-
- /**
- * Index of the measure this upload is for.
- */
- unsigned int measure_index;
-
- /**
- * HTTP status code to use with @e response.
- */
- unsigned int response_code;
-
- /**
- * Index in the legitimization measures table this ID
- * refers to.
- */
- unsigned long long legitimization_measure_serial_id;
-
- /**
- * Response to return, NULL if none yet.
- */
- struct MHD_Response *response;
-
- /**
- * Request we are processing.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for async KYC processing.
- */
- struct TEH_KycMeasureRunContext *kat;
-
- /**
- * Uploaded data, in JSON.
- */
- const json_t *result;
-
- /**
- * Set by the transaction to the legitimization process row.
- */
- uint64_t legi_process_row;
-
- /**
- * Set by the transaction to the affected account payto hash.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * Set by the transaction to true if the account is for a wallet.
- */
- bool is_wallet;
-
-};
-
-
-/**
- * Kept in a DLL.
- */
-static struct UploadContext *uc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct UploadContext *uc_tail;
-
-
-void
-TEH_kyc_upload_cleanup ()
-{
- struct UploadContext *uc;
-
- while (NULL != (uc = uc_head))
- {
- MHD_resume_connection (uc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (uc_head,
- uc_tail,
- uc);
- }
-}
-
-
-/**
- * Function called to clean up upload context.
- *
- * @param[in,out] rc context to clean up
- */
-static void
-upload_cleaner (struct TEH_RequestContext *rc)
-{
- struct UploadContext *uc = rc->rh_ctx;
-
- if (NULL != uc->kat)
- {
- TEH_kyc_run_measure_cancel (uc->kat);
- uc->kat = NULL;
- }
- if (NULL != uc->response)
- {
- MHD_destroy_response (uc->response);
- uc->response = NULL;
- }
- GNUNET_free (uc);
-}
-
-
-/**
- * Function called after the KYC-AML trigger is done.
- *
- * @param cls closure
- * @param ec error code or 0 on success
- * @param detail error message or NULL on success / no info
- */
-static void
-aml_trigger_callback (
- void *cls,
- enum TALER_ErrorCode ec,
- const char *detail)
-{
- struct UploadContext *uc = cls;
-
- uc->kat = NULL;
- GNUNET_assert (NULL == uc->response);
- if (TALER_EC_NONE != ec)
- {
- uc->response_code = TALER_ErrorCode_get_http_status (ec);
- if (0 == uc->response_code)
- {
- GNUNET_break (0);
- uc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- }
- GNUNET_assert (uc->response_code != UINT_MAX);
- uc->response = TALER_MHD_make_error (
- ec,
- detail);
- }
- else
- {
- uc->response_code = MHD_HTTP_NO_CONTENT;
- uc->response = MHD_create_response_from_buffer_static (
- 0,
- ""
- );
- TALER_MHD_add_global_headers (uc->response,
- true);
- }
-
- MHD_resume_connection (uc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (uc_head,
- uc_tail,
- uc);
- TALER_MHD_daemon_trigger ();
-}
-
-
-/**
- * Do the main database transaction.
- *
- * @param cls closure with a `struct UploadContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- */
-static enum GNUNET_DB_QueryStatus
-transact (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct UploadContext *uc = cls;
- struct TEH_RequestContext *rc = uc->rc;
- enum GNUNET_DB_QueryStatus qs;
- json_t *jmeasures;
- bool is_finished = false;
- size_t enc_attributes_len;
- void *enc_attributes;
- const char *error_message;
- char *form_name;
- enum TALER_ErrorCode ec;
-
- qs = TEH_plugin->lookup_completed_legitimization (
- TEH_plugin->cls,
- uc->legitimization_measure_serial_id,
- uc->measure_index,
- &uc->access_token,
- &uc->h_payto,
- &uc->is_wallet,
- &jmeasures,
- &is_finished,
- &enc_attributes_len,
- &enc_attributes);
- /* FIXME: not exactly performant/elegant, should eventually
- modify lookup_completed_legitimization to
- return something if we are purely pending? */
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_completed_legitimization");
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- qs = TEH_plugin->lookup_pending_legitimization (
- TEH_plugin->cls,
- uc->legitimization_measure_serial_id,
- &uc->access_token,
- &uc->h_payto,
- &jmeasures,
- &is_finished,
- &uc->is_wallet);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup_pending_legitimization");
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- break;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
-
- if (NULL != enc_attributes)
- {
- json_t *xattributes;
-
- xattributes
- = TALER_CRYPTO_kyc_attributes_decrypt (
- &TEH_attribute_key,
- enc_attributes,
- enc_attributes_len);
- if (json_equal (xattributes,
- uc->result))
- {
- /* Request is idempotent! */
- json_decref (xattributes);
- GNUNET_free (enc_attributes);
- json_decref (jmeasures);
- if (is_finished)
- {
- *mhd_ret = TALER_MHD_reply_static (
- rc->connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- /* Note: problem below is not here, but likely some previous
- upload of the attributes failed badly in an AML program. */
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
- "attributes known, but legitimization process failed");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- json_decref (xattributes);
- GNUNET_free (enc_attributes);
- json_decref (jmeasures);
- /* Form was already done with with different attributes, conflict! */
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (is_finished)
- {
- /* This should not be possible (is_finished but NULL==enc_attributes),
- but also we should not run logic again if we are finished. */
- GNUNET_break_op (0);
- json_decref (jmeasures);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- ec = TALER_KYCLOGIC_check_form (jmeasures,
- uc->measure_index,
- uc->result,
- &form_name,
- &error_message);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break_op (0);
- json_decref (jmeasures);
- *mhd_ret = TALER_MHD_reply_with_ec (
- rc->connection,
- ec,
- error_message);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- json_decref (jmeasures);
-
- /* Setup KYC process (which we will then immediately 'finish') */
- qs = TEH_plugin->insert_kyc_requirement_process (
- TEH_plugin->cls,
- &uc->h_payto,
- uc->measure_index,
- uc->legitimization_measure_serial_id,
- form_name,
- NULL, /* provider account ID */
- NULL, /* provider legi ID */
- &uc->legi_process_row);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- GNUNET_free (form_name);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_kyc_requirement_process");
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_free (form_name);
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- GNUNET_free (form_name);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "insert_kyc_requirement_process");
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- qs = TEH_kyc_store_attributes (
- uc->legi_process_row,
- &uc->h_payto,
- form_name,
- NULL /* provider account */,
- NULL /* provider legi ID */,
- GNUNET_TIME_UNIT_FOREVER_ABS, /* expiration time */
- uc->result);
- GNUNET_free (form_name);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "kyc_store_attributes");
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "kyc_store_attributes");
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return qs;
- }
- GNUNET_assert (0);
- *mhd_ret = MHD_NO;
- return GNUNET_DB_STATUS_HARD_ERROR;
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_upload (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1])
-{
- struct UploadContext *uc = rc->rh_ctx;
- const char *id = args[0];
-
- if (NULL == uc)
- {
- const char *slash;
- char dummy;
-
- uc = GNUNET_new (struct UploadContext);
- uc->rc = rc;
- uc->result = root;
- rc->rh_ctx = uc;
- rc->rh_cleaner = &upload_cleaner;
- slash = strchr (id, '-');
- if (NULL == slash)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
- rc->url);
- }
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (id,
- slash - id,
- &uc->access_token,
- sizeof (uc->access_token)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "Access token in ID is malformed");
- }
- if (2 !=
- sscanf (slash + 1,
- "%u-%llu%c",
- &uc->measure_index,
- &uc->legitimization_measure_serial_id,
- &dummy))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "ID is malformed");
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "/kyc-upload received form submission\n");
- if ( (NULL != root) &&
- (! json_is_string (json_object_get (root,
- "FORM_ID"))) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "FORM_ID");
- }
- json_dumpf (root,
- stderr,
- JSON_INDENT (2));
- }
- if (NULL != uc->response)
- {
- return MHD_queue_response (rc->connection,
- uc->response_code,
- uc->response);
-
- }
-
- if (GNUNET_OK !=
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SETUP_FAILED,
- NULL);
- }
-
- {
- MHD_RESULT mhd_ret = -1;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "kyc-upload",
- TEH_MT_REQUEST_KYC_UPLOAD,
- &mhd_ret,
- &transact,
- uc))
- return mhd_ret;
- }
-
- uc->kat = TEH_kyc_run_measure_for_attributes (
- &rc->async_scope_id,
- uc->legi_process_row,
- &uc->h_payto,
- uc->is_wallet,
- &aml_trigger_callback,
- uc);
- if (NULL == uc->kat)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
- "TEH_kyc_finished");
- }
- MHD_suspend_connection (uc->rc->connection);
- GNUNET_CONTAINER_DLL_insert (uc_head,
- uc_tail,
- uc);
- return MHD_YES;
-}
diff --git a/src/exchange/taler-exchange-httpd_kyc-upload.h b/src/exchange/taler-exchange-httpd_kyc-upload.h
@@ -1,53 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2019, 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_kyc-upload.h
- * @brief Handle /kyc-upload/$ID requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_UPLOAD_H
-#define TALER_EXCHANGE_HTTPD_KYC_UPLOAD_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Resume suspended connections, called on shutdown.
- */
-void
-TEH_kyc_upload_cleanup (void);
-
-
-/**
- * Handle a "/kyc-upload/$ID" request.
- *
- * @param rc request context
- * @param root json body being uploaded
- * @param args includes the ID from the URL (without "/")
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_upload (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.c b/src/exchange/taler-exchange-httpd_kyc-wallet.c
@@ -1,336 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2021, 2022, 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 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_kyc-wallet.c
- * @brief Handle request for wallet for KYC check.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_kyc-wallet.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Context for the request.
- */
-struct KycRequestContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct KycRequestContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct KycRequestContext *prev;
-
- /**
- * Handle for legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Payto URI of the reserve.
- */
- struct TALER_NormalizedPayto payto_uri;
-
- /**
- * Request context.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Response to return. Note that the response must
- * be queued or destroyed by the callee. NULL
- * if the legitimization check was successful and the handler should return
- * a handler-specific result.
- */
- struct MHD_Response *response;
-
- /**
- * Public key of the reserve/wallet this is about.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * The wallet's public key
- */
- union TALER_AccountPublicKeyP wallet_pub;
-
- /**
- * Balance threshold crossed by the wallet.
- */
- struct TALER_Amount balance;
-
- /**
- * KYC status, with row with the legitimization requirement.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Smallest amount (over any timeframe) that may
- * require additional KYC checks (if @a kyc.ok).
- */
- struct TALER_Amount next_threshold;
-
- /**
- * When do the current KYC rules possibly expire.
- * Only valid if @a kyc.ok.
- */
- struct GNUNET_TIME_Timestamp expiration_date;
-
- /**
- * HTTP status code for @a response, or 0
- */
- unsigned int http_status;
-
-};
-
-
-/**
- * Kept in a DLL.
- */
-static struct KycRequestContext *krc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct KycRequestContext *krc_tail;
-
-
-void
-TEH_kyc_wallet_cleanup ()
-{
- struct KycRequestContext *krc;
-
- while (NULL != (krc = krc_head))
- {
- GNUNET_CONTAINER_DLL_remove (krc_head,
- krc_tail,
- krc);
- MHD_resume_connection (krc->rc->connection);
- }
-}
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Returns the wallet balance.
- *
- * @param cls closure, a `struct KycRequestContext`
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
- */
-static enum GNUNET_DB_QueryStatus
-balance_iterator (void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct KycRequestContext *krc = cls;
- enum GNUNET_GenericReturnValue ret;
-
- (void) limit;
- ret = cb (cb_cls,
- &krc->balance,
- GNUNET_TIME_absolute_get ());
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls must be a `struct KycRequestContext *`
- * @param lcr legitimization check result
- */
-static void
-legi_result_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct KycRequestContext *krc = cls;
-
- TEH_plugin->preflight (TEH_plugin->cls);
- krc->lch = NULL;
- krc->http_status = lcr->http_status;
- krc->response = lcr->response;
- krc->kyc = lcr->kyc;
- krc->next_threshold = lcr->next_threshold;
- krc->expiration_date = lcr->expiration_date;
- GNUNET_CONTAINER_DLL_remove (krc_head,
- krc_tail,
- krc);
- MHD_resume_connection (krc->rc->connection);
- TALER_MHD_daemon_trigger ();
-}
-
-
-/**
- * Function to clean up our rh_ctx in @a rc
- *
- * @param[in,out] rc context to clean up
- */
-static void
-krc_cleaner (struct TEH_RequestContext *rc)
-{
- struct KycRequestContext *krc = rc->rh_ctx;
-
- if (NULL != krc->lch)
- {
- TEH_legitimization_check_cancel (krc->lch);
- krc->lch = NULL;
- }
- GNUNET_free (krc->payto_uri.normalized_payto);
- GNUNET_free (krc);
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_wallet (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[])
-{
- struct KycRequestContext *krc = rc->rh_ctx;
-
- if (NULL == krc)
- {
- krc = GNUNET_new (struct KycRequestContext);
- krc->rc = rc;
- rc->rh_ctx = krc;
- rc->rh_cleaner = &krc_cleaner;
- {
- struct TALER_ReserveSignatureP reserve_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &krc->wallet_pub.reserve_pub),
- TALER_JSON_spec_amount ("balance",
- TEH_currency,
- &krc->balance),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue ret;
-
- (void) args;
- ret = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_account_setup_verify (
- &krc->wallet_pub.reserve_pub,
- &krc->balance,
- &reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
- NULL);
- }
- }
- krc->payto_uri
- = TALER_reserve_make_payto (TEH_base_url,
- &krc->wallet_pub.reserve_pub);
- TALER_normalized_payto_hash (krc->payto_uri,
- &krc->h_payto);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "h_payto of wallet %s is %s\n",
- krc->payto_uri.normalized_payto,
- TALER_B2S (&krc->h_payto));
- {
- struct TALER_FullPayto fake_full_payto;
-
- GNUNET_asprintf (&fake_full_payto.full_payto,
- "%s?receiver-name=wallet",
- krc->payto_uri.normalized_payto);
- krc->lch = TEH_legitimization_check (
- &rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
- fake_full_payto,
- &krc->h_payto,
- &krc->wallet_pub,
- &balance_iterator,
- krc,
- &legi_result_cb,
- krc);
- GNUNET_free (fake_full_payto.full_payto);
- }
- GNUNET_assert (NULL != krc->lch);
- MHD_suspend_connection (rc->connection);
- GNUNET_CONTAINER_DLL_insert (krc_head,
- krc_tail,
- krc);
- return MHD_YES;
- }
- if (NULL != krc->response)
- return MHD_queue_response (rc->connection,
- krc->http_status,
- krc->response);
- if (krc->kyc.ok)
- {
- bool have_ts
- = TALER_amount_is_valid (&krc->next_threshold);
-
- /* KYC not required or already satisfied */
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_timestamp ("expiration_time",
- krc->expiration_date),
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount ("next_threshold",
- have_ts
- ? &krc->next_threshold
- : NULL)));
- }
- return TEH_RESPONSE_reply_kyc_required (rc->connection,
- &krc->h_payto,
- &krc->kyc,
- false);
-}
-
-
-/* end of taler-exchange-httpd_kyc-wallet.c */
diff --git a/src/exchange/taler-exchange-httpd_kyc-wallet.h b/src/exchange/taler-exchange-httpd_kyc-wallet.h
@@ -1,52 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_kyc-wallet.h
- * @brief Handle /kyc-wallet requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_KYC_WALLET_H
-#define TALER_EXCHANGE_HTTPD_KYC_WALLET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_kyc_wallet_cleanup (void);
-
-
-/**
- * Handle a "/kyc-wallet" request. Parses the "reserve_pub" EdDSA key of the
- * reserve and the signature "reserve_sig" which affirms the operation. If OK,
- * a KYC record is created (if missing) and the KYC status returned.
- *
- * @param rc request to handle
- * @param root uploaded JSON data
- * @param args empty array
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_kyc_wallet (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_management_aml-officers.c b/src/exchange/taler-exchange-httpd_management_aml-officers.c
@@ -1,142 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_management_aml-officers.c
- * @brief Handle request to update AML officer status
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * How often do we try the DB operation at most?
- */
-#define MAX_RETRIES 10
-
-
-MHD_RESULT
-TEH_handler_management_aml_officers (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct TALER_AmlOfficerPublicKeyP officer_pub;
- const char *officer_name;
- struct GNUNET_TIME_Timestamp change_date;
- bool is_active;
- bool read_only;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("officer_pub",
- &officer_pub),
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &master_sig),
- GNUNET_JSON_spec_bool ("is_active",
- &is_active),
- GNUNET_JSON_spec_bool ("read_only",
- &read_only),
- GNUNET_JSON_spec_string ("officer_name",
- &officer_name),
- GNUNET_JSON_spec_timestamp ("change_date",
- &change_date),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_aml_officer_status_verify (
- &officer_pub,
- officer_name,
- change_date,
- is_active,
- read_only,
- &TEH_master_public_key,
- &master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_UPDATE_AML_OFFICER_SIGNATURE_INVALID,
- NULL);
- }
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp last_date;
- unsigned int retries_left = MAX_RETRIES;
-
- do {
- qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls,
- &officer_pub,
- &master_sig,
- officer_name,
- is_active,
- read_only,
- change_date,
- &last_date);
- if (0 == --retries_left)
- break;
- } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_aml_officer");
- }
- if (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- change_date))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_AML_OFFICERS_MORE_RECENT_PRESENT,
- NULL);
- }
- }
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_aml-officers.c */
diff --git a/src/exchange/taler-exchange-httpd_management_auditors.c b/src/exchange/taler-exchange-httpd_management_auditors.c
@@ -1,207 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020, 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_management_auditors.c
- * @brief Handle request to add auditor.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for the #add_auditor transaction.
- */
-struct AddAuditorContext
-{
- /**
- * Master signature to store.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Auditor public key this is about.
- */
- struct TALER_AuditorPublicKeyP auditor_pub;
-
- /**
- * Auditor URL this is about.
- */
- const char *auditor_url;
-
- /**
- * Human readable name of the auditor.
- */
- const char *auditor_name;
-
- /**
- * Timestamp for checking against replay attacks.
- */
- struct GNUNET_TIME_Timestamp validity_start;
-
-};
-
-
-/**
- * Function implementing database transaction to add an auditor. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddAuditorContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_auditor (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddAuditorContext *aac = cls;
- struct GNUNET_TIME_Timestamp last_date;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
- &aac->auditor_pub,
- &last_date);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup auditor");
- return qs;
- }
- if ( (0 < qs) &&
- (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- aac->validity_start) ) )
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 == qs)
- qs = TEH_plugin->insert_auditor (TEH_plugin->cls,
- &aac->auditor_pub,
- aac->auditor_url,
- aac->auditor_name,
- aac->validity_start);
- else
- qs = TEH_plugin->update_auditor (TEH_plugin->cls,
- &aac->auditor_pub,
- aac->auditor_url,
- aac->auditor_name,
- aac->validity_start,
- true);
- if (qs < 0)
- {
- GNUNET_break (0);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "add auditor");
- return qs;
- }
- TEH_keys_update_states ();
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_auditors (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct AddAuditorContext aac;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &aac.master_sig),
- GNUNET_JSON_spec_fixed_auto ("auditor_pub",
- &aac.auditor_pub),
- TALER_JSON_spec_web_url ("auditor_url",
- &aac.auditor_url),
- GNUNET_JSON_spec_string ("auditor_name",
- &aac.auditor_name),
- GNUNET_JSON_spec_timestamp ("validity_start",
- &aac.validity_start),
- GNUNET_JSON_spec_end ()
- };
- MHD_RESULT res;
- enum GNUNET_GenericReturnValue ret;
-
- ret = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
- if (GNUNET_OK !=
- TALER_exchange_offline_auditor_add_verify (
- &aac.auditor_pub,
- aac.auditor_url,
- aac.validity_start,
- &TEH_master_public_key,
- &aac.master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID,
- NULL);
- }
-
- ret = TEH_DB_run_transaction (connection,
- "add auditor",
- TEH_MT_REQUEST_OTHER,
- &res,
- &add_auditor,
- &aac);
- if (GNUNET_SYSERR == ret)
- return res;
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_auditors.c */
diff --git a/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c b/src/exchange/taler-exchange-httpd_management_auditors_AP_disable.c
@@ -1,196 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 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_management_auditors_AP_disable.c
- * @brief Handle request to disable auditor.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for the #del_auditor transaction.
- */
-struct DelAuditorContext
-{
-
- /**
- * Auditor public key this is about.
- */
- struct TALER_AuditorPublicKeyP auditor_pub;
-
- /**
- * Auditor URL this is about.
- */
- const char *auditor_url;
-
- /**
- * Timestamp for checking against replay attacks.
- */
- struct GNUNET_TIME_Timestamp validity_end;
-
-};
-
-
-/**
- * Function implementing database transaction to del an auditor. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct DelAuditorContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-del_auditor (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct DelAuditorContext *dac = cls;
- struct GNUNET_TIME_Timestamp last_date;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
- &dac->auditor_pub,
- &last_date);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup auditor");
- return qs;
- }
- if (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- dac->validity_end))
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- qs = TEH_plugin->update_auditor (TEH_plugin->cls,
- &dac->auditor_pub,
- "", /* auditor URL */
- "", /* auditor name */
- dac->validity_end,
- false);
- if (qs < 0)
- {
- GNUNET_break (0);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "del auditor");
- return qs;
- }
- TEH_keys_update_states ();
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_auditors_AP_disable (
- struct MHD_Connection *connection,
- const struct TALER_AuditorPublicKeyP *auditor_pub,
- const json_t *root)
-{
- struct TALER_MasterSignatureP master_sig;
- struct DelAuditorContext dac = {
- .auditor_pub = *auditor_pub
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &master_sig),
- GNUNET_JSON_spec_timestamp ("validity_end",
- &dac.validity_end),
- GNUNET_JSON_spec_end ()
- };
- MHD_RESULT res;
- enum GNUNET_GenericReturnValue ret;
-
- ret = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
- if (GNUNET_OK !=
- TALER_exchange_offline_auditor_del_verify (
- auditor_pub,
- dac.validity_end,
- &TEH_master_public_key,
- &master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID,
- NULL);
- }
-
- ret = TEH_DB_run_transaction (connection,
- "del auditor",
- TEH_MT_REQUEST_OTHER,
- &res,
- &del_auditor,
- &dac);
- if (GNUNET_SYSERR == ret)
- return res;
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_auditors_AP_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c b/src/exchange/taler-exchange-httpd_management_denominations_HDP_revoke.c
@@ -1,94 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 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_management_denominations_HDP_revoke.c
- * @brief Handle denomination revocation requests.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-MHD_RESULT
-TEH_handler_management_denominations_HDP_revoke (
- struct MHD_Connection *connection,
- const struct TALER_DenominationHashP *h_denom_pub,
- const json_t *root)
-{
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &master_sig),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_DB_QueryStatus qs;
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_denomination_revoke_verify (
- h_denom_pub,
- &TEH_master_public_key,
- &master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID,
- NULL);
- }
- qs = TEH_plugin->insert_denomination_revocation (TEH_plugin->cls,
- h_denom_pub,
- &master_sig);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "denomination revocation");
- }
- TEH_keys_denomination_revoke (h_denom_pub);
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_denominations_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_management_drain.c b/src/exchange/taler-exchange-httpd_management_drain.c
@@ -1,195 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_management_drain.c
- * @brief Handle request to drain profits
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Closure for the #drain transaction.
- */
-struct DrainContext
-{
- /**
- * Fee's signature affirming the #TALER_SIGNATURE_MASTER_DRAIN_PROFITS operation.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Wire transfer identifier to use.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * Account to credit.
- */
- struct TALER_FullPayto payto_uri;
-
- /**
- * Configuration section with account to debit.
- */
- const char *account_section;
-
- /**
- * Signature time.
- */
- struct GNUNET_TIME_Timestamp date;
-
- /**
- * Amount to transfer.
- */
- struct TALER_Amount amount;
-
-};
-
-
-/**
- * Function implementing database transaction to drain profits. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct DrainContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-drain (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct DrainContext *dc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->insert_drain_profit (
- TEH_plugin->cls,
- &dc->wtid,
- dc->account_section,
- dc->payto_uri,
- dc->date,
- &dc->amount,
- &dc->master_sig);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert drain profit");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_drain (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct DrainContext dc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("debit_account_section",
- &dc.account_section),
- TALER_JSON_spec_full_payto_uri ("credit_payto_uri",
- &dc.payto_uri),
- GNUNET_JSON_spec_fixed_auto ("wtid",
- &dc.wtid),
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &dc.master_sig),
- GNUNET_JSON_spec_timestamp ("date",
- &dc.date),
- TALER_JSON_spec_amount ("amount",
- TEH_currency,
- &dc.amount),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_profit_drain_verify (
- &dc.wtid,
- dc.date,
- &dc.amount,
- dc.account_section,
- dc.payto_uri,
- &TEH_master_public_key,
- &dc.master_sig))
- {
- /* signature invalid */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_DRAIN_PROFITS_SIGNATURE_INVALID,
- NULL);
- }
-
- {
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
-
- res = TEH_DB_run_transaction (connection,
- "insert drain profit",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &drain,
- &dc);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_drain.c */
diff --git a/src/exchange/taler-exchange-httpd_management_extensions.c b/src/exchange/taler-exchange-httpd_management_extensions.c
@@ -1,300 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_management_extensions.c
- * @brief Handle request to POST /management/extensions
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_extensions.h"
-#include "taler/taler_dbevents.h"
-
-/**
- * Extension carries the necessary data for a particular extension.
- *
- */
-struct Extension
-{
- enum TALER_Extension_Type type;
- json_t *manifest;
-};
-
-/**
- * Closure for the #set_extensions transaction
- */
-struct SetExtensionsContext
-{
- uint32_t num_extensions;
- struct Extension *extensions;
- struct TALER_MasterSignatureP extensions_sig;
-};
-
-/**
- * Function implementing database transaction to set the manifests of
- * extensions. It runs the transaction logic.
- * - IF it returns a non-error code, the transaction logic MUST NOT queue a
- * MHD response.
- * - IF it returns an hard error, the transaction logic MUST queue a MHD
- * response and set @a mhd_ret.
- * - IF it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls closure with a `struct SetExtensionsContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-set_extensions (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct SetExtensionsContext *sec = cls;
-
- /* save the manifests of all extensions */
- for (uint32_t i = 0; i<sec->num_extensions; i++)
- {
- struct Extension *ext = &sec->extensions[i];
- const struct TALER_Extension *taler_ext;
- enum GNUNET_DB_QueryStatus qs;
- char *manifest;
-
- taler_ext = TALER_extensions_get_by_type (ext->type);
- if (NULL == taler_ext)
- {
- /* No such extension found */
- GNUNET_break (0);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- manifest = json_dumps (ext->manifest, JSON_COMPACT | JSON_SORT_KEYS);
- if (NULL == manifest)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_INVALID,
- "convert configuration to string");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = TEH_plugin->set_extension_manifest (
- TEH_plugin->cls,
- taler_ext->name,
- manifest);
-
- free (manifest);
-
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "save extension configuration");
- }
-
- /* Success, trigger event */
- {
- uint32_t nbo_type = htonl (sec->extensions[i].type);
- struct GNUNET_DB_EventHeaderP ev = {
- .size = htons (sizeof (ev)),
- .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
- };
-
- TEH_plugin->event_notify (TEH_plugin->cls,
- &ev,
- &nbo_type,
- sizeof(nbo_type));
- }
-
- }
-
- /* All extensions configured, update the signature */
- TEH_extensions_sig = sec->extensions_sig;
- TEH_extensions_signed = true;
-
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
-}
-
-
-static enum GNUNET_GenericReturnValue
-verify_extensions_from_json (
- const json_t *extensions,
- struct SetExtensionsContext *sec)
-{
- const char*name;
- const struct TALER_Extension *extension;
- size_t i = 0;
- json_t *manifest;
-
- GNUNET_assert (NULL != extensions);
- GNUNET_assert (json_is_object (extensions));
-
- sec->num_extensions = json_object_size (extensions);
- sec->extensions = GNUNET_new_array (sec->num_extensions,
- struct Extension);
-
- json_object_foreach ((json_t *) extensions, name, manifest)
- {
- int critical = 0;
- json_t *config;
- const char *version = NULL;
-
- /* load and verify criticality, version, etc. */
- extension = TALER_extensions_get_by_name (name);
- if (NULL == extension)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "no such extension: %s\n", name);
- return GNUNET_SYSERR;
- }
-
- if (GNUNET_OK !=
- TALER_extensions_parse_manifest (
- manifest, &critical, &version, &config))
- return GNUNET_SYSERR;
-
- if (critical != extension->critical
- || 0 != strcmp (version, extension->version) // FIXME-oec: libtool compare
- || NULL == config
- || GNUNET_OK != extension->load_config (config, NULL))
- return GNUNET_SYSERR;
-
- sec->extensions[i].type = extension->type;
- sec->extensions[i].manifest = json_copy (manifest);
- }
-
- return GNUNET_OK;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_extensions (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- MHD_RESULT ret;
- const json_t *extensions;
- struct SetExtensionsContext sec = {0};
- struct GNUNET_JSON_Specification top_spec[] = {
- GNUNET_JSON_spec_object_const ("extensions",
- &extensions),
- GNUNET_JSON_spec_fixed_auto ("extensions_sig",
- &sec.extensions_sig),
- GNUNET_JSON_spec_end ()
- };
-
- /* Parse the top level json structure */
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- top_spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
-
- /* Verify the signature */
- {
- struct TALER_ExtensionManifestsHashP h_manifests;
-
- if (GNUNET_OK !=
- TALER_JSON_extensions_manifests_hash (extensions,
- &h_manifests) ||
- GNUNET_OK !=
- TALER_exchange_offline_extension_manifests_hash_verify (
- &h_manifests,
- &TEH_master_public_key,
- &sec.extensions_sig))
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid signuture");
- }
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received /management/extensions\n");
-
- /* Now parse individual extensions and signatures from those objects. */
- if (GNUNET_OK !=
- verify_extensions_from_json (extensions, &sec))
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "invalid object");
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received %u extensions\n",
- sec.num_extensions);
-
- /* now run the transaction to persist the configurations */
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TEH_DB_run_transaction (connection,
- "set extensions",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &set_extensions,
- &sec);
-
- if (GNUNET_SYSERR == res)
- goto CLEANUP;
- }
-
- ret = TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-
-CLEANUP:
- for (unsigned int i = 0; i < sec.num_extensions; i++)
- {
- if (NULL != sec.extensions[i].manifest)
- {
- json_decref (sec.extensions[i].manifest);
- }
- }
- GNUNET_free (sec.extensions);
- return ret;
-}
-
-
-/* end of taler-exchange-httpd_management_management_post_extensions.c */
diff --git a/src/exchange/taler-exchange-httpd_management_global_fees.c b/src/exchange/taler-exchange-httpd_management_global_fees.c
@@ -1,261 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020, 2021, 2022 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_management_global_fees.c
- * @brief Handle request to add global fee details
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Closure for the #add_fee transaction.
- */
-struct AddFeeContext
-{
- /**
- * Fee's signature affirming the #TALER_SIGNATURE_MASTER_GLOBAL_FEES operation.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Starting period.
- */
- struct GNUNET_TIME_Timestamp start_time;
-
- /**
- * End of period.
- */
- struct GNUNET_TIME_Timestamp end_time;
-
- /**
- * Global fee amounts.
- */
- struct TALER_GlobalFeeSet fees;
-
- /**
- * When does an unmerged purse expire?
- */
- struct GNUNET_TIME_Relative purse_timeout;
-
- /**
- * When does an account without KYC expire?
- */
- struct GNUNET_TIME_Relative kyc_timeout;
-
- /**
- * When does an account history expire?
- */
- struct GNUNET_TIME_Relative history_expiration;
-
- /**
- * Number of free purses per account.
- */
- uint32_t purse_account_limit;
-
-};
-
-
-/**
- * Function implementing database transaction to add a fee. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddFeeContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_fee (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddFeeContext *afc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_GlobalFeeSet fees;
- struct GNUNET_TIME_Relative purse_timeout;
- struct GNUNET_TIME_Relative history_expiration;
- uint32_t purse_account_limit;
-
- qs = TEH_plugin->lookup_global_fee_by_time (
- TEH_plugin->cls,
- afc->start_time,
- afc->end_time,
- &fees,
- &purse_timeout,
- &history_expiration,
- &purse_account_limit);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup global fee");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
- {
- if ( (GNUNET_OK ==
- TALER_amount_is_valid (&fees.history)) &&
- (0 ==
- TALER_global_fee_set_cmp (&fees,
- &afc->fees)) )
- {
- /* this will trigger the 'success' response */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- else
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_MISMATCH,
- NULL);
- }
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = TEH_plugin->insert_global_fee (
- TEH_plugin->cls,
- afc->start_time,
- afc->end_time,
- &afc->fees,
- afc->purse_timeout,
- afc->history_expiration,
- afc->purse_account_limit,
- &afc->master_sig);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert fee");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_global_fees (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct AddFeeContext afc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &afc.master_sig),
- GNUNET_JSON_spec_timestamp ("fee_start",
- &afc.start_time),
- GNUNET_JSON_spec_timestamp ("fee_end",
- &afc.end_time),
- TALER_JSON_spec_amount ("history_fee",
- TEH_currency,
- &afc.fees.history),
- TALER_JSON_spec_amount ("account_fee",
- TEH_currency,
- &afc.fees.account),
- TALER_JSON_spec_amount ("purse_fee",
- TEH_currency,
- &afc.fees.purse),
- GNUNET_JSON_spec_relative_time ("purse_timeout",
- &afc.purse_timeout),
- GNUNET_JSON_spec_relative_time ("history_expiration",
- &afc.history_expiration),
- GNUNET_JSON_spec_uint32 ("purse_account_limit",
- &afc.purse_account_limit),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_global_fee_verify (
- afc.start_time,
- afc.end_time,
- &afc.fees,
- afc.purse_timeout,
- afc.history_expiration,
- afc.purse_account_limit,
- &TEH_master_public_key,
- &afc.master_sig))
- {
- /* signature invalid */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID,
- NULL);
- }
-
- {
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
-
- res = TEH_DB_run_transaction (connection,
- "add global fee",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &add_fee,
- &afc);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- TEH_keys_update_states ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_global_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_management_partners.c b/src/exchange/taler-exchange-httpd_management_partners.c
@@ -1,132 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_management_partners.c
- * @brief Handle request to add exchange partner
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-MHD_RESULT
-TEH_handler_management_partners (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct TALER_MasterPublicKeyP partner_pub;
- struct GNUNET_TIME_Timestamp start_date;
- struct GNUNET_TIME_Timestamp end_date;
- struct GNUNET_TIME_Relative wad_frequency;
- struct TALER_Amount wad_fee;
- const char *partner_base_url;
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("partner_pub",
- &partner_pub),
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &master_sig),
- TALER_JSON_spec_web_url ("partner_base_url",
- &partner_base_url),
- TALER_JSON_spec_amount ("wad_fee",
- TEH_currency,
- &wad_fee),
- GNUNET_JSON_spec_timestamp ("start_date",
- &start_date),
- GNUNET_JSON_spec_timestamp ("end_date",
- &end_date),
- GNUNET_JSON_spec_relative_time ("wad_frequency",
- &wad_frequency),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_partner_details_verify (
- &partner_pub,
- start_date,
- end_date,
- wad_frequency,
- &wad_fee,
- partner_base_url,
- &TEH_master_public_key,
- &master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_SIGNATURE_INVALID,
- NULL);
- }
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->insert_partner (TEH_plugin->cls,
- &partner_pub,
- start_date,
- end_date,
- wad_frequency,
- &wad_fee,
- partner_base_url,
- &master_sig);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "add_partner");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- /* FIXME-#7271: check for idempotency! */
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_DATA_CONFLICT,
- NULL);
- }
- }
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_partners.c */
diff --git a/src/exchange/taler-exchange-httpd_management_post_keys.c b/src/exchange/taler-exchange-httpd_management_post_keys.c
@@ -1,574 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020-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_management_post_keys.c
- * @brief Handle request to POST /management/keys
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Denomination signature provided.
- */
-struct DenomSig
-{
- /**
- * Hash of a denomination public key.
- */
- struct TALER_DenominationHashP h_denom_pub;
-
- /**
- * Master signature for the @e h_denom_pub.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Fee structure for this key, as per our configuration.
- */
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
-
- /**
- * The full public key.
- */
- struct TALER_DenominationPublicKey denom_pub;
-
-};
-
-
-/**
- * Signkey signature provided.
- */
-struct SigningSig
-{
- /**
- * Online signing key of the exchange.
- */
- struct TALER_ExchangePublicKeyP exchange_pub;
-
- /**
- * Master signature for the @e exchange_pub.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Our meta data on this key.
- */
- struct TALER_EXCHANGEDB_SignkeyMetaData meta;
-
-};
-
-
-/**
- * Closure for the #add_keys transaction.
- */
-struct AddKeysContext
-{
-
- /**
- * Array of @e nd_sigs denomination signatures.
- */
- struct DenomSig *d_sigs;
-
- /**
- * Array of @e ns_sigs signkey signatures.
- */
- struct SigningSig *s_sigs;
-
- /**
- * Our key state.
- */
- struct TEH_KeyStateHandle *ksh;
-
- /**
- * Length of the d_sigs array.
- */
- unsigned int nd_sigs;
-
- /**
- * Length of the n_sigs array.
- */
- unsigned int ns_sigs;
-
-};
-
-
-/**
- * Compare meta-data of two denomination keys for equality,
- * except for the "serial" number.
- *
- * @param m1 meta data to compare to @a m2
- * @param m2 meta data to compare to @a m1
- * @return true if both are equal
- */
-static bool
-denomination_meta_cmp (
- const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m1,
- const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m2)
-{
- if ( (GNUNET_TIME_timestamp_cmp (m1->start,
- !=,
- m2->start)) ||
- (GNUNET_TIME_timestamp_cmp (m1->expire_withdraw,
- !=,
- m2->expire_withdraw)) ||
- (GNUNET_TIME_timestamp_cmp (m1->expire_deposit,
- !=,
- m2->expire_deposit)) ||
- (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
- !=,
- m2->expire_legal)) )
- return false;
- if (0 !=
- TALER_amount_cmp (&m1->value,
- &m2->value))
- return false;
- if (0 !=
- GNUNET_memcmp (&m1->fees,
- &m2->fees))
- return false;
- if (m1->age_mask.bits !=
- m2->age_mask.bits)
- return false;
- return true;
-}
-
-
-/**
- * Compare meta-data of two signing keys for equality.
- *
- * @param m1 meta data to compare to @a m2
- * @param m2 meta data to compare to @a m1
- * @return true if both are equal
- */
-static bool
-signkey_meta_cmp (
- const struct TALER_EXCHANGEDB_SignkeyMetaData *m1,
- const struct TALER_EXCHANGEDB_SignkeyMetaData *m2)
-{
- if ( (GNUNET_TIME_timestamp_cmp (m1->start,
- !=,
- m2->start)) ||
- (GNUNET_TIME_timestamp_cmp (m1->expire_sign,
- !=,
- m2->expire_sign)) ||
- (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
- !=,
- m2->expire_legal)) )
- return false;
- return true;
-}
-
-
-/**
- * Function implementing database transaction to add offline signing keys.
- * Runs the transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddKeysContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_keys (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddKeysContext *akc = cls;
-
- /* activate all denomination keys */
- for (unsigned int i = 0; i<akc->nd_sigs; i++)
- {
- struct DenomSig *d = &akc->d_sigs[i];
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
-
- /* For idempotency, check if the key is already active */
- qs = TEH_plugin->lookup_denomination_key (
- TEH_plugin->cls,
- &d->h_denom_pub,
- &meta);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup denomination key");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (! denomination_meta_cmp (&d->meta,
- &meta))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_DENOMINATION_META_DATA,
- "conflicting meta data previously set for the same denomination key");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Denomination key %s already active, skipping\n",
- GNUNET_h2s (&d->h_denom_pub.hash));
- continue; /* skip, already known */
- }
-
- qs = TEH_plugin->add_denomination_key (
- TEH_plugin->cls,
- &d->h_denom_pub,
- &d->denom_pub,
- &d->meta,
- &d->master_sig);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "activate denomination key");
- return qs;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Added offline signature for denomination `%s'\n",
- GNUNET_h2s (&d->h_denom_pub.hash));
- GNUNET_assert (0 != qs);
- }
-
- for (unsigned int i = 0; i<akc->ns_sigs; i++)
- {
- struct SigningSig *s = &akc->s_sigs[i];
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_EXCHANGEDB_SignkeyMetaData meta;
-
- qs = TEH_plugin->lookup_signing_key (
- TEH_plugin->cls,
- &s->exchange_pub,
- &meta);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup signing key");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- {
- if (! signkey_meta_cmp (&s->meta,
- &meta))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_SIGNKEY_META_DATA,
- "conflicting meta data previously set for the same signing key");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signing key %s already active, skipping\n",
- TALER_B2S (&s->exchange_pub));
- continue; /* skip, already known */
- }
- qs = TEH_plugin->activate_signing_key (
- TEH_plugin->cls,
- &s->exchange_pub,
- &s->meta,
- &s->master_sig);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "activate signing key");
- return qs;
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Added offline signature for signing key `%s'\n",
- TALER_B2S (&s->exchange_pub));
- GNUNET_assert (0 != qs);
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
-}
-
-
-/**
- * Clean up state in @a akc, but do not free @a akc itself
- *
- * @param[in,out] akc state to clean up
- */
-static void
-cleanup_akc (struct AddKeysContext *akc)
-{
- for (unsigned int i = 0; i<akc->nd_sigs; i++)
- {
- struct DenomSig *d = &akc->d_sigs[i];
-
- TALER_denom_pub_free (&d->denom_pub);
- }
- GNUNET_free (akc->d_sigs);
- GNUNET_free (akc->s_sigs);
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_keys (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct AddKeysContext akc = { 0 };
- const json_t *denom_sigs;
- const json_t *signkey_sigs;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("denom_sigs",
- &denom_sigs),
- GNUNET_JSON_spec_array_const ("signkey_sigs",
- &signkey_sigs),
- GNUNET_JSON_spec_end ()
- };
- MHD_RESULT ret;
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received POST /management/keys request\n");
-
- akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
- if (NULL == akc.ksh)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- "no key state (not even for management)");
- }
-
- akc.nd_sigs = json_array_size (denom_sigs);
- akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
- struct DenomSig);
- for (unsigned int i = 0; i<akc.nd_sigs; i++)
- {
- struct DenomSig *d = &akc.d_sigs[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &d->master_sig),
- GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
- &d->h_denom_pub),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- json_array_get (denom_sigs,
- i),
- ispec);
- if (GNUNET_OK != res)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- cleanup_akc (&akc);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
-
- res = TEH_keys_load_fees (akc.ksh,
- &d->h_denom_pub,
- &d->denom_pub,
- &d->meta);
- switch (res)
- {
- case GNUNET_SYSERR:
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- GNUNET_h2s (&d->h_denom_pub.hash));
- cleanup_akc (&akc);
- return ret;
- case GNUNET_NO:
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- GNUNET_h2s (&d->h_denom_pub.hash));
- cleanup_akc (&akc);
- return ret;
- case GNUNET_OK:
- break;
- }
- /* check signature is valid */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_denom_validity_verify (
- &d->h_denom_pub,
- d->meta.start,
- d->meta.expire_withdraw,
- d->meta.expire_deposit,
- d->meta.expire_legal,
- &d->meta.value,
- &d->meta.fees,
- &TEH_master_public_key,
- &d->master_sig))
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
- GNUNET_h2s (&d->h_denom_pub.hash));
- cleanup_akc (&akc);
- return ret;
- }
- }
-
- akc.ns_sigs = json_array_size (signkey_sigs);
- akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
- struct SigningSig);
- for (unsigned int i = 0; i<akc.ns_sigs; i++)
- {
- struct SigningSig *s = &akc.s_sigs[i];
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &s->master_sig),
- GNUNET_JSON_spec_fixed_auto ("exchange_pub",
- &s->exchange_pub),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- json_array_get (signkey_sigs,
- i),
- ispec);
- if (GNUNET_OK != res)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "Failure to handle /management/keys\n");
- cleanup_akc (&akc);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- res = TEH_keys_get_timing (&s->exchange_pub,
- &s->meta);
- switch (res)
- {
- case GNUNET_SYSERR:
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- TALER_B2S (&s->exchange_pub));
- cleanup_akc (&akc);
- return ret;
- case GNUNET_NO:
- /* For idempotency, check if the key is already active */
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
- TALER_B2S (&s->exchange_pub));
- cleanup_akc (&akc);
- return ret;
- case GNUNET_OK:
- break;
- }
-
- /* check signature is valid */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_signkey_validity_verify (
- &s->exchange_pub,
- s->meta.start,
- s->meta.expire_sign,
- s->meta.expire_legal,
- &TEH_master_public_key,
- &s->master_sig))
- {
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
- TALER_B2S (&s->exchange_pub));
- cleanup_akc (&akc);
- return ret;
- }
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received %u denomination and %u signing key signatures\n",
- akc.nd_sigs,
- akc.ns_sigs);
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TEH_DB_run_transaction (connection,
- "add keys",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &add_keys,
- &akc);
- cleanup_akc (&akc);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- TEH_keys_update_states ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_management_post_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c b/src/exchange/taler-exchange-httpd_management_signkey_EP_revoke.c
@@ -1,93 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020 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_management_signkey_EP_revoke.c
- * @brief Handle exchange online signing key revocation requests.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-MHD_RESULT
-TEH_handler_management_signkeys_EP_revoke (
- struct MHD_Connection *connection,
- const struct TALER_ExchangePublicKeyP *exchange_pub,
- const json_t *root)
-{
- struct TALER_MasterSignatureP master_sig;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &master_sig),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_DB_QueryStatus qs;
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_signkey_revoke_verify (exchange_pub,
- &TEH_master_public_key,
- &master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID,
- NULL);
- }
- qs = TEH_plugin->insert_signkey_revocation (TEH_plugin->cls,
- exchange_pub,
- &master_sig);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "signkey revocation");
- }
- TEH_keys_update_states ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_signkey_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_disable.c b/src/exchange/taler-exchange-httpd_management_wire_disable.c
@@ -1,207 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020-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_management_wire_disable.c
- * @brief Handle request to disable wire account.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for the #del_wire transaction.
- */
-struct DelWireContext
-{
- /**
- * Master signature affirming the WIRE DEL operation
- * (includes timestamp).
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Payto:// URI this is about.
- */
- struct TALER_FullPayto payto_uri;
-
- /**
- * Timestamp for checking against replay attacks.
- */
- struct GNUNET_TIME_Timestamp validity_end;
-
-};
-
-
-/**
- * Function implementing database transaction to del an wire. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct DelWireContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-del_wire (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct DelWireContext *awc = cls;
- struct GNUNET_TIME_Timestamp last_date;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
- awc->payto_uri,
- &last_date);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup wire");
- return qs;
- }
- if (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- awc->validity_end))
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- qs = TEH_plugin->update_wire (TEH_plugin->cls,
- awc->payto_uri,
- NULL,
- NULL,
- NULL,
- NULL,
- NULL,
- awc->validity_end,
- NULL,
- NULL,
- 0,
- false);
- if (qs < 0)
- {
- GNUNET_break (0);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "del wire");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_wire_disable (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct DelWireContext awc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig_del",
- &awc.master_sig),
- TALER_JSON_spec_full_payto_uri ("payto_uri",
- &awc.payto_uri),
- GNUNET_JSON_spec_timestamp ("validity_end",
- &awc.validity_end),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- if (GNUNET_OK !=
- TALER_exchange_offline_wire_del_verify (
- awc.payto_uri,
- awc.validity_end,
- &TEH_master_public_key,
- &awc.master_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID,
- NULL);
- }
-
- {
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
-
- res = TEH_DB_run_transaction (connection,
- "del wire",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &del_wire,
- &awc);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- TEH_wire_update_state ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_wire_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_enable.c b/src/exchange/taler-exchange-httpd_management_wire_enable.c
@@ -1,346 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020-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 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_management_wire_enable.c
- * @brief Handle request to add wire account.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for the #add_wire transaction.
- */
-struct AddWireContext
-{
- /**
- * Master signature affirming the WIRE ADD operation
- * (includes timestamp).
- */
- struct TALER_MasterSignatureP master_sig_add;
-
- /**
- * Master signature to share with clients affirming the
- * wire details of the bank.
- */
- struct TALER_MasterSignatureP master_sig_wire;
-
- /**
- * Payto:// URI this is about.
- */
- struct TALER_FullPayto payto_uri;
-
- /**
- * (optional) address of a conversion service for this account.
- */
- const char *conversion_url;
-
- /**
- * (optional) address of an open banking gateway service for this account.
- */
- const char *open_banking_gateway;
-
- /**
- * (optional) address of a wire transfer gateway service for this account.
- */
- const char *wire_transfer_gateway;
-
- /**
- * Restrictions imposed when crediting this account.
- */
- const json_t *credit_restrictions;
-
- /**
- * Restrictions imposed when debiting this account.
- */
- const json_t *debit_restrictions;
-
- /**
- * Timestamp for checking against replay attacks.
- */
- struct GNUNET_TIME_Timestamp validity_start;
-
- /**
- * Label to use for this bank. Default is empty.
- */
- const char *bank_label;
-
- /**
- * Priority of the bank in the list. Default 0.
- */
- int64_t priority;
-
-};
-
-
-/**
- * Function implementing database transaction to add an wire. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddWireContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_wire (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddWireContext *awc = cls;
- struct GNUNET_TIME_Timestamp last_date;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
- awc->payto_uri,
- &last_date);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup wire");
- return qs;
- }
- if ( (0 < qs) &&
- (GNUNET_TIME_timestamp_cmp (last_date,
- >,
- awc->validity_start)) )
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- qs = TEH_plugin->insert_wire (TEH_plugin->cls,
- awc->payto_uri,
- awc->conversion_url,
- awc->open_banking_gateway,
- awc->wire_transfer_gateway,
- awc->debit_restrictions,
- awc->credit_restrictions,
- awc->validity_start,
- &awc->master_sig_wire,
- awc->bank_label,
- awc->priority);
- else
- qs = TEH_plugin->update_wire (TEH_plugin->cls,
- awc->payto_uri,
- awc->conversion_url,
- awc->open_banking_gateway,
- awc->wire_transfer_gateway,
- awc->debit_restrictions,
- awc->credit_restrictions,
- awc->validity_start,
- &awc->master_sig_wire,
- awc->bank_label,
- awc->priority,
- true);
- if (qs < 0)
- {
- GNUNET_break (0);
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "add wire");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_wire (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct AddWireContext awc = {
- .conversion_url = NULL
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_full_payto_uri ("payto_uri",
- &awc.payto_uri),
- GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
- &awc.master_sig_wire),
- GNUNET_JSON_spec_fixed_auto ("master_sig_add",
- &awc.master_sig_add),
- GNUNET_JSON_spec_timestamp ("validity_start",
- &awc.validity_start),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("conversion_url",
- &awc.conversion_url),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("open_banking_gateway",
- &awc.open_banking_gateway),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_web_url ("wire_transfer_gateway",
- &awc.wire_transfer_gateway),
- NULL),
- GNUNET_JSON_spec_array_const ("credit_restrictions",
- &awc.credit_restrictions),
- GNUNET_JSON_spec_array_const ("debit_restrictions",
- &awc.debit_restrictions),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_string ("bank_label",
- &awc.bank_label),
- NULL),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_int64 ("priority",
- &awc.priority),
- NULL),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- {
- char *msg = TALER_payto_validate (awc.payto_uri);
-
- if (NULL != msg)
- {
- MHD_RESULT ret;
-
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
- msg);
- GNUNET_JSON_parse_free (spec);
- GNUNET_free (msg);
- return ret;
- }
- }
- if (GNUNET_OK !=
- TALER_exchange_offline_wire_add_verify (
- awc.payto_uri,
- awc.conversion_url,
- awc.open_banking_gateway,
- awc.wire_transfer_gateway,
- awc.debit_restrictions,
- awc.credit_restrictions,
- awc.validity_start,
- &TEH_master_public_key,
- &awc.master_sig_add))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID,
- NULL);
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_wire_signature_check (
- awc.payto_uri,
- awc.conversion_url,
- awc.open_banking_gateway,
- awc.wire_transfer_gateway,
- awc.debit_restrictions,
- awc.credit_restrictions,
- &TEH_master_public_key,
- &awc.master_sig_wire))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID,
- NULL);
- }
- {
- char *wire_method;
-
- wire_method = TALER_payto_get_method (awc.payto_uri.full_payto);
- if (NULL == wire_method)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "payto:// URI `%s' is malformed\n",
- awc.payto_uri.full_payto);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payto_uri");
- }
- GNUNET_free (wire_method);
- }
-
- {
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
-
- res = TEH_DB_run_transaction (connection,
- "add wire",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &add_wire,
- &awc);
- GNUNET_JSON_parse_free (spec);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- TEH_wire_update_state ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_management_wire_fees.c b/src/exchange/taler-exchange-httpd_management_wire_fees.c
@@ -1,230 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020, 2021, 2022 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_management_wire_fees.c
- * @brief Handle request to add wire fee details
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_management.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for the #add_fee transaction.
- */
-struct AddFeeContext
-{
- /**
- * Fee's signature affirming the #TALER_SIGNATURE_MASTER_WIRE_FEES operation.
- */
- struct TALER_MasterSignatureP master_sig;
-
- /**
- * Wire method this is about.
- */
- const char *wire_method;
-
- /**
- * Starting period.
- */
- struct GNUNET_TIME_Timestamp start_time;
-
- /**
- * End of period.
- */
- struct GNUNET_TIME_Timestamp end_time;
-
- /**
- * Wire fee amounts.
- */
- struct TALER_WireFeeSet fees;
-
-};
-
-
-/**
- * Function implementing database transaction to add a fee. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls closure with a `struct AddFeeContext`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-add_fee (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct AddFeeContext *afc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_WireFeeSet fees;
-
- qs = TEH_plugin->lookup_wire_fee_by_time (
- TEH_plugin->cls,
- afc->wire_method,
- afc->start_time,
- afc->end_time,
- &fees);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "lookup wire fee");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
- {
- if ( (GNUNET_OK ==
- TALER_amount_is_valid (&fees.wire)) &&
- (0 ==
- TALER_wire_fee_set_cmp (&fees,
- &afc->fees)) )
- {
- /* this will trigger the 'success' response */
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- else
- {
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH,
- NULL);
- }
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = TEH_plugin->insert_wire_fee (
- TEH_plugin->cls,
- afc->wire_method,
- afc->start_time,
- afc->end_time,
- &afc->fees,
- &afc->master_sig);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert fee");
- return qs;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_management_post_wire_fees (
- struct MHD_Connection *connection,
- const json_t *root)
-{
- struct AddFeeContext afc;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("master_sig",
- &afc.master_sig),
- GNUNET_JSON_spec_string ("wire_method",
- &afc.wire_method),
- GNUNET_JSON_spec_timestamp ("fee_start",
- &afc.start_time),
- GNUNET_JSON_spec_timestamp ("fee_end",
- &afc.end_time),
- TALER_JSON_spec_amount ("wire_fee",
- TEH_currency,
- &afc.fees.wire),
- TALER_JSON_spec_amount ("closing_fee",
- TEH_currency,
- &afc.fees.closing),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_exchange_offline_wire_fee_verify (
- afc.wire_method,
- afc.start_time,
- afc.end_time,
- &afc.fees,
- &TEH_master_public_key,
- &afc.master_sig))
- {
- /* signature invalid */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID,
- NULL);
- }
-
- {
- enum GNUNET_GenericReturnValue res;
- MHD_RESULT ret;
-
- res = TEH_DB_run_transaction (connection,
- "add wire fee",
- TEH_MT_REQUEST_OTHER,
- &ret,
- &add_fee,
- &afc);
- if (GNUNET_SYSERR == res)
- return ret;
- }
- TEH_wire_update_state ();
- return TALER_MHD_reply_static (
- connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_management_wire_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_melt.c b/src/exchange/taler-exchange-httpd_melt.c
@@ -1,1864 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_melt.c
- * @brief Handle /melt requests
- * @note This endpoint is active since vDOLDPLUS of the protocol API
- * @author Özgür Kesim
- */
-
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler-exchange-httpd.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_melt.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler/taler_util.h"
-
-/**
- * The different type of errors that might occur, sorted by name.
- * Some of them require idempotency checks, which are marked
- * in @e idempotency_check_required below.
- */
-enum MeltError
-{
- MELT_ERROR_NONE = 0,
- MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID,
- MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- MELT_ERROR_AMOUNT_OVERFLOW,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
- MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT,
- MELT_ERROR_BLINDING_SEED_REQUIRED,
- MELT_ERROR_COIN_CIPHER_MISMATCH,
- MELT_COIN_CONFLICTING_DENOMINATION_KEY,
- MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE,
- MELT_ERROR_COIN_SIGNATURE_INVALID,
- MELT_ERROR_COIN_UNKNOWN,
- MELT_ERROR_CONFIRMATION_SIGN,
- MELT_ERROR_CRYPTO_HELPER,
- MELT_ERROR_DB_FETCH_FAILED,
- MELT_ERROR_DB_INVARIANT_FAILURE,
- MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE,
- MELT_ERROR_DB_PREFLIGHT_FAILURE,
- MELT_ERROR_DENOMINATION_EXPIRED,
- MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- MELT_ERROR_DENOMINATION_REVOKED,
- MELT_ERROR_DENOMINATION_SIGN,
- MELT_ERROR_DENOMINATION_SIGNATURE_INVALID,
- MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- MELT_ERROR_DUPLICATE_PLANCHET,
- MELT_ERROR_DUPLICATE_TRANSFER_PUB,
- MELT_ERROR_INSUFFICIENT_FUNDS,
- MELT_ERROR_KEYS_MISSING,
- MELT_ERROR_FEES_EXCEED_CONTRIBUTION,
- MELT_ERROR_NONCE_RESUSE,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
-};
-
-/**
- * With the bits set in this value will be mark the errors
- * that require a check for idempotency before actually
- * returning an error.
- */
-static const uint64_t idempotency_check_required =
- 0
- | (1 << MELT_ERROR_DENOMINATION_EXPIRED)
- | (1 << MELT_ERROR_DENOMINATION_KEY_UNKNOWN)
- | (1 << MELT_ERROR_DENOMINATION_REVOKED)
- | (1 << MELT_ERROR_INSUFFICIENT_FUNDS) /* TODO: is this still correct? Compare exchange_do_refresh.sql */
- | (1 << MELT_ERROR_KEYS_MISSING);
-
-#define IDEMPOTENCY_CHECK_REQUIRED(error) \
- (0 != (idempotency_check_required & (1 << (error))))
-
-/**
- * Context for a /melt request
- */
-struct MeltContext
-{
-
- /**
- * This struct is kept in a DLL.
- */
- struct MeltContext *prev;
- struct MeltContext *next;
-
- /**
- * Processing phase we are in.
- * The ordering here partially matters, as we progress through
- * them by incrementing the phase in the happy path.
- */
- enum MeltPhase
- {
- MELT_PHASE_PARSE,
- MELT_PHASE_CHECK_MELT_VALID,
- MELT_PHASE_CHECK_KEYS,
- MELT_PHASE_CHECK_COIN_SIGNATURE,
- MELT_PHASE_PREPARE_TRANSACTION,
- MELT_PHASE_RUN_TRANSACTION,
- MELT_PHASE_GENERATE_REPLY_SUCCESS,
- MELT_PHASE_GENERATE_REPLY_ERROR,
- MELT_PHASE_RETURN_NO,
- MELT_PHASE_RETURN_YES,
- } phase;
-
-
- /**
- * Request context
- */
- const struct TEH_RequestContext *rc;
-
- /**
- * Current time for the DB transaction.
- */
- struct GNUNET_TIME_Timestamp now;
-
- /**
- * The current key state
- */
- struct TEH_KeyStateHandle *ksh;
-
- /**
- * The melted coin's denomination key
- */
- struct TEH_DenominationKey *melted_coin_denom;
-
- /**
- * Set to true if this coin's denomination was revoked and the operation
- * is thus only allowed for zombie coins where the transaction
- * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
- *
- * TODO: find a better terminology. The sentences in the comments containing
- * "zombie" make semantically _no sense_!
- */
- bool zombie_required;
-
- /**
- * We already checked and noticed that the coin is known. Hence we
- * can skip the "ensure_coin_known" step of the transaction.
- */
- bool coin_is_known;
-
- /**
- * UUID of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
- /**
- * Captures all parameters provided in the JSON request
- */
- struct
- {
-
- /**
- * All fields (from the request or computed)
- * that we persist in the database.
- */
- struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
-
- /**
- * In some error cases we check for idempotency.
- * If we find an entry in the database, we mark this here.
- */
- bool is_idempotent;
-
- /**
- * In some error conditions the request is checked
- * for idempotency and the result from the database
- * is stored here.
- */
- struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh_idem;
-
- /**
- * True if @e blinding_seed is missing in the request
- */
- bool no_blinding_seed;
-
- /**
- * Array @e persis.num_coins of hashes of the public keys
- * of the denominations to refresh.
- */
- struct TALER_DenominationHashP *denoms_h;
-
- /**
- * Array of @e refresh.num_coins blinded coin planchets, arranged
- * in runs of @e refresh.num_coins coins, [0..num_coins)..[0..num_coins),
- * one for each kappa value.
- */
- struct TALER_BlindedPlanchet *planchets[TALER_CNC_KAPPA];
-
- /**
- * @since vDOLDPLUS
- * #TALER_CNC_KAPPA arrays of @e refresh.num_coins transfer public keys
- * in runs of @e num_coins coins, [0..num_coins)..[0..num_coins),
- * one for each kappa value.
- *
- * MAYBE null. If the client has NOT provided the transfer_pubs in the request,
- * @e refresh.is_v27_refresh will be true.
- *
- * TODO: Once v27 clients are gone, this MUST NOT be nulls.
- */
- struct TALER_TransferPublicKeyP *transfer_pubs[TALER_CNC_KAPPA];
-
- /**
- * #TALER_CNC_KAPPA hashes of the batches of @e num_coins coins.
- */
- struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
-
- /**
- * Array @e withdraw.num_r_pubs of indices into @e denoms_h
- * of CS denominations.
- */
- uint32_t *cs_indices;
-
- /**
- * Total (over all coins) amount (excluding fee) committed for the refresh
- */
- struct TALER_Amount amount;
-
- } request;
-
- /**
- * Errors occurring during evaluation of the request are captured in this
- * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
- * message is prepared and sent to the client.
- */
- struct
- {
- /* The (internal) error code */
- enum MeltError code;
-
- /**
- * Some errors require details to be sent to the client.
- * These are captured in this union.
- * Each field is named according to the error that is using it, except
- * commented otherwise.
- */
- union
- {
- const char *request_parameter_malformed;
-
- /**
- * For all errors related to a particular denomination, i.e.
- * #MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- * #MELT_ERROR_DENOMINATION_EXPIRED,
- * #MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- * #MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- * we use this one field.
- */
- struct TALER_DenominationHashP denom_h;
-
- const char *db_fetch_context;
-
- enum TALER_ErrorCode ec_confirmation_sign;
-
- enum TALER_ErrorCode ec_denomination_sign;
-
- /* remaining value of the coin */
- struct TALER_Amount insufficient_funds;
-
- } details;
- } error;
-};
-
-/**
- * The following macros set the given error code,
- * set the phase to Melt_PHASE_GENERATE_REPLY_ERROR,
- * and optionally set the given field (with an optionally given value).
- */
-#define SET_ERROR(mc, ec) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_FIELD(mc, ec, field) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->error.details.field = (field); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_DETAIL(mc, ec, field, value) \
- do \
- { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
- (mc)->error.code = (ec); \
- (mc)->error.details.field = (value); \
- (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-
-/**
- * All melt context is kept in a DLL.
- */
-static struct MeltContext *mc_head;
-static struct MeltContext *mc_tail;
-
-void
-TEH_melt_cleanup ()
-{
- struct MeltContext *mc;
-
- while (NULL != (mc = mc_head))
- {
- GNUNET_CONTAINER_DLL_remove (mc_head,
- mc_tail,
- mc);
- MHD_resume_connection (mc->rc->connection);
- }
-}
-
-
-/**
- * Terminate the main loop by returning the final result.
- *
- * @param[in,out] mc context to update phase for
- * @param mres MHD status to return
- */
-static void
-finish_loop (struct MeltContext *mc,
- MHD_RESULT mres)
-{
- mc->phase = (MHD_YES == mres)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
-}
-
-
-/**
- * Free information in @a re, but not @a re itself.
- *
- * @param[in] re refresh data to free
- */
-static void
-free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *re)
-{
- if (NULL != re->denom_sigs)
- {
- for (size_t i = 0; i<re->num_coins; i++)
- TALER_blinded_denom_sig_free (&re->denom_sigs[i]);
- GNUNET_free (re->denom_sigs);
- }
- GNUNET_free (re->cs_r_values);
- GNUNET_free (re->denom_serials);
- GNUNET_free (re->denom_pub_hashes);
- TALER_denom_sig_free (&re->coin.denom_sig);
-}
-
-
-/**
- * Cleanup routine for melt request.
- * The function is called upon completion of the request
- * that should clean up @a rh_ctx.
- *
- * @param rc request context to clean up
- */
-static void
-clean_melt_rc (struct TEH_RequestContext *rc)
-{
- struct MeltContext *mc = rc->rh_ctx;
-
- GNUNET_free (mc->request.denoms_h);
- for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
- {
- for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
- TALER_blinded_planchet_free (&mc->request.planchets[k][i]);
- GNUNET_free (mc->request.planchets[k]);
- if (! mc->request.refresh.is_v27_refresh)
- GNUNET_free (mc->request.transfer_pubs[k]);
- }
- free_refresh (&mc->request.refresh);
- if (mc->request.is_idempotent)
- free_refresh (&mc->request.refresh_idem);
- GNUNET_free (mc->request.cs_indices);
- GNUNET_free (mc);
-}
-
-
-/**
- * Creates a new context for the incoming melt request
- *
- * @param mc melt request context
- * @param root json body of the request
- */
-static void
-phase_parse_request (
- struct MeltContext *mc,
- const json_t *root)
-{
- const json_t *j_denoms_h;
- const json_t *j_coin_evs;
- const json_t *j_transfer_pubs;
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
- &mc->request.refresh.coin.coin_pub),
- GNUNET_JSON_spec_fixed_auto ("old_denom_pub_h",
- &mc->request.refresh.coin.denom_pub_hash),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("old_age_commitment_h",
- &mc->request.refresh.coin.h_age_commitment),
- &mc->request.refresh.coin.no_age_commitment),
- TALER_JSON_spec_denom_sig ("old_denom_sig",
- &mc->request.refresh.coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("refresh_seed",
- &mc->request.refresh.refresh_seed),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("blinding_seed",
- &mc->request.refresh.blinding_seed),
- &mc->request.refresh.no_blinding_seed),
- TALER_JSON_spec_amount ("value_with_fee",
- TEH_currency,
- &mc->request.refresh.amount_with_fee),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_array_const ("transfer_pubs",
- &j_transfer_pubs),
- &mc->request.refresh.is_v27_refresh),
- GNUNET_JSON_spec_array_const ("denoms_h",
- &j_denoms_h),
- GNUNET_JSON_spec_array_const ("coin_evs",
- &j_coin_evs),
- GNUNET_JSON_spec_fixed_auto ("confirm_sig",
- &mc->request.refresh.coin_sig),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- root,
- spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
-
- /* validate array size */
- GNUNET_static_assert (
- TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
-
- mc->request.refresh.num_coins = json_array_size (j_denoms_h);
- if (0 == mc->request.refresh.num_coins)
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "denoms_h must not be empty");
- return;
- }
- if (TALER_MAX_COINS < mc->request.refresh.num_coins)
- {
- /**
- * The wallet had committed to more than the maximum coins allowed, the
- * reserve has been charged, but now the user can not melt any money
- * from it. Note that the user can't get their money back in this case!
- */
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "maximum number of coins that can be refreshed has been exceeded");
- return;
- }
- if (TALER_CNC_KAPPA != json_array_size (j_coin_evs))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "coin_evs must be an array of length "TALER_CNC_KAPPA_STR);
- return;
- }
- if (! mc->request.refresh.is_v27_refresh &&
- (TALER_CNC_KAPPA != json_array_size (j_transfer_pubs)))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "transfer_pubs must be an array of length "TALER_CNC_KAPPA_STR);
- return;
- }
-
-
- /* Extract the denomination hashes */
- {
- size_t idx;
- json_t *value;
-
- mc->request.denoms_h
- = GNUNET_new_array (mc->request.refresh.num_coins,
- struct TALER_DenominationHashP);
-
- json_array_foreach (j_denoms_h, idx, value) {
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &mc->request.denoms_h[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- value,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
- }
- }
-
- /* Parse blinded envelopes. */
- {
- json_t *j_kappa_planchets;
- size_t kappa;
- struct GNUNET_HashContext *ctx;
-
- /* ctx to calculate the planchet_h */
- ctx = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_assert (NULL != ctx);
-
- json_array_foreach (j_coin_evs, kappa, j_kappa_planchets)
- {
- json_t *j_cev;
- size_t idx;
-
- if (mc->request.refresh.num_coins != json_array_size (j_kappa_planchets))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "coin_evs[i][] size");
- return;
- }
-
- mc->request.planchets[kappa] =
- GNUNET_new_array (mc->request.refresh.num_coins,
- struct TALER_BlindedPlanchet);
-
- json_array_foreach (j_kappa_planchets, idx, j_cev)
- {
- /* Now parse the individual envelopes and calculate the hash of
- * the commitment along the way. */
- struct GNUNET_JSON_Specification kspec[] = {
- TALER_JSON_spec_blinded_planchet (NULL,
- &mc->request.planchets[kappa][idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- j_cev,
- kspec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
- }
- /* Save the hash of the batch of planchets for index kappa */
- TALER_wallet_blinded_planchets_hash (
- mc->request.refresh.num_coins,
- mc->request.planchets[kappa],
- mc->request.denoms_h,
- &mc->request.kappa_planchets_h.tuple[kappa]);
- GNUNET_CRYPTO_hash_context_read (
- ctx,
- &mc->request.kappa_planchets_h.tuple[kappa],
- sizeof(mc->request.kappa_planchets_h.tuple[kappa]));
- }
- /* Finally calculate the total hash over all planchets */
- GNUNET_CRYPTO_hash_context_finish (
- ctx,
- &mc->request.refresh.planchets_h.hash);
- }
- /* Check for duplicate planchets. Technically a bug on
- * the client side that is harmless for us, but still
- * not allowed per protocol
- */
- {
- size_t max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
-
- for (size_t I = 0; I < max_idx - 1; I++)
- {
- size_t ki = I / mc->request.refresh.num_coins;
- size_t ni = I % mc->request.refresh.num_coins;
-
- for (size_t J = I + 1; J < max_idx; J++)
- {
- size_t kj = J / mc->request.refresh.num_coins;
- size_t nj = J % mc->request.refresh.num_coins;
-
- if (0 == TALER_blinded_planchet_cmp (
- &mc->request.planchets[ki][ni],
- &mc->request.planchets[kj][nj]))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_DUPLICATE_TRANSFER_PUB);
- return;
- }
- }
- }
- }
-
- /* Parse the transfer public keys, if applicable */
- if (! mc->request.refresh.is_v27_refresh)
- {
- json_t *j_ktp;
- size_t kappa;
- size_t max_idx;
-
- json_array_foreach (j_transfer_pubs, kappa, j_ktp)
- {
- if (mc->request.refresh.num_coins !=
- json_array_size (j_ktp))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "transfer_pubs[i][] size");
- return;
- }
-
- mc->request.transfer_pubs[kappa] =
- GNUNET_new_array (mc->request.refresh.num_coins,
- struct TALER_TransferPublicKeyP);
-
- /* Parse the batch of @e num_coins transfer public keys
- * at index kappa */
- {
- struct GNUNET_JSON_Specification ktp_spec[] = {
- TALER_JSON_spec_array_fixed (NULL,
- mc->request.refresh.num_coins,
- mc->request.transfer_pubs[kappa],
- sizeof(*mc->request.transfer_pubs[kappa])
- ),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (mc->rc->connection,
- j_ktp,
- ktp_spec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- mc->phase = (GNUNET_NO == res)
- ? MELT_PHASE_RETURN_YES
- : MELT_PHASE_RETURN_NO;
- return;
- }
- }
- }
- /* Check for duplicate transfer public keys. Technically a bug on
- * the client side that is harmless for us, but still
- * not allowed per protocol
- */
- max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
-
- for (size_t I = 0; I < max_idx - 1; I++)
- {
- size_t ki = I / mc->request.refresh.num_coins;
- size_t ni = I % mc->request.refresh.num_coins;
-
- for (size_t J = I + 1; J < max_idx; J++)
- {
- size_t kj = J / mc->request.refresh.num_coins;
- size_t nj = J % mc->request.refresh.num_coins;
-
- if (0 == GNUNET_memcmp (
- &mc->request.transfer_pubs[ki][ni],
- &mc->request.transfer_pubs[kj][nj]))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_DUPLICATE_TRANSFER_PUB);
- return;
- }
- }
- }
- }
-
- mc->ksh = TEH_keys_get_state ();
- if (NULL == mc->ksh)
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_KEYS_MISSING);
- return;
- }
- mc->phase = MELT_PHASE_CHECK_MELT_VALID;
-}
-
-
-/**
- * Check if the given denomination is still or already valid, has not been
- * revoked and potentically supports age restriction.
- *
- * @param[in,out] mc context for the melt operation
- * @param denom_h Hash of the denomination key to check
- * @param[out] pdk denomination key found, might be NULL
- * @return #GNUNET_OK when denomation was found and valid,
- * #GNUNET_NO when denomination is not valid at this time
- * #GNUNET_SYSERR otherwise (denomination invalid), with finish_loop called.
- */
-static enum GNUNET_GenericReturnValue
-find_denomination (
- struct MeltContext *mc,
- const struct TALER_DenominationHashP *denom_h,
- struct TEH_DenominationKey **pdk)
-{
- struct TEH_DenominationKey *dk;
-
- *pdk = NULL;
- GNUNET_assert (NULL != mc->ksh);
- dk = TEH_keys_denomination_by_hash_from_state (mc->ksh,
- denom_h,
- NULL,
- NULL);
- if (NULL == dk)
- {
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
- *pdk = dk;
-
- if (GNUNET_TIME_absolute_is_past (
- dk->meta.expire_withdraw.abs_time))
- {
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_EXPIRED,
- denom_h,
- *denom_h);
- /**
- * Note that we return GNUNET_NO here.
- * This way phase_check_melt_valid can react
- * to it as a non-error case and do the zombie check.
- */
- return GNUNET_NO;
- }
-
- if (GNUNET_TIME_absolute_is_future (
- dk->meta.start.abs_time))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
-
- if (dk->recoup_possible)
- {
- SET_ERROR (mc,
- MELT_ERROR_DENOMINATION_REVOKED);
- return GNUNET_SYSERR;
- }
-
- /* In case of age melt, make sure that the denomination supports age restriction */
- if (! (mc->request.refresh.coin.no_age_commitment) &&
- (0 == dk->denom_pub.age_mask.bits))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- denom_h,
- *denom_h);
- return GNUNET_SYSERR;
- }
- if ((mc->request.refresh.coin.no_age_commitment) &&
- (0 != dk->denom_pub.age_mask.bits))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID);
- return GNUNET_SYSERR;
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Check if the given array of hashes of denomination_keys
- * - belong to valid denominations
- * - calculate the total amount of the denominations including fees
- * for melt.
- *
- * @param mc context of the melt to check keys for
- */
-static void
-phase_check_keys (
- struct MeltContext *mc)
-{
- bool is_cs_denom[mc->request.refresh.num_coins];
-
- memset (is_cs_denom,
- 0,
- sizeof(is_cs_denom));
-
- mc->request.refresh.denom_serials =
- GNUNET_new_array (mc->request.refresh.num_coins,
- uint64_t);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &mc->request.amount));
-
- /* Calculate the total value and withdraw fees for the fresh coins */
- for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
- {
- struct TEH_DenominationKey *dk;
-
- if (GNUNET_OK !=
- find_denomination (mc,
- &mc->request.denoms_h[i],
- &dk))
- return;
-
- if (GNUNET_CRYPTO_BSA_CS ==
- dk->denom_pub.bsign_pub_key->cipher)
- {
- if (mc->request.refresh.no_blinding_seed)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_BLINDING_SEED_REQUIRED);
- return;
- }
- mc->request.refresh.num_cs_r_values++;
- is_cs_denom[i] = true;
- }
- /* Ensure the ciphers from the planchets match the denominations'. */
- {
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- if (dk->denom_pub.bsign_pub_key->cipher !=
- mc->request.planchets[k][i].blinded_message->cipher)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_CIPHER_MISMATCH);
- return;
- }
- }
- }
- /* Accumulate the values */
- if (0 > TALER_amount_add (&mc->request.amount,
- &mc->request.amount,
- &dk->meta.value))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_OVERFLOW);
- return;
- }
- /* Accumulate the withdraw fees for the fresh coins */
- if (0 > TALER_amount_add (&mc->request.amount,
- &mc->request.amount,
- &dk->meta.fees.withdraw))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
- return;
- }
- mc->request.refresh.denom_serials[i] = dk->meta.serial;
- }
-
- /**
- * Calculate the amount (with withdraw fee) plus refresh fee and
- * compare with the value provided by the client in the request.
- */
- {
- struct TALER_Amount amount_with_fee;
-
- if (0 > TALER_amount_add (&amount_with_fee,
- &mc->request.amount,
- &mc->melted_coin_denom->meta.fees.refresh))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
- return;
- }
-
- if (0 != TALER_amount_cmp (&amount_with_fee,
- &mc->request.refresh.amount_with_fee))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT);
- return;
- }
- }
-
- /* Save the indices of CS denominations */
- if (0 < mc->request.refresh.num_cs_r_values)
- {
- size_t j = 0;
-
- mc->request.cs_indices = GNUNET_new_array (
- mc->request.refresh.num_cs_r_values,
- uint32_t);
-
- for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
- {
- if (is_cs_denom[i])
- mc->request.cs_indices[j++] = i;
- }
- }
- mc->phase++;
-}
-
-
-/**
- * Check that the client signature authorizing the melt is valid.
- *
- * @param[in,out] mc request context to check
- */
-static void
-phase_check_coin_signature (
- struct MeltContext *mc)
-{
- /* We can now compute the commitment */
- {
- struct TALER_KappaHashBlindedPlanchetsP k_bps_h = {0};
- struct TALER_KappaTransferPublicKeys k_transfer_pubs = {0};
-
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- TALER_wallet_blinded_planchets_hash (
- mc->request.refresh.num_coins,
- mc->request.planchets[k],
- mc->request.denoms_h,
- &k_bps_h.tuple[k]);
-
- if (! mc->request.refresh.is_v27_refresh)
- {
- k_transfer_pubs.num_transfer_pubs = mc->request.refresh.num_coins;
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- k_transfer_pubs.batch[k] = &mc->request.refresh.transfer_pubs[k];
- }
-
- TALER_refresh_get_commitment (
- &mc->request.refresh.rc,
- &mc->request.refresh.refresh_seed,
- mc->request.no_blinding_seed
- ? NULL
- : &mc->request.refresh.blinding_seed,
- mc->request.refresh.is_v27_refresh
- ? NULL
- : &k_transfer_pubs,
- &k_bps_h,
- &mc->request.refresh.coin.coin_pub,
- &mc->request.refresh.amount_with_fee);
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_melt_verify (
- &mc->request.refresh.amount_with_fee,
- &mc->melted_coin_denom->meta.fees.refresh,
- &mc->request.refresh.rc,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->request.refresh.coin.h_age_commitment,
- &mc->request.refresh.coin.coin_pub,
- &mc->request.refresh.coin_sig))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_SIGNATURE_INVALID);
- return;
- }
-
- mc->phase++;
-}
-
-
-/**
- * Check for information about the melted coin's denomination,
- * extracting its validity status and fee structure.
- * Baseline: check if deposits/refreshes are generally
- * simply still allowed for this denomination.
- *
- * @param mc parsed request information
- */
-static void
-phase_check_melt_valid (struct MeltContext *mc)
-{
- enum MeltPhase current_phase = mc->phase;
- /**
- * Find the old coin's denomination.
- * Note that we return only on GNUNET_SYSERR,
- * because GNUNET_NO for the expired denomination
- * will be handled below, with the zombie-check.
- */
- if (GNUNET_SYSERR ==
- find_denomination (mc,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->melted_coin_denom))
- return;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Melted coin's denomination is worth %s\n",
- TALER_amount2s (&mc->melted_coin_denom->meta.value));
-
- /* sanity-check that "total melt amount > melt fee" */
- if (0 <
- TALER_amount_cmp (&mc->melted_coin_denom->meta.fees.refresh,
- &mc->request.refresh.amount_with_fee))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_FEES_EXCEED_CONTRIBUTION);
- return;
- }
-
- if (GNUNET_OK !=
- TALER_test_coin_valid (&mc->request.refresh.coin,
- &mc->melted_coin_denom->denom_pub))
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_DENOMINATION_SIGNATURE_INVALID);
- return;
- }
-
- /**
- * find_denomination might have set the phase to
- * produce an error, but we are still investigating.
- * We reset the phase.
- */
- mc->phase = current_phase;
- mc->error.code = MELT_ERROR_NONE;
-
- if (GNUNET_TIME_absolute_is_past (
- mc->melted_coin_denom->meta.expire_deposit.abs_time))
- {
- /**
- * We are past deposit expiration time, but maybe this is a zombie?
- */
- struct TALER_DenominationHashP denom_hash;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Check that the coin is dirty (we have seen it before), as we will
- not just allow melting of a *fresh* coin where the denomination was
- revoked (those must be recouped) */
- qs = TEH_plugin->get_coin_denomination (
- TEH_plugin->cls,
- &mc->request.refresh.coin.coin_pub,
- &mc->known_coin_id,
- &denom_hash);
- if (0 > qs)
- {
- /* There is no good reason for a serialization failure here: */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- SET_ERROR (mc,
- MELT_ERROR_DB_FETCH_FAILED);
- return;
- }
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- /* We never saw this coin before, so _this_ justification is not OK.
- * Note that the error was already set in find_denominations. */
- GNUNET_assert (MELT_ERROR_DENOMINATION_EXPIRED ==
- mc->error.code);
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR ==
- mc->phase);
- return;
- }
- /* sanity check */
- if (0 !=
- GNUNET_memcmp (&denom_hash,
- &mc->request.refresh.coin.denom_pub_hash))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (mc,
- MELT_COIN_CONFLICTING_DENOMINATION_KEY,
- denom_h,
- denom_hash);
- return;
- }
- /* Minor optimization: no need to run the
- "ensure_coin_known" part of the transaction */
- mc->coin_is_known = true;
- /* check later that zombie is satisfied */
- mc->zombie_required = true;
- }
- mc->phase++;
-}
-
-
-/**
- * The request for melt was parsed successfully.
- * Sign and persist the chosen blinded coins for the reveal step.
- *
- * @param mc The context for the current melt request
- */
-static void
-phase_prepare_transaction (
- struct MeltContext *mc)
-{
- mc->request.refresh.denom_sigs
- = GNUNET_new_array (
- mc->request.refresh.num_coins,
- struct TALER_BlindedDenominationSignature);
- mc->request.refresh.noreveal_index =
- GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
- TALER_CNC_KAPPA);
-
- /* Choose and sign the coins */
- {
- struct TEH_CoinSignData csds[mc->request.refresh.num_coins];
- enum TALER_ErrorCode ec_denomination_sign;
- size_t noreveal_idx = mc->request.refresh.noreveal_index;
-
- memset (csds,
- 0,
- sizeof(csds));
-
- /* Pick the chosen blinded coins */
- for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
- {
- csds[i].bp = &mc->request.planchets[noreveal_idx][i];
- csds[i].h_denom_pub = &mc->request.denoms_h[i];
- }
-
- ec_denomination_sign = TEH_keys_denomination_batch_sign (
- mc->request.refresh.num_coins,
- csds,
- true, /* for melt */
- mc->request.refresh.denom_sigs);
- if (TALER_EC_NONE != ec_denomination_sign)
- {
- GNUNET_break (0);
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_DENOMINATION_SIGN,
- ec_denomination_sign);
- return;
- }
-
- /* Save the hash of chosen planchets */
- mc->request.refresh.selected_h =
- mc->request.kappa_planchets_h.tuple[noreveal_idx];
-
- /* If applicable, save the chosen transfer public keys */
- if (! mc->request.refresh.is_v27_refresh)
- mc->request.refresh.transfer_pubs =
- mc->request.transfer_pubs[noreveal_idx];
-
- /**
- * For the denominations with cipher CS, calculate the R-values
- * and save the choices we made now, as at a later point, the
- * private keys for the denominations might now be available anymore
- * to make the same choice again.
- */
- if (0 < mc->request.refresh.num_cs_r_values)
- {
- size_t num_cs_r_values = mc->request.refresh.num_cs_r_values;
- struct TEH_CsDeriveData cdds[num_cs_r_values];
- struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
-
- memset (nonces,
- 0,
- sizeof(nonces));
- mc->request.refresh.cs_r_values =
- GNUNET_new_array (num_cs_r_values,
- struct GNUNET_CRYPTO_CSPublicRPairP);
- mc->request.refresh.cs_r_choices = 0;
-
- GNUNET_assert (! mc->request.refresh.no_blinding_seed);
- TALER_cs_derive_nonces_from_seed (
- &mc->request.refresh.blinding_seed,
- true, /* for melt */
- num_cs_r_values,
- mc->request.cs_indices,
- nonces);
-
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = mc->request.cs_indices[i];
-
- GNUNET_assert (idx < mc->request.refresh.num_coins);
- cdds[i].h_denom_pub = &mc->request.denoms_h[idx];
- cdds[i].nonce = &nonces[i];
- }
-
- /**
- * Let the crypto helper generate the R-values and
- * make the choices
- */
- if (TALER_EC_NONE !=
- TEH_keys_denomination_cs_batch_r_pub_simple (
- mc->request.refresh.num_cs_r_values,
- cdds,
- true, /* for melt */
- mc->request.refresh.cs_r_values))
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_CRYPTO_HELPER);
- return;
- }
-
- /* Now save the choices for the selected bits */
- GNUNET_assert (num_cs_r_values <= 64);
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = mc->request.cs_indices[i];
-
- struct TALER_BlindedDenominationSignature *sig =
- &mc->request.refresh.denom_sigs[idx];
- uint64_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
-
- mc->request.refresh.cs_r_choices |= bit << i;
- GNUNET_static_assert (
- TALER_MAX_COINS <=
- sizeof(mc->request.refresh.cs_r_choices) * 8);
- }
- }
- }
- mc->phase++;
-}
-
-
-/**
- * Generates response for the melt request.
- *
- * @param mc melt operation context
- */
-static void
-phase_generate_reply_success (struct MeltContext *mc)
-{
- struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *db_obj;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec_confirmation_sign;
-
- db_obj = mc->request.is_idempotent
- ? &mc->request.refresh_idem
- : &mc->request.refresh;
- ec_confirmation_sign =
- TALER_exchange_online_melt_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &db_obj->rc,
- db_obj->noreveal_index,
- &pub,
- &sig);
- if (TALER_EC_NONE != ec_confirmation_sign)
- {
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_CONFIRMATION_SIGN,
- ec_confirmation_sign);
- return;
- }
-
- finish_loop (mc,
- TALER_MHD_REPLY_JSON_PACK (
- mc->rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_uint64 ("noreveal_index",
- db_obj->noreveal_index),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub)));
-}
-
-
-/**
- * Check if the melt request is replayed and we already have an answer.
- * If so, replay the existing answer and return the HTTP response.
- *
- * @param[in,out] mc parsed request data
- * @return true if the request is idempotent with an existing request
- * false if we did not find the request in the DB and did not set @a mret
- */
-static bool
-melt_is_idempotent (
- struct MeltContext *mc)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_refresh (
- TEH_plugin->cls,
- &mc->request.refresh.rc,
- &mc->request.refresh_idem);
- if (0 > qs)
- {
- /* FIXME: soft error not handled correctly! */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "get_refresh");
- return true; /* Well, kind-of. */
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return false;
-
- mc->request.is_idempotent = true;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "request is idempotent\n");
-
- /* Generate idempotent reply */
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT]++;
- mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
- mc->error.code = MELT_ERROR_NONE;
- return true;
-}
-
-
-/**
- * Reports an error, potentially with details.
- * That is, it puts a error-type specific response into the MHD queue.
- * It will do a idempotency check first, if needed for the error type.
- *
- * @param mc melt context
- */
-static void
-phase_generate_reply_error (
- struct MeltContext *mc)
-{
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
- GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
-
- if (IDEMPOTENCY_CHECK_REQUIRED (mc->error.code) &&
- melt_is_idempotent (mc))
- {
- return;
- }
-
- switch (mc->error.code)
- {
- case MELT_ERROR_NONE:
- break;
- case MELT_ERROR_REQUEST_PARAMETER_MALFORMED:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- mc->error.details.request_parameter_malformed));
- return;
- case MELT_ERROR_KEYS_MISSING:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL));
- return;
- case MELT_ERROR_DB_FETCH_FAILED:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- mc->error.details.db_fetch_context));
- return;
- case MELT_ERROR_DB_INVARIANT_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- NULL));
- return;
- case MELT_ERROR_DB_PREFLIGHT_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_COMMIT_FAILED,
- "make_coin_known"));
- return;
- case MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure"));
- return;
- case MELT_ERROR_COIN_UNKNOWN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
- NULL));
- return;
- case MELT_COIN_CONFLICTING_DENOMINATION_KEY:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
- TALER_B2S (&mc->error.details.denom_h)));
- return;
- case MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_SIGN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- mc->error.details.ec_denomination_sign,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_SIGNATURE_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_error (mc->rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL));
- return;
- case MELT_ERROR_DENOMINATION_KEY_UNKNOWN:
- GNUNET_break_op (0);
- finish_loop (mc,
- TEH_RESPONSE_reply_unknown_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h));
- return;
- case MELT_ERROR_DENOMINATION_EXPIRED:
- GNUNET_break_op (0);
- finish_loop (mc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "MELT"));
- return;
- case MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
- finish_loop (mc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- mc->rc->connection,
- &mc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "MELT"));
- return;
- case MELT_ERROR_DENOMINATION_REVOKED:
- GNUNET_break_op (0);
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- NULL));
- return;
- case MELT_ERROR_COIN_CIPHER_MISMATCH:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL));
- return;
- case MELT_ERROR_BLINDING_SEED_REQUIRED:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "blinding_seed"));
- return;
- case MELT_ERROR_CRYPTO_HELPER:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL));
- return;
- case MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
- {
- char msg[256];
-
- GNUNET_snprintf (msg,
- sizeof(msg),
- "denomination %s does not support age restriction",
- GNUNET_h2s (&mc->error.details.denom_h.hash));
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- msg));
- return;
- }
- case MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
- "old_age_commitment_h"));
- return;
- case MELT_ERROR_AMOUNT_OVERFLOW:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "amount"));
- return;
- case MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "amount+fee"));
- return;
- case MELT_ERROR_FEES_EXCEED_CONTRIBUTION:
- finish_loop (mc,
- TALER_MHD_reply_with_error (mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
- NULL));
- return;
- case MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
- "value_with_fee incorrect"));
- return;
- case MELT_ERROR_CONFIRMATION_SIGN:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- mc->error.details.ec_confirmation_sign,
- NULL));
- return;
- case MELT_ERROR_INSUFFICIENT_FUNDS:
- finish_loop (mc,
- TEH_RESPONSE_reply_coin_insufficient_funds (
- mc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &mc->request.refresh.coin.denom_pub_hash,
- &mc->request.refresh.coin.coin_pub));
- return;
- case MELT_ERROR_DUPLICATE_PLANCHET:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
- "duplicate planchet"));
- return;
- case MELT_ERROR_DUPLICATE_TRANSFER_PUB:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
- "duplicate transfer_pub"));
- return;
- case MELT_ERROR_NONCE_RESUSE:
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error */
- "nonce reuse"));
- return;
- case MELT_ERROR_COIN_SIGNATURE_INVALID:
- finish_loop (mc,
- TALER_MHD_reply_with_ec (
- mc->rc->connection,
- TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
- NULL));
- return;
- }
- GNUNET_break (0);
- finish_loop (mc,
- TALER_MHD_reply_with_error (
- mc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "error phase without error"));
-}
-
-
-/**
- * Function implementing melt transaction. Runs the
- * transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response. IF it returns an hard error,
- * the transaction logic MUST queue a MHD response and set @a mhd_ret.
- * IF it returns the soft error code, the function MAY be called again
- * to retry and MUST not queue a MHD response.
- *
- * @param cls a `struct MeltContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-melt_transaction (
- void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct MeltContext *mc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool balance_ok;
- bool found;
- bool nonce_reuse;
- uint32_t noreveal_index;
- struct TALER_Amount insufficient_funds;
-
- (void) connection;
- (void) mhd_ret;
-
- qs = TEH_plugin->do_refresh (TEH_plugin->cls,
- &mc->request.refresh,
- &mc->now,
- &found,
- &noreveal_index,
- &mc->zombie_required,
- &nonce_reuse,
- &balance_ok,
- &insufficient_funds);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- SET_ERROR_WITH_DETAIL (mc,
- MELT_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "do_refresh");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_UNKNOWN);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (found)
- {
- /**
- * This request is idempotent, set the nonreveal_index
- * to the previous one and reply success.
- */
- mc->request.refresh.noreveal_index = noreveal_index;
- mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
- mc->error.code = MELT_ERROR_NONE;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
- if (nonce_reuse)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_NONCE_RESUSE);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_FIELD (mc,
- MELT_ERROR_INSUFFICIENT_FUNDS,
- insufficient_funds);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (mc->zombie_required)
- {
- GNUNET_break_op (0);
- SET_ERROR (mc,
- MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
- return qs;
-}
-
-
-/**
- * The request was prepared successfully.
- * Run the main DB transaction.
- *
- * @param mc The context for the current melt request
- */
-static void
-phase_run_transaction (
- struct MeltContext *mc)
-{
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- SET_ERROR (mc,
- MELT_ERROR_DB_PREFLIGHT_FAILURE);
- return;
- }
-
- /* first, make sure coin is known */
- if (! mc->coin_is_known)
- {
- MHD_RESULT mhd_ret = -1;
- enum GNUNET_DB_QueryStatus qs;
-
- for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
- {
- qs = TEH_make_coin_known (&mc->request.refresh.coin,
- mc->rc->connection,
- &mc->known_coin_id,
- &mhd_ret);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
- if (0 > qs)
- {
- GNUNET_break (0);
- /* Check if an answer has been queued */
- switch (mhd_ret)
- {
- case MHD_NO:
- mc->phase = MELT_PHASE_RETURN_NO;
- return;
- case MHD_YES:
- mc->phase = MELT_PHASE_RETURN_YES;
- return;
- default: /* ignore */
- ;
- }
- SET_ERROR (mc,
- MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE);
- return;
- }
- }
-
- /* run main database transaction */
- {
- MHD_RESULT mhd_ret = -1;
- enum GNUNET_GenericReturnValue ret;
- enum MeltPhase current_phase = mc->phase;
-
- GNUNET_assert (MELT_PHASE_RUN_TRANSACTION ==
- current_phase);
- ret = TEH_DB_run_transaction (mc->rc->connection,
- "run melt",
- TEH_MT_REQUEST_MELT,
- &mhd_ret,
- &melt_transaction,
- mc);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- /* Check if an answer has been queued */
- switch (mhd_ret)
- {
- case MHD_NO:
- mc->phase = MELT_PHASE_RETURN_NO;
- return;
- case MHD_YES:
- mc->phase = MELT_PHASE_RETURN_YES;
- return;
- default: /* ignore */
- ;
- }
- GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
- GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
- return;
- }
- /**
- * In case of idempotency (which is not an error condition),
- * the phase has changed in melt_transaction.
- * We simple return.
- */
- if (current_phase != mc->phase)
- return;
- }
- mc->phase++;
-}
-
-
-MHD_RESULT
-TEH_handler_melt (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0])
-{
- struct MeltContext *mc = rc->rh_ctx;
-
- (void) args;
- if (NULL == mc)
- {
- mc = GNUNET_new (struct MeltContext);
- rc->rh_ctx = mc;
- rc->rh_cleaner = &clean_melt_rc;
- mc->rc = rc;
- mc->now = GNUNET_TIME_timestamp_get ();
- }
-
- while (true)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "melt processing in phase %d\n",
- mc->phase);
- switch (mc->phase)
- {
- case MELT_PHASE_PARSE:
- phase_parse_request (mc,
- root);
- break;
- case MELT_PHASE_CHECK_MELT_VALID:
- phase_check_melt_valid (mc);
- break;
- case MELT_PHASE_CHECK_KEYS:
- phase_check_keys (mc);
- break;
- case MELT_PHASE_CHECK_COIN_SIGNATURE:
- phase_check_coin_signature (mc);
- break;
- case MELT_PHASE_PREPARE_TRANSACTION:
- phase_prepare_transaction (mc);
- break;
- case MELT_PHASE_RUN_TRANSACTION:
- phase_run_transaction (mc);
- break;
- case MELT_PHASE_GENERATE_REPLY_SUCCESS:
- phase_generate_reply_success (mc);
- break;
- case MELT_PHASE_GENERATE_REPLY_ERROR:
- phase_generate_reply_error (mc);
- break;
- case MELT_PHASE_RETURN_YES:
- return MHD_YES;
- case MELT_PHASE_RETURN_NO:
- return MHD_NO;
- }
- }
-}
diff --git a/src/exchange/taler-exchange-httpd_melt.h b/src/exchange/taler-exchange-httpd_melt.h
@@ -1,52 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_melt.h
- * @brief Handle /melt requests, starting with vDOLDPLUS of the protocol
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_MELT_H
-#define TALER_EXCHANGE_HTTPD_MELT_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_melt_cleanup (void);
-
-
-/**
- * Handle a "/melt" request. Parses the request into the JSON
- * components and validates the melted coins, the signature and
- * execute the melt as database transaction.
- *
- * @param rc the request context
- * @param root uploaded JSON data
- * @param args array of additional options, not used
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_melt (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_metrics.c b/src/exchange/taler-exchange-httpd_metrics.c
@@ -1,160 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2015-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_metrics.c
- * @brief Handle /metrics requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_json_lib.h>
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_metrics.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include <jansson.h>
-
-
-unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
-
-unsigned long long TEH_METRICS_withdraw_num_coins;
-
-unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
-
-unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
-
-unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
-
-unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
-
-unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
-
-
-MHD_RESULT
-TEH_handler_metrics (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- char *reply;
- struct MHD_Response *resp;
- MHD_RESULT ret;
-
- (void) args;
- GNUNET_asprintf (&reply,
- "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
- "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
- "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
- "taler_exchange_success_transactions{type=\"%s\"} %llu\n"
- "# HELP taler_exchange_serialization_failures "
- " number of database serialization errors by type\n"
- "# TYPE taler_exchange_serialization_failures counter\n"
- "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
- "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
- "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
- "taler_exchange_serialization_failures{type=\"%s\"} %llu\n"
- "# HELP taler_exchange_received_requests "
- " number of received requests by type\n"
- "# TYPE taler_exchange_received_requests counter\n"
- "taler_exchange_received_requests{type=\"%s\"} %llu\n"
- "taler_exchange_received_requests{type=\"%s\"} %llu\n"
- "taler_exchange_received_requests{type=\"%s\"} %llu\n"
- "taler_exchange_received_requests{type=\"%s\"} %llu\n"
- "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
-#if NOT_YET_IMPLEMENTED
- "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
- "taler_exchange_idempotent_requests{type=\"%s\"} %llu\n"
-#endif
- "# HELP taler_exchange_num_signatures "
- " number of signatures created by cipher\n"
- "# TYPE taler_exchange_num_signatures counter\n"
- "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
- "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
- "taler_exchange_num_signatures{type=\"%s\"} %llu\n"
- "# HELP taler_exchange_num_signature_verifications "
- " number of signatures verified by cipher\n"
- "# TYPE taler_exchange_num_signature_verifications counter\n"
- "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
- "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
- "taler_exchange_num_signature_verifications{type=\"%s\"} %llu\n"
- "# HELP taler_exchange_num_keyexchanges "
- " number of key exchanges done by cipher\n"
- "# TYPE taler_exchange_num_keyexchanges counter\n"
- "taler_exchange_num_keyexchanges{type=\"%s\"} %llu\n"
- "# HELP taler_exchange_withdraw_num_coins "
- " number of coins withdrawn in a withdraw request\n"
- "# TYPE taler_exchange_withdraw_num_coins counter\n"
- "taler_exchange_withdraw_num_coins{} %llu\n",
- "deposit",
- TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT],
- "withdraw",
- TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW],
- "melt",
- TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT],
- "refresh-reveal",
- TEH_METRICS_num_success[TEH_MT_SUCCESS_REFRESH_REVEAL],
- "other",
- TEH_METRICS_num_conflict[TEH_MT_REQUEST_OTHER],
- "deposit",
- TEH_METRICS_num_conflict[TEH_MT_REQUEST_DEPOSIT],
- "withdraw",
- TEH_METRICS_num_conflict[TEH_MT_REQUEST_WITHDRAW],
- "melt",
- TEH_METRICS_num_conflict[TEH_MT_REQUEST_MELT],
- "other",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_OTHER],
- "deposit",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_DEPOSIT],
- "withdraw",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_WITHDRAW],
- "melt",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_MELT],
- "withdraw",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW],
-#if NOT_YET_IMPLEMENTED
- "deposit",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT],
- "melt",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT],
- "kyc-upload",
- TEH_METRICS_num_requests[TEH_MT_REQUEST_KYC_UPLOAD],
-#endif
- "rsa",
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_RSA],
- "cs",
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_CS],
- "eddsa",
- TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_EDDSA],
- "rsa",
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA],
- "cs",
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS],
- "eddsa",
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA],
- "ecdh",
- TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_ECDH],
- TEH_METRICS_withdraw_num_coins);
- resp = MHD_create_response_from_buffer (strlen (reply),
- reply,
- MHD_RESPMEM_MUST_FREE);
- ret = MHD_queue_response (rc->connection,
- MHD_HTTP_OK,
- resp);
- MHD_destroy_response (resp);
- return ret;
-}
-
-
-/* end of taler-exchange-httpd_metrics.c */
diff --git a/src/exchange/taler-exchange-httpd_metrics.h b/src/exchange/taler-exchange-httpd_metrics.h
@@ -1,132 +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_metrics.h
- * @brief Handle /metrics requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_METRICS_H
-#define TALER_EXCHANGE_HTTPD_METRICS_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Request types for which we collect metrics.
- */
-enum TEH_MetricTypeRequest
-{
- TEH_MT_REQUEST_OTHER = 0,
- TEH_MT_REQUEST_DEPOSIT = 1,
- TEH_MT_REQUEST_WITHDRAW = 2,
- TEH_MT_REQUEST_MELT = 3,
- TEH_MT_REQUEST_PURSE_CREATE = 4,
- TEH_MT_REQUEST_PURSE_MERGE = 5,
- TEH_MT_REQUEST_RESERVE_PURSE = 6,
- TEH_MT_REQUEST_PURSE_DEPOSIT = 7,
- TEH_MT_REQUEST_IDEMPOTENT_DEPOSIT = 8,
- TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW = 9,
- TEH_MT_REQUEST_IDEMPOTENT_MELT = 10,
- TEH_MT_REQUEST_BATCH_DEPOSIT = 11,
- TEH_MT_REQUEST_POLICY_FULFILLMENT = 12,
- TEH_MT_REQUEST_KYC_UPLOAD = 13,
- TEH_MT_REQUEST_COUNT = 14 /* MUST BE LAST! */
-};
-
-/**
- * Success types for which we collect metrics.
- */
-enum TEH_MetricTypeSuccess
-{
- TEH_MT_SUCCESS_DEPOSIT = 0,
- TEH_MT_SUCCESS_WITHDRAW = 1,
- TEH_MT_SUCCESS_MELT = 2,
- TEH_MT_SUCCESS_REFRESH_REVEAL = 3,
- TEH_MT_SUCCESS_WITHDRAW_REVEAL = 4,
- TEH_MT_SUCCESS_COUNT = 5 /* MUST BE LAST! */
-};
-
-/**
- * Cipher types for which we collect signature metrics.
- */
-enum TEH_MetricTypeSignature
-{
- TEH_MT_SIGNATURE_RSA = 0,
- TEH_MT_SIGNATURE_CS = 1,
- TEH_MT_SIGNATURE_EDDSA = 2,
- TEH_MT_SIGNATURE_COUNT = 3
-};
-
-/**
- * Cipher types for which we collect key exchange metrics.
- */
-enum TEH_MetricTypeKeyX
-{
- TEH_MT_KEYX_ECDH = 0,
- TEH_MT_KEYX_COUNT = 1
-};
-
-/**
- * Number of requests handled of the respective type.
- */
-extern unsigned long long TEH_METRICS_num_requests[TEH_MT_REQUEST_COUNT];
-
-/**
- * Number of successful requests handled of the respective type.
- */
-extern unsigned long long TEH_METRICS_num_success[TEH_MT_SUCCESS_COUNT];
-
-/**
- * Number of coins withdrawn in a withdraw request
- */
-extern unsigned long long TEH_METRICS_withdraw_num_coins;
-
-/**
- * Number of serialization errors encountered when
- * handling requests of the respective type.
- */
-extern unsigned long long TEH_METRICS_num_conflict[TEH_MT_REQUEST_COUNT];
-
-/**
- * Number of signatures created by the respective cipher.
- */
-extern unsigned long long TEH_METRICS_num_signatures[TEH_MT_SIGNATURE_COUNT];
-
-/**
- * Number of signatures verified by the respective cipher.
- */
-extern unsigned long long TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_COUNT];
-
-/**
- * Number of key exchanges done with the respective cipher.
- */
-extern unsigned long long TEH_METRICS_num_keyexchanges[TEH_MT_KEYX_COUNT];
-
-/**
- * Handle a "/metrics" 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_metrics (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-decision.c b/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-decision.c
@@ -0,0 +1,535 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023-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 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_post-aml-OFFICER_PUB-decision.c
+ * @brief Handle POST request about an AML decision.
+ * @author Christian Grothoff
+ * @author Florian Dold
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h"
+
+
+/**
+ * Context used for processing the AML decision request.
+ */
+struct AmlDecisionContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AmlDecisionContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct AmlDecisionContext *prev;
+
+ /**
+ * HTTP status code to use with @e response.
+ */
+ unsigned int response_code;
+
+ /**
+ * Response to return, NULL if none yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for async KYC processing.
+ */
+ struct TEH_KycMeasureRunContext *kat;
+
+};
+
+/**
+ * Kept in a DLL.
+ */
+static struct AmlDecisionContext *adc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct AmlDecisionContext *adc_tail;
+
+
+void
+TEH_aml_decision_cleanup ()
+{
+ struct AmlDecisionContext *adc;
+
+ while (NULL != (adc = adc_head))
+ {
+ MHD_resume_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (adc_head,
+ adc_tail,
+ adc);
+ }
+}
+
+
+/**
+ * Function called to clean up aml decision context.
+ *
+ * @param[in,out] rc context to clean up
+ */
+static void
+aml_decision_cleaner (struct TEH_RequestContext *rc)
+{
+ struct AmlDecisionContext *adc = rc->rh_ctx;
+
+ if (NULL != adc->kat)
+ {
+ TEH_kyc_run_measure_cancel (adc->kat);
+ adc->kat = NULL;
+ }
+ if (NULL != adc->response)
+ {
+ MHD_destroy_response (adc->response);
+ adc->response = NULL;
+ }
+ GNUNET_free (adc);
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param ec error code or 0 on success
+ * @param detail error message or NULL on success / no info
+ */
+static void
+aml_trigger_callback (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ struct AmlDecisionContext *adc = cls;
+
+ adc->kat = NULL;
+ GNUNET_assert (NULL == adc->response);
+
+ if (TALER_EC_NONE != ec)
+ {
+ adc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ adc->response = TALER_MHD_make_error (
+ ec,
+ detail);
+ }
+ else
+ {
+ adc->response_code = MHD_HTTP_NO_CONTENT;
+ adc->response = MHD_create_response_from_buffer_static (
+ 0,
+ "");
+ TALER_MHD_add_global_headers (adc->response,
+ true);
+ }
+
+ GNUNET_assert (NULL != adc->response);
+
+ MHD_resume_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (adc_head,
+ adc_tail,
+ adc);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+MHD_RESULT
+TEH_handler_post_aml_decision (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root)
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct AmlDecisionContext *adc = rc->rh_ctx;
+ const char *justification;
+ const char *new_measures = NULL;
+ bool to_investigate;
+ struct GNUNET_TIME_Timestamp decision_time;
+ struct GNUNET_TIME_Timestamp attributes_expiration
+ = GNUNET_TIME_UNIT_ZERO_TS;
+ const json_t *new_rules;
+ const json_t *events = NULL;
+ const json_t *properties = NULL;
+ const json_t *attributes = NULL;
+ struct TALER_FullPayto payto_uri = {
+ .full_payto = NULL
+ };
+ struct TALER_NormalizedPaytoHashP h_payto;
+ struct TALER_AmlOfficerSignatureP officer_sig;
+ struct TALER_KYCLOGIC_LegitimizationRuleSet *lrs = NULL;
+ uint64_t legi_measure_serial_id = 0;
+ MHD_RESULT ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string (
+ "new_measures",
+ &new_measures),
+ NULL),
+ GNUNET_JSON_spec_string ("justification",
+ &justification),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_full_payto_uri ("payto_uri",
+ &payto_uri),
+ NULL),
+ GNUNET_JSON_spec_fixed_auto ("h_payto",
+ &h_payto),
+ GNUNET_JSON_spec_object_const ("new_rules",
+ &new_rules),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("properties",
+ &properties),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("events",
+ &events),
+ NULL),
+ GNUNET_JSON_spec_bool ("keep_investigating",
+ &to_investigate),
+ GNUNET_JSON_spec_fixed_auto ("officer_sig",
+ &officer_sig),
+ GNUNET_JSON_spec_timestamp ("decision_time",
+ &decision_time),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("attributes_expiration",
+ &attributes_expiration),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("attributes",
+ &attributes),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Timestamp expiration_time;
+ json_t *jmeasures = NULL;
+ bool is_wallet;
+ size_t eas = 0;
+ void *ea = NULL;
+
+ if (NULL == adc)
+ {
+ /* Initialize context */
+ adc = GNUNET_new (struct AmlDecisionContext);
+ adc->rc = rc;
+ rc->rh_ctx = adc;
+ rc->rh_cleaner = aml_decision_cleaner;
+ }
+
+ if (NULL != adc->response)
+ {
+ ret = MHD_queue_response (rc->connection,
+ adc->response_code,
+ adc->response);
+ goto done;
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ ret = MHD_NO; /* hard failure */
+ goto done;
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ ret = MHD_YES /* failure */;
+ goto done;
+ }
+ if ( (NULL != attributes) &&
+ (! json_is_string (json_object_get (attributes,
+ "FORM_ID"))) )
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "attributes['FORM_ID']");
+ goto done;
+ }
+ }
+ if (NULL != payto_uri.full_payto)
+ {
+ struct TALER_NormalizedPaytoHashP h_payto2;
+
+ TALER_full_payto_normalize_and_hash (payto_uri,
+ &h_payto2);
+ if (0 !=
+ GNUNET_memcmp (&h_payto,
+ &h_payto2))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ goto done;
+ }
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_officer_aml_decision_verify (
+ justification,
+ decision_time,
+ &h_payto,
+ new_rules,
+ properties,
+ new_measures,
+ to_investigate,
+ officer_pub,
+ &officer_sig,
+ attributes_expiration,
+ attributes))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AML_DECISION_ADD_SIGNATURE_INVALID,
+ NULL);
+ goto done;
+ }
+
+ lrs = TALER_KYCLOGIC_rules_parse (new_rules);
+ if (NULL == lrs)
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "legitimization rule malformed");
+ goto done;
+ }
+
+ expiration_time = TALER_KYCLOGIC_rules_get_expiration (lrs);
+ if ( (NULL != new_measures) &&
+ (0 != strlen (new_measures)) )
+ {
+ jmeasures
+ = TALER_KYCLOGIC_get_jmeasures (lrs,
+ new_measures);
+ if (NULL == jmeasures)
+ {
+ GNUNET_break_op (0);
+ /* Request specified a new_measure for which the given
+ rule set does not work as it does not define the measure */
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "new_measures/new_rules");
+ goto done;
+ }
+ }
+
+ {
+ size_t num_events = json_array_size (events);
+ const char *sevents[GNUNET_NZL (num_events)];
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp last_date;
+ bool invalid_officer = true;
+ bool unknown_account = false;
+ struct GNUNET_HashCode h_attr = { 0 };
+ const char *form_id = NULL;
+
+ if (NULL != attributes)
+ {
+ form_id = json_string_value (json_object_get (attributes,
+ "FORM_ID"));
+ TALER_json_hash (attributes,
+ &h_attr);
+ TALER_CRYPTO_kyc_attributes_encrypt (&TEH_attribute_key,
+ attributes,
+ &ea,
+ &eas);
+ }
+
+ for (size_t i = 0; i<num_events; i++)
+ {
+ sevents[i] = json_string_value (json_array_get (events,
+ i));
+ if (NULL == sevents[i])
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "events");
+ goto done;
+ }
+ }
+ /* We keep 'new_measures' around mostly so that
+ the auditor can later verify officer_sig */
+ qs = TEH_plugin->insert_aml_decision (TEH_plugin->cls,
+ payto_uri,
+ &h_payto,
+ decision_time,
+ expiration_time,
+ properties,
+ new_rules,
+ to_investigate,
+ new_measures,
+ jmeasures,
+ justification,
+ officer_pub,
+ &officer_sig,
+ num_events,
+ sevents,
+ form_id,
+ eas, /* enc_attributes_size*/
+ ea, /* enc_attributes*/
+ NULL != attributes
+ ? &h_attr
+ : NULL, /* attributes_hash */
+ attributes_expiration,
+ &invalid_officer,
+ &unknown_account,
+ &last_date,
+ &legi_measure_serial_id,
+ &is_wallet);
+ json_decref (jmeasures);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_aml_decision");
+ goto done;
+ }
+ if (invalid_officer)
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AML_DECISION_INVALID_OFFICER,
+ NULL);
+ goto done;
+ }
+ if (unknown_account)
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_BANK_ACCOUNT_UNKNOWN,
+ "h_payto");
+ goto done;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ decision_time))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_AML_DECISION_MORE_RECENT_PRESENT,
+ NULL);
+ goto done;
+ }
+ }
+ /* Run instant measure if necessary */
+ {
+ const struct TALER_KYCLOGIC_Measure *instant_ms = NULL;
+
+ if (NULL != new_measures)
+ {
+ instant_ms = TALER_KYCLOGIC_get_instant_measure (lrs,
+ new_measures);
+ }
+
+ if (NULL != instant_ms)
+ {
+ /* We have an 'instant' measure which means we must run the
+ AML program immediately instead of waiting for the account owner
+ to select some measure and contribute their KYC data. */
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Running instant measure after AML decision\n");
+ adc->kat = TEH_kyc_run_measure_directly (
+ &rc->async_scope_id,
+ instant_ms,
+ &h_payto,
+ is_wallet,
+ &aml_trigger_callback,
+ adc
+ );
+ if (NULL == adc->kat)
+ {
+ GNUNET_break (0);
+ ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
+ "TEH_kyc_finished");
+ goto done;
+ }
+ MHD_suspend_connection (adc->rc->connection);
+ GNUNET_CONTAINER_DLL_insert (adc_head,
+ adc_tail,
+ adc);
+ ret = MHD_YES;
+ goto done;
+ }
+ }
+ ret = TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ goto done;
+
+done:
+ TALER_KYCLOGIC_rules_free (lrs);
+ GNUNET_free (ea);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_aml-decision.c */
diff --git a/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h b/src/exchange/taler-exchange-httpd_post-aml-OFFICER_PUB-decision.h
@@ -0,0 +1,67 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-aml-OFFICER_PUB-decision.h
+ * @brief Handle /aml/$OFFICER_PUB/decision requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_AML_OFFICER_PUB_DECISION_H
+#define TALER_EXCHANGE_HTTPD_POST_AML_OFFICER_PUB_DECISION_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/aml/$OFFICER_PUB/decision" request. Parses the decision
+ * details, checks the signatures and if appropriately authorized executes
+ * the decision.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_post_aml_decision (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const json_t *root);
+
+
+/**
+ * Handle a GET "/aml/$OFFICER_PUB/decisions" request. Parses the request
+ * details, checks the signatures and if appropriately authorized returns
+ * the matching decisions.
+ *
+ * @param rc request context
+ * @param officer_pub public key of the AML officer who made the request
+ * @param args GET arguments (should be the state)
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_aml_decisions_get (
+ struct TEH_RequestContext *rc,
+ const struct TALER_AmlOfficerPublicKeyP *officer_pub,
+ const char *const args[]);
+
+/**
+ * Clean up running POST /aml/$OFFICER_PUB/decisions requests.
+ */
+void
+TEH_aml_decision_cleanup (void);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c b/src/exchange/taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c
@@ -0,0 +1,232 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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_post-auditors-AUDITOR_PUB-H_DENOM_PUB.c
+ * @brief Handle request to add auditor signature on a denomination.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #add_auditor_denom_sig transaction.
+ */
+struct AddAuditorDenomContext
+{
+ /**
+ * Auditor's signature affirming the AUDITORS XXX operation
+ * (includes timestamp).
+ */
+ struct TALER_AuditorSignatureP auditor_sig;
+
+ /**
+ * Denomination this is about.
+ */
+ const struct TALER_DenominationHashP *h_denom_pub;
+
+ /**
+ * Auditor this is about.
+ */
+ const struct TALER_AuditorPublicKeyP *auditor_pub;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an auditors. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddAuditorDenomContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_auditor_denom_sig (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddAuditorDenomContext *awc = cls;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+ enum GNUNET_DB_QueryStatus qs;
+ char *auditor_url;
+ bool enabled;
+
+ qs = TEH_plugin->lookup_denomination_key (
+ TEH_plugin->cls,
+ awc->h_denom_pub,
+ &meta);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup denomination key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ GNUNET_h2s (&awc->h_denom_pub->hash));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->lookup_auditor_status (
+ TEH_plugin->cls,
+ awc->auditor_pub,
+ &auditor_url,
+ &enabled);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup auditor");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_PRECONDITION_FAILED,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_UNKNOWN,
+ TALER_B2S (awc->auditor_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! enabled)
+ {
+ GNUNET_free (auditor_url);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_INACTIVE,
+ TALER_B2S (awc->auditor_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_auditor_denom_validity_verify (
+ auditor_url,
+ awc->h_denom_pub,
+ &TEH_master_public_key,
+ meta.start,
+ meta.expire_withdraw,
+ meta.expire_deposit,
+ meta.expire_legal,
+ &meta.value,
+ &meta.fees,
+ awc->auditor_pub,
+ &awc->auditor_sig))
+ {
+ GNUNET_free (auditor_url);
+ /* signature invalid */
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_AUDITORS_AUDITOR_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (auditor_url);
+
+ qs = TEH_plugin->insert_auditor_denom_sig (TEH_plugin->cls,
+ awc->h_denom_pub,
+ awc->auditor_pub,
+ &awc->auditor_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add auditor signature");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_auditors (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root)
+{
+ struct AddAuditorDenomContext awc = {
+ .auditor_pub = auditor_pub,
+ .h_denom_pub = h_denom_pub
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("auditor_sig",
+ &awc.auditor_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ ret = TEH_DB_run_transaction (connection,
+ "add auditor denom sig",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &add_auditor_denom_sig,
+ &awc);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_auditors.c */
diff --git a/src/exchange/taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h b/src/exchange/taler-exchange-httpd_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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_post-auditors-AUDITOR_PUB-H_DENOM_PUB.h
+ * @brief Handlers for the /auditors/ endpoints
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_AUDITORS_AUDITOR_PUB_H_DENOM_PUB_H
+#define TALER_EXCHANGE_HTTPD_POST_AUDITORS_AUDITOR_PUB_H_DENOM_PUB_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/auditors/$AUDITOR_PUB/$H_DENOM_PUB" request.
+ *
+ * @param connection the MHD connection to handle
+ * @param root uploaded JSON data
+ * @param auditor_pub public key of the auditor
+ * @param h_denom_pub hash of the denomination public key
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_auditors (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-batch-deposit.c b/src/exchange/taler-exchange-httpd_post-batch-deposit.c
@@ -0,0 +1,1207 @@
+/*
+ This file is part of TALER
+ Copyright (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 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_post-batch-deposit.c
+ * @brief Handle /batch-deposit requests; parses the POST and JSON and
+ * verifies the coin signatures before handing things off
+ * to the database.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_extensions_policy.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_post-batch-deposit.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for #batch_deposit_transaction.
+ */
+struct BatchDepositContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct BatchDepositContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct BatchDepositContext *prev;
+
+ /**
+ * The request we are working on.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for the legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Array with the individual coin deposit fees.
+ */
+ struct TALER_Amount *deposit_fees;
+
+ /**
+ * Information about deposited coins.
+ */
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
+
+ /**
+ * Additional details for policy extension relevant for this
+ * deposit operation, possibly NULL!
+ */
+ json_t *policy_json;
+
+ /**
+ * Response to return, if set.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * KYC status of the reserve used for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Hash over @e policy_details, might be all zero
+ */
+ struct TALER_ExtensionPolicyHashP h_policy;
+
+ /**
+ * Hash over the merchant's payto://-URI with the wire salt.
+ */
+ struct TALER_MerchantWireHashP h_wire;
+
+ /**
+ * When @e policy_details are persisted, this contains the id of the record
+ * in the policy_details table.
+ */
+ uint64_t policy_details_serial_id;
+
+ /**
+ * Hash over the normalized payto://-URI of the account we are
+ * depositing into.
+ */
+ struct TALER_NormalizedPaytoHashP nph;
+
+ /**
+ * Our timestamp (when we received the request).
+ * Possibly updated by the transaction if the
+ * request is idempotent (was repeated).
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Total amount that is accumulated with this deposit,
+ * without fee.
+ */
+ struct TALER_Amount accumulated_total_without_fee;
+
+ /**
+ * Details about the batch deposit operation.
+ */
+ struct TALER_EXCHANGEDB_BatchDeposit bd;
+
+ /**
+ * If @e policy_json was present, the corresponding policy extension
+ * calculates these details. These will be persisted in the policy_details
+ * table.
+ */
+ struct TALER_PolicyDetails policy_details;
+
+ /**
+ * HTTP status to return with @e response, or 0.
+ */
+ unsigned int http_status;
+
+ /**
+ * Our current state in the state machine.
+ */
+ enum
+ {
+ BDC_PHASE_INIT = 0,
+ BDC_PHASE_PARSE = 1,
+ BDC_PHASE_POLICY = 2,
+ BDC_PHASE_KYC = 3,
+ BDC_PHASE_TRANSACT = 4,
+ BDC_PHASE_REPLY_SUCCESS = 5,
+ BDC_PHASE_SUSPENDED,
+ BDC_PHASE_CHECK_KYC_RESULT,
+ BDC_PHASE_GENERATE_REPLY_FAILURE,
+ BDC_PHASE_RETURN_YES,
+ BDC_PHASE_RETURN_NO,
+ } phase;
+
+ /**
+ * True, if no policy was present in the request. Then
+ * @e policy_json is NULL and @e h_policy will be all zero.
+ */
+ bool has_no_policy;
+
+ /**
+ * KYC failed because a KYC auth transfer is needed
+ * to establish the merchant_pub.
+ */
+ bool bad_kyc_auth;
+};
+
+
+/**
+ * Head of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_head;
+
+/**
+ * Tail of list of suspended batch deposit operations.
+ */
+static struct BatchDepositContext *bdc_tail;
+
+
+void
+TEH_batch_deposit_cleanup ()
+{
+ struct BatchDepositContext *bdc;
+
+ while (NULL != (bdc = bdc_head))
+ {
+ GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase);
+ bdc->phase = BDC_PHASE_RETURN_NO;
+ MHD_resume_connection (bdc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (bdc_head,
+ bdc_tail,
+ bdc);
+ }
+}
+
+
+/**
+ * Terminate the main loop by returning the final
+ * result.
+ *
+ * @param[in,out] bdc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct BatchDepositContext *bdc,
+ MHD_RESULT mres)
+{
+ bdc->phase = (MHD_YES == mres)
+ ? BDC_PHASE_RETURN_YES
+ : BDC_PHASE_RETURN_NO;
+}
+
+
+/**
+ * Send confirmation of batch deposit success to client. This function will
+ * create a signed message affirming the given information and return it to
+ * the client. By this, the exchange affirms that the coins had sufficient
+ * (residual) value for the specified transaction and that it will execute the
+ * requested batch deposit operation with the given wiring details.
+ *
+ * @param[in,out] bdc information about the batch deposit
+ */
+static void
+bdc_phase_reply_success (
+ struct BatchDepositContext *bdc)
+{
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
+ const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
+ enum TALER_ErrorCode ec;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+
+ for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
+ csigs[i] = &bd->cdis[i].csig;
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_deposit_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &bd->h_contract_terms,
+ &bdc->h_wire,
+ bdc->has_no_policy ? NULL : &bdc->h_policy,
+ bdc->exchange_timestamp,
+ bd->wire_deadline,
+ bd->refund_deadline,
+ &bdc->accumulated_total_without_fee,
+ bd->num_cdis,
+ csigs,
+ &bdc->bd.merchant_pub,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_ec (bdc->rc->connection,
+ ec,
+ NULL));
+ return;
+ }
+ finish_loop (bdc,
+ TALER_MHD_REPLY_JSON_PACK (
+ bdc->rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ bdc->exchange_timestamp),
+ TALER_JSON_pack_amount ("accumulated_total_without_fee",
+ &bdc->accumulated_total_without_fee),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig)));
+}
+
+
+/**
+ * Execute database transaction for /batch-deposit. Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls a `struct BatchDepositContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+batch_deposit_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct BatchDepositContext *bdc = cls;
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
+ enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
+ uint32_t bad_balance_coin_index = UINT32_MAX;
+ bool balance_ok;
+ bool in_conflict;
+
+ /* If the deposit has a policy associated to it, persist it. This will
+ * insert or update the record. */
+ if (! bdc->has_no_policy)
+ {
+ qs = TEH_plugin->persist_policy_details (
+ TEH_plugin->cls,
+ &bdc->policy_details,
+ &bdc->bd.policy_details_serial_id,
+ &bdc->accumulated_total_without_fee,
+ &bdc->policy_details.fulfillment_state);
+ if (qs < 0)
+ return qs;
+
+ bdc->bd.policy_blocked =
+ bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
+ }
+
+ /* FIXME-#9373: replace by batch insert! */
+ for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
+ {
+ const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
+ = &bdc->cdis[i];
+ uint64_t known_coin_id;
+
+ qs = TEH_make_coin_known (&cdi->coin,
+ connection,
+ &known_coin_id,
+ mhd_ret);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "make coin known (%s) returned %d\n",
+ TALER_B2S (&cdi->coin.coin_pub),
+ qs);
+ if (qs < 0)
+ return qs;
+ }
+ qs = TEH_plugin->do_deposit (
+ TEH_plugin->cls,
+ bd,
+ bdc->deposit_fees,
+ &bdc->exchange_timestamp,
+ &bdc->accumulated_total_without_fee,
+ &balance_ok,
+ &bad_balance_coin_index,
+ &in_conflict);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store /batch-deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "do_deposit returned: %d / %s[%u] / %s\n",
+ qs,
+ balance_ok ? "balance ok" : "balance insufficient",
+ (unsigned int) bad_balance_coin_index,
+ in_conflict ? "in conflict" : "no conflict");
+ if (in_conflict)
+ {
+ struct TALER_MerchantWireHashP h_wire;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TEH_plugin->get_wire_hash_for_contract (
+ TEH_plugin->cls,
+ &bd->merchant_pub,
+ &bd->h_contract_terms,
+ &h_wire))
+ {
+ TALER_LOG_WARNING (
+ "Failed to retrieve conflicting contract details from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "batch-deposit");
+ return qs;
+ }
+
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_conflicting_contract (
+ connection,
+ TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
+ &h_wire);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_assert (bad_balance_coin_index < bdc->bd.num_cdis);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "returning history of conflicting coin (%s)\n",
+ TALER_B2S (&bdc->cdis[bad_balance_coin_index].coin.coin_pub));
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash,
+ &bdc->cdis[bad_balance_coin_index].coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
+ return qs;
+}
+
+
+/**
+ * Run database transaction.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_transact (struct BatchDepositContext *bdc)
+{
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure"));
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (bdc->rc->connection,
+ "execute batch deposit",
+ TEH_MT_REQUEST_BATCH_DEPOSIT,
+ &mhd_ret,
+ &batch_deposit_transaction,
+ bdc))
+ {
+ finish_loop (bdc,
+ mhd_ret);
+ return;
+ }
+ bdc->phase++;
+}
+
+
+/**
+ * Check if the @a bdc is replayed and we already have an
+ * answer. If so, replay the existing answer and return the
+ * HTTP response.
+ *
+ * @param bdc parsed request data
+ * @return true if the request is idempotent with an existing request
+ * false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+check_request_idempotent (
+ struct BatchDepositContext *bdc)
+{
+ const struct TEH_RequestContext *rc = bdc->rc;
+ enum GNUNET_DB_QueryStatus qs;
+ bool is_idempotent;
+
+ qs = TEH_plugin->do_check_deposit_idempotent (
+ TEH_plugin->cls,
+ &bdc->bd,
+ &bdc->exchange_timestamp,
+ &is_idempotent);
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_check_deposit_idempotent"));
+ return true;
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
+ (! is_idempotent) )
+ return false;
+ bdc->phase = BDC_PHASE_REPLY_SUCCESS;
+ return true;
+}
+
+
+/**
+ * Check the KYC result.
+ *
+ * @param bdc storage for request processing
+ */
+static void
+bdc_phase_check_kyc_result (struct BatchDepositContext *bdc)
+{
+ /* return final positive response */
+ if ( (! bdc->kyc.ok) ||
+ (bdc->bad_kyc_auth) )
+ {
+ if (check_request_idempotent (bdc))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request is idempotent!\n");
+ return;
+ }
+ /* KYC required */
+ finish_loop (bdc,
+ TEH_RESPONSE_reply_kyc_required (
+ bdc->rc->connection,
+ &bdc->nph,
+ &bdc->kyc,
+ bdc->bad_kyc_auth));
+ return;
+ }
+ bdc->phase = BDC_PHASE_TRANSACT;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+deposit_legi_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct BatchDepositContext *bdc = cls;
+
+ bdc->lch = NULL;
+ GNUNET_assert (BDC_PHASE_SUSPENDED ==
+ bdc->phase);
+ MHD_resume_connection (bdc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (bdc_head,
+ bdc_tail,
+ bdc);
+ TALER_MHD_daemon_trigger ();
+ if (NULL != lcr->response)
+ {
+ bdc->response = lcr->response;
+ bdc->http_status = lcr->http_status;
+ bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE;
+ return;
+ }
+ bdc->kyc = lcr->kyc;
+ bdc->bad_kyc_auth = lcr->bad_kyc_auth;
+ bdc->phase = BDC_PHASE_CHECK_KYC_RESULT;
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant transaction amounts for a
+ * particular time range. Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and account to iterate
+ * over events for
+ * @param limit maximum time-range for which events should be fetched
+ * (timestamp in the past)
+ * @param cb function to call on each event found, events must be returned
+ * in reverse chronological order
+ * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposit_amount_cb (
+ void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct BatchDepositContext *bdc = cls;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signaling amount %s for KYC check during deposit\n",
+ TALER_amount2s (&bdc->accumulated_total_without_fee));
+ ret = cb (cb_cls,
+ &bdc->accumulated_total_without_fee,
+ bdc->exchange_timestamp.abs_time);
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ qs = TEH_plugin->select_deposit_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &bdc->nph,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this deposit and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+ return qs;
+}
+
+
+/**
+ * Run KYC check.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_kyc (struct BatchDepositContext *bdc)
+{
+ if (GNUNET_YES != TEH_enable_kyc)
+ {
+ bdc->phase++;
+ return;
+ }
+ TALER_full_payto_normalize_and_hash (bdc->bd.receiver_wire_account,
+ &bdc->nph);
+ bdc->lch = TEH_legitimization_check2 (
+ &bdc->rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
+ bdc->bd.receiver_wire_account,
+ &bdc->nph,
+ &bdc->bd.merchant_pub,
+ &deposit_amount_cb,
+ bdc,
+ &deposit_legi_cb,
+ bdc);
+ GNUNET_assert (NULL != bdc->lch);
+ GNUNET_CONTAINER_DLL_insert (bdc_head,
+ bdc_tail,
+ bdc);
+ MHD_suspend_connection (bdc->rc->connection);
+ bdc->phase = BDC_PHASE_SUSPENDED;
+}
+
+
+/**
+ * Handle policy.
+ *
+ * @param[in,out] bdc request context
+ */
+static void
+bdc_phase_policy (struct BatchDepositContext *bdc)
+{
+ const char *error_hint = NULL;
+
+ if (bdc->has_no_policy)
+ {
+ bdc->phase++;
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_extensions_create_policy_details (
+ TEH_currency,
+ bdc->policy_json,
+ &bdc->policy_details,
+ &error_hint))
+ {
+ GNUNET_break_op (0);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
+ error_hint));
+ return;
+ }
+
+ TALER_deposit_policy_hash (bdc->policy_json,
+ &bdc->h_policy);
+ bdc->phase++;
+}
+
+
+/**
+ * Parse per-coin deposit information from @a jcoin
+ * into @a deposit. Fill in generic information from
+ * @a ctx.
+ *
+ * @param bdc information about the overall batch
+ * @param jcoin coin data to parse
+ * @param[out] cdi where to store the result
+ * @param[out] deposit_fee where to write the deposit fee
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (const struct BatchDepositContext *bdc,
+ json_t *jcoin,
+ struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
+ struct TALER_Amount *deposit_fee)
+{
+ const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("contribution",
+ TEH_currency,
+ &cdi->amount_with_fee),
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &cdi->coin.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("ub_sig",
+ &cdi->coin.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("coin_pub",
+ &cdi->coin.coin_pub),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &cdi->coin.h_age_commitment),
+ &cdi->coin.no_age_commitment),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &cdi->csig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ if (GNUNET_OK !=
+ (res = TALER_MHD_parse_json_data (bdc->rc->connection,
+ jcoin,
+ spec)))
+ return res;
+ /* check denomination exists and is valid */
+ {
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ dk = TEH_keys_denomination_by_hash (
+ &cdi->coin.denom_pub_hash,
+ bdc->rc->connection,
+ &mret);
+ if (NULL == dk)
+ {
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES == mret)
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (0 > TALER_amount_cmp (&dk->meta.value,
+ &cdi->amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for deposits */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ bdc->rc->connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ bdc->rc->connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (dk->recoup_possible)
+ {
+ /* This denomination has been revoked */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ bdc->rc->connection,
+ &cdi->coin.denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ "DEPOSIT"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ cdi->coin.denom_sig.unblinded_sig->cipher)
+ {
+ /* denomination cipher and denomination signature cipher not the same */
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ *deposit_fee = dk->meta.fees.deposit;
+ /* check coin signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (&cdi->coin,
+ &dk->denom_pub))
+ {
+ TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ }
+ if (0 < TALER_amount_cmp (deposit_fee,
+ &cdi->amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
+ NULL))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_deposit_verify (
+ &cdi->amount_with_fee,
+ deposit_fee,
+ &bdc->h_wire,
+ &bd->h_contract_terms,
+ &bd->wallet_data_hash,
+ cdi->coin.no_age_commitment
+ ? NULL
+ : &cdi->coin.h_age_commitment,
+ NULL != bdc->policy_json ? &bdc->h_policy : NULL,
+ &cdi->coin.denom_pub_hash,
+ bd->wallet_timestamp,
+ &bd->merchant_pub,
+ bd->refund_deadline,
+ &cdi->coin.coin_pub,
+ &cdi->csig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
+ GNUNET_JSON_parse_free (spec);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
+ TALER_B2S (&cdi->coin.coin_pub)))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * Run processing phase that parses the request.
+ *
+ * @param[in,out] bdc request context
+ * @param root JSON object that was POSTed
+ */
+static void
+bdc_phase_parse (struct BatchDepositContext *bdc,
+ const json_t *root)
+{
+ struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
+ const json_t *coins;
+ const json_t *policy_json;
+ bool no_refund_deadline = true;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_full_payto_uri ("merchant_payto_uri",
+ &bd->receiver_wire_account),
+ GNUNET_JSON_spec_fixed_auto ("wire_salt",
+ &bd->wire_salt),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &bd->merchant_pub),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &bd->merchant_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &bd->h_contract_terms),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
+ &bd->wallet_data_hash),
+ &bd->no_wallet_data_hash),
+ GNUNET_JSON_spec_array_const ("coins",
+ &coins),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_object_const ("policy",
+ &policy_json),
+ &bdc->has_no_policy),
+ GNUNET_JSON_spec_timestamp ("timestamp",
+ &bd->wallet_timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("extra_wire_subject_metadata",
+ &bd->extra_wire_subject_metadata),
+ &bd->no_wallet_data_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("refund_deadline",
+ &bd->refund_deadline),
+ &no_refund_deadline),
+ GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
+ &bd->wire_deadline),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (bdc->rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ /* hard failure */
+ GNUNET_break (0);
+ finish_loop (bdc,
+ MHD_NO);
+ return;
+ }
+ if (GNUNET_NO == res)
+ {
+ /* failure */
+ GNUNET_break_op (0);
+ finish_loop (bdc,
+ MHD_YES);
+ return;
+ }
+ }
+ if (! TALER_is_valid_subject_metadata_string (
+ bd->extra_wire_subject_metadata))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "extra_wire_subject_metadata"));
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_merchant_contract_verify (
+ &bd->h_contract_terms,
+ &bd->merchant_pub,
+ &bd->merchant_sig))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "merchant_sig"));
+ return;
+ }
+ bdc->policy_json
+ = json_incref ((json_t *) policy_json);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Batch deposit into contract %s\n",
+ GNUNET_h2s (&bd->h_contract_terms.hash));
+
+ /* validate merchant's wire details (as far as we can) */
+ {
+ char *emsg;
+
+ emsg = TALER_payto_validate (bd->receiver_wire_account);
+ if (NULL != emsg)
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ ret = TALER_MHD_reply_with_error (bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ emsg);
+ GNUNET_free (emsg);
+ finish_loop (bdc,
+ ret);
+ return;
+ }
+ }
+ if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
+ >,
+ bd->wire_deadline))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
+ NULL));
+ return;
+ }
+ if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
+ NULL));
+ return;
+ }
+ TALER_full_payto_hash (bd->receiver_wire_account,
+ &bd->wire_target_h_payto);
+ TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
+ &bd->wire_salt,
+ &bdc->h_wire);
+ bd->num_cdis = json_array_size (coins);
+ if (0 == bd->num_cdis)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coins"));
+ return;
+ }
+ if (TALER_MAX_COINS < bd->num_cdis)
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ finish_loop (bdc,
+ TALER_MHD_reply_with_error (
+ bdc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "coins"));
+ return;
+ }
+
+ bdc->cdis
+ = GNUNET_new_array (bd->num_cdis,
+ struct TALER_EXCHANGEDB_CoinDepositInformation);
+ bdc->deposit_fees
+ = GNUNET_new_array (bd->num_cdis,
+ struct TALER_Amount);
+ bd->cdis = bdc->cdis;
+ for (unsigned i = 0; i<bd->num_cdis; i++)
+ {
+ struct TALER_Amount amount_without_fee;
+ enum GNUNET_GenericReturnValue res;
+
+ res = parse_coin (bdc,
+ json_array_get (coins,
+ i),
+ &bdc->cdis[i],
+ &bdc->deposit_fees[i]);
+ if (GNUNET_OK != res)
+ {
+ finish_loop (bdc,
+ (GNUNET_NO == res)
+ ? MHD_YES
+ : MHD_NO);
+ return;
+ }
+ GNUNET_assert (0 <=
+ TALER_amount_subtract (
+ &amount_without_fee,
+ &bdc->cdis[i].amount_with_fee,
+ &bdc->deposit_fees[i]));
+
+ GNUNET_assert (0 <=
+ TALER_amount_add (
+ &bdc->accumulated_total_without_fee,
+ &bdc->accumulated_total_without_fee,
+ &amount_without_fee));
+ }
+
+ GNUNET_JSON_parse_free (spec);
+ bdc->phase++;
+}
+
+
+/**
+ * Function called to clean up a context.
+ *
+ * @param rc request context with data to clean up
+ */
+static void
+bdc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct BatchDepositContext *bdc = rc->rh_ctx;
+
+ if (NULL != bdc->lch)
+ {
+ TEH_legitimization_check_cancel (bdc->lch);
+ bdc->lch = NULL;
+ }
+ if (0 != bdc->cdis)
+ {
+ for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
+ TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig);
+ GNUNET_free (bdc->cdis);
+ }
+ GNUNET_free (bdc->deposit_fees);
+ json_decref (bdc->policy_json);
+ GNUNET_free (bdc);
+}
+
+
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct BatchDepositContext *bdc = rc->rh_ctx;
+
+ (void) args;
+ if (NULL == bdc)
+ {
+ bdc = GNUNET_new (struct BatchDepositContext);
+ bdc->rc = rc;
+ rc->rh_ctx = bdc;
+ rc->rh_cleaner = &bdc_cleaner;
+ bdc->phase = BDC_PHASE_PARSE;
+ bdc->exchange_timestamp = GNUNET_TIME_timestamp_get ();
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &bdc->accumulated_total_without_fee));
+ }
+ while (1)
+ {
+ switch (bdc->phase)
+ {
+ case BDC_PHASE_INIT:
+ GNUNET_break (0);
+ bdc->phase = BDC_PHASE_RETURN_NO;
+ break;
+ case BDC_PHASE_PARSE:
+ bdc_phase_parse (bdc,
+ root);
+ break;
+ case BDC_PHASE_POLICY:
+ bdc_phase_policy (bdc);
+ break;
+ case BDC_PHASE_KYC:
+ bdc_phase_kyc (bdc);
+ break;
+ case BDC_PHASE_TRANSACT:
+ bdc_phase_transact (bdc);
+ break;
+ case BDC_PHASE_REPLY_SUCCESS:
+ bdc_phase_reply_success (bdc);
+ break;
+ case BDC_PHASE_SUSPENDED:
+ return MHD_YES;
+ case BDC_PHASE_CHECK_KYC_RESULT:
+ bdc_phase_check_kyc_result (bdc);
+ break;
+ case BDC_PHASE_GENERATE_REPLY_FAILURE:
+ return MHD_queue_response (bdc->rc->connection,
+ bdc->http_status,
+ bdc->response);
+ case BDC_PHASE_RETURN_YES:
+ return MHD_YES;
+ case BDC_PHASE_RETURN_NO:
+ return MHD_NO;
+ }
+ }
+}
+
+
+/* end of taler-exchange-httpd_batch-deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_post-batch-deposit.h b/src/exchange/taler-exchange-httpd_post-batch-deposit.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014 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_post-batch-deposit.h
+ * @brief Handle /batch-deposit requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_BATCH_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_POST_BATCH_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resumes all suspended batch deposit requests
+ * during cleanup.
+ */
+void
+TEH_batch_deposit_cleanup (void);
+
+
+/**
+ * Handle a "/batch-deposit" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #deposit_transaction() to
+ * further check the details of the operation specified. If everything checks
+ * out, this will ultimately lead to the "/batch-deposit" being executed, or
+ * rejected.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args arguments, empty in this case
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-blinding-prepare.c b/src/exchange/taler-exchange-httpd_post-blinding-prepare.c
@@ -0,0 +1,223 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-blinding-prepare.c
+ * @brief Handle /blinding-prepare requests
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_post-blinding-prepare.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+MHD_RESULT
+TEH_handler_blinding_prepare (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct TALER_BlindingMasterSeedP blinding_seed;
+ const char *cipher;
+ const char *operation;
+ const json_t *j_nks;
+ size_t num;
+ bool is_melt;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_string ("operation",
+ &operation),
+ GNUNET_JSON_spec_fixed_auto ("seed",
+ &blinding_seed),
+ GNUNET_JSON_spec_array_const ("nks",
+ &j_nks),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) args;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != res)
+ return (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES;
+ }
+ if (0 == strcmp (operation,
+ "melt"))
+ {
+ is_melt = true;
+ }
+ else if (0 == strcmp (operation,
+ "withdraw"))
+ {
+ is_melt = false;
+ }
+ else
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "operation");
+ }
+
+ num = json_array_size (j_nks);
+ if ((0 == num) ||
+ (TALER_MAX_COINS < num))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_NEW_DENOMS_ARRAY_SIZE_EXCESSIVE,
+ "nks");
+ }
+ if (0 != strcmp (cipher, "CS"))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ " cipher");
+ }
+
+ {
+ uint32_t cs_indices[num];
+ struct TALER_DenominationHashP h_denom_pubs[num];
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[num];
+
+ for (size_t i = 0; i < num; i++)
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification nks_spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &h_denom_pubs[i]),
+ GNUNET_JSON_spec_uint32 ("coin_offset",
+ &cs_indices[i]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_array (rc->connection,
+ j_nks,
+ nks_spec,
+ i,
+ -1);
+ if (GNUNET_OK != res)
+ return (GNUNET_SYSERR == res)
+ ? MHD_NO
+ : MHD_YES;
+
+ if (TALER_MAX_COINS < cs_indices[i])
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "nks");
+ }
+ }
+
+ TALER_cs_derive_nonces_from_seed (&blinding_seed,
+ is_melt,
+ num,
+ cs_indices,
+ nonces);
+ {
+ struct TEH_KeyStateHandle *ksh;
+ struct GNUNET_CRYPTO_CSPublicRPairP r_pubs[num];
+ size_t err_idx;
+ enum TALER_ErrorCode ec;
+
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+
+ ec =
+ TEH_keys_denomination_cs_batch_r_pub (ksh,
+ num,
+ h_denom_pubs,
+ nonces,
+ is_melt,
+ r_pubs,
+ &err_idx);
+ switch (ec)
+ {
+ case TALER_EC_NONE:
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN:
+ return TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ rc->connection,
+ &h_denom_pubs[err_idx]);
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_INVALID_DENOMINATION_CIPHER_FOR_OPERATION:
+ return TEH_RESPONSE_reply_invalid_denom_cipher_for_operation (
+ rc->connection,
+ &h_denom_pubs[err_idx]);
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED:
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &h_denom_pubs[err_idx],
+ ec,
+ "blinding-prepare");
+ break;
+ case TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE:
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ rc->connection,
+ &h_denom_pubs[err_idx],
+ ec,
+ "blinding-prepare");
+ break;
+ default:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (rc->connection,
+ ec,
+ NULL);
+ break;
+ }
+
+ /* Finally, create the response */
+ {
+ struct TALER_BlindingPrepareResponse response = {
+ .num = num,
+ .cipher = GNUNET_CRYPTO_BSA_CS,
+ .details.cs = r_pubs,
+ };
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_blinding_prepare_response (NULL,
+ &response));
+ }
+ }
+ }
+}
+
+
+/* end of taler-exchange-httpd_blinding_prepare.c */
diff --git a/src/exchange/taler-exchange-httpd_post-blinding-prepare.h b/src/exchange/taler-exchange-httpd_post-blinding-prepare.h
@@ -0,0 +1,40 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-blinding-prepare.h
+ * @brief Handle /blinding-prepare requests
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_BLINDING_PREPARE_H
+#define TALER_EXCHANGE_HTTPD_POST_BLINDING_PREPARE_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Handle a "/blinding-prepare" request.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_blinding_prepare (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-coins-COIN_PUB-refund.c b/src/exchange/taler-exchange-httpd_post-coins-COIN_PUB-refund.c
@@ -0,0 +1,369 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2020 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_post-coins-COIN_PUB-refund.c
+ * @brief Handle refund requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_post-coins-COIN_PUB-refund.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * How often do we retry after soft database errors?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Generate successful refund confirmation message.
+ *
+ * @param connection connection to the client
+ * @param coin_pub public key of the coin
+ * @param refund details about the successful refund
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_refund_success (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const struct TALER_EXCHANGEDB_RefundListEntry *refund)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_refund_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &refund->h_contract_terms,
+ coin_pub,
+ &refund->merchant_pub,
+ refund->rtransaction_id,
+ &refund->refund_amount,
+ &pub,
+ &sig)))
+ {
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Closure for refund_transaction().
+ */
+struct RefundContext
+{
+ /**
+ * Details about the deposit operation.
+ */
+ const struct TALER_EXCHANGEDB_Refund *refund;
+
+ /**
+ * Deposit fee of the coin.
+ */
+ struct TALER_Amount deposit_fee;
+
+ /**
+ * Unique ID of the coin in known_coins.
+ */
+ uint64_t known_coin_id;
+};
+
+
+/**
+ * Execute a "/refund" transaction. Returns a confirmation that the
+ * refund was successful, or a failure if we are not aware of a
+ * matching /deposit or if it is too late to do the refund.
+ *
+ * IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+refund_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct RefundContext *rctx = cls;
+ const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
+ enum GNUNET_DB_QueryStatus qs;
+ bool not_found;
+ bool refund_ok;
+ bool conflict;
+ bool gone;
+
+ /* Finally, store new refund data */
+ qs = TEH_plugin->do_refund (TEH_plugin->cls,
+ refund,
+ &rctx->deposit_fee,
+ rctx->known_coin_id,
+ ¬_found,
+ &refund_ok,
+ &gone,
+ &conflict);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do refund");
+ return qs;
+ }
+
+ if (gone)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
+ &refund->coin.denom_pub_hash,
+ &refund->coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (not_found)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! refund_ok)
+ {
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
+ &refund->coin.denom_pub_hash,
+ &refund->coin.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+/**
+ * We have parsed the JSON information about the refund, do some basic
+ * sanity checks (especially that the signature on the coin is valid)
+ * and then execute the refund. Note that we need the DB to check
+ * the fee structure, so this is not done here.
+ *
+ * @param connection the MHD connection to handle
+ * @param[in,out] refund information about the refund
+ * @return MHD result code
+ */
+static MHD_RESULT
+verify_and_execute_refund (struct MHD_Connection *connection,
+ struct TALER_EXCHANGEDB_Refund *refund)
+{
+ struct RefundContext rctx = {
+ .refund = refund
+ };
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_merchant_refund_verify (&refund->coin.coin_pub,
+ &refund->details.h_contract_terms,
+ refund->details.rtransaction_id,
+ &refund->details.refund_amount,
+ &refund->details.merchant_pub,
+ &refund->details.merchant_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on refund request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ /* Fetch the coin's denomination (hash) */
+ for (unsigned int i = 0; i < MAX_RETRIES; i++)
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
+ &refund->coin.coin_pub,
+ &rctx.known_coin_id,
+ &refund->coin.denom_pub_hash);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ if (i < MAX_RETRIES - 1)
+ continue;
+ /* otherwise: fall-through */
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_coin_denomination");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ MHD_RESULT res;
+ char *dhs;
+
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ dhs = GNUNET_STRINGS_data_to_string_alloc (
+ &refund->coin.denom_pub_hash,
+ sizeof (refund->coin.denom_pub_hash));
+ res = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND,
+ dhs);
+ GNUNET_free (dhs);
+ return res;
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ {
+ /* Obtain information about the coin's denomination! */
+ struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ {
+ /* DKI not found, but we do have a coin with this DK in our database;
+ not good... */
+ GNUNET_break (0);
+ return mret;
+ }
+ refund->details.refund_fee = dk->meta.fees.refund;
+ rctx.deposit_fee = dk->meta.fees.deposit;
+ }
+
+ /* Finally run the actual transaction logic */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "run refund",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &refund_transaction,
+ &rctx))
+ {
+ return mhd_ret;
+ }
+ }
+ return reply_refund_success (connection,
+ &refund->coin.coin_pub,
+ &refund->details);
+}
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_refund (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root)
+{
+ struct TALER_EXCHANGEDB_Refund refund = {
+ .details.refund_fee.currency = {0} /* set to invalid, just to be sure */
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("refund_amount",
+ TEH_currency,
+ &refund.details.refund_amount),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &refund.details.h_contract_terms),
+ GNUNET_JSON_spec_fixed_auto ("merchant_pub",
+ &refund.details.merchant_pub),
+ GNUNET_JSON_spec_uint64 ("rtransaction_id",
+ &refund.details.rtransaction_id),
+ GNUNET_JSON_spec_fixed_auto ("merchant_sig",
+ &refund.details.merchant_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ refund.coin.coin_pub = *coin_pub;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ {
+ MHD_RESULT res;
+
+ res = verify_and_execute_refund (connection,
+ &refund);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_refund.c */
diff --git a/src/exchange/taler-exchange-httpd_post-coins-COIN_PUB-refund.h b/src/exchange/taler-exchange-httpd_post-coins-COIN_PUB-refund.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 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_post-coins-COIN_PUB-refund.h
+ * @brief Handle /refund requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_COINS_COIN_PUB_REFUND_H
+#define TALER_EXCHANGE_HTTPD_POST_COINS_COIN_PUB_REFUND_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_refund() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_refund (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-start-ID.c b/src/exchange/taler-exchange-httpd_post-kyc-start-ID.c
@@ -0,0 +1,537 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021-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 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_post-kyc-start-ID.c
+ * @brief Handle request for starting a KYC process with an external provider.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_post-kyc-start-ID.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * POST request in asynchronous processing.
+ */
+struct KycPoller
+{
+
+ /**
+ * Access token for the KYC data of the account.
+ */
+ struct TALER_AccountAccessTokenP access_token;
+
+ /**
+ * Authorization hash for the selected measure.
+ */
+ struct TALER_KycMeasureAuthorizationHashP shv;
+
+ /**
+ * Hash of the payto:// URI we are starting to the KYC for.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycPoller *prev;
+
+ /**
+ * Connection we are handling.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Logic for @e ih
+ */
+ struct TALER_KYCLOGIC_Plugin *ih_logic;
+
+ /**
+ * Handle to asynchronously running KYC initiation
+ * request.
+ */
+ struct TALER_KYCLOGIC_InitiateHandle *ih;
+
+ /**
+ * Set of applicable KYC measures.
+ */
+ json_t *jmeasures;
+
+ /**
+ * Where to redirect the user to start the KYC process.
+ */
+ char *redirect_url;
+
+ /**
+ * Set to the name of the KYC provider.
+ */
+ const char *provider_name;
+
+ /**
+ * Set to error details, on error (@ec not TALER_EC_NONE).
+ */
+ char *hint;
+
+ /**
+ * Row of the requirement being started.
+ */
+ unsigned long long legitimization_measure_serial_id;
+
+ /**
+ * Row of KYC process being initiated.
+ */
+ uint64_t process_row;
+
+ /**
+ * Index of the measure this upload is for.
+ */
+ unsigned int measure_index;
+
+ /**
+ * Set to error encountered with KYC logic, if any.
+ */
+ enum TALER_ErrorCode ec;
+
+ /**
+ * True if we are still suspended.
+ */
+ bool suspended;
+
+ /**
+ * True if @e h_payto is for a wallet
+ */
+ bool is_wallet;
+
+};
+
+
+/**
+ * Head of list of requests in asynchronous processing.
+ */
+static struct KycPoller *kyp_head;
+
+/**
+ * Tail of list of requests in asynchronous processing.
+ */
+static struct KycPoller *kyp_tail;
+
+
+void
+TEH_kyc_start_cleanup ()
+{
+ struct KycPoller *kyp;
+
+ while (NULL != (kyp = kyp_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
+ if (kyp->suspended)
+ {
+ kyp->suspended = false;
+ MHD_resume_connection (kyp->connection);
+ }
+ }
+}
+
+
+/**
+ * Function called once a connection is done to
+ * clean up the `struct ReservePoller` state.
+ *
+ * @param rc context to clean up for
+ */
+static void
+kyp_cleanup (struct TEH_RequestContext *rc)
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+
+ GNUNET_assert (! kyp->suspended);
+ if (NULL != kyp->ih)
+ {
+ kyp->ih_logic->initiate_cancel (kyp->ih);
+ kyp->ih = NULL;
+ }
+ GNUNET_free (kyp->redirect_url);
+ GNUNET_free (kyp->hint);
+ json_decref (kyp->jmeasures);
+ GNUNET_free (kyp);
+}
+
+
+/**
+ * Function called with the result of a KYC initiation
+ * operation.
+ *
+ * @param cls closure with our `struct KycPoller *`
+ * @param ec #TALER_EC_NONE on success
+ * @param redirect_url set to where to redirect the user on success, NULL on failure
+ * @param provider_user_id set to user ID at the provider, or NULL if not supported or unknown
+ * @param provider_legitimization_id set to legitimization process ID at the provider, or NULL if not supported or unknown
+ * @param error_msg_hint set to additional details to return to user, NULL on success
+ */
+static void
+initiate_cb (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *redirect_url,
+ const char *provider_user_id,
+ const char *provider_legitimization_id,
+ const char *error_msg_hint)
+{
+ struct KycPoller *kyp = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ kyp->ih = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "KYC initiation `%s' completed with ec=%d (%s)\n",
+ provider_legitimization_id,
+ ec,
+ (TALER_EC_NONE == ec)
+ ? redirect_url
+ : error_msg_hint);
+ kyp->ec = ec;
+ if (TALER_EC_NONE == ec)
+ {
+ kyp->redirect_url = GNUNET_strdup (redirect_url);
+ }
+ else
+ {
+ kyp->hint = GNUNET_strdup (error_msg_hint);
+ }
+ qs = TEH_plugin->update_kyc_process_by_row (
+ TEH_plugin->cls,
+ kyp->process_row,
+ kyp->provider_name,
+ &kyp->h_payto,
+ provider_user_id,
+ provider_legitimization_id,
+ redirect_url,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ ec,
+ error_msg_hint,
+ TALER_EC_NONE != ec);
+ if (qs <= 0)
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC requirement update failed for %s with status %d at %s:%u\n",
+ TALER_B2S (&kyp->h_payto),
+ qs,
+ __FILE__,
+ __LINE__);
+ GNUNET_assert (kyp->suspended);
+ kyp->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_start (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1])
+{
+ struct KycPoller *kyp = rc->rh_ctx;
+
+ (void) root;
+ if (NULL == kyp)
+ {
+ const char *id = args[0];
+ enum GNUNET_DB_QueryStatus qs;
+ const struct TALER_KYCLOGIC_KycProvider *provider;
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+ bool is_finished;
+ const json_t *context;
+
+ kyp = GNUNET_new (struct KycPoller);
+ kyp->connection = rc->connection;
+ rc->rh_ctx = kyp;
+ rc->rh_cleaner = &kyp_cleanup;
+
+ {
+ char dummy;
+ const char *slash;
+
+ slash = strchr (id, '-');
+ if (NULL == slash)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (id,
+ slash - id,
+ &kyp->shv,
+ sizeof (kyp->shv)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Authorization hash in ID is malformed");
+ }
+ if (2 !=
+ sscanf (slash + 1,
+ "%u-%llu%c",
+ &kyp->measure_index,
+ &kyp->legitimization_measure_serial_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "ID is malformed");
+ }
+ }
+ qs = TEH_plugin->lookup_pending_legitimization (
+ TEH_plugin->cls,
+ kyp->legitimization_measure_serial_id,
+ &kyp->access_token,
+ &kyp->h_payto,
+ &kyp->jmeasures,
+ &is_finished,
+ &kyp->is_wallet);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR != qs);
+ return TALER_MHD_reply_with_ec (
+ rc->connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_pending_legitimization");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (is_finished)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
+ rc->url);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Found pending legitimization %llu with measures\n",
+ (unsigned long long) kyp->legitimization_measure_serial_id);
+ json_dumpf (kyp->jmeasures,
+ stderr,
+ JSON_INDENT (2));
+ fprintf (stderr,
+ "\n");
+
+ {
+ struct TALER_KycMeasureAuthorizationHashP shv2;
+
+ TALER_kyc_measure_authorization_hash (
+ &kyp->access_token,
+ kyp->legitimization_measure_serial_id,
+ kyp->measure_index,
+ &shv2);
+ if (0 !=
+ GNUNET_memcmp (&kyp->shv,
+ &shv2))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ }
+
+ {
+ const char *check_name;
+ const char *prog_name;
+
+ kyp->ec = TALER_KYCLOGIC_select_measure (
+ kyp->jmeasures,
+ kyp->measure_index,
+ &check_name,
+ &prog_name,
+ &context);
+ if (TALER_EC_NONE != kyp->ec)
+ {
+ /* return EC in next call to this function */
+ GNUNET_break_op (0);
+ kyp->hint
+ = GNUNET_strdup ("TALER_KYCLOGIC_select_measure");
+ return MHD_YES;
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Selected measure is %llu with check `%s'\n",
+ (unsigned long long) kyp->measure_index,
+ check_name);
+ if (NULL != context)
+ {
+ json_dumpf (context,
+ stderr,
+ JSON_INDENT (2));
+ fprintf (stderr,
+ "\n");
+ }
+
+ provider = TALER_KYCLOGIC_check_to_provider (
+ check_name);
+ if (NULL == provider)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_KYC_INVALID_LOGIC_TO_CHECK,
+ check_name);
+ }
+ }
+
+ TALER_KYCLOGIC_provider_to_logic (
+ provider,
+ &kyp->ih_logic,
+ &pd,
+ &kyp->provider_name);
+
+ /* FIXME-#9419: the next two DB interactions should be ONE
+ transaction */
+ /* Check if we already initiated this process */
+ qs = TEH_plugin->get_pending_kyc_requirement_process (
+ TEH_plugin->cls,
+ &kyp->h_payto,
+ kyp->provider_name,
+ &kyp->redirect_url);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ /* Simple query, never should be a soft error. */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_pending_kyc_requirement_process");
+ }
+ if (NULL != kyp->redirect_url)
+ return MHD_YES; /* success, return the redirect URL
+ (in next call to this function) */
+
+ /* set up new requirement process */
+ qs = TEH_plugin->insert_kyc_requirement_process (
+ TEH_plugin->cls,
+ &kyp->h_payto,
+ kyp->measure_index,
+ kyp->legitimization_measure_serial_id,
+ kyp->provider_name,
+ NULL, /* provider_account_id */
+ NULL, /* provider_legitimziation_id */
+ &kyp->process_row);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_requirement_process");
+ }
+
+ kyp->ih = kyp->ih_logic->initiate (
+ kyp->ih_logic->cls,
+ pd,
+ &kyp->h_payto,
+ kyp->process_row,
+ context,
+ &initiate_cb,
+ kyp);
+ if (NULL == kyp->ih)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ "initiate KYC process");
+ }
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+ }
+
+ if ( (TALER_EC_NONE != kyp->ec) ||
+ (NULL == kyp->redirect_url) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "KYC process failed to start with error %d (%s)\n",
+ (int) kyp->ec,
+ kyp->hint);
+ if (TALER_EC_NONE == kyp->ec)
+ {
+ GNUNET_break (0);
+ kyp->ec = TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE;
+ }
+ return TALER_MHD_reply_with_ec (rc->connection,
+ kyp->ec,
+ kyp->hint);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_string ("redirect_url",
+ kyp->redirect_url));
+}
+
+
+/* end of taler-exchange-httpd_kyc-start.c */
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-start-ID.h b/src/exchange/taler-exchange-httpd_post-kyc-start-ID.h
@@ -0,0 +1,50 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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 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_post-kyc-start-ID.h
+ * @brief Handle /kyc-start requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_KYC_START_ID_H
+#define TALER_EXCHANGE_HTTPD_POST_KYC_START_ID_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume suspended requests for /kyc-start.
+ */
+void
+TEH_kyc_start_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-start" request.
+ *
+ * @param rc request to handle
+ * @param root uploaded JSON data (empty by current API)
+ * @param args array with the ID
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_start (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-upload-ID.c b/src/exchange/taler-exchange-httpd_post-kyc-upload-ID.c
@@ -0,0 +1,567 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-kyc-upload-ID.c
+ * @brief Handle /kyc-upload/$ID request
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_post-kyc-upload-ID.h"
+
+#define MAX_RETRIES 3
+
+/**
+ * Context used for processing the KYC upload req
+ */
+struct UploadContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct UploadContext *prev;
+
+ /**
+ * Access token for the KYC data of the account.
+ */
+ struct TALER_AccountAccessTokenP access_token;
+
+ /**
+ * Index of the measure this upload is for.
+ */
+ unsigned int measure_index;
+
+ /**
+ * HTTP status code to use with @e response.
+ */
+ unsigned int response_code;
+
+ /**
+ * Index in the legitimization measures table this ID
+ * refers to.
+ */
+ unsigned long long legitimization_measure_serial_id;
+
+ /**
+ * Response to return, NULL if none yet.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Request we are processing.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for async KYC processing.
+ */
+ struct TEH_KycMeasureRunContext *kat;
+
+ /**
+ * Uploaded data, in JSON.
+ */
+ const json_t *result;
+
+ /**
+ * Set by the transaction to the legitimization process row.
+ */
+ uint64_t legi_process_row;
+
+ /**
+ * Set by the transaction to the affected account payto hash.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * Set by the transaction to true if the account is for a wallet.
+ */
+ bool is_wallet;
+
+};
+
+
+/**
+ * Kept in a DLL.
+ */
+static struct UploadContext *uc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct UploadContext *uc_tail;
+
+
+void
+TEH_kyc_upload_cleanup ()
+{
+ struct UploadContext *uc;
+
+ while (NULL != (uc = uc_head))
+ {
+ MHD_resume_connection (uc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (uc_head,
+ uc_tail,
+ uc);
+ }
+}
+
+
+/**
+ * Function called to clean up upload context.
+ *
+ * @param[in,out] rc context to clean up
+ */
+static void
+upload_cleaner (struct TEH_RequestContext *rc)
+{
+ struct UploadContext *uc = rc->rh_ctx;
+
+ if (NULL != uc->kat)
+ {
+ TEH_kyc_run_measure_cancel (uc->kat);
+ uc->kat = NULL;
+ }
+ if (NULL != uc->response)
+ {
+ MHD_destroy_response (uc->response);
+ uc->response = NULL;
+ }
+ GNUNET_free (uc);
+}
+
+
+/**
+ * Function called after the KYC-AML trigger is done.
+ *
+ * @param cls closure
+ * @param ec error code or 0 on success
+ * @param detail error message or NULL on success / no info
+ */
+static void
+aml_trigger_callback (
+ void *cls,
+ enum TALER_ErrorCode ec,
+ const char *detail)
+{
+ struct UploadContext *uc = cls;
+
+ uc->kat = NULL;
+ GNUNET_assert (NULL == uc->response);
+ if (TALER_EC_NONE != ec)
+ {
+ uc->response_code = TALER_ErrorCode_get_http_status (ec);
+ if (0 == uc->response_code)
+ {
+ GNUNET_break (0);
+ uc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ GNUNET_assert (uc->response_code != UINT_MAX);
+ uc->response = TALER_MHD_make_error (
+ ec,
+ detail);
+ }
+ else
+ {
+ uc->response_code = MHD_HTTP_NO_CONTENT;
+ uc->response = MHD_create_response_from_buffer_static (
+ 0,
+ ""
+ );
+ TALER_MHD_add_global_headers (uc->response,
+ true);
+ }
+
+ MHD_resume_connection (uc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (uc_head,
+ uc_tail,
+ uc);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Do the main database transaction.
+ *
+ * @param cls closure with a `struct UploadContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ */
+static enum GNUNET_DB_QueryStatus
+transact (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct UploadContext *uc = cls;
+ struct TEH_RequestContext *rc = uc->rc;
+ enum GNUNET_DB_QueryStatus qs;
+ json_t *jmeasures;
+ bool is_finished = false;
+ size_t enc_attributes_len;
+ void *enc_attributes;
+ const char *error_message;
+ char *form_name;
+ enum TALER_ErrorCode ec;
+
+ qs = TEH_plugin->lookup_completed_legitimization (
+ TEH_plugin->cls,
+ uc->legitimization_measure_serial_id,
+ uc->measure_index,
+ &uc->access_token,
+ &uc->h_payto,
+ &uc->is_wallet,
+ &jmeasures,
+ &is_finished,
+ &enc_attributes_len,
+ &enc_attributes);
+ /* FIXME: not exactly performant/elegant, should eventually
+ modify lookup_completed_legitimization to
+ return something if we are purely pending? */
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_completed_legitimization");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ qs = TEH_plugin->lookup_pending_legitimization (
+ TEH_plugin->cls,
+ uc->legitimization_measure_serial_id,
+ &uc->access_token,
+ &uc->h_payto,
+ &jmeasures,
+ &is_finished,
+ &uc->is_wallet);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup_pending_legitimization");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ if (NULL != enc_attributes)
+ {
+ json_t *xattributes;
+
+ xattributes
+ = TALER_CRYPTO_kyc_attributes_decrypt (
+ &TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_len);
+ if (json_equal (xattributes,
+ uc->result))
+ {
+ /* Request is idempotent! */
+ json_decref (xattributes);
+ GNUNET_free (enc_attributes);
+ json_decref (jmeasures);
+ if (is_finished)
+ {
+ *mhd_ret = TALER_MHD_reply_static (
+ rc->connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ /* Note: problem below is not here, but likely some previous
+ upload of the attributes failed badly in an AML program. */
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
+ "attributes known, but legitimization process failed");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ json_decref (xattributes);
+ GNUNET_free (enc_attributes);
+ json_decref (jmeasures);
+ /* Form was already done with with different attributes, conflict! */
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (is_finished)
+ {
+ /* This should not be possible (is_finished but NULL==enc_attributes),
+ but also we should not run logic again if we are finished. */
+ GNUNET_break_op (0);
+ json_decref (jmeasures);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ ec = TALER_KYCLOGIC_check_form (jmeasures,
+ uc->measure_index,
+ uc->result,
+ &form_name,
+ &error_message);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break_op (0);
+ json_decref (jmeasures);
+ *mhd_ret = TALER_MHD_reply_with_ec (
+ rc->connection,
+ ec,
+ error_message);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ json_decref (jmeasures);
+
+ /* Setup KYC process (which we will then immediately 'finish') */
+ qs = TEH_plugin->insert_kyc_requirement_process (
+ TEH_plugin->cls,
+ &uc->h_payto,
+ uc->measure_index,
+ uc->legitimization_measure_serial_id,
+ form_name,
+ NULL, /* provider account ID */
+ NULL, /* provider legi ID */
+ &uc->legi_process_row);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (form_name);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_kyc_requirement_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_free (form_name);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ GNUNET_free (form_name);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "insert_kyc_requirement_process");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = TEH_kyc_store_attributes (
+ uc->legi_process_row,
+ &uc->h_payto,
+ form_name,
+ NULL /* provider account */,
+ NULL /* provider legi ID */,
+ GNUNET_TIME_UNIT_FOREVER_ABS, /* expiration time */
+ uc->result);
+ GNUNET_free (form_name);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "kyc_store_attributes");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "kyc_store_attributes");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return qs;
+ }
+ GNUNET_assert (0);
+ *mhd_ret = MHD_NO;
+ return GNUNET_DB_STATUS_HARD_ERROR;
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_upload (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1])
+{
+ struct UploadContext *uc = rc->rh_ctx;
+ const char *id = args[0];
+
+ if (NULL == uc)
+ {
+ const char *slash;
+ char dummy;
+
+ uc = GNUNET_new (struct UploadContext);
+ uc->rc = rc;
+ uc->result = root;
+ rc->rh_ctx = uc;
+ rc->rh_cleaner = &upload_cleaner;
+ slash = strchr (id, '-');
+ if (NULL == slash)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
+ rc->url);
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (id,
+ slash - id,
+ &uc->access_token,
+ sizeof (uc->access_token)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "Access token in ID is malformed");
+ }
+ if (2 !=
+ sscanf (slash + 1,
+ "%u-%llu%c",
+ &uc->measure_index,
+ &uc->legitimization_measure_serial_id,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "ID is malformed");
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "/kyc-upload received form submission\n");
+ if ( (NULL != root) &&
+ (! json_is_string (json_object_get (root,
+ "FORM_ID"))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "FORM_ID");
+ }
+ json_dumpf (root,
+ stderr,
+ JSON_INDENT (2));
+ }
+ if (NULL != uc->response)
+ {
+ return MHD_queue_response (rc->connection,
+ uc->response_code,
+ uc->response);
+
+ }
+
+ if (GNUNET_OK !=
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_SETUP_FAILED,
+ NULL);
+ }
+
+ {
+ MHD_RESULT mhd_ret = -1;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "kyc-upload",
+ TEH_MT_REQUEST_KYC_UPLOAD,
+ &mhd_ret,
+ &transact,
+ uc))
+ return mhd_ret;
+ }
+
+ uc->kat = TEH_kyc_run_measure_for_attributes (
+ &rc->async_scope_id,
+ uc->legi_process_row,
+ &uc->h_payto,
+ uc->is_wallet,
+ &aml_trigger_callback,
+ uc);
+ if (NULL == uc->kat)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
+ "TEH_kyc_finished");
+ }
+ MHD_suspend_connection (uc->rc->connection);
+ GNUNET_CONTAINER_DLL_insert (uc_head,
+ uc_tail,
+ uc);
+ return MHD_YES;
+}
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-upload-ID.h b/src/exchange/taler-exchange-httpd_post-kyc-upload-ID.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 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_post-kyc-upload-ID.h
+ * @brief Handle /kyc-upload/$ID requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_KYC_UPLOAD_ID_H
+#define TALER_EXCHANGE_HTTPD_POST_KYC_UPLOAD_ID_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Resume suspended connections, called on shutdown.
+ */
+void
+TEH_kyc_upload_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-upload/$ID" request.
+ *
+ * @param rc request context
+ * @param root json body being uploaded
+ * @param args includes the ID from the URL (without "/")
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_upload (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-wallet.c b/src/exchange/taler-exchange-httpd_post-kyc-wallet.c
@@ -0,0 +1,336 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021, 2022, 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 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_post-kyc-wallet.c
+ * @brief Handle request for wallet for KYC check.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_post-kyc-wallet.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Context for the request.
+ */
+struct KycRequestContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycRequestContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct KycRequestContext *prev;
+
+ /**
+ * Handle for legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Payto URI of the reserve.
+ */
+ struct TALER_NormalizedPayto payto_uri;
+
+ /**
+ * Request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Response to return. Note that the response must
+ * be queued or destroyed by the callee. NULL
+ * if the legitimization check was successful and the handler should return
+ * a handler-specific result.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Public key of the reserve/wallet this is about.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * The wallet's public key
+ */
+ union TALER_AccountPublicKeyP wallet_pub;
+
+ /**
+ * Balance threshold crossed by the wallet.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * KYC status, with row with the legitimization requirement.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Smallest amount (over any timeframe) that may
+ * require additional KYC checks (if @a kyc.ok).
+ */
+ struct TALER_Amount next_threshold;
+
+ /**
+ * When do the current KYC rules possibly expire.
+ * Only valid if @a kyc.ok.
+ */
+ struct GNUNET_TIME_Timestamp expiration_date;
+
+ /**
+ * HTTP status code for @a response, or 0
+ */
+ unsigned int http_status;
+
+};
+
+
+/**
+ * Kept in a DLL.
+ */
+static struct KycRequestContext *krc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct KycRequestContext *krc_tail;
+
+
+void
+TEH_kyc_wallet_cleanup ()
+{
+ struct KycRequestContext *krc;
+
+ while (NULL != (krc = krc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (krc_head,
+ krc_tail,
+ krc);
+ MHD_resume_connection (krc->rc->connection);
+ }
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Returns the wallet balance.
+ *
+ * @param cls closure, a `struct KycRequestContext`
+ * @param limit maximum time-range for which events
+ * should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ * events must be returned in reverse chronological
+ * order
+ * @param cb_cls closure for @a cb
+ */
+static enum GNUNET_DB_QueryStatus
+balance_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct KycRequestContext *krc = cls;
+ enum GNUNET_GenericReturnValue ret;
+
+ (void) limit;
+ ret = cb (cb_cls,
+ &krc->balance,
+ GNUNET_TIME_absolute_get ());
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls must be a `struct KycRequestContext *`
+ * @param lcr legitimization check result
+ */
+static void
+legi_result_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct KycRequestContext *krc = cls;
+
+ TEH_plugin->preflight (TEH_plugin->cls);
+ krc->lch = NULL;
+ krc->http_status = lcr->http_status;
+ krc->response = lcr->response;
+ krc->kyc = lcr->kyc;
+ krc->next_threshold = lcr->next_threshold;
+ krc->expiration_date = lcr->expiration_date;
+ GNUNET_CONTAINER_DLL_remove (krc_head,
+ krc_tail,
+ krc);
+ MHD_resume_connection (krc->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Function to clean up our rh_ctx in @a rc
+ *
+ * @param[in,out] rc context to clean up
+ */
+static void
+krc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct KycRequestContext *krc = rc->rh_ctx;
+
+ if (NULL != krc->lch)
+ {
+ TEH_legitimization_check_cancel (krc->lch);
+ krc->lch = NULL;
+ }
+ GNUNET_free (krc->payto_uri.normalized_payto);
+ GNUNET_free (krc);
+}
+
+
+MHD_RESULT
+TEH_handler_kyc_wallet (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[])
+{
+ struct KycRequestContext *krc = rc->rh_ctx;
+
+ if (NULL == krc)
+ {
+ krc = GNUNET_new (struct KycRequestContext);
+ krc->rc = rc;
+ rc->rh_ctx = krc;
+ rc->rh_cleaner = &krc_cleaner;
+ {
+ struct TALER_ReserveSignatureP reserve_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &krc->wallet_pub.reserve_pub),
+ TALER_JSON_spec_amount ("balance",
+ TEH_currency,
+ &krc->balance),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue ret;
+
+ (void) args;
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_account_setup_verify (
+ &krc->wallet_pub.reserve_pub,
+ &krc->balance,
+ &reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_KYC_WALLET_SIGNATURE_INVALID,
+ NULL);
+ }
+ }
+ krc->payto_uri
+ = TALER_reserve_make_payto (TEH_base_url,
+ &krc->wallet_pub.reserve_pub);
+ TALER_normalized_payto_hash (krc->payto_uri,
+ &krc->h_payto);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "h_payto of wallet %s is %s\n",
+ krc->payto_uri.normalized_payto,
+ TALER_B2S (&krc->h_payto));
+ {
+ struct TALER_FullPayto fake_full_payto;
+
+ GNUNET_asprintf (&fake_full_payto.full_payto,
+ "%s?receiver-name=wallet",
+ krc->payto_uri.normalized_payto);
+ krc->lch = TEH_legitimization_check (
+ &rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_WALLET_BALANCE,
+ fake_full_payto,
+ &krc->h_payto,
+ &krc->wallet_pub,
+ &balance_iterator,
+ krc,
+ &legi_result_cb,
+ krc);
+ GNUNET_free (fake_full_payto.full_payto);
+ }
+ GNUNET_assert (NULL != krc->lch);
+ MHD_suspend_connection (rc->connection);
+ GNUNET_CONTAINER_DLL_insert (krc_head,
+ krc_tail,
+ krc);
+ return MHD_YES;
+ }
+ if (NULL != krc->response)
+ return MHD_queue_response (rc->connection,
+ krc->http_status,
+ krc->response);
+ if (krc->kyc.ok)
+ {
+ bool have_ts
+ = TALER_amount_is_valid (&krc->next_threshold);
+
+ /* KYC not required or already satisfied */
+ return TALER_MHD_REPLY_JSON_PACK (
+ rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ krc->expiration_date),
+ GNUNET_JSON_pack_allow_null (
+ TALER_JSON_pack_amount ("next_threshold",
+ have_ts
+ ? &krc->next_threshold
+ : NULL)));
+ }
+ return TEH_RESPONSE_reply_kyc_required (rc->connection,
+ &krc->h_payto,
+ &krc->kyc,
+ false);
+}
+
+
+/* end of taler-exchange-httpd_kyc-wallet.c */
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-wallet.h b/src/exchange/taler-exchange-httpd_post-kyc-wallet.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-kyc-wallet.h
+ * @brief Handle /kyc-wallet requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_KYC_WALLET_H
+#define TALER_EXCHANGE_HTTPD_POST_KYC_WALLET_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_kyc_wallet_cleanup (void);
+
+
+/**
+ * Handle a "/kyc-wallet" request. Parses the "reserve_pub" EdDSA key of the
+ * reserve and the signature "reserve_sig" which affirms the operation. If OK,
+ * a KYC record is created (if missing) and the KYC status returned.
+ *
+ * @param rc request to handle
+ * @param root uploaded JSON data
+ * @param args empty array
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_kyc_wallet (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-management-aml-officers.c b/src/exchange/taler-exchange-httpd_post-management-aml-officers.c
@@ -0,0 +1,142 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-management-aml-officers.c
+ * @brief Handle request to update AML officer status
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How often do we try the DB operation at most?
+ */
+#define MAX_RETRIES 10
+
+
+MHD_RESULT
+TEH_handler_management_aml_officers (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct TALER_AmlOfficerPublicKeyP officer_pub;
+ const char *officer_name;
+ struct GNUNET_TIME_Timestamp change_date;
+ bool is_active;
+ bool read_only;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("officer_pub",
+ &officer_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_bool ("is_active",
+ &is_active),
+ GNUNET_JSON_spec_bool ("read_only",
+ &read_only),
+ GNUNET_JSON_spec_string ("officer_name",
+ &officer_name),
+ GNUNET_JSON_spec_timestamp ("change_date",
+ &change_date),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_aml_officer_status_verify (
+ &officer_pub,
+ officer_name,
+ change_date,
+ is_active,
+ read_only,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_UPDATE_AML_OFFICER_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp last_date;
+ unsigned int retries_left = MAX_RETRIES;
+
+ do {
+ qs = TEH_plugin->insert_aml_officer (TEH_plugin->cls,
+ &officer_pub,
+ &master_sig,
+ officer_name,
+ is_active,
+ read_only,
+ change_date,
+ &last_date);
+ if (0 == --retries_left)
+ break;
+ } while (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_aml_officer");
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ change_date))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AML_OFFICERS_MORE_RECENT_PRESENT,
+ NULL);
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_aml-officers.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-auditors-AUDITOR_PUB-disable.c b/src/exchange/taler-exchange-httpd_post-management-auditors-AUDITOR_PUB-disable.c
@@ -0,0 +1,196 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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_post-management-auditors-AUDITOR_PUB-disable.c
+ * @brief Handle request to disable auditor.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for the #del_auditor transaction.
+ */
+struct DelAuditorContext
+{
+
+ /**
+ * Auditor public key this is about.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Auditor URL this is about.
+ */
+ const char *auditor_url;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_end;
+
+};
+
+
+/**
+ * Function implementing database transaction to del an auditor. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct DelAuditorContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+del_auditor (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DelAuditorContext *dac = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
+ &dac->auditor_pub,
+ &last_date);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup auditor");
+ return qs;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ dac->validity_end))
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->update_auditor (TEH_plugin->cls,
+ &dac->auditor_pub,
+ "", /* auditor URL */
+ "", /* auditor name */
+ dac->validity_end,
+ false);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "del auditor");
+ return qs;
+ }
+ TEH_keys_update_states ();
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_auditors_AP_disable (
+ struct MHD_Connection *connection,
+ const struct TALER_AuditorPublicKeyP *auditor_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct DelAuditorContext dac = {
+ .auditor_pub = *auditor_pub
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &dac.validity_end),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ if (GNUNET_OK !=
+ TALER_exchange_offline_auditor_del_verify (
+ auditor_pub,
+ dac.validity_end,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_DEL_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ ret = TEH_DB_run_transaction (connection,
+ "del auditor",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &del_auditor,
+ &dac);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_auditors_AP_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-auditors.c b/src/exchange/taler-exchange-httpd_post-management-auditors.c
@@ -0,0 +1,207 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 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_post-management-auditors.c
+ * @brief Handle request to add auditor.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for the #add_auditor transaction.
+ */
+struct AddAuditorContext
+{
+ /**
+ * Master signature to store.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Auditor public key this is about.
+ */
+ struct TALER_AuditorPublicKeyP auditor_pub;
+
+ /**
+ * Auditor URL this is about.
+ */
+ const char *auditor_url;
+
+ /**
+ * Human readable name of the auditor.
+ */
+ const char *auditor_name;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_start;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an auditor. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddAuditorContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_auditor (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddAuditorContext *aac = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_auditor_timestamp (TEH_plugin->cls,
+ &aac->auditor_pub,
+ &last_date);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup auditor");
+ return qs;
+ }
+ if ( (0 < qs) &&
+ (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ aac->validity_start) ) )
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 == qs)
+ qs = TEH_plugin->insert_auditor (TEH_plugin->cls,
+ &aac->auditor_pub,
+ aac->auditor_url,
+ aac->auditor_name,
+ aac->validity_start);
+ else
+ qs = TEH_plugin->update_auditor (TEH_plugin->cls,
+ &aac->auditor_pub,
+ aac->auditor_url,
+ aac->auditor_name,
+ aac->validity_start,
+ true);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add auditor");
+ return qs;
+ }
+ TEH_keys_update_states ();
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_auditors (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddAuditorContext aac;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &aac.master_sig),
+ GNUNET_JSON_spec_fixed_auto ("auditor_pub",
+ &aac.auditor_pub),
+ TALER_JSON_spec_web_url ("auditor_url",
+ &aac.auditor_url),
+ GNUNET_JSON_spec_string ("auditor_name",
+ &aac.auditor_name),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &aac.validity_start),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT res;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ if (GNUNET_OK !=
+ TALER_exchange_offline_auditor_add_verify (
+ &aac.auditor_pub,
+ aac.auditor_url,
+ aac.validity_start,
+ &TEH_master_public_key,
+ &aac.master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_AUDITOR_ADD_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ ret = TEH_DB_run_transaction (connection,
+ "add auditor",
+ TEH_MT_REQUEST_OTHER,
+ &res,
+ &add_auditor,
+ &aac);
+ if (GNUNET_SYSERR == ret)
+ return res;
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_auditors.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-denominations-H_DENOM_PUB-revoke.c b/src/exchange/taler-exchange-httpd_post-management-denominations-H_DENOM_PUB-revoke.c
@@ -0,0 +1,94 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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_post-management-denominations-H_DENOM_PUB-revoke.c
+ * @brief Handle denomination revocation requests.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+MHD_RESULT
+TEH_handler_management_denominations_HDP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_DenominationHashP *h_denom_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denomination_revoke_verify (
+ h_denom_pub,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_DENOMINATION_REVOKE_SIGNATURE_INVALID,
+ NULL);
+ }
+ qs = TEH_plugin->insert_denomination_revocation (TEH_plugin->cls,
+ h_denom_pub,
+ &master_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "denomination revocation");
+ }
+ TEH_keys_denomination_revoke (h_denom_pub);
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_denominations_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-drain.c b/src/exchange/taler-exchange-httpd_post-management-drain.c
@@ -0,0 +1,195 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-management-drain.c
+ * @brief Handle request to drain profits
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #drain transaction.
+ */
+struct DrainContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_DRAIN_PROFITS operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Wire transfer identifier to use.
+ */
+ struct TALER_WireTransferIdentifierRawP wtid;
+
+ /**
+ * Account to credit.
+ */
+ struct TALER_FullPayto payto_uri;
+
+ /**
+ * Configuration section with account to debit.
+ */
+ const char *account_section;
+
+ /**
+ * Signature time.
+ */
+ struct GNUNET_TIME_Timestamp date;
+
+ /**
+ * Amount to transfer.
+ */
+ struct TALER_Amount amount;
+
+};
+
+
+/**
+ * Function implementing database transaction to drain profits. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct DrainContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+drain (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DrainContext *dc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_drain_profit (
+ TEH_plugin->cls,
+ &dc->wtid,
+ dc->account_section,
+ dc->payto_uri,
+ dc->date,
+ &dc->amount,
+ &dc->master_sig);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert drain profit");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_drain (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct DrainContext dc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("debit_account_section",
+ &dc.account_section),
+ TALER_JSON_spec_full_payto_uri ("credit_payto_uri",
+ &dc.payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("wtid",
+ &dc.wtid),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &dc.master_sig),
+ GNUNET_JSON_spec_timestamp ("date",
+ &dc.date),
+ TALER_JSON_spec_amount ("amount",
+ TEH_currency,
+ &dc.amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_profit_drain_verify (
+ &dc.wtid,
+ dc.date,
+ &dc.amount,
+ dc.account_section,
+ dc.payto_uri,
+ &TEH_master_public_key,
+ &dc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_DRAIN_PROFITS_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "insert drain profit",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &drain,
+ &dc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_drain.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-extensions.c b/src/exchange/taler-exchange-httpd_post-management-extensions.c
@@ -0,0 +1,300 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-management-extensions.c
+ * @brief Handle request to POST /management/extensions
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_extensions.h"
+#include "taler/taler_dbevents.h"
+
+/**
+ * Extension carries the necessary data for a particular extension.
+ *
+ */
+struct Extension
+{
+ enum TALER_Extension_Type type;
+ json_t *manifest;
+};
+
+/**
+ * Closure for the #set_extensions transaction
+ */
+struct SetExtensionsContext
+{
+ uint32_t num_extensions;
+ struct Extension *extensions;
+ struct TALER_MasterSignatureP extensions_sig;
+};
+
+/**
+ * Function implementing database transaction to set the manifests of
+ * extensions. It runs the transaction logic.
+ * - IF it returns a non-error code, the transaction logic MUST NOT queue a
+ * MHD response.
+ * - IF it returns an hard error, the transaction logic MUST queue a MHD
+ * response and set @a mhd_ret.
+ * - IF it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct SetExtensionsContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+set_extensions (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct SetExtensionsContext *sec = cls;
+
+ /* save the manifests of all extensions */
+ for (uint32_t i = 0; i<sec->num_extensions; i++)
+ {
+ struct Extension *ext = &sec->extensions[i];
+ const struct TALER_Extension *taler_ext;
+ enum GNUNET_DB_QueryStatus qs;
+ char *manifest;
+
+ taler_ext = TALER_extensions_get_by_type (ext->type);
+ if (NULL == taler_ext)
+ {
+ /* No such extension found */
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ manifest = json_dumps (ext->manifest, JSON_COMPACT | JSON_SORT_KEYS);
+ if (NULL == manifest)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_INVALID,
+ "convert configuration to string");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->set_extension_manifest (
+ TEH_plugin->cls,
+ taler_ext->name,
+ manifest);
+
+ free (manifest);
+
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "save extension configuration");
+ }
+
+ /* Success, trigger event */
+ {
+ uint32_t nbo_type = htonl (sec->extensions[i].type);
+ struct GNUNET_DB_EventHeaderP ev = {
+ .size = htons (sizeof (ev)),
+ .type = htons (TALER_DBEVENT_EXCHANGE_EXTENSIONS_UPDATED)
+ };
+
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &ev,
+ &nbo_type,
+ sizeof(nbo_type));
+ }
+
+ }
+
+ /* All extensions configured, update the signature */
+ TEH_extensions_sig = sec->extensions_sig;
+ TEH_extensions_signed = true;
+
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
+}
+
+
+static enum GNUNET_GenericReturnValue
+verify_extensions_from_json (
+ const json_t *extensions,
+ struct SetExtensionsContext *sec)
+{
+ const char*name;
+ const struct TALER_Extension *extension;
+ size_t i = 0;
+ json_t *manifest;
+
+ GNUNET_assert (NULL != extensions);
+ GNUNET_assert (json_is_object (extensions));
+
+ sec->num_extensions = json_object_size (extensions);
+ sec->extensions = GNUNET_new_array (sec->num_extensions,
+ struct Extension);
+
+ json_object_foreach ((json_t *) extensions, name, manifest)
+ {
+ int critical = 0;
+ json_t *config;
+ const char *version = NULL;
+
+ /* load and verify criticality, version, etc. */
+ extension = TALER_extensions_get_by_name (name);
+ if (NULL == extension)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "no such extension: %s\n", name);
+ return GNUNET_SYSERR;
+ }
+
+ if (GNUNET_OK !=
+ TALER_extensions_parse_manifest (
+ manifest, &critical, &version, &config))
+ return GNUNET_SYSERR;
+
+ if (critical != extension->critical
+ || 0 != strcmp (version, extension->version) // FIXME-oec: libtool compare
+ || NULL == config
+ || GNUNET_OK != extension->load_config (config, NULL))
+ return GNUNET_SYSERR;
+
+ sec->extensions[i].type = extension->type;
+ sec->extensions[i].manifest = json_copy (manifest);
+ }
+
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_extensions (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ MHD_RESULT ret;
+ const json_t *extensions;
+ struct SetExtensionsContext sec = {0};
+ struct GNUNET_JSON_Specification top_spec[] = {
+ GNUNET_JSON_spec_object_const ("extensions",
+ &extensions),
+ GNUNET_JSON_spec_fixed_auto ("extensions_sig",
+ &sec.extensions_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /* Parse the top level json structure */
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ top_spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+
+ /* Verify the signature */
+ {
+ struct TALER_ExtensionManifestsHashP h_manifests;
+
+ if (GNUNET_OK !=
+ TALER_JSON_extensions_manifests_hash (extensions,
+ &h_manifests) ||
+ GNUNET_OK !=
+ TALER_exchange_offline_extension_manifests_hash_verify (
+ &h_manifests,
+ &TEH_master_public_key,
+ &sec.extensions_sig))
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid signuture");
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received /management/extensions\n");
+
+ /* Now parse individual extensions and signatures from those objects. */
+ if (GNUNET_OK !=
+ verify_extensions_from_json (extensions, &sec))
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "invalid object");
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received %u extensions\n",
+ sec.num_extensions);
+
+ /* now run the transaction to persist the configurations */
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_DB_run_transaction (connection,
+ "set extensions",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &set_extensions,
+ &sec);
+
+ if (GNUNET_SYSERR == res)
+ goto CLEANUP;
+ }
+
+ ret = TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+
+CLEANUP:
+ for (unsigned int i = 0; i < sec.num_extensions; i++)
+ {
+ if (NULL != sec.extensions[i].manifest)
+ {
+ json_decref (sec.extensions[i].manifest);
+ }
+ }
+ GNUNET_free (sec.extensions);
+ return ret;
+}
+
+
+/* end of taler-exchange-httpd_management_management_post_extensions.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-global-fees.c b/src/exchange/taler-exchange-httpd_post-management-global-fees.c
@@ -0,0 +1,261 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2021, 2022 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_post-management-global-fees.c
+ * @brief Handle request to add global fee details
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Closure for the #add_fee transaction.
+ */
+struct AddFeeContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_GLOBAL_FEES operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Starting period.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * End of period.
+ */
+ struct GNUNET_TIME_Timestamp end_time;
+
+ /**
+ * Global fee amounts.
+ */
+ struct TALER_GlobalFeeSet fees;
+
+ /**
+ * When does an unmerged purse expire?
+ */
+ struct GNUNET_TIME_Relative purse_timeout;
+
+ /**
+ * When does an account without KYC expire?
+ */
+ struct GNUNET_TIME_Relative kyc_timeout;
+
+ /**
+ * When does an account history expire?
+ */
+ struct GNUNET_TIME_Relative history_expiration;
+
+ /**
+ * Number of free purses per account.
+ */
+ uint32_t purse_account_limit;
+
+};
+
+
+/**
+ * Function implementing database transaction to add a fee. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddFeeContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_fee (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddFeeContext *afc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_GlobalFeeSet fees;
+ struct GNUNET_TIME_Relative purse_timeout;
+ struct GNUNET_TIME_Relative history_expiration;
+ uint32_t purse_account_limit;
+
+ qs = TEH_plugin->lookup_global_fee_by_time (
+ TEH_plugin->cls,
+ afc->start_time,
+ afc->end_time,
+ &fees,
+ &purse_timeout,
+ &history_expiration,
+ &purse_account_limit);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup global fee");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&fees.history)) &&
+ (0 ==
+ TALER_global_fee_set_cmp (&fees,
+ &afc->fees)) )
+ {
+ /* this will trigger the 'success' response */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_MISMATCH,
+ NULL);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_global_fee (
+ TEH_plugin->cls,
+ afc->start_time,
+ afc->end_time,
+ &afc->fees,
+ afc->purse_timeout,
+ afc->history_expiration,
+ afc->purse_account_limit,
+ &afc->master_sig);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert fee");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_global_fees (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddFeeContext afc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &afc.master_sig),
+ GNUNET_JSON_spec_timestamp ("fee_start",
+ &afc.start_time),
+ GNUNET_JSON_spec_timestamp ("fee_end",
+ &afc.end_time),
+ TALER_JSON_spec_amount ("history_fee",
+ TEH_currency,
+ &afc.fees.history),
+ TALER_JSON_spec_amount ("account_fee",
+ TEH_currency,
+ &afc.fees.account),
+ TALER_JSON_spec_amount ("purse_fee",
+ TEH_currency,
+ &afc.fees.purse),
+ GNUNET_JSON_spec_relative_time ("purse_timeout",
+ &afc.purse_timeout),
+ GNUNET_JSON_spec_relative_time ("history_expiration",
+ &afc.history_expiration),
+ GNUNET_JSON_spec_uint32 ("purse_account_limit",
+ &afc.purse_account_limit),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_global_fee_verify (
+ afc.start_time,
+ afc.end_time,
+ &afc.fees,
+ afc.purse_timeout,
+ afc.history_expiration,
+ afc.purse_account_limit,
+ &TEH_master_public_key,
+ &afc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_GLOBAL_FEE_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add global fee",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_fee,
+ &afc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_global_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-keys.c b/src/exchange/taler-exchange-httpd_post-management-keys.c
@@ -0,0 +1,574 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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_post-management-keys.c
+ * @brief Handle request to POST /management/keys
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * Denomination signature provided.
+ */
+struct DenomSig
+{
+ /**
+ * Hash of a denomination public key.
+ */
+ struct TALER_DenominationHashP h_denom_pub;
+
+ /**
+ * Master signature for the @e h_denom_pub.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Fee structure for this key, as per our configuration.
+ */
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /**
+ * The full public key.
+ */
+ struct TALER_DenominationPublicKey denom_pub;
+
+};
+
+
+/**
+ * Signkey signature provided.
+ */
+struct SigningSig
+{
+ /**
+ * Online signing key of the exchange.
+ */
+ struct TALER_ExchangePublicKeyP exchange_pub;
+
+ /**
+ * Master signature for the @e exchange_pub.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Our meta data on this key.
+ */
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+};
+
+
+/**
+ * Closure for the #add_keys transaction.
+ */
+struct AddKeysContext
+{
+
+ /**
+ * Array of @e nd_sigs denomination signatures.
+ */
+ struct DenomSig *d_sigs;
+
+ /**
+ * Array of @e ns_sigs signkey signatures.
+ */
+ struct SigningSig *s_sigs;
+
+ /**
+ * Our key state.
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * Length of the d_sigs array.
+ */
+ unsigned int nd_sigs;
+
+ /**
+ * Length of the n_sigs array.
+ */
+ unsigned int ns_sigs;
+
+};
+
+
+/**
+ * Compare meta-data of two denomination keys for equality,
+ * except for the "serial" number.
+ *
+ * @param m1 meta data to compare to @a m2
+ * @param m2 meta data to compare to @a m1
+ * @return true if both are equal
+ */
+static bool
+denomination_meta_cmp (
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m1,
+ const struct TALER_EXCHANGEDB_DenominationKeyMetaData *m2)
+{
+ if ( (GNUNET_TIME_timestamp_cmp (m1->start,
+ !=,
+ m2->start)) ||
+ (GNUNET_TIME_timestamp_cmp (m1->expire_withdraw,
+ !=,
+ m2->expire_withdraw)) ||
+ (GNUNET_TIME_timestamp_cmp (m1->expire_deposit,
+ !=,
+ m2->expire_deposit)) ||
+ (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
+ !=,
+ m2->expire_legal)) )
+ return false;
+ if (0 !=
+ TALER_amount_cmp (&m1->value,
+ &m2->value))
+ return false;
+ if (0 !=
+ GNUNET_memcmp (&m1->fees,
+ &m2->fees))
+ return false;
+ if (m1->age_mask.bits !=
+ m2->age_mask.bits)
+ return false;
+ return true;
+}
+
+
+/**
+ * Compare meta-data of two signing keys for equality.
+ *
+ * @param m1 meta data to compare to @a m2
+ * @param m2 meta data to compare to @a m1
+ * @return true if both are equal
+ */
+static bool
+signkey_meta_cmp (
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *m1,
+ const struct TALER_EXCHANGEDB_SignkeyMetaData *m2)
+{
+ if ( (GNUNET_TIME_timestamp_cmp (m1->start,
+ !=,
+ m2->start)) ||
+ (GNUNET_TIME_timestamp_cmp (m1->expire_sign,
+ !=,
+ m2->expire_sign)) ||
+ (GNUNET_TIME_timestamp_cmp (m1->expire_legal,
+ !=,
+ m2->expire_legal)) )
+ return false;
+ return true;
+}
+
+
+/**
+ * Function implementing database transaction to add offline signing keys.
+ * Runs the transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddKeysContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_keys (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddKeysContext *akc = cls;
+
+ /* activate all denomination keys */
+ for (unsigned int i = 0; i<akc->nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc->d_sigs[i];
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_DenominationKeyMetaData meta;
+
+ /* For idempotency, check if the key is already active */
+ qs = TEH_plugin->lookup_denomination_key (
+ TEH_plugin->cls,
+ &d->h_denom_pub,
+ &meta);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup denomination key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (! denomination_meta_cmp (&d->meta,
+ &meta))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_DENOMINATION_META_DATA,
+ "conflicting meta data previously set for the same denomination key");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Denomination key %s already active, skipping\n",
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ continue; /* skip, already known */
+ }
+
+ qs = TEH_plugin->add_denomination_key (
+ TEH_plugin->cls,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta,
+ &d->master_sig);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "activate denomination key");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Added offline signature for denomination `%s'\n",
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ GNUNET_assert (0 != qs);
+ }
+
+ for (unsigned int i = 0; i<akc->ns_sigs; i++)
+ {
+ struct SigningSig *s = &akc->s_sigs[i];
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_EXCHANGEDB_SignkeyMetaData meta;
+
+ qs = TEH_plugin->lookup_signing_key (
+ TEH_plugin->cls,
+ &s->exchange_pub,
+ &meta);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup signing key");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (! signkey_meta_cmp (&s->meta,
+ &meta))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_CONFLICTING_SIGNKEY_META_DATA,
+ "conflicting meta data previously set for the same signing key");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signing key %s already active, skipping\n",
+ TALER_B2S (&s->exchange_pub));
+ continue; /* skip, already known */
+ }
+ qs = TEH_plugin->activate_signing_key (
+ TEH_plugin->cls,
+ &s->exchange_pub,
+ &s->meta,
+ &s->master_sig);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "activate signing key");
+ return qs;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Added offline signature for signing key `%s'\n",
+ TALER_B2S (&s->exchange_pub));
+ GNUNET_assert (0 != qs);
+ }
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* only 'success', so >=0, matters here */
+}
+
+
+/**
+ * Clean up state in @a akc, but do not free @a akc itself
+ *
+ * @param[in,out] akc state to clean up
+ */
+static void
+cleanup_akc (struct AddKeysContext *akc)
+{
+ for (unsigned int i = 0; i<akc->nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc->d_sigs[i];
+
+ TALER_denom_pub_free (&d->denom_pub);
+ }
+ GNUNET_free (akc->d_sigs);
+ GNUNET_free (akc->s_sigs);
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_keys (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddKeysContext akc = { 0 };
+ const json_t *denom_sigs;
+ const json_t *signkey_sigs;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("denom_sigs",
+ &denom_sigs),
+ GNUNET_JSON_spec_array_const ("signkey_sigs",
+ &signkey_sigs),
+ GNUNET_JSON_spec_end ()
+ };
+ MHD_RESULT ret;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received POST /management/keys request\n");
+
+ akc.ksh = TEH_keys_get_state_for_management_only (); /* may start its own transaction, thus must be done here, before we run ours! */
+ if (NULL == akc.ksh)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ "no key state (not even for management)");
+ }
+
+ akc.nd_sigs = json_array_size (denom_sigs);
+ akc.d_sigs = GNUNET_new_array (akc.nd_sigs,
+ struct DenomSig);
+ for (unsigned int i = 0; i<akc.nd_sigs; i++)
+ {
+ struct DenomSig *d = &akc.d_sigs[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &d->master_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_denom_pub",
+ &d->h_denom_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ json_array_get (denom_sigs,
+ i),
+ ispec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+
+ res = TEH_keys_load_fees (akc.ksh,
+ &d->h_denom_pub,
+ &d->denom_pub,
+ &d->meta);
+ switch (res)
+ {
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
+ break;
+ }
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_denom_validity_verify (
+ &d->h_denom_pub,
+ d->meta.start,
+ d->meta.expire_withdraw,
+ d->meta.expire_deposit,
+ d->meta.expire_legal,
+ &d->meta.value,
+ &d->meta.fees,
+ &TEH_master_public_key,
+ &d->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_DENOMKEY_ADD_SIGNATURE_INVALID,
+ GNUNET_h2s (&d->h_denom_pub.hash));
+ cleanup_akc (&akc);
+ return ret;
+ }
+ }
+
+ akc.ns_sigs = json_array_size (signkey_sigs);
+ akc.s_sigs = GNUNET_new_array (akc.ns_sigs,
+ struct SigningSig);
+ for (unsigned int i = 0; i<akc.ns_sigs; i++)
+ {
+ struct SigningSig *s = &akc.s_sigs[i];
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &s->master_sig),
+ GNUNET_JSON_spec_fixed_auto ("exchange_pub",
+ &s->exchange_pub),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ json_array_get (signkey_sigs,
+ i),
+ ispec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failure to handle /management/keys\n");
+ cleanup_akc (&akc);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ res = TEH_keys_get_timing (&s->exchange_pub,
+ &s->meta);
+ switch (res)
+ {
+ case GNUNET_SYSERR:
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_NO:
+ /* For idempotency, check if the key is already active */
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_UNKNOWN,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ case GNUNET_OK:
+ break;
+ }
+
+ /* check signature is valid */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_validity_verify (
+ &s->exchange_pub,
+ s->meta.start,
+ s->meta.expire_sign,
+ s->meta.expire_legal,
+ &TEH_master_public_key,
+ &s->master_sig))
+ {
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_KEYS_SIGNKEY_ADD_SIGNATURE_INVALID,
+ TALER_B2S (&s->exchange_pub));
+ cleanup_akc (&akc);
+ return ret;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received %u denomination and %u signing key signatures\n",
+ akc.nd_sigs,
+ akc.ns_sigs);
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_DB_run_transaction (connection,
+ "add keys",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_keys,
+ &akc);
+ cleanup_akc (&akc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_management_post_keys.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-partners.c b/src/exchange/taler-exchange-httpd_post-management-partners.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-management-partners.c
+ * @brief Handle request to add exchange partner
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+MHD_RESULT
+TEH_handler_management_partners (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct TALER_MasterPublicKeyP partner_pub;
+ struct GNUNET_TIME_Timestamp start_date;
+ struct GNUNET_TIME_Timestamp end_date;
+ struct GNUNET_TIME_Relative wad_frequency;
+ struct TALER_Amount wad_fee;
+ const char *partner_base_url;
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("partner_pub",
+ &partner_pub),
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ TALER_JSON_spec_web_url ("partner_base_url",
+ &partner_base_url),
+ TALER_JSON_spec_amount ("wad_fee",
+ TEH_currency,
+ &wad_fee),
+ GNUNET_JSON_spec_timestamp ("start_date",
+ &start_date),
+ GNUNET_JSON_spec_timestamp ("end_date",
+ &end_date),
+ GNUNET_JSON_spec_relative_time ("wad_frequency",
+ &wad_frequency),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_partner_details_verify (
+ &partner_pub,
+ start_date,
+ end_date,
+ wad_frequency,
+ &wad_fee,
+ partner_base_url,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->insert_partner (TEH_plugin->cls,
+ &partner_pub,
+ start_date,
+ end_date,
+ wad_frequency,
+ &wad_fee,
+ partner_base_url,
+ &master_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add_partner");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* FIXME-#7271: check for idempotency! */
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_ADD_PARTNER_DATA_CONFLICT,
+ NULL);
+ }
+ }
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_partners.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-signkeys-EXCHANGE_PUB-revoke.c b/src/exchange/taler-exchange-httpd_post-management-signkeys-EXCHANGE_PUB-revoke.c
@@ -0,0 +1,93 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020 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_post-management-signkeys-EXCHANGE_PUB-revoke.c
+ * @brief Handle exchange online signing key revocation requests.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+MHD_RESULT
+TEH_handler_management_signkeys_EP_revoke (
+ struct MHD_Connection *connection,
+ const struct TALER_ExchangePublicKeyP *exchange_pub,
+ const json_t *root)
+{
+ struct TALER_MasterSignatureP master_sig;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &master_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_signkey_revoke_verify (exchange_pub,
+ &TEH_master_public_key,
+ &master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_SIGNKEY_REVOKE_SIGNATURE_INVALID,
+ NULL);
+ }
+ qs = TEH_plugin->insert_signkey_revocation (TEH_plugin->cls,
+ exchange_pub,
+ &master_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "signkey revocation");
+ }
+ TEH_keys_update_states ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_signkey_HDP_revoke.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-wire-disable.c b/src/exchange/taler-exchange-httpd_post-management-wire-disable.c
@@ -0,0 +1,207 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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_post-management-wire-disable.c
+ * @brief Handle request to disable wire account.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for the #del_wire transaction.
+ */
+struct DelWireContext
+{
+ /**
+ * Master signature affirming the WIRE DEL operation
+ * (includes timestamp).
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Payto:// URI this is about.
+ */
+ struct TALER_FullPayto payto_uri;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_end;
+
+};
+
+
+/**
+ * Function implementing database transaction to del an wire. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct DelWireContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+del_wire (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct DelWireContext *awc = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
+ awc->payto_uri,
+ &last_date);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup wire");
+ return qs;
+ }
+ if (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ awc->validity_end))
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_NOT_FOUND,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ qs = TEH_plugin->update_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ awc->validity_end,
+ NULL,
+ NULL,
+ 0,
+ false);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "del wire");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire_disable (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct DelWireContext awc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig_del",
+ &awc.master_sig),
+ TALER_JSON_spec_full_payto_uri ("payto_uri",
+ &awc.payto_uri),
+ GNUNET_JSON_spec_timestamp ("validity_end",
+ &awc.validity_end),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_del_verify (
+ awc.payto_uri,
+ awc.validity_end,
+ &TEH_master_public_key,
+ &awc.master_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DEL_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "del wire",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &del_wire,
+ &awc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire_disable.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-wire-fee.c b/src/exchange/taler-exchange-httpd_post-management-wire-fee.c
@@ -0,0 +1,230 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020, 2021, 2022 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_post-management-wire-fee.c
+ * @brief Handle request to add wire fee details
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for the #add_fee transaction.
+ */
+struct AddFeeContext
+{
+ /**
+ * Fee's signature affirming the #TALER_SIGNATURE_MASTER_WIRE_FEES operation.
+ */
+ struct TALER_MasterSignatureP master_sig;
+
+ /**
+ * Wire method this is about.
+ */
+ const char *wire_method;
+
+ /**
+ * Starting period.
+ */
+ struct GNUNET_TIME_Timestamp start_time;
+
+ /**
+ * End of period.
+ */
+ struct GNUNET_TIME_Timestamp end_time;
+
+ /**
+ * Wire fee amounts.
+ */
+ struct TALER_WireFeeSet fees;
+
+};
+
+
+/**
+ * Function implementing database transaction to add a fee. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddFeeContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_fee (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddFeeContext *afc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_WireFeeSet fees;
+
+ qs = TEH_plugin->lookup_wire_fee_by_time (
+ TEH_plugin->cls,
+ afc->wire_method,
+ afc->start_time,
+ afc->end_time,
+ &fees);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup wire fee");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != qs)
+ {
+ if ( (GNUNET_OK ==
+ TALER_amount_is_valid (&fees.wire)) &&
+ (0 ==
+ TALER_wire_fee_set_cmp (&fees,
+ &afc->fees)) )
+ {
+ /* this will trigger the 'success' response */
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_MISMATCH,
+ NULL);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_wire_fee (
+ TEH_plugin->cls,
+ afc->wire_method,
+ afc->start_time,
+ afc->end_time,
+ &afc->fees,
+ &afc->master_sig);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert fee");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire_fees (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddFeeContext afc;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("master_sig",
+ &afc.master_sig),
+ GNUNET_JSON_spec_string ("wire_method",
+ &afc.wire_method),
+ GNUNET_JSON_spec_timestamp ("fee_start",
+ &afc.start_time),
+ GNUNET_JSON_spec_timestamp ("fee_end",
+ &afc.end_time),
+ TALER_JSON_spec_amount ("wire_fee",
+ TEH_currency,
+ &afc.fees.wire),
+ TALER_JSON_spec_amount ("closing_fee",
+ TEH_currency,
+ &afc.fees.closing),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_fee_verify (
+ afc.wire_method,
+ afc.start_time,
+ afc.end_time,
+ &afc.fees,
+ &TEH_master_public_key,
+ &afc.master_sig))
+ {
+ /* signature invalid */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_FEE_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add wire fee",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_fee,
+ &afc);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire_fees.c */
diff --git a/src/exchange/taler-exchange-httpd_post-management-wire.c b/src/exchange/taler-exchange-httpd_post-management-wire.c
@@ -0,0 +1,346 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2020-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 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_post-management-wire.c
+ * @brief Handle request to add wire account.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_signatures.h"
+#include "taler-exchange-httpd_management.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for the #add_wire transaction.
+ */
+struct AddWireContext
+{
+ /**
+ * Master signature affirming the WIRE ADD operation
+ * (includes timestamp).
+ */
+ struct TALER_MasterSignatureP master_sig_add;
+
+ /**
+ * Master signature to share with clients affirming the
+ * wire details of the bank.
+ */
+ struct TALER_MasterSignatureP master_sig_wire;
+
+ /**
+ * Payto:// URI this is about.
+ */
+ struct TALER_FullPayto payto_uri;
+
+ /**
+ * (optional) address of a conversion service for this account.
+ */
+ const char *conversion_url;
+
+ /**
+ * (optional) address of an open banking gateway service for this account.
+ */
+ const char *open_banking_gateway;
+
+ /**
+ * (optional) address of a wire transfer gateway service for this account.
+ */
+ const char *wire_transfer_gateway;
+
+ /**
+ * Restrictions imposed when crediting this account.
+ */
+ const json_t *credit_restrictions;
+
+ /**
+ * Restrictions imposed when debiting this account.
+ */
+ const json_t *debit_restrictions;
+
+ /**
+ * Timestamp for checking against replay attacks.
+ */
+ struct GNUNET_TIME_Timestamp validity_start;
+
+ /**
+ * Label to use for this bank. Default is empty.
+ */
+ const char *bank_label;
+
+ /**
+ * Priority of the bank in the list. Default 0.
+ */
+ int64_t priority;
+
+};
+
+
+/**
+ * Function implementing database transaction to add an wire. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls closure with a `struct AddWireContext`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+add_wire (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct AddWireContext *awc = cls;
+ struct GNUNET_TIME_Timestamp last_date;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->lookup_wire_timestamp (TEH_plugin->cls,
+ awc->payto_uri,
+ &last_date);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup wire");
+ return qs;
+ }
+ if ( (0 < qs) &&
+ (GNUNET_TIME_timestamp_cmp (last_date,
+ >,
+ awc->validity_start)) )
+ {
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_MORE_RECENT_PRESENT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ qs = TEH_plugin->insert_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ awc->conversion_url,
+ awc->open_banking_gateway,
+ awc->wire_transfer_gateway,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
+ awc->validity_start,
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority);
+ else
+ qs = TEH_plugin->update_wire (TEH_plugin->cls,
+ awc->payto_uri,
+ awc->conversion_url,
+ awc->open_banking_gateway,
+ awc->wire_transfer_gateway,
+ awc->debit_restrictions,
+ awc->credit_restrictions,
+ awc->validity_start,
+ &awc->master_sig_wire,
+ awc->bank_label,
+ awc->priority,
+ true);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "add wire");
+ return qs;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_management_post_wire (
+ struct MHD_Connection *connection,
+ const json_t *root)
+{
+ struct AddWireContext awc = {
+ .conversion_url = NULL
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_full_payto_uri ("payto_uri",
+ &awc.payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("master_sig_wire",
+ &awc.master_sig_wire),
+ GNUNET_JSON_spec_fixed_auto ("master_sig_add",
+ &awc.master_sig_add),
+ GNUNET_JSON_spec_timestamp ("validity_start",
+ &awc.validity_start),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("conversion_url",
+ &awc.conversion_url),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("open_banking_gateway",
+ &awc.open_banking_gateway),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_web_url ("wire_transfer_gateway",
+ &awc.wire_transfer_gateway),
+ NULL),
+ GNUNET_JSON_spec_array_const ("credit_restrictions",
+ &awc.credit_restrictions),
+ GNUNET_JSON_spec_array_const ("debit_restrictions",
+ &awc.debit_restrictions),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_string ("bank_label",
+ &awc.bank_label),
+ NULL),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_int64 ("priority",
+ &awc.priority),
+ NULL),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == res)
+ return MHD_YES; /* failure */
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ {
+ char *msg = TALER_payto_validate (awc.payto_uri);
+
+ if (NULL != msg)
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
+ msg);
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_free (msg);
+ return ret;
+ }
+ }
+ if (GNUNET_OK !=
+ TALER_exchange_offline_wire_add_verify (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.open_banking_gateway,
+ awc.wire_transfer_gateway,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ awc.validity_start,
+ &TEH_master_public_key,
+ &awc.master_sig_add))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_ADD_SIGNATURE_INVALID,
+ NULL);
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_exchange_wire_signature_check (
+ awc.payto_uri,
+ awc.conversion_url,
+ awc.open_banking_gateway,
+ awc.wire_transfer_gateway,
+ awc.debit_restrictions,
+ awc.credit_restrictions,
+ &TEH_master_public_key,
+ &awc.master_sig_wire))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_MANAGEMENT_WIRE_DETAILS_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ char *wire_method;
+
+ wire_method = TALER_payto_get_method (awc.payto_uri.full_payto);
+ if (NULL == wire_method)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "payto:// URI `%s' is malformed\n",
+ awc.payto_uri.full_payto);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ GNUNET_free (wire_method);
+ }
+
+ {
+ enum GNUNET_GenericReturnValue res;
+ MHD_RESULT ret;
+
+ res = TEH_DB_run_transaction (connection,
+ "add wire",
+ TEH_MT_REQUEST_OTHER,
+ &ret,
+ &add_wire,
+ &awc);
+ GNUNET_JSON_parse_free (spec);
+ if (GNUNET_SYSERR == res)
+ return ret;
+ }
+ TEH_wire_update_state ();
+ return TALER_MHD_reply_static (
+ connection,
+ MHD_HTTP_NO_CONTENT,
+ NULL,
+ NULL,
+ 0);
+}
+
+
+/* end of taler-exchange-httpd_management_wire.c */
diff --git a/src/exchange/taler-exchange-httpd_post-melt.c b/src/exchange/taler-exchange-httpd_post-melt.c
@@ -0,0 +1,1864 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-melt.c
+ * @brief Handle /melt requests
+ * @note This endpoint is active since vDOLDPLUS of the protocol API
+ * @author Özgür Kesim
+ */
+
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_post-melt.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler/taler_util.h"
+
+/**
+ * The different type of errors that might occur, sorted by name.
+ * Some of them require idempotency checks, which are marked
+ * in @e idempotency_check_required below.
+ */
+enum MeltError
+{
+ MELT_ERROR_NONE = 0,
+ MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID,
+ MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ MELT_ERROR_AMOUNT_OVERFLOW,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
+ MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT,
+ MELT_ERROR_BLINDING_SEED_REQUIRED,
+ MELT_ERROR_COIN_CIPHER_MISMATCH,
+ MELT_COIN_CONFLICTING_DENOMINATION_KEY,
+ MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE,
+ MELT_ERROR_COIN_SIGNATURE_INVALID,
+ MELT_ERROR_COIN_UNKNOWN,
+ MELT_ERROR_CONFIRMATION_SIGN,
+ MELT_ERROR_CRYPTO_HELPER,
+ MELT_ERROR_DB_FETCH_FAILED,
+ MELT_ERROR_DB_INVARIANT_FAILURE,
+ MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE,
+ MELT_ERROR_DB_PREFLIGHT_FAILURE,
+ MELT_ERROR_DENOMINATION_EXPIRED,
+ MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ MELT_ERROR_DENOMINATION_REVOKED,
+ MELT_ERROR_DENOMINATION_SIGN,
+ MELT_ERROR_DENOMINATION_SIGNATURE_INVALID,
+ MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ MELT_ERROR_DUPLICATE_PLANCHET,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB,
+ MELT_ERROR_INSUFFICIENT_FUNDS,
+ MELT_ERROR_KEYS_MISSING,
+ MELT_ERROR_FEES_EXCEED_CONTRIBUTION,
+ MELT_ERROR_NONCE_RESUSE,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+};
+
+/**
+ * With the bits set in this value will be mark the errors
+ * that require a check for idempotency before actually
+ * returning an error.
+ */
+static const uint64_t idempotency_check_required =
+ 0
+ | (1 << MELT_ERROR_DENOMINATION_EXPIRED)
+ | (1 << MELT_ERROR_DENOMINATION_KEY_UNKNOWN)
+ | (1 << MELT_ERROR_DENOMINATION_REVOKED)
+ | (1 << MELT_ERROR_INSUFFICIENT_FUNDS) /* TODO: is this still correct? Compare exchange_do_refresh.sql */
+ | (1 << MELT_ERROR_KEYS_MISSING);
+
+#define IDEMPOTENCY_CHECK_REQUIRED(error) \
+ (0 != (idempotency_check_required & (1 << (error))))
+
+/**
+ * Context for a /melt request
+ */
+struct MeltContext
+{
+
+ /**
+ * This struct is kept in a DLL.
+ */
+ struct MeltContext *prev;
+ struct MeltContext *next;
+
+ /**
+ * Processing phase we are in.
+ * The ordering here partially matters, as we progress through
+ * them by incrementing the phase in the happy path.
+ */
+ enum MeltPhase
+ {
+ MELT_PHASE_PARSE,
+ MELT_PHASE_CHECK_MELT_VALID,
+ MELT_PHASE_CHECK_KEYS,
+ MELT_PHASE_CHECK_COIN_SIGNATURE,
+ MELT_PHASE_PREPARE_TRANSACTION,
+ MELT_PHASE_RUN_TRANSACTION,
+ MELT_PHASE_GENERATE_REPLY_SUCCESS,
+ MELT_PHASE_GENERATE_REPLY_ERROR,
+ MELT_PHASE_RETURN_NO,
+ MELT_PHASE_RETURN_YES,
+ } phase;
+
+
+ /**
+ * Request context
+ */
+ const struct TEH_RequestContext *rc;
+
+ /**
+ * Current time for the DB transaction.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * The current key state
+ */
+ struct TEH_KeyStateHandle *ksh;
+
+ /**
+ * The melted coin's denomination key
+ */
+ struct TEH_DenominationKey *melted_coin_denom;
+
+ /**
+ * Set to true if this coin's denomination was revoked and the operation
+ * is thus only allowed for zombie coins where the transaction
+ * history includes a #TALER_EXCHANGEDB_TT_OLD_COIN_RECOUP.
+ *
+ * TODO: find a better terminology. The sentences in the comments containing
+ * "zombie" make semantically _no sense_!
+ */
+ bool zombie_required;
+
+ /**
+ * We already checked and noticed that the coin is known. Hence we
+ * can skip the "ensure_coin_known" step of the transaction.
+ */
+ bool coin_is_known;
+
+ /**
+ * UUID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Captures all parameters provided in the JSON request
+ */
+ struct
+ {
+
+ /**
+ * All fields (from the request or computed)
+ * that we persist in the database.
+ */
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
+
+ /**
+ * In some error cases we check for idempotency.
+ * If we find an entry in the database, we mark this here.
+ */
+ bool is_idempotent;
+
+ /**
+ * In some error conditions the request is checked
+ * for idempotency and the result from the database
+ * is stored here.
+ */
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh_idem;
+
+ /**
+ * True if @e blinding_seed is missing in the request
+ */
+ bool no_blinding_seed;
+
+ /**
+ * Array @e persis.num_coins of hashes of the public keys
+ * of the denominations to refresh.
+ */
+ struct TALER_DenominationHashP *denoms_h;
+
+ /**
+ * Array of @e refresh.num_coins blinded coin planchets, arranged
+ * in runs of @e refresh.num_coins coins, [0..num_coins)..[0..num_coins),
+ * one for each kappa value.
+ */
+ struct TALER_BlindedPlanchet *planchets[TALER_CNC_KAPPA];
+
+ /**
+ * @since vDOLDPLUS
+ * #TALER_CNC_KAPPA arrays of @e refresh.num_coins transfer public keys
+ * in runs of @e num_coins coins, [0..num_coins)..[0..num_coins),
+ * one for each kappa value.
+ *
+ * MAYBE null. If the client has NOT provided the transfer_pubs in the request,
+ * @e refresh.is_v27_refresh will be true.
+ *
+ * TODO: Once v27 clients are gone, this MUST NOT be nulls.
+ */
+ struct TALER_TransferPublicKeyP *transfer_pubs[TALER_CNC_KAPPA];
+
+ /**
+ * #TALER_CNC_KAPPA hashes of the batches of @e num_coins coins.
+ */
+ struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
+
+ /**
+ * Array @e withdraw.num_r_pubs of indices into @e denoms_h
+ * of CS denominations.
+ */
+ uint32_t *cs_indices;
+
+ /**
+ * Total (over all coins) amount (excluding fee) committed for the refresh
+ */
+ struct TALER_Amount amount;
+
+ } request;
+
+ /**
+ * Errors occurring during evaluation of the request are captured in this
+ * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
+ * message is prepared and sent to the client.
+ */
+ struct
+ {
+ /* The (internal) error code */
+ enum MeltError code;
+
+ /**
+ * Some errors require details to be sent to the client.
+ * These are captured in this union.
+ * Each field is named according to the error that is using it, except
+ * commented otherwise.
+ */
+ union
+ {
+ const char *request_parameter_malformed;
+
+ /**
+ * For all errors related to a particular denomination, i.e.
+ * #MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ * #MELT_ERROR_DENOMINATION_EXPIRED,
+ * #MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ * #MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ * we use this one field.
+ */
+ struct TALER_DenominationHashP denom_h;
+
+ const char *db_fetch_context;
+
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ enum TALER_ErrorCode ec_denomination_sign;
+
+ /* remaining value of the coin */
+ struct TALER_Amount insufficient_funds;
+
+ } details;
+ } error;
+};
+
+/**
+ * The following macros set the given error code,
+ * set the phase to Melt_PHASE_GENERATE_REPLY_ERROR,
+ * and optionally set the given field (with an optionally given value).
+ */
+#define SET_ERROR(mc, ec) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_FIELD(mc, ec, field) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->error.details.field = (field); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_DETAIL(mc, ec, field, value) \
+ do \
+ { GNUNET_static_assert (MELT_ERROR_NONE != ec); \
+ (mc)->error.code = (ec); \
+ (mc)->error.details.field = (value); \
+ (mc)->phase = MELT_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+
+/**
+ * All melt context is kept in a DLL.
+ */
+static struct MeltContext *mc_head;
+static struct MeltContext *mc_tail;
+
+void
+TEH_melt_cleanup ()
+{
+ struct MeltContext *mc;
+
+ while (NULL != (mc = mc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (mc_head,
+ mc_tail,
+ mc);
+ MHD_resume_connection (mc->rc->connection);
+ }
+}
+
+
+/**
+ * Terminate the main loop by returning the final result.
+ *
+ * @param[in,out] mc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct MeltContext *mc,
+ MHD_RESULT mres)
+{
+ mc->phase = (MHD_YES == mres)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+}
+
+
+/**
+ * Free information in @a re, but not @a re itself.
+ *
+ * @param[in] re refresh data to free
+ */
+static void
+free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *re)
+{
+ if (NULL != re->denom_sigs)
+ {
+ for (size_t i = 0; i<re->num_coins; i++)
+ TALER_blinded_denom_sig_free (&re->denom_sigs[i]);
+ GNUNET_free (re->denom_sigs);
+ }
+ GNUNET_free (re->cs_r_values);
+ GNUNET_free (re->denom_serials);
+ GNUNET_free (re->denom_pub_hashes);
+ TALER_denom_sig_free (&re->coin.denom_sig);
+}
+
+
+/**
+ * Cleanup routine for melt request.
+ * The function is called upon completion of the request
+ * that should clean up @a rh_ctx.
+ *
+ * @param rc request context to clean up
+ */
+static void
+clean_melt_rc (struct TEH_RequestContext *rc)
+{
+ struct MeltContext *mc = rc->rh_ctx;
+
+ GNUNET_free (mc->request.denoms_h);
+ for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
+ {
+ for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
+ TALER_blinded_planchet_free (&mc->request.planchets[k][i]);
+ GNUNET_free (mc->request.planchets[k]);
+ if (! mc->request.refresh.is_v27_refresh)
+ GNUNET_free (mc->request.transfer_pubs[k]);
+ }
+ free_refresh (&mc->request.refresh);
+ if (mc->request.is_idempotent)
+ free_refresh (&mc->request.refresh_idem);
+ GNUNET_free (mc->request.cs_indices);
+ GNUNET_free (mc);
+}
+
+
+/**
+ * Creates a new context for the incoming melt request
+ *
+ * @param mc melt request context
+ * @param root json body of the request
+ */
+static void
+phase_parse_request (
+ struct MeltContext *mc,
+ const json_t *root)
+{
+ const json_t *j_denoms_h;
+ const json_t *j_coin_evs;
+ const json_t *j_transfer_pubs;
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("old_coin_pub",
+ &mc->request.refresh.coin.coin_pub),
+ GNUNET_JSON_spec_fixed_auto ("old_denom_pub_h",
+ &mc->request.refresh.coin.denom_pub_hash),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("old_age_commitment_h",
+ &mc->request.refresh.coin.h_age_commitment),
+ &mc->request.refresh.coin.no_age_commitment),
+ TALER_JSON_spec_denom_sig ("old_denom_sig",
+ &mc->request.refresh.coin.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("refresh_seed",
+ &mc->request.refresh.refresh_seed),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("blinding_seed",
+ &mc->request.refresh.blinding_seed),
+ &mc->request.refresh.no_blinding_seed),
+ TALER_JSON_spec_amount ("value_with_fee",
+ TEH_currency,
+ &mc->request.refresh.amount_with_fee),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_array_const ("transfer_pubs",
+ &j_transfer_pubs),
+ &mc->request.refresh.is_v27_refresh),
+ GNUNET_JSON_spec_array_const ("denoms_h",
+ &j_denoms_h),
+ GNUNET_JSON_spec_array_const ("coin_evs",
+ &j_coin_evs),
+ GNUNET_JSON_spec_fixed_auto ("confirm_sig",
+ &mc->request.refresh.coin_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+
+ /* validate array size */
+ GNUNET_static_assert (
+ TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
+
+ mc->request.refresh.num_coins = json_array_size (j_denoms_h);
+ if (0 == mc->request.refresh.num_coins)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "denoms_h must not be empty");
+ return;
+ }
+ if (TALER_MAX_COINS < mc->request.refresh.num_coins)
+ {
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not melt any money
+ * from it. Note that the user can't get their money back in this case!
+ */
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "maximum number of coins that can be refreshed has been exceeded");
+ return;
+ }
+ if (TALER_CNC_KAPPA != json_array_size (j_coin_evs))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "coin_evs must be an array of length "TALER_CNC_KAPPA_STR);
+ return;
+ }
+ if (! mc->request.refresh.is_v27_refresh &&
+ (TALER_CNC_KAPPA != json_array_size (j_transfer_pubs)))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "transfer_pubs must be an array of length "TALER_CNC_KAPPA_STR);
+ return;
+ }
+
+
+ /* Extract the denomination hashes */
+ {
+ size_t idx;
+ json_t *value;
+
+ mc->request.denoms_h
+ = GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denoms_h, idx, value) {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &mc->request.denoms_h[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ value,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ }
+
+ /* Parse blinded envelopes. */
+ {
+ json_t *j_kappa_planchets;
+ size_t kappa;
+ struct GNUNET_HashContext *ctx;
+
+ /* ctx to calculate the planchet_h */
+ ctx = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != ctx);
+
+ json_array_foreach (j_coin_evs, kappa, j_kappa_planchets)
+ {
+ json_t *j_cev;
+ size_t idx;
+
+ if (mc->request.refresh.num_coins != json_array_size (j_kappa_planchets))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "coin_evs[i][] size");
+ return;
+ }
+
+ mc->request.planchets[kappa] =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_BlindedPlanchet);
+
+ json_array_foreach (j_kappa_planchets, idx, j_cev)
+ {
+ /* Now parse the individual envelopes and calculate the hash of
+ * the commitment along the way. */
+ struct GNUNET_JSON_Specification kspec[] = {
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &mc->request.planchets[kappa][idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ j_cev,
+ kspec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ /* Save the hash of the batch of planchets for index kappa */
+ TALER_wallet_blinded_planchets_hash (
+ mc->request.refresh.num_coins,
+ mc->request.planchets[kappa],
+ mc->request.denoms_h,
+ &mc->request.kappa_planchets_h.tuple[kappa]);
+ GNUNET_CRYPTO_hash_context_read (
+ ctx,
+ &mc->request.kappa_planchets_h.tuple[kappa],
+ sizeof(mc->request.kappa_planchets_h.tuple[kappa]));
+ }
+ /* Finally calculate the total hash over all planchets */
+ GNUNET_CRYPTO_hash_context_finish (
+ ctx,
+ &mc->request.refresh.planchets_h.hash);
+ }
+ /* Check for duplicate planchets. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol
+ */
+ {
+ size_t max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
+
+ for (size_t I = 0; I < max_idx - 1; I++)
+ {
+ size_t ki = I / mc->request.refresh.num_coins;
+ size_t ni = I % mc->request.refresh.num_coins;
+
+ for (size_t J = I + 1; J < max_idx; J++)
+ {
+ size_t kj = J / mc->request.refresh.num_coins;
+ size_t nj = J % mc->request.refresh.num_coins;
+
+ if (0 == TALER_blinded_planchet_cmp (
+ &mc->request.planchets[ki][ni],
+ &mc->request.planchets[kj][nj]))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB);
+ return;
+ }
+ }
+ }
+ }
+
+ /* Parse the transfer public keys, if applicable */
+ if (! mc->request.refresh.is_v27_refresh)
+ {
+ json_t *j_ktp;
+ size_t kappa;
+ size_t max_idx;
+
+ json_array_foreach (j_transfer_pubs, kappa, j_ktp)
+ {
+ if (mc->request.refresh.num_coins !=
+ json_array_size (j_ktp))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "transfer_pubs[i][] size");
+ return;
+ }
+
+ mc->request.transfer_pubs[kappa] =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ struct TALER_TransferPublicKeyP);
+
+ /* Parse the batch of @e num_coins transfer public keys
+ * at index kappa */
+ {
+ struct GNUNET_JSON_Specification ktp_spec[] = {
+ TALER_JSON_spec_array_fixed (NULL,
+ mc->request.refresh.num_coins,
+ mc->request.transfer_pubs[kappa],
+ sizeof(*mc->request.transfer_pubs[kappa])
+ ),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (mc->rc->connection,
+ j_ktp,
+ ktp_spec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ mc->phase = (GNUNET_NO == res)
+ ? MELT_PHASE_RETURN_YES
+ : MELT_PHASE_RETURN_NO;
+ return;
+ }
+ }
+ }
+ /* Check for duplicate transfer public keys. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol
+ */
+ max_idx = TALER_CNC_KAPPA * mc->request.refresh.num_coins;
+
+ for (size_t I = 0; I < max_idx - 1; I++)
+ {
+ size_t ki = I / mc->request.refresh.num_coins;
+ size_t ni = I % mc->request.refresh.num_coins;
+
+ for (size_t J = I + 1; J < max_idx; J++)
+ {
+ size_t kj = J / mc->request.refresh.num_coins;
+ size_t nj = J % mc->request.refresh.num_coins;
+
+ if (0 == GNUNET_memcmp (
+ &mc->request.transfer_pubs[ki][ni],
+ &mc->request.transfer_pubs[kj][nj]))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DUPLICATE_TRANSFER_PUB);
+ return;
+ }
+ }
+ }
+ }
+
+ mc->ksh = TEH_keys_get_state ();
+ if (NULL == mc->ksh)
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_KEYS_MISSING);
+ return;
+ }
+ mc->phase = MELT_PHASE_CHECK_MELT_VALID;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and potentically supports age restriction.
+ *
+ * @param[in,out] mc context for the melt operation
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] pdk denomination key found, might be NULL
+ * @return #GNUNET_OK when denomation was found and valid,
+ * #GNUNET_NO when denomination is not valid at this time
+ * #GNUNET_SYSERR otherwise (denomination invalid), with finish_loop called.
+ */
+static enum GNUNET_GenericReturnValue
+find_denomination (
+ struct MeltContext *mc,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk)
+{
+ struct TEH_DenominationKey *dk;
+
+ *pdk = NULL;
+ GNUNET_assert (NULL != mc->ksh);
+ dk = TEH_keys_denomination_by_hash_from_state (mc->ksh,
+ denom_h,
+ NULL,
+ NULL);
+ if (NULL == dk)
+ {
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_KEY_UNKNOWN,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+ *pdk = dk;
+
+ if (GNUNET_TIME_absolute_is_past (
+ dk->meta.expire_withdraw.abs_time))
+ {
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_EXPIRED,
+ denom_h,
+ *denom_h);
+ /**
+ * Note that we return GNUNET_NO here.
+ * This way phase_check_melt_valid can react
+ * to it as a non-error case and do the zombie check.
+ */
+ return GNUNET_NO;
+ }
+
+ if (GNUNET_TIME_absolute_is_future (
+ dk->meta.start.abs_time))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+
+ if (dk->recoup_possible)
+ {
+ SET_ERROR (mc,
+ MELT_ERROR_DENOMINATION_REVOKED);
+ return GNUNET_SYSERR;
+ }
+
+ /* In case of age melt, make sure that the denomination supports age restriction */
+ if (! (mc->request.refresh.coin.no_age_commitment) &&
+ (0 == dk->denom_pub.age_mask.bits))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ denom_h,
+ *denom_h);
+ return GNUNET_SYSERR;
+ }
+ if ((mc->request.refresh.coin.no_age_commitment) &&
+ (0 != dk->denom_pub.age_mask.bits))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys
+ * - belong to valid denominations
+ * - calculate the total amount of the denominations including fees
+ * for melt.
+ *
+ * @param mc context of the melt to check keys for
+ */
+static void
+phase_check_keys (
+ struct MeltContext *mc)
+{
+ bool is_cs_denom[mc->request.refresh.num_coins];
+
+ memset (is_cs_denom,
+ 0,
+ sizeof(is_cs_denom));
+
+ mc->request.refresh.denom_serials =
+ GNUNET_new_array (mc->request.refresh.num_coins,
+ uint64_t);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &mc->request.amount));
+
+ /* Calculate the total value and withdraw fees for the fresh coins */
+ for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
+ {
+ struct TEH_DenominationKey *dk;
+
+ if (GNUNET_OK !=
+ find_denomination (mc,
+ &mc->request.denoms_h[i],
+ &dk))
+ return;
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ dk->denom_pub.bsign_pub_key->cipher)
+ {
+ if (mc->request.refresh.no_blinding_seed)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_BLINDING_SEED_REQUIRED);
+ return;
+ }
+ mc->request.refresh.num_cs_r_values++;
+ is_cs_denom[i] = true;
+ }
+ /* Ensure the ciphers from the planchets match the denominations'. */
+ {
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ mc->request.planchets[k][i].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_CIPHER_MISMATCH);
+ return;
+ }
+ }
+ }
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&mc->request.amount,
+ &mc->request.amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_OVERFLOW);
+ return;
+ }
+ /* Accumulate the withdraw fees for the fresh coins */
+ if (0 > TALER_amount_add (&mc->request.amount,
+ &mc->request.amount,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
+ return;
+ }
+ mc->request.refresh.denom_serials[i] = dk->meta.serial;
+ }
+
+ /**
+ * Calculate the amount (with withdraw fee) plus refresh fee and
+ * compare with the value provided by the client in the request.
+ */
+ {
+ struct TALER_Amount amount_with_fee;
+
+ if (0 > TALER_amount_add (&amount_with_fee,
+ &mc->request.amount,
+ &mc->melted_coin_denom->meta.fees.refresh))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
+ return;
+ }
+
+ if (0 != TALER_amount_cmp (&amount_with_fee,
+ &mc->request.refresh.amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT);
+ return;
+ }
+ }
+
+ /* Save the indices of CS denominations */
+ if (0 < mc->request.refresh.num_cs_r_values)
+ {
+ size_t j = 0;
+
+ mc->request.cs_indices = GNUNET_new_array (
+ mc->request.refresh.num_cs_r_values,
+ uint32_t);
+
+ for (size_t i = 0; i < mc->request.refresh.num_coins; i++)
+ {
+ if (is_cs_denom[i])
+ mc->request.cs_indices[j++] = i;
+ }
+ }
+ mc->phase++;
+}
+
+
+/**
+ * Check that the client signature authorizing the melt is valid.
+ *
+ * @param[in,out] mc request context to check
+ */
+static void
+phase_check_coin_signature (
+ struct MeltContext *mc)
+{
+ /* We can now compute the commitment */
+ {
+ struct TALER_KappaHashBlindedPlanchetsP k_bps_h = {0};
+ struct TALER_KappaTransferPublicKeys k_transfer_pubs = {0};
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ TALER_wallet_blinded_planchets_hash (
+ mc->request.refresh.num_coins,
+ mc->request.planchets[k],
+ mc->request.denoms_h,
+ &k_bps_h.tuple[k]);
+
+ if (! mc->request.refresh.is_v27_refresh)
+ {
+ k_transfer_pubs.num_transfer_pubs = mc->request.refresh.num_coins;
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ k_transfer_pubs.batch[k] = &mc->request.refresh.transfer_pubs[k];
+ }
+
+ TALER_refresh_get_commitment (
+ &mc->request.refresh.rc,
+ &mc->request.refresh.refresh_seed,
+ mc->request.no_blinding_seed
+ ? NULL
+ : &mc->request.refresh.blinding_seed,
+ mc->request.refresh.is_v27_refresh
+ ? NULL
+ : &k_transfer_pubs,
+ &k_bps_h,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->request.refresh.amount_with_fee);
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_melt_verify (
+ &mc->request.refresh.amount_with_fee,
+ &mc->melted_coin_denom->meta.fees.refresh,
+ &mc->request.refresh.rc,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->request.refresh.coin.h_age_commitment,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->request.refresh.coin_sig))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_SIGNATURE_INVALID);
+ return;
+ }
+
+ mc->phase++;
+}
+
+
+/**
+ * Check for information about the melted coin's denomination,
+ * extracting its validity status and fee structure.
+ * Baseline: check if deposits/refreshes are generally
+ * simply still allowed for this denomination.
+ *
+ * @param mc parsed request information
+ */
+static void
+phase_check_melt_valid (struct MeltContext *mc)
+{
+ enum MeltPhase current_phase = mc->phase;
+ /**
+ * Find the old coin's denomination.
+ * Note that we return only on GNUNET_SYSERR,
+ * because GNUNET_NO for the expired denomination
+ * will be handled below, with the zombie-check.
+ */
+ if (GNUNET_SYSERR ==
+ find_denomination (mc,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->melted_coin_denom))
+ return;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Melted coin's denomination is worth %s\n",
+ TALER_amount2s (&mc->melted_coin_denom->meta.value));
+
+ /* sanity-check that "total melt amount > melt fee" */
+ if (0 <
+ TALER_amount_cmp (&mc->melted_coin_denom->meta.fees.refresh,
+ &mc->request.refresh.amount_with_fee))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_FEES_EXCEED_CONTRIBUTION);
+ return;
+ }
+
+ if (GNUNET_OK !=
+ TALER_test_coin_valid (&mc->request.refresh.coin,
+ &mc->melted_coin_denom->denom_pub))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DENOMINATION_SIGNATURE_INVALID);
+ return;
+ }
+
+ /**
+ * find_denomination might have set the phase to
+ * produce an error, but we are still investigating.
+ * We reset the phase.
+ */
+ mc->phase = current_phase;
+ mc->error.code = MELT_ERROR_NONE;
+
+ if (GNUNET_TIME_absolute_is_past (
+ mc->melted_coin_denom->meta.expire_deposit.abs_time))
+ {
+ /**
+ * We are past deposit expiration time, but maybe this is a zombie?
+ */
+ struct TALER_DenominationHashP denom_hash;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Check that the coin is dirty (we have seen it before), as we will
+ not just allow melting of a *fresh* coin where the denomination was
+ revoked (those must be recouped) */
+ qs = TEH_plugin->get_coin_denomination (
+ TEH_plugin->cls,
+ &mc->request.refresh.coin.coin_pub,
+ &mc->known_coin_id,
+ &denom_hash);
+ if (0 > qs)
+ {
+ /* There is no good reason for a serialization failure here: */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ SET_ERROR (mc,
+ MELT_ERROR_DB_FETCH_FAILED);
+ return;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ /* We never saw this coin before, so _this_ justification is not OK.
+ * Note that the error was already set in find_denominations. */
+ GNUNET_assert (MELT_ERROR_DENOMINATION_EXPIRED ==
+ mc->error.code);
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR ==
+ mc->phase);
+ return;
+ }
+ /* sanity check */
+ if (0 !=
+ GNUNET_memcmp (&denom_hash,
+ &mc->request.refresh.coin.denom_pub_hash))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_COIN_CONFLICTING_DENOMINATION_KEY,
+ denom_h,
+ denom_hash);
+ return;
+ }
+ /* Minor optimization: no need to run the
+ "ensure_coin_known" part of the transaction */
+ mc->coin_is_known = true;
+ /* check later that zombie is satisfied */
+ mc->zombie_required = true;
+ }
+ mc->phase++;
+}
+
+
+/**
+ * The request for melt was parsed successfully.
+ * Sign and persist the chosen blinded coins for the reveal step.
+ *
+ * @param mc The context for the current melt request
+ */
+static void
+phase_prepare_transaction (
+ struct MeltContext *mc)
+{
+ mc->request.refresh.denom_sigs
+ = GNUNET_new_array (
+ mc->request.refresh.num_coins,
+ struct TALER_BlindedDenominationSignature);
+ mc->request.refresh.noreveal_index =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+
+ /* Choose and sign the coins */
+ {
+ struct TEH_CoinSignData csds[mc->request.refresh.num_coins];
+ enum TALER_ErrorCode ec_denomination_sign;
+ size_t noreveal_idx = mc->request.refresh.noreveal_index;
+
+ memset (csds,
+ 0,
+ sizeof(csds));
+
+ /* Pick the chosen blinded coins */
+ for (size_t i = 0; i<mc->request.refresh.num_coins; i++)
+ {
+ csds[i].bp = &mc->request.planchets[noreveal_idx][i];
+ csds[i].h_denom_pub = &mc->request.denoms_h[i];
+ }
+
+ ec_denomination_sign = TEH_keys_denomination_batch_sign (
+ mc->request.refresh.num_coins,
+ csds,
+ true, /* for melt */
+ mc->request.refresh.denom_sigs);
+ if (TALER_EC_NONE != ec_denomination_sign)
+ {
+ GNUNET_break (0);
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_DENOMINATION_SIGN,
+ ec_denomination_sign);
+ return;
+ }
+
+ /* Save the hash of chosen planchets */
+ mc->request.refresh.selected_h =
+ mc->request.kappa_planchets_h.tuple[noreveal_idx];
+
+ /* If applicable, save the chosen transfer public keys */
+ if (! mc->request.refresh.is_v27_refresh)
+ mc->request.refresh.transfer_pubs =
+ mc->request.transfer_pubs[noreveal_idx];
+
+ /**
+ * For the denominations with cipher CS, calculate the R-values
+ * and save the choices we made now, as at a later point, the
+ * private keys for the denominations might now be available anymore
+ * to make the same choice again.
+ */
+ if (0 < mc->request.refresh.num_cs_r_values)
+ {
+ size_t num_cs_r_values = mc->request.refresh.num_cs_r_values;
+ struct TEH_CsDeriveData cdds[num_cs_r_values];
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
+
+ memset (nonces,
+ 0,
+ sizeof(nonces));
+ mc->request.refresh.cs_r_values =
+ GNUNET_new_array (num_cs_r_values,
+ struct GNUNET_CRYPTO_CSPublicRPairP);
+ mc->request.refresh.cs_r_choices = 0;
+
+ GNUNET_assert (! mc->request.refresh.no_blinding_seed);
+ TALER_cs_derive_nonces_from_seed (
+ &mc->request.refresh.blinding_seed,
+ true, /* for melt */
+ num_cs_r_values,
+ mc->request.cs_indices,
+ nonces);
+
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = mc->request.cs_indices[i];
+
+ GNUNET_assert (idx < mc->request.refresh.num_coins);
+ cdds[i].h_denom_pub = &mc->request.denoms_h[idx];
+ cdds[i].nonce = &nonces[i];
+ }
+
+ /**
+ * Let the crypto helper generate the R-values and
+ * make the choices
+ */
+ if (TALER_EC_NONE !=
+ TEH_keys_denomination_cs_batch_r_pub_simple (
+ mc->request.refresh.num_cs_r_values,
+ cdds,
+ true, /* for melt */
+ mc->request.refresh.cs_r_values))
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_CRYPTO_HELPER);
+ return;
+ }
+
+ /* Now save the choices for the selected bits */
+ GNUNET_assert (num_cs_r_values <= 64);
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = mc->request.cs_indices[i];
+
+ struct TALER_BlindedDenominationSignature *sig =
+ &mc->request.refresh.denom_sigs[idx];
+ uint64_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
+
+ mc->request.refresh.cs_r_choices |= bit << i;
+ GNUNET_static_assert (
+ TALER_MAX_COINS <=
+ sizeof(mc->request.refresh.cs_r_choices) * 8);
+ }
+ }
+ }
+ mc->phase++;
+}
+
+
+/**
+ * Generates response for the melt request.
+ *
+ * @param mc melt operation context
+ */
+static void
+phase_generate_reply_success (struct MeltContext *mc)
+{
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *db_obj;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ db_obj = mc->request.is_idempotent
+ ? &mc->request.refresh_idem
+ : &mc->request.refresh;
+ ec_confirmation_sign =
+ TALER_exchange_online_melt_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &db_obj->rc,
+ db_obj->noreveal_index,
+ &pub,
+ &sig);
+ if (TALER_EC_NONE != ec_confirmation_sign)
+ {
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_CONFIRMATION_SIGN,
+ ec_confirmation_sign);
+ return;
+ }
+
+ finish_loop (mc,
+ TALER_MHD_REPLY_JSON_PACK (
+ mc->rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_uint64 ("noreveal_index",
+ db_obj->noreveal_index),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub)));
+}
+
+
+/**
+ * Check if the melt request is replayed and we already have an answer.
+ * If so, replay the existing answer and return the HTTP response.
+ *
+ * @param[in,out] mc parsed request data
+ * @return true if the request is idempotent with an existing request
+ * false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+melt_is_idempotent (
+ struct MeltContext *mc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_refresh (
+ TEH_plugin->cls,
+ &mc->request.refresh.rc,
+ &mc->request.refresh_idem);
+ if (0 > qs)
+ {
+ /* FIXME: soft error not handled correctly! */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "get_refresh");
+ return true; /* Well, kind-of. */
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
+
+ mc->request.is_idempotent = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "request is idempotent\n");
+
+ /* Generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_MELT]++;
+ mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
+ mc->error.code = MELT_ERROR_NONE;
+ return true;
+}
+
+
+/**
+ * Reports an error, potentially with details.
+ * That is, it puts a error-type specific response into the MHD queue.
+ * It will do a idempotency check first, if needed for the error type.
+ *
+ * @param mc melt context
+ */
+static void
+phase_generate_reply_error (
+ struct MeltContext *mc)
+{
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
+ GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
+
+ if (IDEMPOTENCY_CHECK_REQUIRED (mc->error.code) &&
+ melt_is_idempotent (mc))
+ {
+ return;
+ }
+
+ switch (mc->error.code)
+ {
+ case MELT_ERROR_NONE:
+ break;
+ case MELT_ERROR_REQUEST_PARAMETER_MALFORMED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ mc->error.details.request_parameter_malformed));
+ return;
+ case MELT_ERROR_KEYS_MISSING:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL));
+ return;
+ case MELT_ERROR_DB_FETCH_FAILED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ mc->error.details.db_fetch_context));
+ return;
+ case MELT_ERROR_DB_INVARIANT_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case MELT_ERROR_DB_PREFLIGHT_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_COMMIT_FAILED,
+ "make_coin_known"));
+ return;
+ case MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure"));
+ return;
+ case MELT_ERROR_COIN_UNKNOWN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_UNKNOWN,
+ NULL));
+ return;
+ case MELT_COIN_CONFLICTING_DENOMINATION_KEY:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_COIN_CONFLICTING_DENOMINATION_KEY,
+ TALER_B2S (&mc->error.details.denom_h)));
+ return;
+ case MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_COIN_EXPIRED_NO_ZOMBIE,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_SIGN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ mc->error.details.ec_denomination_sign,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_SIGNATURE_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (mc->rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL));
+ return;
+ case MELT_ERROR_DENOMINATION_KEY_UNKNOWN:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h));
+ return;
+ case MELT_ERROR_DENOMINATION_EXPIRED:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "MELT"));
+ return;
+ case MELT_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
+ finish_loop (mc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ mc->rc->connection,
+ &mc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "MELT"));
+ return;
+ case MELT_ERROR_DENOMINATION_REVOKED:
+ GNUNET_break_op (0);
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL));
+ return;
+ case MELT_ERROR_COIN_CIPHER_MISMATCH:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL));
+ return;
+ case MELT_ERROR_BLINDING_SEED_REQUIRED:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "blinding_seed"));
+ return;
+ case MELT_ERROR_CRYPTO_HELPER:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case MELT_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
+ {
+ char msg[256];
+
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&mc->error.details.denom_h.hash));
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg));
+ return;
+ }
+ case MELT_ERROR_AGE_RESTRICTION_COMMITMENT_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ "old_age_commitment_h"));
+ return;
+ case MELT_ERROR_AMOUNT_OVERFLOW:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "amount"));
+ return;
+ case MELT_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "amount+fee"));
+ return;
+ case MELT_ERROR_FEES_EXCEED_CONTRIBUTION:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_MELT_FEES_EXCEED_CONTRIBUTION,
+ NULL));
+ return;
+ case MELT_ERROR_AMOUNT_WITH_FEE_INCORRECT:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_COST_CALCULATION_OVERFLOW,
+ "value_with_fee incorrect"));
+ return;
+ case MELT_ERROR_CONFIRMATION_SIGN:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ mc->error.details.ec_confirmation_sign,
+ NULL));
+ return;
+ case MELT_ERROR_INSUFFICIENT_FUNDS:
+ finish_loop (mc,
+ TEH_RESPONSE_reply_coin_insufficient_funds (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &mc->request.refresh.coin.denom_pub_hash,
+ &mc->request.refresh.coin.coin_pub));
+ return;
+ case MELT_ERROR_DUPLICATE_PLANCHET:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
+ "duplicate planchet"));
+ return;
+ case MELT_ERROR_DUPLICATE_TRANSFER_PUB:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error! */
+ "duplicate transfer_pub"));
+ return;
+ case MELT_ERROR_NONCE_RESUSE:
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED, /* FIXME: new error */
+ "nonce reuse"));
+ return;
+ case MELT_ERROR_COIN_SIGNATURE_INVALID:
+ finish_loop (mc,
+ TALER_MHD_reply_with_ec (
+ mc->rc->connection,
+ TALER_EC_EXCHANGE_MELT_COIN_SIGNATURE_INVALID,
+ NULL));
+ return;
+ }
+ GNUNET_break (0);
+ finish_loop (mc,
+ TALER_MHD_reply_with_error (
+ mc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "error phase without error"));
+}
+
+
+/**
+ * Function implementing melt transaction. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response. IF it returns an hard error,
+ * the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again
+ * to retry and MUST not queue a MHD response.
+ *
+ * @param cls a `struct MeltContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+melt_transaction (
+ void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct MeltContext *mc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool balance_ok;
+ bool found;
+ bool nonce_reuse;
+ uint32_t noreveal_index;
+ struct TALER_Amount insufficient_funds;
+
+ (void) connection;
+ (void) mhd_ret;
+
+ qs = TEH_plugin->do_refresh (TEH_plugin->cls,
+ &mc->request.refresh,
+ &mc->now,
+ &found,
+ &noreveal_index,
+ &mc->zombie_required,
+ &nonce_reuse,
+ &balance_ok,
+ &insufficient_funds);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ SET_ERROR_WITH_DETAIL (mc,
+ MELT_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "do_refresh");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_UNKNOWN);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (found)
+ {
+ /**
+ * This request is idempotent, set the nonreveal_index
+ * to the previous one and reply success.
+ */
+ mc->request.refresh.noreveal_index = noreveal_index;
+ mc->phase = MELT_PHASE_GENERATE_REPLY_SUCCESS;
+ mc->error.code = MELT_ERROR_NONE;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+ if (nonce_reuse)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_NONCE_RESUSE);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_FIELD (mc,
+ MELT_ERROR_INSUFFICIENT_FUNDS,
+ insufficient_funds);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (mc->zombie_required)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (mc,
+ MELT_ERROR_COIN_EXPIRED_NO_ZOMBIE);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_MELT]++;
+ return qs;
+}
+
+
+/**
+ * The request was prepared successfully.
+ * Run the main DB transaction.
+ *
+ * @param mc The context for the current melt request
+ */
+static void
+phase_run_transaction (
+ struct MeltContext *mc)
+{
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ SET_ERROR (mc,
+ MELT_ERROR_DB_PREFLIGHT_FAILURE);
+ return;
+ }
+
+ /* first, make sure coin is known */
+ if (! mc->coin_is_known)
+ {
+ MHD_RESULT mhd_ret = -1;
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int tries = 0; tries<MAX_TRANSACTION_COMMIT_RETRIES; tries++)
+ {
+ qs = TEH_make_coin_known (&mc->request.refresh.coin,
+ mc->rc->connection,
+ &mc->known_coin_id,
+ &mhd_ret);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ /* Check if an answer has been queued */
+ switch (mhd_ret)
+ {
+ case MHD_NO:
+ mc->phase = MELT_PHASE_RETURN_NO;
+ return;
+ case MHD_YES:
+ mc->phase = MELT_PHASE_RETURN_YES;
+ return;
+ default: /* ignore */
+ ;
+ }
+ SET_ERROR (mc,
+ MELT_ERROR_DB_MAKE_COIN_KNOW_FAILURE);
+ return;
+ }
+ }
+
+ /* run main database transaction */
+ {
+ MHD_RESULT mhd_ret = -1;
+ enum GNUNET_GenericReturnValue ret;
+ enum MeltPhase current_phase = mc->phase;
+
+ GNUNET_assert (MELT_PHASE_RUN_TRANSACTION ==
+ current_phase);
+ ret = TEH_DB_run_transaction (mc->rc->connection,
+ "run melt",
+ TEH_MT_REQUEST_MELT,
+ &mhd_ret,
+ &melt_transaction,
+ mc);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ /* Check if an answer has been queued */
+ switch (mhd_ret)
+ {
+ case MHD_NO:
+ mc->phase = MELT_PHASE_RETURN_NO;
+ return;
+ case MHD_YES:
+ mc->phase = MELT_PHASE_RETURN_YES;
+ return;
+ default: /* ignore */
+ ;
+ }
+ GNUNET_assert (MELT_ERROR_NONE != mc->error.code);
+ GNUNET_assert (MELT_PHASE_GENERATE_REPLY_ERROR == mc->phase);
+ return;
+ }
+ /**
+ * In case of idempotency (which is not an error condition),
+ * the phase has changed in melt_transaction.
+ * We simple return.
+ */
+ if (current_phase != mc->phase)
+ return;
+ }
+ mc->phase++;
+}
+
+
+MHD_RESULT
+TEH_handler_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0])
+{
+ struct MeltContext *mc = rc->rh_ctx;
+
+ (void) args;
+ if (NULL == mc)
+ {
+ mc = GNUNET_new (struct MeltContext);
+ rc->rh_ctx = mc;
+ rc->rh_cleaner = &clean_melt_rc;
+ mc->rc = rc;
+ mc->now = GNUNET_TIME_timestamp_get ();
+ }
+
+ while (true)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "melt processing in phase %d\n",
+ mc->phase);
+ switch (mc->phase)
+ {
+ case MELT_PHASE_PARSE:
+ phase_parse_request (mc,
+ root);
+ break;
+ case MELT_PHASE_CHECK_MELT_VALID:
+ phase_check_melt_valid (mc);
+ break;
+ case MELT_PHASE_CHECK_KEYS:
+ phase_check_keys (mc);
+ break;
+ case MELT_PHASE_CHECK_COIN_SIGNATURE:
+ phase_check_coin_signature (mc);
+ break;
+ case MELT_PHASE_PREPARE_TRANSACTION:
+ phase_prepare_transaction (mc);
+ break;
+ case MELT_PHASE_RUN_TRANSACTION:
+ phase_run_transaction (mc);
+ break;
+ case MELT_PHASE_GENERATE_REPLY_SUCCESS:
+ phase_generate_reply_success (mc);
+ break;
+ case MELT_PHASE_GENERATE_REPLY_ERROR:
+ phase_generate_reply_error (mc);
+ break;
+ case MELT_PHASE_RETURN_YES:
+ return MHD_YES;
+ case MELT_PHASE_RETURN_NO:
+ return MHD_NO;
+ }
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_post-melt.h b/src/exchange/taler-exchange-httpd_post-melt.h
@@ -0,0 +1,52 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-melt.h
+ * @brief Handle /melt requests, starting with vDOLDPLUS of the protocol
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_MELT_H
+#define TALER_EXCHANGE_HTTPD_POST_MELT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_melt_cleanup (void);
+
+
+/**
+ * Handle a "/melt" request. Parses the request into the JSON
+ * components and validates the melted coins, the signature and
+ * execute the melt as database transaction.
+ *
+ * @param rc the request context
+ * @param root uploaded JSON data
+ * @param args array of additional options, not used
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0]);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.c
@@ -0,0 +1,664 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-purses-PURSE_PUB-create.c
+ * @brief Handle /purses/$PID/create requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-create.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for #create_transaction.
+ */
+struct PurseCreateContext
+{
+
+ /**
+ * Total actually deposited by all the coins.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Encrypted contract of for the purse.
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Signature of the client affiming this request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Fundamental details about the purse.
+ */
+ struct TEH_PurseDetails pd;
+
+ /**
+ * Array of coins being deposited.
+ */
+ struct TEH_PurseDepositedCoin *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Do we have an @e econtract?
+ */
+ bool no_econtract;
+
+};
+
+
+/**
+ * Execute database transaction for /purses/$PID/create. Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
+ * a MHD response. IF it returns an hard error, the transaction logic MUST
+ * queue a MHD response and set @a mhd_ret. IF it returns the soft error
+ * code, the function MAY be called again to retry and MUST not queue a MHD
+ * response.
+ *
+ * @param cls a `struct PurseCreateContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+create_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseCreateContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount purse_fee;
+ bool in_conflict = true;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &purse_fee));
+ /* 1) create purse */
+ qs = TEH_plugin->insert_purse_request (
+ TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &pcc->merge_pub,
+ pcc->pd.purse_expiration,
+ &pcc->pd.h_contract_terms,
+ pcc->min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ &purse_fee,
+ &pcc->pd.target_amount,
+ &pcc->purse_sig,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store create purse information in database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_Amount target_amount;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig;
+ uint32_t min_age;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_request (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &merge_pub,
+ &purse_expiration,
+ &h_contract_terms,
+ &min_age,
+ &target_amount,
+ &balance,
+ &purse_sig);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA),
+ TALER_JSON_pack_amount ("amount",
+ &target_amount),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* 2) deposit all coins */
+ for (unsigned int i = 0; i<pcc->num_coins; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
+ bool balance_ok = false;
+ bool conflict = true;
+ bool too_late = true;
+
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &coin->cpi.coin_pub,
+ &coin->amount,
+ &coin->coin_sig,
+ &coin->amount_minus_fee,
+ &balance_ok,
+ &too_late,
+ &conflict);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store purse deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Coin %s has insufficient balance for purse deposit of amount %s\n",
+ TALER_B2S (&coin->cpi.coin_pub),
+ TALER_amount2s (&coin->amount));
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (too_late)
+ {
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "too late to deposit on purse creation");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ struct TALER_Amount amount;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHashP phac;
+ char *partner_url = NULL;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &coin->cpi.coin_pub,
+ &amount,
+ &h_denom_pub,
+ &phac,
+ &coin_sig,
+ &partner_url);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse deposit information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin_pub),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("h_age_restrictions",
+ &phac),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ TALER_JSON_pack_amount ("amount",
+ &amount));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ /* 3) if present, persist contract */
+ if (! pcc->no_econtract)
+ {
+ in_conflict = true;
+ qs = TEH_plugin->insert_contract (TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &pcc->econtract,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING ("Failed to store purse information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse create contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_EncryptedContract econtract;
+ struct GNUNET_HashCode h_econtract;
+
+ qs = TEH_plugin->select_contract_by_purse (
+ TEH_plugin->cls,
+ &pcc->pd.purse_pub,
+ &econtract);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store fetch contract information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_CRYPTO_hash (econtract.econtract,
+ econtract.econtract_size,
+ &h_econtract);
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("h_econtract",
+ &h_econtract),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract.econtract_sig),
+ GNUNET_JSON_pack_data_auto ("contract_pub",
+ &econtract.contract_pub));
+ GNUNET_free (econtract.econtract);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[in,out] pcc request context
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (struct MHD_Connection *connection,
+ struct PurseCreateContext *pcc,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin)
+{
+ enum GNUNET_GenericReturnValue iret;
+
+ if (GNUNET_OK !=
+ (iret = TEH_common_purse_deposit_parse_coin (connection,
+ coin,
+ jcoin)))
+ return iret;
+ if (GNUNET_OK !=
+ (iret = TEH_common_deposit_check_purse_deposit (
+ connection,
+ coin,
+ &pcc->pd.purse_pub,
+ pcc->min_age)))
+ return iret;
+ if (0 >
+ TALER_amount_add (&pcc->deposit_total,
+ &pcc->deposit_total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "total deposit contribution"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_purses_create (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct PurseCreateContext pcc = {
+ .pd.purse_pub = *purse_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ const json_t *deposits;
+ json_t *deposit;
+ unsigned int idx;
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("amount",
+ TEH_currency,
+ &pcc.pd.target_amount),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &pcc.min_age),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_econtract ("econtract",
+ &pcc.econtract),
+ &pcc.no_econtract),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &pcc.merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_sig",
+ &pcc.purse_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &pcc.pd.h_contract_terms),
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &pcc.pd.purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+ const struct TEH_GlobalFee *gf;
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &pcc.deposit_total));
+ if (GNUNET_TIME_timestamp_cmp (pcc.pd.purse_expiration,
+ <,
+ pcc.exchange_timestamp))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW,
+ NULL);
+ }
+ if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time))
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER,
+ NULL);
+ }
+ pcc.num_coins = json_array_size (deposits);
+ if ( (0 == pcc.num_coins) ||
+ (pcc.num_coins > TALER_MAX_COINS) )
+ {
+ GNUNET_break_op (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "deposits");
+ }
+ {
+ struct TEH_KeyStateHandle *keys;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ gf = TEH_keys_global_fee_by_time (keys,
+ pcc.exchange_timestamp);
+ }
+ if (NULL == gf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create purse: global fees not configured!\n");
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
+ NULL);
+ }
+ /* parse deposits */
+ pcc.coins = GNUNET_new_array (pcc.num_coins,
+ struct TEH_PurseDepositedCoin);
+ json_array_foreach (deposits, idx, deposit)
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
+
+ res = parse_coin (connection,
+ &pcc,
+ coin,
+ deposit);
+ if (GNUNET_OK != res)
+ {
+ for (unsigned int i = 0; i<idx; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ }
+
+ if (0 < TALER_amount_cmp (&gf->fees.purse,
+ &pcc.deposit_total))
+ {
+ GNUNET_break_op (0);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE,
+ NULL);
+ }
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (
+ pcc.pd.purse_expiration,
+ &pcc.pd.h_contract_terms,
+ &pcc.merge_pub,
+ pcc.min_age,
+ &pcc.pd.target_amount,
+ &pcc.pd.purse_pub,
+ &pcc.purse_sig))
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if ( (! pcc.no_econtract) &&
+ (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (pcc.econtract.econtract,
+ pcc.econtract.econtract_size,
+ &pcc.econtract.contract_pub,
+ purse_pub,
+ &pcc.econtract.econtract_sig)) )
+ {
+ TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
+ NULL);
+ }
+
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse create",
+ TEH_MT_REQUEST_PURSE_CREATE,
+ &mhd_ret,
+ &create_transaction,
+ &pcc))
+ {
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return mhd_ret;
+ }
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = TEH_RESPONSE_reply_purse_created (connection,
+ pcc.exchange_timestamp,
+ &pcc.deposit_total,
+ &pcc.pd);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_purses_create.c */
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.h b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-create.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-purses-PURSE_PUB-create.h
+ * @brief Handle /purses/$PID/create requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_CREATE_H
+#define TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_CREATE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/purses/$PURSE_PUB/create" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #create_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses create" being executed, or rejected.
+ *
+ * @param rc connection to handle
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_create (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.c
@@ -0,0 +1,509 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-purses-PURSE_PUB-deposit.c
+ * @brief Handle /purses/$PID/deposit requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler/taler_dbevents.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for #deposit_transaction.
+ */
+struct PurseDepositContext
+{
+ /**
+ * Public key of the purse we are creating.
+ */
+ const struct TALER_PurseContractPublicKeyP *purse_pub;
+
+ /**
+ * Total amount to be put into the purse.
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Total actually deposited by all the coins.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * When should the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * Hash of the contract (needed for signing).
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Array of coins being deposited.
+ */
+ struct TEH_PurseDepositedCoin *coins;
+
+ /**
+ * Length of the @e coins array.
+ */
+ unsigned int num_coins;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+};
+
+
+/**
+ * Send confirmation of purse creation success to client.
+ *
+ * @param connection connection to the client
+ * @param pcc details about the request that succeeded
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_deposit_success (struct MHD_Connection *connection,
+ const struct PurseDepositContext *pcc)
+{
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_created_sign (
+ &TEH_keys_exchange_sign_,
+ pcc->exchange_timestamp,
+ pcc->purse_expiration,
+ &pcc->amount,
+ &pcc->deposit_total,
+ pcc->purse_pub,
+ &pcc->h_contract_terms,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("total_deposited",
+ &pcc->deposit_total),
+ TALER_JSON_pack_amount ("purse_value_after_fees",
+ &pcc->amount),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ pcc->exchange_timestamp),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ pcc->purse_expiration),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &pcc->h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Execute database transaction for /purses/$PID/deposit. Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
+ * a MHD response. IF it returns an hard error, the transaction logic MUST
+ * queue a MHD response and set @a mhd_ret. IF it returns the soft error
+ * code, the function MAY be called again to retry and MUST not queue a MHD
+ * response.
+ *
+ * @param cls a `struct PurseDepositContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+deposit_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseDepositContext *pcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ for (unsigned int i = 0; i<pcc->num_coins; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
+ bool balance_ok = false;
+ bool conflict = true;
+ bool too_late = true;
+
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
+ pcc->purse_pub,
+ &coin->cpi.coin_pub,
+ &coin->amount,
+ &coin->coin_sig,
+ &coin->amount_minus_fee,
+ &balance_ok,
+ &too_late,
+ &conflict);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store purse deposit information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! balance_ok)
+ {
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (too_late)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret
+ = TALER_MHD_reply_with_ec (
+ connection,
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_DECIDED_ALREADY,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (conflict)
+ {
+ struct TALER_Amount amount;
+ struct TALER_CoinSpendPublicKeyP coin_pub;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_DenominationHashP h_denom_pub;
+ struct TALER_AgeCommitmentHashP phac;
+ char *partner_url = NULL;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
+ pcc->purse_pub,
+ &coin->cpi.coin_pub,
+ &amount,
+ &h_denom_pub,
+ &phac,
+ &coin_sig,
+ &partner_url);
+ if (qs < 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse deposit information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get purse deposit");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("coin_pub",
+ &coin_pub),
+ GNUNET_JSON_pack_data_auto ("h_denom_pub",
+ &h_denom_pub),
+ GNUNET_JSON_pack_data_auto ("h_age_commitment",
+ &phac),
+ GNUNET_JSON_pack_data_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ TALER_JSON_pack_amount ("amount",
+ &amount));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+/**
+ * Parse a coin and check signature of the coin and the denomination
+ * signature over the coin.
+ *
+ * @param[in,out] connection our HTTP connection
+ * @param[in,out] pcc request context
+ * @param[out] coin coin to initialize
+ * @param jcoin coin to parse
+ * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
+ * #GNUNET_SYSERR on failure and no error could be returned
+ */
+static enum GNUNET_GenericReturnValue
+parse_coin (struct MHD_Connection *connection,
+ struct PurseDepositContext *pcc,
+ struct TEH_PurseDepositedCoin *coin,
+ const json_t *jcoin)
+{
+ enum GNUNET_GenericReturnValue iret;
+
+ if (GNUNET_OK !=
+ (iret = TEH_common_purse_deposit_parse_coin (connection,
+ coin,
+ jcoin)))
+ return iret;
+ if (GNUNET_OK !=
+ (iret = TEH_common_deposit_check_purse_deposit (
+ connection,
+ coin,
+ pcc->purse_pub,
+ pcc->min_age)))
+ return iret;
+ if (0 >
+ TALER_amount_add (&pcc->deposit_total,
+ &pcc->deposit_total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ "total deposit contribution");
+ }
+ return GNUNET_OK;
+}
+
+
+MHD_RESULT
+TEH_handler_purses_deposit (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct MHD_Connection *connection = rc->connection;
+ struct PurseDepositContext pcc = {
+ .purse_pub = purse_pub,
+ .exchange_timestamp = GNUNET_TIME_timestamp_get ()
+ };
+ const json_t *deposits;
+ json_t *deposit;
+ unsigned int idx;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_array_const ("deposits",
+ &deposits),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &pcc.deposit_total));
+ pcc.num_coins = (unsigned int) json_array_size (deposits);
+ if ( (0 == pcc.num_coins) ||
+ (((size_t) pcc.num_coins) != json_array_size (deposits)) ||
+ (pcc.num_coins > TALER_MAX_COINS) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "deposits");
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp create_timestamp;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ bool was_deleted;
+ bool was_refunded;
+
+ qs = TEH_plugin->select_purse (
+ TEH_plugin->cls,
+ pcc.purse_pub,
+ &create_timestamp,
+ &pcc.purse_expiration,
+ &pcc.amount,
+ &pcc.deposit_total,
+ &pcc.h_contract_terms,
+ &merge_timestamp,
+ &was_deleted,
+ &was_refunded);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break; /* handled below */
+ }
+ if (was_refunded ||
+ was_deleted)
+ {
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ was_deleted
+ ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
+ : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ GNUNET_TIME_timestamp2s (pcc.purse_expiration));
+ }
+ }
+
+ /* parse deposits */
+ pcc.coins = GNUNET_new_array (pcc.num_coins,
+ struct TEH_PurseDepositedCoin);
+ json_array_foreach (deposits, idx, deposit)
+ {
+ enum GNUNET_GenericReturnValue res;
+ struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
+
+ res = parse_coin (connection,
+ &pcc,
+ coin,
+ deposit);
+ if (GNUNET_OK != res)
+ {
+ for (unsigned int i = 0; i<idx; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ }
+ }
+
+ if (GNUNET_SYSERR ==
+ TEH_plugin->preflight (TEH_plugin->cls))
+ {
+ GNUNET_break (0);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_START_FAILED,
+ "preflight failure");
+ }
+
+ /* execute transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute purse deposit",
+ TEH_MT_REQUEST_PURSE_DEPOSIT,
+ &mhd_ret,
+ &deposit_transaction,
+ &pcc))
+ {
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return mhd_ret;
+ }
+ }
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
+ .purse_pub = *pcc.purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse deposit %s\n",
+ TALER_B2S (pcc.purse_pub));
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = reply_deposit_success (connection,
+ &pcc);
+ for (unsigned int i = 0; i<pcc.num_coins; i++)
+ TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
+ GNUNET_free (pcc.coins);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_purses_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-deposit.h
@@ -0,0 +1,47 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-purses-PURSE_PUB-deposit.h
+ * @brief Handle /purses/$PID/deposit requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_DEPOSIT_H
+#define TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_DEPOSIT_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/purses/$PURSE_PUB/deposit" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #deposit_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses deposit" being executed, or rejected.
+ *
+ * @param rc request to handle
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_deposit (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.c b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.c
@@ -0,0 +1,815 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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 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_post-purses-PURSE_PUB-merge.c
+ * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
+ * verifies the reserve signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_dbevents.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_post-purses-PURSE_PUB-merge.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for #merge_transaction.
+ */
+struct PurseMergeContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PurseMergeContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct PurseMergeContext *prev;
+
+ /**
+ * Our request.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for the legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Fees that apply to this operation.
+ */
+ const struct TALER_WireFeeSet *wf;
+
+ /**
+ * Base URL of the exchange provider hosting the reserve.
+ */
+ char *provider_url;
+
+ /**
+ * URI of the account the purse is to be merged into.
+ * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
+ */
+ struct TALER_NormalizedPayto payto_uri;
+
+ /**
+ * Response to return, if set.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Public key of the purse we are creating.
+ */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+
+ /**
+ * Total amount to be put into the purse.
+ */
+ struct TALER_Amount target_amount;
+
+ /**
+ * Current amount in the purse.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * When should the purse expire.
+ */
+ struct GNUNET_TIME_Timestamp purse_expiration;
+
+ /**
+ * When the client signed the merge.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Signature of the reservce affiming this request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Signature of the client affiming the merge.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Public key of the reserve (account), as extracted from @e payto_uri.
+ */
+ union TALER_AccountPublicKeyP account_pub;
+
+ /**
+ * Hash of the contract terms of the purse.
+ */
+ struct TALER_PrivateContractHashP h_contract_terms;
+
+ /**
+ * Hash of the @e payto_uri.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * KYC status of the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * HTTP status to return with @e response, or 0.
+ */
+ unsigned int http_status;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Set to true if this request was suspended.
+ */
+ bool suspended;
+};
+
+
+/**
+ * Kept in a DLL.
+ */
+static struct PurseMergeContext *pmc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct PurseMergeContext *pmc_tail;
+
+
+void
+TEH_purses_merge_cleanup ()
+{
+ struct PurseMergeContext *pmc;
+
+ while (NULL != (pmc = pmc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (pmc_head,
+ pmc_tail,
+ pmc);
+ MHD_resume_connection (pmc->rc->connection);
+ }
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+legi_result_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct PurseMergeContext *pmc = cls;
+
+ pmc->lch = NULL;
+ MHD_resume_connection (pmc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (pmc_head,
+ pmc_tail,
+ pmc);
+ TALER_MHD_daemon_trigger ();
+ if (NULL != lcr->response)
+ {
+ pmc->response = lcr->response;
+ pmc->http_status = lcr->http_status;
+ return;
+ }
+ pmc->kyc = lcr->kyc;
+}
+
+
+/**
+ * Send confirmation of purse creation success to client.
+ *
+ * @param pmc details about the request that succeeded
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_merge_success (const struct PurseMergeContext *pmc)
+{
+ struct MHD_Connection *connection = pmc->rc->connection;
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec;
+ struct TALER_Amount merge_amount;
+
+ if (0 <
+ TALER_amount_cmp (&pmc->balance,
+ &pmc->target_amount))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_JSON_pack_amount ("balance",
+ &pmc->balance),
+ TALER_JSON_pack_amount ("target_amount",
+ &pmc->target_amount));
+ }
+ if ( (NULL == pmc->provider_url) ||
+ (0 == strcmp (pmc->provider_url,
+ TEH_base_url)) )
+ {
+ /* wad fee is always zero if we stay at our own exchange */
+ merge_amount = pmc->target_amount;
+ }
+ else
+ {
+#if WAD_NOT_IMPLEMENTED /* #7271 */
+ /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
+ if (0 >
+ TALER_amount_subtract (&merge_amount,
+ &pmc->target_amount,
+ &wad_fee))
+ {
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &merge_amount));
+ }
+#else
+ merge_amount = pmc->target_amount;
+#endif
+ }
+ if (TALER_EC_NONE !=
+ (ec = TALER_exchange_online_purse_merged_sign (
+ &TEH_keys_exchange_sign_,
+ pmc->exchange_timestamp,
+ pmc->purse_expiration,
+ &merge_amount,
+ &pmc->purse_pub,
+ &pmc->h_contract_terms,
+ &pmc->account_pub.reserve_pub,
+ (NULL != pmc->provider_url)
+ ? pmc->provider_url
+ : TEH_base_url,
+ &pub,
+ &sig)))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("merge_amount",
+ &merge_amount),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ pmc->exchange_timestamp),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub));
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls a `struct PurseMergeContext`
+ * @param limit maximum time-range for which events
+ * should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ * events must be returned in reverse chronological
+ * order
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+amount_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct PurseMergeContext *pmc = cls;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = cb (cb_cls,
+ &pmc->target_amount,
+ GNUNET_TIME_absolute_get ());
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ qs = TEH_plugin->select_merge_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &pmc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this merge and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+ return qs;
+}
+
+
+/**
+ * Execute database transaction for /purses/$PID/merge. Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
+ * a MHD response. IF it returns an hard error, the transaction logic MUST
+ * queue a MHD response and set @a mhd_ret. IF it returns the soft error
+ * code, the function MAY be called again to retry and MUST not queue a MHD
+ * response.
+ *
+ * @param cls a `struct PurseMergeContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+merge_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct PurseMergeContext *pmc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool in_conflict = true;
+ bool no_balance = true;
+ bool no_partner = true;
+
+ qs = TEH_plugin->do_purse_merge (
+ TEH_plugin->cls,
+ &pmc->purse_pub,
+ &pmc->merge_sig,
+ pmc->merge_timestamp,
+ &pmc->reserve_sig,
+ pmc->provider_url,
+ &pmc->account_pub.reserve_pub,
+ &no_partner,
+ &no_balance,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse merge");
+ return qs;
+ }
+ if (no_partner)
+ {
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
+ pmc->provider_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (no_balance)
+ {
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYMENT_REQUIRED,
+ TALER_EC_EXCHANGE_PURSE_NOT_FULL,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ char *partner_url = NULL;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
+
+ qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
+ &pmc->purse_pub,
+ &merge_sig,
+ &merge_timestamp,
+ &partner_url,
+ &reserve_pub,
+ &refunded);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to fetch merge purse information from database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse merge");
+ return qs;
+ }
+ if (refunded)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (0 !=
+ GNUNET_memcmp (&merge_sig,
+ &pmc->merge_sig))
+ {
+ *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_pack_allow_null (
+ GNUNET_JSON_pack_string ("partner_url",
+ partner_url)),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ /* idempotent! */
+ *mhd_ret = reply_merge_success (pmc);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ return qs;
+}
+
+
+/**
+ * Purse-merge-specific cleanup routine. Function called
+ * upon completion of the request that should
+ * clean up @a rh_ctx. Can be NULL.
+ *
+ * @param rc request context to clean up
+ */
+static void
+clean_purse_merge_rc (struct TEH_RequestContext *rc)
+{
+ struct PurseMergeContext *pmc = rc->rh_ctx;
+
+ if (NULL != pmc->lch)
+ {
+ TEH_legitimization_check_cancel (pmc->lch);
+ pmc->lch = NULL;
+ }
+ GNUNET_free (pmc->provider_url);
+ GNUNET_free (pmc);
+}
+
+
+MHD_RESULT
+TEH_handler_purses_merge (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root)
+{
+ struct PurseMergeContext *pmc = rc->rh_ctx;
+
+ if (NULL == pmc)
+ {
+ pmc = GNUNET_new (struct PurseMergeContext);
+ rc->rh_ctx = pmc;
+ rc->rh_cleaner = &clean_purse_merge_rc;
+ pmc->rc = rc;
+ pmc->purse_pub = *purse_pub;
+ pmc->exchange_timestamp
+ = GNUNET_TIME_timestamp_get ();
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_normalized_payto_uri ("payto_uri",
+ &pmc->payto_uri),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &pmc->reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("merge_sig",
+ &pmc->merge_sig),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &pmc->merge_timestamp),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ {
+ struct TALER_PurseContractSignatureP purse_sig;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* Fetch purse details */
+ qs = TEH_plugin->get_purse_request (
+ TEH_plugin->cls,
+ &pmc->purse_pub,
+ &pmc->merge_pub,
+ &pmc->purse_expiration,
+ &pmc->h_contract_terms,
+ &pmc->min_age,
+ &pmc->target_amount,
+ &pmc->balance,
+ &purse_sig);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ }
+ }
+
+ /* check signatures */
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (
+ pmc->payto_uri,
+ pmc->merge_timestamp,
+ &pmc->purse_pub,
+ &pmc->merge_pub,
+ &pmc->merge_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
+ NULL);
+ }
+
+ /* parse 'payto_uri' into pmc->account_pub and provider_url */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Received payto: `%s'\n",
+ pmc->payto_uri.normalized_payto);
+ if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
+ "payto://taler-reserve/",
+ strlen ("payto://taler-reserve/"))) &&
+ (0 != strncmp (pmc->payto_uri.normalized_payto,
+ "payto://taler-reserve-http/",
+ strlen ("payto://taler-reserve-http/"))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+
+ {
+ bool http;
+ const char *host;
+ const char *slash;
+
+ http = (0 == strncmp (pmc->payto_uri.normalized_payto,
+ "payto://taler-reserve-http/",
+ strlen ("payto://taler-reserve-http/")));
+ host = &pmc->payto_uri.normalized_payto[http
+ ? strlen ("payto://taler-reserve-http/")
+ : strlen ("payto://taler-reserve/")];
+ slash = strchr (host,
+ '/');
+ if (NULL == slash)
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ GNUNET_asprintf (&pmc->provider_url,
+ "%s://%.*s/",
+ http ? "http" : "https",
+ (int) (slash - host),
+ host);
+ slash++;
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ slash,
+ strlen (slash),
+ &pmc->account_pub.reserve_pub,
+ sizeof (pmc->account_pub.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "payto_uri");
+ }
+ }
+ TALER_normalized_payto_hash (pmc->payto_uri,
+ &pmc->h_payto);
+ if (0 == strcmp (pmc->provider_url,
+ TEH_base_url))
+ {
+ /* we use NULL to represent 'self' as the provider */
+ GNUNET_free (pmc->provider_url);
+ }
+ else
+ {
+ char *method = GNUNET_strdup ("FIXME-WAD #7271");
+
+ /* FIXME-#7271: lookup wire method by pmc.provider_url! */
+ pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
+ method);
+ if (NULL == pmc->wf)
+ {
+ MHD_RESULT res;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot merge purse: wire fees not configured!\n");
+ res = TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
+ method);
+ GNUNET_free (method);
+ return res;
+ }
+ GNUNET_free (method);
+ }
+
+ {
+ struct TALER_Amount zero_purse_fee;
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (
+ pmc->target_amount.currency,
+ &zero_purse_fee));
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (
+ pmc->merge_timestamp,
+ &pmc->purse_pub,
+ pmc->purse_expiration,
+ &pmc->h_contract_terms,
+ &pmc->target_amount,
+ &zero_purse_fee,
+ pmc->min_age,
+ TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
+ &pmc->account_pub.reserve_pub,
+ &pmc->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
+ NULL);
+ }
+ }
+ {
+ struct TALER_FullPayto fake_full_payto;
+
+ GNUNET_asprintf (&fake_full_payto.full_payto,
+ "%s?receiver-name=wallet",
+ pmc->payto_uri.normalized_payto);
+ pmc->lch = TEH_legitimization_check (
+ &rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
+ fake_full_payto,
+ &pmc->h_payto,
+ &pmc->account_pub,
+ &amount_iterator,
+ pmc,
+ &legi_result_cb,
+ pmc);
+ GNUNET_free (fake_full_payto.full_payto);
+ }
+ GNUNET_assert (NULL != pmc->lch);
+ MHD_suspend_connection (rc->connection);
+ GNUNET_CONTAINER_DLL_insert (pmc_head,
+ pmc_tail,
+ pmc);
+ return MHD_YES;
+ }
+ if (NULL != pmc->response)
+ {
+ return MHD_queue_response (rc->connection,
+ pmc->http_status,
+ pmc->response);
+ }
+ if (! pmc->kyc.ok)
+ return TEH_RESPONSE_reply_kyc_required (
+ rc->connection,
+ &pmc->h_payto,
+ &pmc->kyc,
+ false);
+
+ /* execute merge transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "execute purse merge",
+ TEH_MT_REQUEST_PURSE_MERGE,
+ &mhd_ret,
+ &merge_transaction,
+ pmc))
+ {
+ return mhd_ret;
+ }
+ }
+
+ {
+ struct TALER_PurseEventP rep = {
+ .header.size = htons (sizeof (rep)),
+ .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
+ .purse_pub = pmc->purse_pub
+ };
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Notifying about purse merge\n");
+ TEH_plugin->event_notify (TEH_plugin->cls,
+ &rep.header,
+ NULL,
+ 0);
+ }
+
+ /* generate regular response */
+ return reply_merge_success (pmc);
+}
+
+
+/* end of taler-exchange-httpd_purses_merge.c */
diff --git a/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.h b/src/exchange/taler-exchange-httpd_post-purses-PURSE_PUB-merge.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-purses-PURSE_PUB-merge.h
+ * @brief Handle /purses/$PID/merge requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_MERGE_H
+#define TALER_EXCHANGE_HTTPD_POST_PURSES_PURSE_PUB_MERGE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_purses_merge_cleanup (void);
+
+
+/**
+ * Handle a "/purses/$PURSE_PUB/merge" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #merge_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses merge" being executed, or rejected.
+ *
+ * @param rc request to handle
+ * @param purse_pub public key of the purse
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_purses_merge (
+ struct TEH_RequestContext *rc,
+ const struct TALER_PurseContractPublicKeyP *purse_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-recoup-refresh.c b/src/exchange/taler-exchange-httpd_post-recoup-refresh.c
@@ -0,0 +1,431 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-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_post-recoup-refresh.c
+ * @brief Handle /recoup-refresh requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_db.h"
+#include "taler-exchange-httpd_post-recoup-refresh.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler/taler_exchangedb_lib.h"
+
+
+/**
+ * Closure for #recoup_refresh_transaction().
+ */
+struct RecoupContext
+{
+
+ /**
+ * Set by #recoup_transaction() to the old coin that will
+ * receive the recoup.
+ */
+ struct TALER_CoinSpendPublicKeyP old_coin_pub;
+
+ /**
+ * Details about the coin.
+ */
+ const struct TALER_CoinPublicInfo *coin;
+
+ /**
+ * Key used to blind the coin.
+ */
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
+
+ /**
+ * Signature of the coin requesting recoup.
+ */
+ const struct TALER_CoinSpendSignatureP *coin_sig;
+
+ /**
+ * Unique ID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Unique ID of the refresh reveal context of the melt for the new coin.
+ */
+ uint64_t rrc_serial;
+
+ /**
+ * Set by #recoup_transaction to the timestamp when the recoup
+ * was accepted.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+};
+
+
+/**
+ * Execute a "recoup-refresh". The validity of the coin and signature have
+ * already been checked. The database must now check that the coin is not
+ * (double) spent, and execute the transaction.
+ *
+ * IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls the `struct RecoupContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+recoup_refresh_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct RecoupContext *pc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool recoup_ok;
+ bool internal_failure;
+
+ /* Finally, store new refund data */
+ pc->now = GNUNET_TIME_timestamp_get ();
+ qs = TEH_plugin->do_recoup_refresh (TEH_plugin->cls,
+ &pc->old_coin_pub,
+ pc->rrc_serial,
+ pc->coin_bks,
+ &pc->coin->coin_pub,
+ pc->known_coin_id,
+ pc->coin_sig,
+ &pc->now,
+ &recoup_ok,
+ &internal_failure);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_recoup_refresh");
+ return qs;
+ }
+
+ if (internal_failure)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "coin transaction history");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! recoup_ok)
+ {
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &pc->coin->denom_pub_hash,
+ &pc->coin->coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+/**
+ * We have parsed the JSON information about the recoup request. Do
+ * some basic sanity checks (especially that the signature on the
+ * request and coin is valid) and then execute the recoup operation.
+ * Note that we need the DB to check the fee structure, so this is not
+ * done here but during the recoup_transaction().
+ *
+ * @param connection the MHD connection to handle
+ * @param coin information about the coin
+ * @param exchange_vals values contributed by the exchange
+ * during refresh
+ * @param coin_bks blinding data of the coin (to be checked)
+ * @param nonce withdraw nonce (if CS is used)
+ * @param coin_sig signature of the coin
+ * @return MHD result code
+ */
+static MHD_RESULT
+verify_and_execute_recoup_refresh (
+ struct MHD_Connection *connection,
+ const struct TALER_CoinPublicInfo *coin,
+ const struct TALER_ExchangeBlindingValues *exchange_vals,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct RecoupContext pc;
+ const struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+ struct TALER_BlindedCoinHashP h_blind;
+
+ /* check denomination exists and is in recoup mode */
+ dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ return mret;
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "RECOUP-REFRESH");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "RECOUP-REFRESH");
+ }
+ if (! dk->recoup_possible)
+ {
+ /* This denomination is not eligible for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE,
+ "RECOUP-REFRESH");
+ }
+
+ /* check denomination signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (coin,
+ &dk->denom_pub))
+ {
+ TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ /* check recoup request signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash,
+ coin_bks,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
+
+ if (GNUNET_OK !=
+ TALER_denom_blind (&dk->denom_pub,
+ coin_bks,
+ nonce,
+ &coin->h_age_commitment,
+ &coin->coin_pub,
+ exchange_vals,
+ &c_hash,
+ &blinded_planchet))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
+ NULL);
+ }
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &h_blind);
+ TALER_blinded_planchet_free (&blinded_planchet);
+ }
+
+ pc.coin_sig = coin_sig;
+ pc.coin_bks = coin_bks;
+ pc.coin = coin;
+
+ {
+ MHD_RESULT mhd_ret = MHD_NO;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* make sure coin is 'known' in database */
+ qs = TEH_make_coin_known (coin,
+ connection,
+ &pc.known_coin_id,
+ &mhd_ret);
+ /* no transaction => no serialization failures should be possible */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ if (qs < 0)
+ return mhd_ret;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
+ &h_blind,
+ &pc.old_coin_pub,
+ &pc.rrc_serial);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_old_coin_by_h_blind");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup-refresh requested for unknown envelope %s\n",
+ GNUNET_h2s (&h_blind.hash));
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND,
+ NULL);
+ }
+ }
+
+ /* Perform actual recoup transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "run recoup-refresh",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &recoup_refresh_transaction,
+ &pc))
+ return mhd_ret;
+ }
+ /* Recoup succeeded, return result */
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto (
+ "old_coin_pub",
+ &pc.old_coin_pub));
+}
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup_refresh (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_CoinPublicInfo coin = {0};
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_ExchangeBlindingValues exchange_vals;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ TALER_JSON_spec_exchange_blinding_values ("ewv",
+ &exchange_vals),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
+ &coin_bks),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ &no_nonce),
+ GNUNET_JSON_spec_end ()
+ };
+
+ memset (&coin,
+ 0,
+ sizeof (coin));
+ coin.coin_pub = *coin_pub;
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ {
+ MHD_RESULT res;
+
+ res = verify_and_execute_recoup_refresh (connection,
+ &coin,
+ &exchange_vals,
+ &coin_bks,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &coin_sig);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_recoup-refresh.c */
diff --git a/src/exchange/taler-exchange-httpd_post-recoup-refresh.h b/src/exchange/taler-exchange-httpd_post-recoup-refresh.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017, 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_post-recoup-refresh.h
+ * @brief Handle /recoup-refresh requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RECOUP_REFRESH_H
+#define TALER_EXCHANGE_HTTPD_POST_RECOUP_REFRESH_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup_refresh (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-recoup-withdraw.c b/src/exchange/taler-exchange-httpd_post-recoup-withdraw.c
@@ -0,0 +1,443 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017-2022 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_post-recoup-withdraw.c
+ * @brief Handle /recoup requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_db.h"
+#include "taler-exchange-httpd_post-recoup-withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler/taler_exchangedb_lib.h"
+
+/**
+ * Closure for #recoup_transaction.
+ */
+struct RecoupContext
+{
+ /**
+ * Hash identifying the withdraw request.
+ */
+ struct TALER_BlindedCoinHashP h_coin_ev;
+
+ /**
+ * Set by #recoup_transaction() to the reserve that will
+ * receive the recoup, if #refreshed is #GNUNET_NO.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Details about the coin.
+ */
+ const struct TALER_CoinPublicInfo *coin;
+
+ /**
+ * Key used to blind the coin.
+ */
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
+
+ /**
+ * Signature of the coin requesting recoup.
+ */
+ const struct TALER_CoinSpendSignatureP *coin_sig;
+
+ /**
+ * Unique ID of the withdraw operation in the withdraw table.
+ */
+ uint64_t withdraw_serial_id;
+
+ /**
+ * Unique ID of the coin in the known_coins table.
+ */
+ uint64_t known_coin_id;
+
+ /**
+ * Set by #recoup_transaction to the timestamp when the recoup
+ * was accepted.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+};
+
+
+/**
+ * Execute a "recoup". The validity of the coin and signature have
+ * already been checked. The database must now check that the coin is
+ * not (double) spent, and execute the transaction.
+ *
+ * IF it returns a non-error code, the transaction logic MUST
+ * NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
+ * it returns the soft error code, the function MAY be called again to
+ * retry and MUST not queue a MHD response.
+ *
+ * @param cls the `struct RecoupContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+recoup_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct RecoupContext *pc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool recoup_ok;
+ bool internal_failure;
+
+ /* Finally, store new refund data */
+ pc->now = GNUNET_TIME_timestamp_get ();
+ qs = TEH_plugin->do_recoup (TEH_plugin->cls,
+ &pc->reserve_pub,
+ pc->withdraw_serial_id,
+ pc->coin_bks,
+ &pc->coin->coin_pub,
+ pc->known_coin_id,
+ pc->coin_sig,
+ &pc->now,
+ &recoup_ok,
+ &internal_failure);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_recoup");
+ return qs;
+ }
+
+ if (internal_failure)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "do_recoup");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (! recoup_ok)
+ {
+ *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &pc->coin->denom_pub_hash,
+ &pc->coin->coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+/**
+ * We have parsed the JSON information about the recoup request. Do
+ * some basic sanity checks (especially that the signature on the
+ * request and coin is valid) and then execute the recoup operation.
+ * Note that we need the DB to check the fee structure, so this is not
+ * done here but during the recoup_transaction().
+ *
+ * @param connection the MHD connection to handle
+ * @param coin information about the coin
+ * @param exchange_vals values contributed by the exchange
+ * during withdrawal
+ * @param coin_bks blinding data of the coin (to be checked)
+ * @param h_planchets The hash of the commitment of the original withdraw request
+ * @param nonce coin's nonce if CS is used
+ * @param coin_sig signature of the coin
+ * @return MHD result code
+ */
+static MHD_RESULT
+verify_and_execute_recoup (
+ struct MHD_Connection *connection,
+ const struct TALER_CoinPublicInfo *coin,
+ const struct TALER_ExchangeBlindingValues *exchange_vals,
+ const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
+ const struct TALER_HashBlindedPlanchetsP *h_planchets,
+ const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_CoinSpendSignatureP *coin_sig)
+{
+ struct RecoupContext pc;
+ const struct TEH_DenominationKey *dk;
+ MHD_RESULT mret;
+
+ /* check denomination exists and is in recoup mode */
+ dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
+ connection,
+ &mret);
+ if (NULL == dk)
+ return mret;
+ if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
+ {
+ /* This denomination is past the expiration time for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "RECOUP");
+ }
+ if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
+ {
+ /* This denomination is not yet valid */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "RECOUP");
+ }
+ if (! dk->recoup_possible)
+ {
+ /* This denomination is not eligible for recoup */
+ return TEH_RESPONSE_reply_expired_denom_pub_hash (
+ connection,
+ &coin->denom_pub_hash,
+ TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
+ "RECOUP");
+ }
+
+ /* check denomination signature */
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_RSA:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
+ break;
+ default:
+ break;
+ }
+ if (GNUNET_YES !=
+ TALER_test_coin_valid (coin,
+ &dk->denom_pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ /* check recoup request signature */
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_recoup_verify (&coin->denom_pub_hash,
+ coin_bks,
+ &coin->coin_pub,
+ coin_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
+ NULL);
+ }
+
+ /* re-compute client-side blinding so we can
+ (a bit later) check that this coin was indeed
+ signed by us. */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_BlindedPlanchet blinded_planchet;
+
+ if (GNUNET_OK !=
+ TALER_denom_blind (&dk->denom_pub,
+ coin_bks,
+ nonce,
+ &coin->h_age_commitment,
+ &coin->coin_pub,
+ exchange_vals,
+ &c_hash,
+ &blinded_planchet))
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
+ NULL);
+ }
+ TALER_coin_ev_hash (&blinded_planchet,
+ &coin->denom_pub_hash,
+ &pc.h_coin_ev);
+ TALER_blinded_planchet_free (&blinded_planchet);
+ }
+
+ pc.coin_sig = coin_sig;
+ pc.coin_bks = coin_bks;
+ pc.coin = coin;
+
+ {
+ MHD_RESULT mhd_ret = MHD_NO;
+ enum GNUNET_DB_QueryStatus qs;
+
+ /* make sure coin is 'known' in database */
+ qs = TEH_make_coin_known (coin,
+ connection,
+ &pc.known_coin_id,
+ &mhd_ret);
+ /* no transaction => no serialization failures should be possible */
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ if (qs < 0)
+ return mhd_ret;
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = TEH_plugin->get_reserve_by_h_planchets (
+ TEH_plugin->cls,
+ h_planchets,
+ &pc.reserve_pub,
+ &pc.withdraw_serial_id);
+ if (0 > qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_reserve_by_commitment");
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Recoup requested for unknown envelope %s\n",
+ GNUNET_h2s (&pc.h_coin_ev.hash));
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
+ NULL);
+ }
+ }
+
+ /* Perform actual recoup transaction */
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "run recoup",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &recoup_transaction,
+ &pc))
+ return mhd_ret;
+ }
+ /* Recoup succeeded, return result */
+ return TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto (
+ "reserve_pub",
+ &pc.reserve_pub));
+}
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_CoinPublicInfo coin;
+ union GNUNET_CRYPTO_BlindingSecretP coin_bks;
+ struct TALER_CoinSpendSignatureP coin_sig;
+ struct TALER_ExchangeBlindingValues exchange_vals;
+ struct TALER_HashBlindedPlanchetsP h_planchets;
+ union GNUNET_CRYPTO_BlindSessionNonce nonce;
+ bool no_nonce;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
+ &coin.denom_pub_hash),
+ TALER_JSON_spec_denom_sig ("denom_sig",
+ &coin.denom_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_planchets",
+ &h_planchets),
+ TALER_JSON_spec_exchange_blinding_values ("ewv",
+ &exchange_vals),
+ GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
+ &coin_bks),
+ GNUNET_JSON_spec_fixed_auto ("coin_sig",
+ &coin_sig),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
+ &coin.h_age_commitment),
+ &coin.no_age_commitment),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("nonce",
+ &nonce),
+ &no_nonce),
+ GNUNET_JSON_spec_end ()
+ };
+
+ memset (&coin,
+ 0,
+ sizeof (coin));
+ coin.coin_pub = *coin_pub;
+ ret = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == ret)
+ return MHD_NO; /* hard failure */
+ if (GNUNET_NO == ret)
+ return MHD_YES; /* failure */
+ {
+ MHD_RESULT res;
+
+ res = verify_and_execute_recoup (connection,
+ &coin,
+ &exchange_vals,
+ &coin_bks,
+ &h_planchets,
+ no_nonce
+ ? NULL
+ : &nonce,
+ &coin_sig);
+ GNUNET_JSON_parse_free (spec);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_recoup.c */
diff --git a/src/exchange/taler-exchange-httpd_post-recoup-withdraw.h b/src/exchange/taler-exchange-httpd_post-recoup-withdraw.h
@@ -0,0 +1,46 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2017 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_post-recoup-withdraw.h
+ * @brief Handle /recoup requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RECOUP_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_POST_RECOUP_WITHDRAW_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #verify_and_execute_recoup() to further
+ * check the details of the operation specified. If everything checks out,
+ * this will ultimately lead to the refund being executed, or rejected.
+ *
+ * @param connection the MHD connection to handle
+ * @param coin_pub public key of the coin
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_recoup (struct MHD_Connection *connection,
+ const struct TALER_CoinSpendPublicKeyP *coin_pub,
+ const json_t *root);
+
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-close.c b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-close.c
@@ -0,0 +1,575 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2022 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_post-reserves-RESERVE_PUB-close.c
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_close_transaction.
+ */
+struct ReserveCloseContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReserveCloseContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReserveCloseContext *prev;
+
+ /**
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Where to wire the funds, may be NULL.
+ */
+ struct TALER_FullPayto payto_uri;
+
+ /**
+ * Response to return. Note that the response must
+ * be queued or destroyed by the callee. NULL
+ * if the legitimization check was successful and the handler should return
+ * a handler-specific result.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Amount that will be wired (after closing fees).
+ */
+ struct TALER_Amount wire_amount;
+
+ /**
+ * Current balance of the reserve.
+ */
+ struct TALER_Amount balance;
+
+ /**
+ * Hash of the @e payto_uri, if given (otherwise zero).
+ */
+ struct TALER_FullPaytoHashP h_payto;
+
+ /**
+ * KYC status for the request.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Hash of the payto-URI that was used for the KYC decision.
+ */
+ struct TALER_NormalizedPaytoHashP kyc_payto;
+
+ /**
+ * Query status from the amount_it() helper function.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+ /**
+ * HTTP status code for @a response, or 0
+ */
+ unsigned int http_status;
+
+ /**
+ * Set to true if the request was suspended.
+ */
+ bool suspended;
+
+ /**
+ * Set to true if the request was suspended.
+ */
+ bool resumed;
+};
+
+
+/**
+ * Kept in a DLL.
+ */
+static struct ReserveCloseContext *rcc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct ReserveCloseContext *rcc_tail;
+
+
+void
+TEH_reserves_close_cleanup ()
+{
+ struct ReserveCloseContext *rcc;
+
+ while (NULL != (rcc = rcc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rcc_head,
+ rcc_tail,
+ rcc);
+ MHD_resume_connection (rcc->rc->connection);
+ }
+}
+
+
+/**
+ * Send reserve close to client.
+ *
+ * @param rhc reserve close to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_close_success (
+ const struct ReserveCloseContext *rhc)
+{
+ struct MHD_Connection *connection = rhc->rc->connection;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ TALER_JSON_pack_amount ("wire_amount",
+ &rhc->wire_amount));
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+reserve_close_legi_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct ReserveCloseContext *rcc = cls;
+
+ rcc->lch = NULL;
+ rcc->http_status = lcr->http_status;
+ rcc->response = lcr->response;
+ rcc->kyc = lcr->kyc;
+ GNUNET_CONTAINER_DLL_remove (rcc_head,
+ rcc_tail,
+ rcc);
+ MHD_resume_connection (rcc->rc->connection);
+ rcc->resumed = true;
+ rcc->suspended = false;
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and
+ * account to iterate over events for
+ * @param limit maximum time-range for which events
+ * should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ * events must be returned in reverse chronological
+ * order
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+amount_it (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct ReserveCloseContext *rcc = cls;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = cb (cb_cls,
+ &rcc->balance,
+ GNUNET_TIME_absolute_get ());
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ rcc->qs
+ = TEH_plugin->iterate_reserve_close_info (
+ TEH_plugin->cls,
+ &rcc->kyc_payto,
+ limit,
+ cb,
+ cb_cls);
+ return rcc->qs;
+}
+
+
+/**
+ * Function implementing /reserves/$RID/close transaction. Given the public
+ * key of a reserve, return the associated transaction close. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls a `struct ReserveCloseContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_close_transaction (
+ void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveCloseContext *rcc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_FullPayto payto_uri = {
+ .full_payto = NULL
+ };
+ const struct TALER_WireFeeSet *wf;
+
+ qs = TEH_plugin->select_reserve_close_info (
+ TEH_plugin->cls,
+ &rcc->reserve_pub,
+ &rcc->balance,
+ &payto_uri);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_reserve_close_info");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *mhd_ret
+ = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+
+ if ( (NULL == rcc->payto_uri.full_payto) &&
+ (NULL == payto_uri.full_payto) )
+ {
+ *mhd_ret
+ = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if ( (! rcc->resumed) &&
+ (NULL != rcc->payto_uri.full_payto) &&
+ ( (NULL == payto_uri.full_payto) ||
+ (0 != TALER_full_payto_cmp (payto_uri,
+ rcc->payto_uri)) ) )
+ {
+ /* KYC check may be needed: we're not returning
+ the money to the account that funded the reserve
+ in the first place. */
+
+ TALER_full_payto_normalize_and_hash (rcc->payto_uri,
+ &rcc->kyc_payto);
+ rcc->lch = TEH_legitimization_check (
+ &rcc->rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
+ rcc->payto_uri,
+ &rcc->kyc_payto,
+ NULL, /* no account_pub: this is about the origin/destination account */
+ &amount_it,
+ rcc,
+ &reserve_close_legi_cb,
+ rcc);
+ GNUNET_assert (NULL != rcc->lch);
+ GNUNET_CONTAINER_DLL_insert (rcc_head,
+ rcc_tail,
+ rcc);
+ MHD_suspend_connection (rcc->rc->connection);
+ rcc->suspended = true;
+ GNUNET_free (payto_uri.full_payto);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ rcc->kyc.ok = true;
+ if (NULL == rcc->payto_uri.full_payto)
+ rcc->payto_uri = payto_uri;
+
+ {
+ char *method;
+
+ method = TALER_payto_get_method (rcc->payto_uri.full_payto);
+ wf = TEH_wire_fees_by_time (rcc->timestamp,
+ method);
+ if (NULL == wf)
+ {
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
+ method);
+ GNUNET_free (method);
+ GNUNET_free (payto_uri.full_payto);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_free (method);
+ }
+
+ if (0 >
+ TALER_amount_subtract (&rcc->wire_amount,
+ &rcc->balance,
+ &wf->closing))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client attempted to close reserve with insufficient balance.\n");
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rcc->wire_amount));
+ *mhd_ret = reply_reserve_close_success (rcc);
+ GNUNET_free (payto_uri.full_payto);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ qs = TEH_plugin->insert_close_request (
+ TEH_plugin->cls,
+ &rcc->reserve_pub,
+ payto_uri,
+ &rcc->reserve_sig,
+ rcc->timestamp,
+ &rcc->balance,
+ &wf->closing);
+ GNUNET_free (payto_uri.full_payto);
+ rcc->payto_uri.full_payto = NULL;
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ {
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "insert_close_request");
+ return qs;
+ }
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ return qs;
+ }
+ return qs;
+}
+
+
+/**
+ * Cleanup routine. Function called
+ * upon completion of the request that should
+ * clean up @a rh_ctx. Can be NULL.
+ *
+ * @param rc request to clean up context for
+ */
+static void
+reserve_close_cleanup (struct TEH_RequestContext *rc)
+{
+ struct ReserveCloseContext *rcc = rc->rh_ctx;
+
+ if (NULL != rcc->lch)
+ {
+ TEH_legitimization_check_cancel (rcc->lch);
+ rcc->lch = NULL;
+ }
+ GNUNET_free (rcc);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_close (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct ReserveCloseContext *rcc = rc->rh_ctx;
+ MHD_RESULT mhd_ret;
+
+ if (NULL == rcc)
+ {
+ rcc = GNUNET_new (struct ReserveCloseContext);
+ rc->rh_ctx = rcc;
+ rc->rh_cleaner = &reserve_close_cleanup;
+ rcc->reserve_pub = *reserve_pub;
+ rcc->rc = rc;
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rcc->timestamp),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_full_payto_uri ("payto_uri",
+ &rcc->payto_uri),
+ NULL),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rcc->reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (
+ now.abs_time,
+ rcc->timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+ }
+
+ if (NULL != rcc->payto_uri.full_payto)
+ TALER_full_payto_hash (rcc->payto_uri,
+ &rcc->h_payto);
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_close_verify (
+ rcc->timestamp,
+ &rcc->h_payto,
+ &rcc->reserve_pub,
+ &rcc->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
+ NULL);
+ }
+ }
+ if (NULL != rcc->response)
+ return MHD_queue_response (rc->connection,
+ rcc->http_status,
+ rcc->response);
+ if (rcc->resumed &&
+ (! rcc->kyc.ok) )
+ {
+ if (0 == rcc->kyc.requirement_row)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "requirement row not set");
+ }
+ return TEH_RESPONSE_reply_kyc_required (
+ rc->connection,
+ &rcc->kyc_payto,
+ &rcc->kyc,
+ false);
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "reserve close",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_close_transaction,
+ rcc))
+ {
+ return mhd_ret;
+ }
+ if (rcc->suspended)
+ return MHD_YES;
+ return reply_reserve_close_success (rcc);
+}
+
+
+/* end of taler-exchange-httpd_reserves_close.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-close.h
@@ -0,0 +1,48 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-reserves-RESERVE_PUB-close.h
+ * @brief Handle /reserves/$RESERVE_PUB/close requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_CLOSE_H
+#define TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_CLOSE_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_reserves_close_cleanup (void);
+
+/**
+ * Handle a POST "/reserves/$RID/close" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_close (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.c
@@ -0,0 +1,471 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-reserves-RESERVE_PUB-open.c
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_mhd_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_dbevents.h"
+#include "taler-exchange-httpd_common_deposit.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_open_transaction.
+ */
+struct ReserveOpenContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ const struct TALER_ReservePublicKeyP *reserve_pub;
+
+ /**
+ * Desired (minimum) expiration time for the reserve.
+ */
+ struct GNUNET_TIME_Timestamp desired_expiration;
+
+ /**
+ * Actual expiration time for the reserve.
+ */
+ struct GNUNET_TIME_Timestamp reserve_expiration;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Global fees applying to the request.
+ */
+ const struct TEH_GlobalFee *gf;
+
+ /**
+ * Amount to be paid from the reserve.
+ */
+ struct TALER_Amount reserve_payment;
+
+ /**
+ * Actual cost to open the reserve.
+ */
+ struct TALER_Amount open_cost;
+
+ /**
+ * Total amount that was deposited.
+ */
+ struct TALER_Amount total;
+
+ /**
+ * Information about payments by coin.
+ */
+ struct TEH_PurseDepositedCoin *payments;
+
+ /**
+ * Length of the @e payments array.
+ */
+ unsigned int payments_len;
+
+ /**
+ * Desired minimum purse limit.
+ */
+ uint32_t purse_limit;
+
+ /**
+ * Set to true if the reserve balance is too low
+ * for the operation.
+ */
+ bool no_funds;
+
+};
+
+
+/**
+ * Send reserve open to client.
+ *
+ * @param connection connection to the client
+ * @param rsc reserve open data to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_open_success (struct MHD_Connection *connection,
+ const struct ReserveOpenContext *rsc)
+{
+ struct GNUNET_TIME_Timestamp now;
+ struct GNUNET_TIME_Timestamp re;
+ unsigned int status;
+
+ status = MHD_HTTP_OK;
+ if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+ <,
+ rsc->desired_expiration))
+ status = MHD_HTTP_PAYMENT_REQUIRED;
+ now = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
+ <,
+ now))
+ re = now;
+ else
+ re = rsc->reserve_expiration;
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ status,
+ GNUNET_JSON_pack_timestamp ("reserve_expiration",
+ re),
+ TALER_JSON_pack_amount ("open_cost",
+ &rsc->open_cost));
+}
+
+
+/**
+ * Cleans up information in @a rsc, but does not
+ * free @a rsc itself (allocated on the stack!).
+ *
+ * @param[in] rsc struct with information to clean up
+ */
+static void
+cleanup_rsc (struct ReserveOpenContext *rsc)
+{
+ for (unsigned int i = 0; i<rsc->payments_len; i++)
+ {
+ TEH_common_purse_deposit_free_coin (&rsc->payments[i]);
+ }
+ GNUNET_free (rsc->payments);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/open transaction. Given the public
+ * key of a reserve, return the associated transaction open. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls a `struct ReserveOpenContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_open_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveOpenContext *rsc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_Amount reserve_balance;
+
+ for (unsigned int i = 0; i<rsc->payments_len; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &rsc->payments[i];
+ bool insufficient_funds = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Make coin %u known\n",
+ i);
+ qs = TEH_make_coin_known (&coin->cpi,
+ connection,
+ &coin->known_coin_id,
+ mhd_ret);
+ if (qs < 0)
+ return qs;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Insert open deposit %u known\n",
+ i);
+ qs = TEH_plugin->insert_reserve_open_deposit (
+ TEH_plugin->cls,
+ &coin->cpi,
+ &coin->coin_sig,
+ coin->known_coin_id,
+ &coin->amount,
+ &rsc->reserve_sig,
+ rsc->reserve_pub,
+ &insufficient_funds);
+ /* 0 == qs is fine, then the coin was already
+ spent for this very operation as identified
+ by reserve_sig! */
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert_reserve_open_deposit");
+ return qs;
+ }
+ if (insufficient_funds)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handle insufficient funds\n");
+ *mhd_ret
+ = TEH_RESPONSE_reply_coin_insufficient_funds (
+ connection,
+ TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
+ &coin->cpi.denom_pub_hash,
+ &coin->cpi.coin_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Do reserve open with reserve payment of %s\n",
+ TALER_amount2s (&rsc->total));
+ qs = TEH_plugin->do_reserve_open (TEH_plugin->cls,
+ /* inputs */
+ rsc->reserve_pub,
+ &rsc->total,
+ &rsc->reserve_payment,
+ rsc->purse_limit,
+ &rsc->reserve_sig,
+ rsc->desired_expiration,
+ rsc->timestamp,
+ &rsc->gf->fees.account,
+ /* outputs */
+ &rsc->no_funds,
+ &reserve_balance,
+ &rsc->open_cost,
+ &rsc->reserve_expiration);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "do_reserve_open");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ if (rsc->no_funds)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ *mhd_ret
+ = TEH_RESPONSE_reply_reserve_insufficient_balance (
+ connection,
+ TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
+ &reserve_balance,
+ &rsc->reserve_payment,
+ rsc->reserve_pub);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct ReserveOpenContext rsc;
+ const json_t *payments;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rsc.timestamp),
+ GNUNET_JSON_spec_timestamp ("reserve_expiration",
+ &rsc.desired_expiration),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rsc.reserve_sig),
+ GNUNET_JSON_spec_uint32 ("purse_limit",
+ &rsc.purse_limit),
+ GNUNET_JSON_spec_array_const ("payments",
+ &payments),
+ TALER_JSON_spec_amount ("reserve_payment",
+ TEH_currency,
+ &rsc.reserve_payment),
+ GNUNET_JSON_spec_end ()
+ };
+
+ rsc.reserve_pub = reserve_pub;
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+
+ {
+ struct GNUNET_TIME_Timestamp now;
+
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+ rsc.timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+ }
+
+ rsc.payments_len = json_array_size (payments);
+ rsc.payments = GNUNET_new_array (rsc.payments_len,
+ struct TEH_PurseDepositedCoin);
+ rsc.total = rsc.reserve_payment;
+ for (unsigned int i = 0; i<rsc.payments_len; i++)
+ {
+ struct TEH_PurseDepositedCoin *coin = &rsc.payments[i];
+ enum GNUNET_GenericReturnValue res;
+
+ res = TEH_common_purse_deposit_parse_coin (
+ rc->connection,
+ coin,
+ json_array_get (payments,
+ i));
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ cleanup_rsc (&rsc);
+ return MHD_YES; /* failure */
+ }
+ if (0 >
+ TALER_amount_add (&rsc.total,
+ &rsc.total,
+ &coin->amount_minus_fee))
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
+ NULL);
+ }
+ }
+
+ {
+ struct TEH_KeyStateHandle *keys;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ rsc.gf = TEH_keys_global_fee_by_time (keys,
+ rsc.timestamp);
+ }
+ if (NULL == rsc.gf)
+ {
+ GNUNET_break (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
+ NULL);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_open_verify (&rsc.reserve_payment,
+ rsc.timestamp,
+ rsc.desired_expiration,
+ rsc.purse_limit,
+ reserve_pub,
+ &rsc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ cleanup_rsc (&rsc);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE,
+ NULL);
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "reserve open",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_open_transaction,
+ &rsc))
+ {
+ cleanup_rsc (&rsc);
+ return mhd_ret;
+ }
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ mhd_ret = reply_reserve_open_success (rc->connection,
+ &rsc);
+ cleanup_rsc (&rsc);
+ return mhd_ret;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_open.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-open.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-reserves-RESERVE_PUB-open.h
+ * @brief Handle /reserves/$RESERVE_PUB/open requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_OPEN_H
+#define TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_OPEN_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves/$RID/open" request.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded body from the client
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_open (struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.c b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.c
@@ -0,0 +1,894 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022-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 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_post-reserves-RESERVE_PUB-purse.c
+ * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
+ * verifies the coin signature before handing things off
+ * to the database.
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_json_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include <pthread.h>
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler/taler_exchangedb_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+
+/**
+ * Closure for #purse_transaction.
+ */
+struct ReservePurseContext
+{
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePurseContext *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct ReservePurseContext *prev;
+
+ /**
+ * Our request context.
+ */
+ struct TEH_RequestContext *rc;
+
+ /**
+ * Handle for legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Response to return. Note that the response must
+ * be queued or destroyed by the callee. NULL
+ * if the legitimization check was successful and the handler should return
+ * a handler-specific result.
+ */
+ struct MHD_Response *response;
+
+ /**
+ * Payto URI for the reserve.
+ */
+ struct TALER_NormalizedPayto payto_uri;
+
+ /**
+ * Public key of the account (reserve) we are creating a purse for.
+ */
+ union TALER_AccountPublicKeyP account_pub;
+
+ /**
+ * Signature of the reserve affirming the merge.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Purse fee the client is willing to pay.
+ */
+ struct TALER_Amount purse_fee;
+
+ /**
+ * Total amount already put into the purse.
+ */
+ struct TALER_Amount deposit_total;
+
+ /**
+ * Merge time.
+ */
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+
+ /**
+ * Our current time.
+ */
+ struct GNUNET_TIME_Timestamp exchange_timestamp;
+
+ /**
+ * Details about an encrypted contract, if any.
+ */
+ struct TALER_EncryptedContract econtract;
+
+ /**
+ * Merge key for the purse.
+ */
+ struct TALER_PurseMergePublicKeyP merge_pub;
+
+ /**
+ * Merge affirmation by the @e merge_pub.
+ */
+ struct TALER_PurseMergeSignatureP merge_sig;
+
+ /**
+ * Signature of the client affiming this request.
+ */
+ struct TALER_PurseContractSignatureP purse_sig;
+
+ /**
+ * Fundamental details about the purse.
+ */
+ struct TEH_PurseDetails pd;
+
+ /**
+ * Hash of the @e payto_uri.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * KYC status of the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Minimum age for deposits into this purse.
+ */
+ uint32_t min_age;
+
+ /**
+ * Flags for the operation.
+ */
+ enum TALER_WalletAccountMergeFlags flags;
+
+ /**
+ * HTTP status code for @a response, or 0
+ */
+ unsigned int http_status;
+
+ /**
+ * Do we lack an @e econtract?
+ */
+ bool no_econtract;
+
+ /**
+ * Set to true if the purse_fee was not given in the REST request.
+ */
+ bool no_purse_fee;
+
+};
+
+/**
+ * Kept in a DLL.
+ */
+static struct ReservePurseContext *rpc_head;
+
+/**
+ * Kept in a DLL.
+ */
+static struct ReservePurseContext *rpc_tail;
+
+
+void
+TEH_reserves_purse_cleanup ()
+{
+ struct ReservePurseContext *rpc;
+
+ while (NULL != (rpc = rpc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (rpc_head,
+ rpc_tail,
+ rpc);
+ MHD_resume_connection (rpc->rc->connection);
+ }
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant
+ * transaction amounts for a particular time range.
+ * Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls a `struct ReservePurseContext`
+ * @param limit maximum time-range for which events
+ * should be fetched (timestamp in the past)
+ * @param cb function to call on each event found,
+ * events must be returned in reverse chronological
+ * order
+ * @param cb_cls closure for @a cb
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+amount_iterator (void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct ReservePurseContext *rpc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = cb (cb_cls,
+ &rpc->pd.target_amount,
+ GNUNET_TIME_absolute_get ());
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ qs = TEH_plugin->select_merge_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &rpc->h_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this merge and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+ return qs;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+reserve_purse_legi_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct ReservePurseContext *rpc = cls;
+
+ rpc->lch = NULL;
+ rpc->http_status = lcr->http_status;
+ rpc->response = lcr->response;
+ rpc->kyc = lcr->kyc;
+ GNUNET_CONTAINER_DLL_remove (rpc_head,
+ rpc_tail,
+ rpc);
+ MHD_resume_connection (rpc->rc->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Execute database transaction for /reserves/$PID/purse. Runs the transaction
+ * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
+ * a MHD response. IF it returns an hard error, the transaction logic MUST
+ * queue a MHD response and set @a mhd_ret. IF it returns the soft error
+ * code, the function MAY be called again to retry and MUST not queue a MHD
+ * response.
+ *
+ * @param cls a `struct ReservePurseContext`
+ * @param connection MHD request context
+ * @param[out] mhd_ret set to MHD status on error
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+purse_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReservePurseContext *rpc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ {
+ bool in_conflict = true;
+
+ /* 1) store purse */
+ qs = TEH_plugin->insert_purse_request (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->merge_pub,
+ rpc->pd.purse_expiration,
+ &rpc->pd.h_contract_terms,
+ rpc->min_age,
+ rpc->flags,
+ &rpc->purse_fee,
+ &rpc->pd.target_amount,
+ &rpc->purse_sig,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0);
+ *mhd_ret =
+ TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "insert purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return qs;
+ if (in_conflict)
+ {
+ struct TALER_PurseMergePublicKeyP merge_pub;
+ struct GNUNET_TIME_Timestamp purse_expiration;
+ struct TALER_PrivateContractHashP h_contract_terms;
+ struct TALER_Amount target_amount;
+ struct TALER_Amount balance;
+ struct TALER_PurseContractSignatureP purse_sig;
+ uint32_t min_age;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->get_purse_request (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &merge_pub,
+ &purse_expiration,
+ &h_contract_terms,
+ &min_age,
+ &target_amount,
+ &balance,
+ &purse_sig);
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse request");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
+ TALER_JSON_pack_amount ("amount",
+ &target_amount),
+ GNUNET_JSON_pack_uint64 ("min_age",
+ min_age),
+ GNUNET_JSON_pack_timestamp ("purse_expiration",
+ purse_expiration),
+ GNUNET_JSON_pack_data_auto ("purse_sig",
+ &purse_sig),
+ GNUNET_JSON_pack_data_auto ("h_contract_terms",
+ &h_contract_terms),
+ GNUNET_JSON_pack_data_auto ("merge_pub",
+ &merge_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ }
+
+ /* 2) create purse with reserve (and debit reserve for purse creation!) */
+ {
+ bool in_conflict = true;
+ bool insufficient_funds = true;
+ bool no_reserve = true;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating purse with flags %d\n",
+ rpc->flags);
+ qs = TEH_plugin->do_reserve_purse (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->merge_sig,
+ rpc->merge_timestamp,
+ &rpc->reserve_sig,
+ (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == rpc->flags)
+ ? NULL
+ : &rpc->purse_fee,
+ &rpc->account_pub.reserve_pub,
+ &in_conflict,
+ &no_reserve,
+ &insufficient_funds);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING (
+ "Failed to store purse merge information in database\n");
+ *mhd_ret =
+ TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do reserve purse");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ /* same purse already merged into a different reserve!? */
+ struct TALER_PurseContractPublicKeyP purse_pub;
+ struct TALER_PurseMergeSignatureP merge_sig;
+ struct GNUNET_TIME_Timestamp merge_timestamp;
+ char *partner_url;
+ struct TALER_ReservePublicKeyP reserve_pub;
+ bool refunded;
+
+ TEH_plugin->rollback (TEH_plugin->cls);
+ qs = TEH_plugin->select_purse_merge (
+ TEH_plugin->cls,
+ &purse_pub,
+ &merge_sig,
+ &merge_timestamp,
+ &partner_url,
+ &reserve_pub,
+ &refunded);
+ if (qs <= 0)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to fetch purse merge information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select purse merge");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (refunded)
+ {
+ /* This is a bit of a strange case ... */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Purse was already refunded\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_GONE,
+ TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
+ NULL);
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_string ("partner_url",
+ NULL == partner_url
+ ? TEH_base_url
+ : partner_url),
+ GNUNET_JSON_pack_timestamp ("merge_timestamp",
+ merge_timestamp),
+ GNUNET_JSON_pack_data_auto ("merge_sig",
+ &merge_sig),
+ GNUNET_JSON_pack_data_auto ("reserve_pub",
+ &reserve_pub));
+ GNUNET_free (partner_url);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if ( (no_reserve) &&
+ ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
+ == rpc->flags) ||
+ (! TALER_amount_is_zero (&rpc->purse_fee)) ) )
+ {
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (insufficient_funds)
+ {
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ /* 3) if present, persist contract */
+ if (! rpc->no_econtract)
+ {
+ bool in_conflict = true;
+
+ qs = TEH_plugin->insert_contract (TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &rpc->econtract,
+ &in_conflict);
+ if (qs < 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ TALER_LOG_WARNING ("Failed to store purse information in database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "purse purse contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ if (in_conflict)
+ {
+ struct TALER_EncryptedContract econtract;
+ struct GNUNET_HashCode h_econtract;
+
+ qs = TEH_plugin->select_contract_by_purse (
+ TEH_plugin->cls,
+ &rpc->pd.purse_pub,
+ &econtract);
+ if (qs <= 0)
+ {
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ return qs;
+ GNUNET_break (0 != qs);
+ TALER_LOG_WARNING (
+ "Failed to store fetch contract information from database\n");
+ *mhd_ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select contract");
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ GNUNET_CRYPTO_hash (econtract.econtract,
+ econtract.econtract_size,
+ &h_econtract);
+ *mhd_ret
+ = TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_CONFLICT,
+ TALER_JSON_pack_ec (
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
+ GNUNET_JSON_pack_data_auto ("h_econtract",
+ &h_econtract),
+ GNUNET_JSON_pack_data_auto ("econtract_sig",
+ &econtract.econtract_sig),
+ GNUNET_JSON_pack_data_auto ("contract_pub",
+ &econtract.contract_pub));
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ }
+ return qs;
+}
+
+
+/**
+ * Function to clean up our rh_ctx in @a rc
+ *
+ * @param[in,out] rc context to clean up
+ */
+static void
+rpc_cleaner (struct TEH_RequestContext *rc)
+{
+ struct ReservePurseContext *rpc = rc->rh_ctx;
+
+ if (NULL != rpc->lch)
+ {
+ TEH_legitimization_check_cancel (rpc->lch);
+ rpc->lch = NULL;
+ }
+ GNUNET_free (rpc->econtract.econtract);
+ GNUNET_free (rpc->payto_uri.normalized_payto);
+ GNUNET_free (rpc);
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_purse (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root)
+{
+ struct ReservePurseContext *rpc = rc->rh_ctx;
+ struct MHD_Connection *connection = rc->connection;
+
+ if (NULL == rpc)
+ {
+ rpc = GNUNET_new (struct ReservePurseContext);
+ rc->rh_ctx = rpc;
+ rc->rh_cleaner = &rpc_cleaner;
+ rpc->rc = rc;
+ rpc->account_pub.reserve_pub = *reserve_pub;
+ rpc->exchange_timestamp
+ = GNUNET_TIME_timestamp_get ();
+ rpc->no_purse_fee = true;
+
+
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_amount ("purse_value",
+ TEH_currency,
+ &rpc->pd.target_amount),
+ GNUNET_JSON_spec_uint32 ("min_age",
+ &rpc->min_age),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_amount ("purse_fee",
+ TEH_currency,
+ &rpc->purse_fee),
+ &rpc->no_purse_fee),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_econtract ("econtract",
+ &rpc->econtract),
+ &rpc->no_econtract),
+ GNUNET_JSON_spec_fixed_auto ("merge_pub",
+ &rpc->merge_pub),
+ GNUNET_JSON_spec_fixed_auto ("merge_sig",
+ &rpc->merge_sig),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rpc->reserve_sig),
+ GNUNET_JSON_spec_fixed_auto ("purse_pub",
+ &rpc->pd.purse_pub),
+ GNUNET_JSON_spec_fixed_auto ("purse_sig",
+ &rpc->purse_sig),
+ GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
+ &rpc->pd.h_contract_terms),
+ GNUNET_JSON_spec_timestamp ("merge_timestamp",
+ &rpc->merge_timestamp),
+ GNUNET_JSON_spec_timestamp ("purse_expiration",
+ &rpc->pd.purse_expiration),
+ GNUNET_JSON_spec_end ()
+ };
+
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ }
+
+ rpc->payto_uri
+ = TALER_reserve_make_payto (TEH_base_url,
+ &rpc->account_pub.reserve_pub);
+ TALER_normalized_payto_hash (rpc->payto_uri,
+ &rpc->h_payto);
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+
+
+ if (GNUNET_OK !=
+ TALER_wallet_purse_merge_verify (rpc->payto_uri,
+ rpc->merge_timestamp,
+ &rpc->pd.purse_pub,
+ &rpc->merge_pub,
+ &rpc->merge_sig))
+ {
+ MHD_RESULT ret;
+
+ GNUNET_break_op (0);
+ ret = TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
+ rpc->payto_uri.normalized_payto);
+ return ret;
+ }
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rpc->deposit_total));
+ if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration,
+ <,
+ rpc->exchange_timestamp))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
+ NULL);
+ }
+ if (GNUNET_TIME_absolute_is_never (
+ rpc->pd.purse_expiration.abs_time))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
+ NULL);
+ }
+
+ {
+ struct TEH_KeyStateHandle *keys;
+ const struct TEH_GlobalFee *gf;
+
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ }
+ gf = TEH_keys_global_fee_by_time (keys,
+ rpc->exchange_timestamp);
+ if (NULL == gf)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Cannot create purse: global fees not configured!\n");
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
+ NULL);
+ }
+ if (rpc->no_purse_fee)
+ {
+ rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &rpc->purse_fee));
+ }
+ else
+ {
+ rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
+ if (-1 ==
+ TALER_amount_cmp (&rpc->purse_fee,
+ &gf->fees.purse))
+ {
+ /* rpc->purse_fee is below gf.fees.purse! */
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
+ TALER_amount2s (&gf->fees.purse));
+ }
+ }
+ }
+
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_purse_create_verify (
+ rpc->pd.purse_expiration,
+ &rpc->pd.h_contract_terms,
+ &rpc->merge_pub,
+ rpc->min_age,
+ &rpc->pd.target_amount,
+ &rpc->pd.purse_pub,
+ &rpc->purse_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if (GNUNET_OK !=
+ TALER_wallet_account_merge_verify (
+ rpc->merge_timestamp,
+ &rpc->pd.purse_pub,
+ rpc->pd.purse_expiration,
+ &rpc->pd.h_contract_terms,
+ &rpc->pd.target_amount,
+ &rpc->purse_fee,
+ rpc->min_age,
+ rpc->flags,
+ &rpc->account_pub.reserve_pub,
+ &rpc->reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
+ NULL);
+ }
+ if ( (! rpc->no_econtract) &&
+ (GNUNET_OK !=
+ TALER_wallet_econtract_upload_verify (
+ rpc->econtract.econtract,
+ rpc->econtract.econtract_size,
+ &rpc->econtract.contract_pub,
+ &rpc->pd.purse_pub,
+ &rpc->econtract.econtract_sig))
+ )
+ {
+ TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
+ NULL);
+ }
+ {
+ struct TALER_FullPayto fake_full_payto;
+
+ GNUNET_asprintf (&fake_full_payto.full_payto,
+ "%s?receiver-name=wallet",
+ rpc->payto_uri.normalized_payto);
+ rpc->lch = TEH_legitimization_check (
+ &rpc->rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
+ fake_full_payto,
+ &rpc->h_payto,
+ &rpc->account_pub,
+ &amount_iterator,
+ rpc,
+ &reserve_purse_legi_cb,
+ rpc);
+ GNUNET_free (fake_full_payto.full_payto);
+ }
+ GNUNET_assert (NULL != rpc->lch);
+ MHD_suspend_connection (rc->connection);
+ GNUNET_CONTAINER_DLL_insert (rpc_head,
+ rpc_tail,
+ rpc);
+ return MHD_YES;
+ }
+ if (NULL != rpc->response)
+ return MHD_queue_response (connection,
+ rpc->http_status,
+ rpc->response);
+ if (! rpc->kyc.ok)
+ {
+ if (0 == rpc->kyc.requirement_row)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "requirement row not set");
+ }
+ return TEH_RESPONSE_reply_kyc_required (
+ connection,
+ &rpc->h_payto,
+ &rpc->kyc,
+ false);
+ }
+
+ {
+ MHD_RESULT mhd_ret;
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (connection,
+ "execute reserve purse",
+ TEH_MT_REQUEST_RESERVE_PURSE,
+ &mhd_ret,
+ &purse_transaction,
+ rpc))
+ {
+ return mhd_ret;
+ }
+ }
+
+ /* generate regular response */
+ {
+ MHD_RESULT res;
+
+ res = TEH_RESPONSE_reply_purse_created (
+ connection,
+ rpc->exchange_timestamp,
+ &rpc->deposit_total,
+ &rpc->pd);
+ return res;
+ }
+}
+
+
+/* end of taler-exchange-httpd_reserves_purse.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h b/src/exchange/taler-exchange-httpd_post-reserves-RESERVE_PUB-purse.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-reserves-RESERVE_PUB-purse.h
+ * @brief Handle /reserves/$RID/purse requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_PURSE_H
+#define TALER_EXCHANGE_HTTPD_POST_RESERVES_RESERVE_PUB_PURSE_H
+
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_reserves_purse_cleanup (void);
+
+
+/**
+ * Handle a "/reserves/$RESERVE_PUB/purse" request. Parses the JSON, and, if
+ * successful, passes the JSON data to #create_transaction() to further check
+ * the details of the operation specified. If everything checks out, this
+ * will ultimately lead to the "purses create" being executed, or rejected.
+ *
+ * @param rc request context
+ * @param reserve_pub public key of the reserve
+ * @param root uploaded JSON data
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_purse (
+ struct TEH_RequestContext *rc,
+ const struct TALER_ReservePublicKeyP *reserve_pub,
+ const json_t *root);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c b/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.c
@@ -0,0 +1,391 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014-2022, 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 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_post-reserves-attest-RESERVE_PUB.c
+ * @brief Handle /reserves/$RESERVE_PUB/attest requests
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler/taler_dbevents.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h"
+#include "taler-exchange-httpd_responses.h"
+
+
+/**
+ * How far do we allow a client's time to be off when
+ * checking the request timestamp?
+ */
+#define TIMESTAMP_TOLERANCE \
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
+
+
+/**
+ * Closure for #reserve_attest_transaction.
+ */
+struct ReserveAttestContext
+{
+ /**
+ * Public key of the reserve the inquiry is about.
+ */
+ struct TALER_ReservePublicKeyP reserve_pub;
+
+ /**
+ * Hash of the payto URI of this reserve.
+ */
+ struct TALER_NormalizedPaytoHashP h_payto;
+
+ /**
+ * Timestamp of the request.
+ */
+ struct GNUNET_TIME_Timestamp timestamp;
+
+ /**
+ * Expiration time for the attestation.
+ */
+ struct GNUNET_TIME_Timestamp etime;
+
+ /**
+ * List of requested details.
+ */
+ const json_t *details;
+
+ /**
+ * Client signature approving the request.
+ */
+ struct TALER_ReserveSignatureP reserve_sig;
+
+ /**
+ * Attributes we are affirming. JSON object.
+ */
+ json_t *json_attest;
+
+ /**
+ * Database error codes encountered.
+ */
+ enum GNUNET_DB_QueryStatus qs;
+
+ /**
+ * Set to true if we did not find the reserve.
+ */
+ bool not_found;
+
+};
+
+
+/**
+ * Send reserve attest to client.
+ *
+ * @param connection connection to the client
+ * @param rhc reserve attest to return
+ * @return MHD result code
+ */
+static MHD_RESULT
+reply_reserve_attest_success (struct MHD_Connection *connection,
+ const struct ReserveAttestContext *rhc)
+{
+ struct TALER_ExchangeSignatureP exchange_sig;
+ struct TALER_ExchangePublicKeyP exchange_pub;
+ enum TALER_ErrorCode ec;
+ struct GNUNET_TIME_Timestamp now;
+
+ if (NULL == rhc->json_attest)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
+ NULL);
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ ec = TALER_exchange_online_reserve_attest_details_sign (
+ &TEH_keys_exchange_sign_,
+ now,
+ rhc->etime,
+ &rhc->reserve_pub,
+ rhc->json_attest,
+ &exchange_pub,
+ &exchange_sig);
+ if (TALER_EC_NONE != ec)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_ec (connection,
+ ec,
+ NULL);
+ }
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &exchange_sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &exchange_pub),
+ GNUNET_JSON_pack_timestamp ("exchange_timestamp",
+ now),
+ GNUNET_JSON_pack_timestamp ("expiration_time",
+ rhc->etime),
+ GNUNET_JSON_pack_object_steal ("attributes",
+ rhc->json_attest));
+}
+
+
+/**
+ * Function called with information about all applicable
+ * legitimization processes for the given user. Finds the
+ * available attributes and merges them into our result
+ * set based on the details requested by the client.
+ *
+ * @param cls our `struct ReserveAttestContext *`
+ * @param h_payto account for which the attribute data is stored
+ * @param provider_name provider that must be checked
+ * @param collection_time when was the data collected
+ * @param expiration_time when does the data expire
+ * @param enc_attributes_size number of bytes in @a enc_attributes
+ * @param enc_attributes encrypted attribute data
+ */
+static void
+kyc_process_cb (void *cls,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const char *provider_name,
+ struct GNUNET_TIME_Timestamp collection_time,
+ struct GNUNET_TIME_Timestamp expiration_time,
+ size_t enc_attributes_size,
+ const void *enc_attributes)
+{
+ struct ReserveAttestContext *rsc = cls;
+ json_t *attrs;
+ json_t *val;
+ const char *name;
+ bool match = false;
+
+ if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
+ return;
+ attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
+ enc_attributes,
+ enc_attributes_size);
+ if (NULL == attrs)
+ {
+ GNUNET_break (0);
+ return;
+ }
+ json_object_foreach (attrs, name, val)
+ {
+ bool requested = strcmp (name,
+ "FORM_ID"); /* we always return the FORM_ID */
+ size_t idx;
+ json_t *str;
+
+ if (NULL != json_object_get (rsc->json_attest,
+ name))
+ continue; /* duplicate */
+ json_array_foreach (rsc->details, idx, str)
+ {
+ if (0 == strcmp (json_string_value (str),
+ name))
+ {
+ requested = true;
+ break;
+ }
+ }
+ if (! requested)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Skipping attribute `%s': not requested\n",
+ name);
+ continue;
+ }
+ match = true;
+ GNUNET_assert (0 ==
+ json_object_set (rsc->json_attest, /* NOT set_new! */
+ name,
+ val));
+ }
+ json_decref (attrs);
+ if (! match)
+ return;
+ rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
+ rsc->etime);
+}
+
+
+/**
+ * Function implementing /reserves/$RID/attest transaction. Given the public
+ * key of a reserve, return the associated transaction attest. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction logic
+ * MUST NOT queue a MHD response. IF it returns an hard error, the
+ * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
+ * returns the soft error code, the function MAY be called again to retry and
+ * MUST not queue a MHD response.
+ *
+ * @param cls a `struct ReserveAttestContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!); unused
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+reserve_attest_transaction (void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct ReserveAttestContext *rsc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ rsc->json_attest = json_object ();
+ GNUNET_assert (NULL != rsc->json_attest);
+ qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
+ &rsc->h_payto,
+ &kyc_process_cb,
+ rsc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ *mhd_ret
+ = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "select_kyc_attributes");
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rsc->not_found = true;
+ return qs;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ rsc->not_found = false;
+ break;
+ }
+ return qs;
+}
+
+
+MHD_RESULT
+TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1])
+{
+ struct ReserveAttestContext rsc = {
+ .etime = GNUNET_TIME_UNIT_FOREVER_TS
+ };
+ MHD_RESULT mhd_ret;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_timestamp ("request_timestamp",
+ &rsc.timestamp),
+ GNUNET_JSON_spec_array_const ("details",
+ &rsc.details),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &rsc.reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_TIME_Timestamp now;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (args[0],
+ strlen (args[0]),
+ &rsc.reserve_pub,
+ sizeof (rsc.reserve_pub)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
+ args[0]);
+ }
+ {
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO; /* hard failure */
+ }
+ if (GNUNET_NO == res)
+ {
+ GNUNET_break_op (0);
+ return MHD_YES; /* failure */
+ }
+ }
+ now = GNUNET_TIME_timestamp_get ();
+ if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
+ rsc.timestamp.abs_time,
+ TIMESTAMP_TOLERANCE))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
+ NULL);
+ }
+
+ if (GNUNET_OK !=
+ TALER_wallet_reserve_attest_request_verify (rsc.timestamp,
+ rsc.details,
+ &rsc.reserve_pub,
+ &rsc.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE,
+ NULL);
+ }
+
+ {
+ struct TALER_NormalizedPayto payto_uri;
+
+ payto_uri = TALER_reserve_make_payto (TEH_base_url,
+ &rsc.reserve_pub);
+ TALER_normalized_payto_hash (payto_uri,
+ &rsc.h_payto);
+ GNUNET_free (payto_uri.normalized_payto);
+ }
+
+ if (GNUNET_OK !=
+ TEH_DB_run_transaction (rc->connection,
+ "post reserve attest",
+ TEH_MT_REQUEST_OTHER,
+ &mhd_ret,
+ &reserve_attest_transaction,
+ &rsc))
+ {
+ return mhd_ret;
+ }
+ if (rsc.not_found)
+ {
+ json_decref (rsc.json_attest);
+ return TALER_MHD_reply_with_error (rc->connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ args[0]);
+ }
+ return reply_reserve_attest_success (rc->connection,
+ &rsc);
+}
+
+
+/* end of taler-exchange-httpd_reserves_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h b/src/exchange/taler-exchange-httpd_post-reserves-attest-RESERVE_PUB.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2022 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_post-reserves-attest-RESERVE_PUB.h
+ * @brief Handle /reserves/$RESERVE_PUB/attest requests
+ * @author Christian Grothoff
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_RESERVES_ATTEST_RESERVE_PUB_H
+#define TALER_EXCHANGE_HTTPD_POST_RESERVES_ATTEST_RESERVE_PUB_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a POST "/reserves-attest/$RID" request.
+ *
+ * @param rc request context
+ * @param root uploaded body from the client
+ * @param args args[0] has public key of the reserve
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[1]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reveal-melt.c b/src/exchange/taler-exchange-httpd_post-reveal-melt.c
@@ -0,0 +1,877 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023,2025 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_post-reveal-melt.c
+ * @brief Handle /reveal-melt requests
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd_get-metrics.h"
+#include "taler/taler_error_codes.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_post-reveal-melt.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+#define KAPPA_MINUS_1 (TALER_CNC_KAPPA - 1)
+
+
+/**
+ * State for an /reveal-melt operation.
+ */
+struct MeltRevealContext
+{
+
+ /**
+ * Commitment for the melt operation, previously called by the
+ * client.
+ */
+ struct TALER_RefreshCommitmentP rc;
+
+ /**
+ * The data from the original melt. Will be retrieved from
+ * the DB via @a rc.
+ */
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
+
+ /**
+ * True, if @e signatures were not provided in the request.
+ */
+ bool no_signatures;
+
+ /**
+ * @since v27
+ * @deprecated after vDOLDPLUS
+ *
+ * TALER_CNC_KAPPA-1 disclosed signatures for public refresh nonces.
+ */
+ struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1];
+
+ /**
+ * True, if @e transfer_secret_seeds were not provided in the request.
+ */
+ bool no_transfer_secret_seeds;
+
+ /**
+ * @since vDOLDPLUS
+ *
+ * The transfer secret seeds for the revealed batches of coins.
+ */
+ struct TALER_PrivateRefreshBatchSeedP transfer_secret_seeds[KAPPA_MINUS_1];
+
+ /**
+ * True, if no @e age_commitment was provided
+ */
+ bool no_age_commitment;
+
+ /**
+ * If @e no_age_commitment is false, the age commitment of
+ * the old coin. Needed to ensure that the age commitment
+ * is applied correctly to the fresh coins.
+ */
+ struct TALER_AgeCommitment age_commitment;
+};
+
+
+/**
+ * Check if the request belongs to an existing refresh request.
+ * If so, sets the refresh object with the request data.
+ * Otherwise, it queues an appropriate MHD response.
+ *
+ * @param connection The HTTP connection to the client
+ * @param rc Original commitment value sent with the melt request
+ * @param[out] refresh Data from the original refresh request
+ * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
+ * @return #GNUNET_OK if the refresh request has been found,
+ * #GNUNET_SYSERR if we did not find the request in the DB
+ */
+static enum GNUNET_GenericReturnValue
+find_original_refresh (
+ struct MHD_Connection *connection,
+ const struct TALER_RefreshCommitmentP *rc,
+ struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int retry = 0; retry < 3; retry++)
+ {
+ qs = TEH_plugin->get_refresh (TEH_plugin->cls,
+ rc,
+ refresh);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_refresh");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_refresh");
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Verify that the age commitment is sound, that is, if the
+ * previous /melt provided a hash, ensure we have the corresponding
+ * age commitment. Or not, if it wasn't provided.
+ *
+ * @param connection The MHD connection to handle
+ * @param actx The context of the operation, only partially built at this time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return #GNUNET_OK on success, otherwise a reply is queued for MHD and @a mhd_ret is set
+ */
+static enum GNUNET_GenericReturnValue
+compare_age_commitment (
+ struct MHD_Connection *connection,
+ struct MeltRevealContext *actx,
+ MHD_RESULT *mhd_ret)
+{
+ if (actx->no_age_commitment !=
+ actx->refresh.coin.no_age_commitment)
+ {
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ if (! actx->no_age_commitment)
+ {
+ struct TALER_AgeCommitmentHashP ach;
+
+ actx->age_commitment.mask = TEH_age_restriction_config.mask;
+ TALER_age_commitment_hash (
+ &actx->age_commitment,
+ &ach);
+ if (0 != GNUNET_memcmp (
+ &actx->refresh.coin.h_age_commitment,
+ &ach))
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Derives an planchet from a given input and returns
+ * blinded planchets detail
+ *
+ * @param connection Connection to the client
+ * @param denom_key The denomination key
+ * @param secret The secret to a planchet
+ * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL
+ * @param nonce The derived nonce needed for CS denomination
+ * @param old_age_commitment The age commitment of the old coin, might be NULL
+ * @param[out] detail planchet detail to write to write
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message
+ * written to the client and @e result set.
+ */
+static enum GNUNET_GenericReturnValue
+calculate_blinded_detail (
+ struct MHD_Connection *connection,
+ struct TEH_DenominationKey *denom_key,
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub,
+ union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ const struct TALER_AgeCommitment *old_age_commitment,
+ struct TALER_PlanchetDetail *detail,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_AgeCommitmentHashP ach;
+ bool no_age_commitment = (NULL == old_age_commitment);
+
+ /* calculate age commitment hash */
+ if (! no_age_commitment)
+ {
+ struct TALER_AgeCommitment nac;
+
+ TALER_age_commitment_derive_from_secret (old_age_commitment,
+ secret,
+ &nac);
+ TALER_age_commitment_hash (&nac,
+ &ach);
+ TALER_age_commitment_free (&nac);
+ }
+
+ /* Next: calculate planchet */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = denom_key->denom_pub.bsign_pub_key->cipher
+ };
+ struct TALER_ExchangeBlindingValues blinding_values = {
+ .blinding_inputs = &bi
+ };
+
+ switch (bi.cipher)
+ {
+ case GNUNET_CRYPTO_BSA_CS:
+ GNUNET_assert (NULL != r_pub);
+ GNUNET_assert (NULL != nonce);
+ bi.details.cs_values = *r_pub;
+ break;
+ case GNUNET_CRYPTO_BSA_RSA:
+ break;
+ default:
+ GNUNET_assert (0);
+ }
+
+ TALER_planchet_blinding_secret_create (secret,
+ &blinding_values,
+ &bks);
+ TALER_planchet_setup_coin_priv (secret,
+ &blinding_values,
+ &coin_priv);
+ ret = TALER_planchet_prepare (&denom_key->denom_pub,
+ &blinding_values,
+ &bks,
+ nonce,
+ &coin_priv,
+ no_age_commitment
+ ? NULL
+ : &ach,
+ &c_hash,
+ detail);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ GNUNET_JSON_pack_string (
+ "details",
+ "failed to prepare planchet from base key"));
+ return ret;
+ }
+ }
+ return ret;
+}
+
+
+/**
+ * @brief Checks the validity of the disclosed signatures as follows:
+ * - Verifies the validity of the disclosed signatures with the old coin's public key
+ * - Derives the seeds for disclosed fresh coins
+ * - Derives the fresh coins from the seeds
+ * - Derives new age commitment
+ * - Calculates the blinded coin planchet hashes
+ * - Calculates the refresh commitment from above data
+ * - Compares the calculated commitment with existing one
+ *
+ * The derivation of a fresh coin from the old coin is defined in
+ * https://docs.taler.net/design-documents/062-pq-refresh.html
+ *
+ * The derivation of age-commitment from a coin's age-commitment
+ * https://docs.taler.net/design-documents/024-age-restriction.html#melt
+ *
+ * @param con HTTP-connection to the client
+ * @param rf Original refresh object from the previous /melt request
+ * @param old_age_commitment The age commitment of the original coin
+ * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many, maybe NULL
+ * @param rev_batch_seeds The seeds for the transfer secrets of the disclosed coins, KAPPA_MINUS_1 many, maybe NULL
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+verify_commitment (
+ struct MHD_Connection *con,
+ const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
+ const struct TALER_AgeCommitment *old_age_commitment,
+ const struct TALER_PrivateRefreshNonceSignatureP (*signatures)[KAPPA_MINUS_1],
+ const struct TALER_PrivateRefreshBatchSeedP (*rev_batch_seeds)[KAPPA_MINUS_1],
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TEH_KeyStateHandle *keys;
+ struct TEH_DenominationKey *denom_keys[rf->num_coins];
+ struct TALER_DenominationHashP *denoms_h[rf->num_coins];
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_fee;
+ bool is_cs[rf->num_coins];
+ size_t cs_count = 0;
+
+ GNUNET_assert (rf->noreveal_index < TALER_CNC_KAPPA);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_fee));
+ memset (denom_keys,
+ 0,
+ sizeof(denom_keys));
+ memset (is_cs,
+ 0,
+ sizeof(is_cs));
+
+ /**
+ * Consistency check:
+ * If the refresh was for v27, signatures must not be NULL,
+ * otherwise the revealed batch seeds must not be NULL.
+ */
+ if (rf->is_v27_refresh)
+ {
+ if (NULL == signatures)
+ {
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "signatures missing");
+ return GNUNET_SYSERR;
+ }
+ }
+ else /* vDOLDPLUS */
+ {
+ if (NULL == rev_batch_seeds)
+ {
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "batch_seeds missing");
+ return GNUNET_SYSERR;
+ }
+ }
+ /**
+ * We need the current keys in memory for the meta-data of the denominations
+ */
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /**
+ * Find the denomination keys from the original request to /melt
+ * and keep track of those of type CS.
+ */
+ for (size_t i = 0; i < rf->num_coins; i++)
+ {
+ denom_keys[i] =
+ TEH_keys_denomination_by_serial_from_state (
+ keys,
+ rf->denom_serials[i]);
+ if (NULL == denom_keys[i])
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate amount and fees */
+ GNUNET_assert (0 <= TALER_amount_add (&total_amount,
+ &total_amount,
+ &denom_keys[i]->meta.value));
+ GNUNET_assert (0 <= TALER_amount_add (&total_fee,
+ &total_fee,
+ &denom_keys[i]->meta.fees.refresh));
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ denom_keys[i]->denom_pub.bsign_pub_key->cipher)
+ {
+ is_cs[i] = true;
+ cs_count++;
+ }
+
+ /* Remember the hash of the public key of the denomination for later */
+ denoms_h[i] = &denom_keys[i]->h_denom_pub;
+ }
+
+ /**
+ * Sanity check:
+ * The number CS denominations must match those from the /melt request
+ */
+ if (cs_count != rf->num_cs_r_values)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ /**
+ * First things first for v27 clients: Verify the signature of the old coin
+ * over the refresh nonce. This proves the ownership
+ * for the fresh coin.
+ */
+ if (rf->is_v27_refresh)
+ {
+ size_t sig_idx = 0;
+ struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
+
+ GNUNET_assert (NULL != signatures);
+
+ /**
+ * We expand the provided refresh_seed from the original call to /melt,
+ * into kappa many batch seeds, from which we will later use all except the
+ * noreveal_index one.
+ */
+ TALER_refresh_expand_kappa_nonces_v27 (
+ &rf->refresh_seed,
+ &kappa_nonces);
+
+ for (uint8_t k=0; k < TALER_CNC_KAPPA; k++)
+ {
+ if (rf->noreveal_index == k)
+ continue;
+ if (GNUNET_OK !=
+ TALER_wallet_refresh_nonce_verify (
+ &rf->coin.coin_pub,
+ &kappa_nonces.tuple[k],
+ rf->num_coins,
+ denoms_h,
+ k,
+ &(*signatures)[sig_idx++]))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ /**
+ * In the following scope, we start collecting blinded coin planchet hashes,
+ * either those persisted from the original request to /melt, or we
+ * derive and calculate them from the provided signatures, after having
+ * verified that each of them was signed by the old coin's private key.
+ *
+ * After collecting the blinded coin planchet hashes, we can then get
+ * the commitment for the calculated values and compare the result with
+ * the commitment from the /melt request.
+ */
+ {
+ struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
+ union GNUNET_CRYPTO_BlindSessionNonce b_nonces[GNUNET_NZL (cs_count)];
+ size_t cs_idx = 0; /* [0...cs_count) */
+ uint8_t sig_idx = 0; /* [0..KAPPA_MINUS_1) */
+ /* These two are only necessary for non-v27 clients, but this is the common case. */
+ struct TALER_TransferPublicKeyP k_tpbs[TALER_CNC_KAPPA][rf->num_coins];
+ struct TALER_KappaTransferPublicKeys kappa_transfer_pubs = {
+ .num_transfer_pubs = rf->num_coins
+ };
+ /**
+ * First, derive the blinding nonces for the CS denominations all at once.
+ */
+ if (0 < cs_count)
+ {
+ uint32_t cs_indices[cs_count];
+ size_t idx = 0; /* [0...cs_count) */
+
+ for (size_t i = 0; i < rf->num_coins; i++)
+ if (is_cs[i])
+ cs_indices[idx++] = i;
+
+ TALER_cs_derive_only_cs_blind_nonces_from_seed (&rf->blinding_seed,
+ true, /* for melt */
+ cs_count,
+ cs_indices,
+ b_nonces);
+ }
+ /**
+ * We handle the kappa batches of rf->num_coins depths first.
+ */
+ for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
+ {
+ if (k == rf->noreveal_index)
+ {
+ /**
+ * We take the stored value for the hash of selected batch
+ */
+ kappa_planchets_h.tuple[k] = rf->selected_h;
+ }
+ else
+ {
+ /**
+ * We have to generate all the planchets' details from
+ * the disclosed input material and generate the
+ * hashes of them.
+ */
+ struct TALER_PlanchetMasterSecretP planchet_secrets[rf->num_coins];
+ struct TALER_PlanchetDetail details[rf->num_coins];
+
+ memset (planchet_secrets,
+ 0,
+ sizeof(planchet_secrets));
+ memset (details,
+ 0,
+ sizeof(details));
+
+ GNUNET_assert (sig_idx < KAPPA_MINUS_1);
+
+ /**
+ * Expand from the k-th signature all num_coin planchet secrets,
+ * except for the noreveal_index.
+ */
+ if (rf->is_v27_refresh)
+ {
+ TALER_refresh_signature_to_secrets_v27 (
+ &(*signatures)[sig_idx++],
+ rf->num_coins,
+ planchet_secrets);
+ }
+ else /* vDOLDPLUS */
+ {
+ TALER_refresh_expand_batch_seed_to_transfer_data (
+ &(*rev_batch_seeds)[sig_idx++],
+ &rf->coin.coin_pub,
+ rf->num_coins,
+ planchet_secrets,
+ k_tpbs[k]);
+
+ kappa_transfer_pubs.batch[k] = k_tpbs[k];
+ }
+ /**
+ * Reset the index for the CS denominations.
+ */
+ cs_idx = 0;
+
+ for (size_t coin_idx = 0; coin_idx < rf->num_coins; coin_idx++)
+ {
+ struct GNUNET_CRYPTO_CSPublicRPairP *rp;
+ union GNUNET_CRYPTO_BlindSessionNonce *np;
+
+ if (is_cs[coin_idx])
+ {
+ GNUNET_assert (cs_idx < cs_count);
+ np = &b_nonces[cs_idx];
+ rp = &rf->cs_r_values[cs_idx];
+ cs_idx++;
+ }
+ else
+ {
+ np = NULL;
+ rp = NULL;
+ }
+ ret = calculate_blinded_detail (con,
+ denom_keys[coin_idx],
+ &planchet_secrets[coin_idx],
+ rp,
+ np,
+ old_age_commitment,
+ &details[coin_idx],
+ result);
+ if (GNUNET_OK != ret)
+ return GNUNET_SYSERR;
+ }
+ /**
+ * Now we can generate the hashes for the kappa-th batch of coins
+ */
+ TALER_wallet_blinded_planchet_details_hash (
+ rf->num_coins,
+ details,
+ &kappa_planchets_h.tuple[k]);
+
+ for (size_t i =0; i<rf->num_coins; i++)
+ TALER_planchet_detail_free (&details[i]);
+ }
+ }
+ /**
+ * Finally, calculate the refresh commitment and compare it with the original.
+ */
+ {
+ struct TALER_RefreshCommitmentP rc;
+
+ if (rf->is_v27_refresh)
+ {
+ TALER_refresh_get_commitment_v27 (&rc,
+ &rf->refresh_seed,
+ rf->no_blinding_seed
+ ? NULL
+ : &rf->blinding_seed,
+ &kappa_planchets_h,
+ &rf->coin.coin_pub,
+ &rf->amount_with_fee);
+ }
+ else
+ {
+ TALER_refresh_get_commitment (&rc,
+ &rf->refresh_seed,
+ rf->no_blinding_seed
+ ? NULL
+ : &rf->blinding_seed,
+ &kappa_transfer_pubs,
+ &kappa_planchets_h,
+ &rf->coin.coin_pub,
+ &rf->amount_with_fee);
+ }
+
+ if (0 != GNUNET_CRYPTO_hash_cmp (
+ &rf->rc.session_hash,
+ &rc.session_hash))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
+ "rc");
+ return GNUNET_SYSERR;
+ }
+ }
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Commit the successful reveal to the database
+ *
+ * @param con HTTP-connection to the client
+ * @param rc Original refresh commitment from the previous /melt request
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+commit_reveal (
+ struct MHD_Connection *con,
+ const struct TALER_RefreshCommitmentP *rc,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int retry = 0; retry < 3; retry++)
+ {
+ qs = TEH_plugin->mark_refresh_reveal_success (
+ TEH_plugin->cls,
+ rc);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (con,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark_refresh_reveal_success");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "mark_refresh_reveal_success");
+ return GNUNET_SYSERR;
+
+}
+
+
+/**
+ * @brief Send a response for "/reveal-melt"
+ *
+ * @param connection The http connection to the client to send the response to
+ * @param refresh The data from the previous call to /melt with signatures
+ * @return a MHD result code
+ */
+static MHD_RESULT
+reply_melt_reveal_success (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh)
+{
+ json_t *list = json_array ();
+ GNUNET_assert (NULL != list);
+
+ for (unsigned int i = 0; i < refresh->num_coins; i++)
+ {
+ json_t *obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (NULL,
+ &refresh->denom_sigs[i]));
+ GNUNET_assert (0 ==
+ json_array_append_new (list,
+ obj));
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ list));
+}
+
+
+MHD_RESULT
+TEH_handler_reveal_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2])
+{
+ MHD_RESULT result = MHD_NO;
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct MeltRevealContext actx = {0};
+ struct GNUNET_JSON_Specification sig_tuple[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.signatures[0]),
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.signatures[1]),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification seeds_tuple[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.transfer_secret_seeds[0]),
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &actx.transfer_secret_seeds[1]),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("rc",
+ &actx.rc),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_tuple_of ("signatures",
+ sig_tuple),
+ &actx.no_signatures),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_tuple_of ("batch_seeds",
+ seeds_tuple),
+ &actx.no_transfer_secret_seeds),
+ GNUNET_JSON_spec_mark_optional (
+ TALER_JSON_spec_age_commitment ("age_commitment",
+ &actx.age_commitment),
+ &actx.no_age_commitment),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /**
+ * Note that above, we have hard-wired
+ * the size of TALER_CNC_KAPPA.
+ * Let's make sure we keep this in sync.
+ */
+ _Static_assert (KAPPA_MINUS_1 == 2,
+ "TALER_CNC_KAPPA isn't 3!?!?");
+
+ /* Parse JSON body*/
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+
+ (void) args;
+
+ do {
+ /* Find original commitment */
+ if (GNUNET_OK !=
+ find_original_refresh (
+ rc->connection,
+ &actx.rc,
+ &actx.refresh,
+ &result))
+ break;
+
+ /* Compare age commitment with the hash from the /melt request, if present */
+ if (GNUNET_OK !=
+ compare_age_commitment (
+ rc->connection,
+ &actx,
+ &result))
+ break;
+
+ /* verify the commitment */
+ if (GNUNET_OK !=
+ verify_commitment (
+ rc->connection,
+ &actx.refresh,
+ actx.no_age_commitment
+ ? NULL
+ : &actx.age_commitment,
+ actx.no_signatures
+ ? NULL
+ : &actx.signatures,
+ actx.no_transfer_secret_seeds
+ ? NULL
+ : &actx.transfer_secret_seeds,
+ &result))
+ break;
+
+ if (GNUNET_OK !=
+ commit_reveal (rc->connection,
+ &actx.rc,
+ &result))
+ break;
+
+ /* Finally, return the signatures */
+ result = reply_melt_reveal_success (rc->connection,
+ &actx.refresh);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ if (NULL != actx.refresh.denom_sigs)
+ for (unsigned int i = 0; i<actx.refresh.num_coins; i++)
+ TALER_blinded_denom_sig_free (&actx.refresh.denom_sigs[i]);
+ GNUNET_free (actx.refresh.denom_sigs);
+ GNUNET_free (actx.refresh.denom_pub_hashes);
+ GNUNET_free (actx.refresh.denom_serials);
+ GNUNET_free (actx.refresh.cs_r_values);
+ return result;
+}
+
+
+/* end of taler-exchange-httpd_reveal_melt.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reveal-melt.h b/src/exchange/taler-exchange-httpd_post-reveal-melt.h
@@ -0,0 +1,58 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-reveal-melt.h
+ * @brief Handle /reveal-melt requests
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_REVEAL_MELT_H
+#define TALER_EXCHANGE_HTTPD_POST_REVEAL_MELT_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reveal-melt" request.
+ *
+ * The client got a noreveal_index in response to a previous request
+ * /melt. It now has to reveal all n*(kappa-1) signatures (except for the
+ * noreveal_index), signed by the old coin private key, over the coin's
+ * specific nonce, which is derived per coin from the refresh_seed,
+ * From that signature also all other coin-relevant data (blinding, age
+ * restriction, nonce) are derived from.
+ *
+ * The exchange computes those values, potentially ensures that age restriction
+ * is correctly applied, calculates the hash of the blinded envelopes, and -
+ * together with the non-disclosed blinded envelopes - compares the hash of the
+ * calculated melt commitment against the original.
+ *
+ * If all those checks and the used denominations turn out to be correct, the
+ * exchange signs all blinded envelopes with their appropriate denomination
+ * keys.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args not used
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reveal_melt (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[2]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c b/src/exchange/taler-exchange-httpd_post-reveal-withdraw.c
@@ -0,0 +1,637 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2023,2025 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_post-reveal-withdraw.c
+ * @brief Handle /reveal-withdraw requests
+ * @author Özgür Kesim
+ */
+#include "taler/platform.h"
+#include <gnunet/gnunet_common.h>
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include <microhttpd.h>
+#include "taler-exchange-httpd_get-metrics.h"
+#include "taler/taler_error_codes.h"
+#include "taler/taler_exchangedb_plugin.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_mhd.h"
+#include "taler-exchange-httpd_post-reveal-withdraw.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+
+/**
+ * State for an /reveal-withdraw operation.
+ */
+struct WithdrawRevealContext
+{
+
+ /**
+ * Commitment for the withdraw operation, previously called by the
+ * client.
+ */
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ /**
+ * TALER_CNC_KAPPA-1 secrets for disclosed coin batches.
+ */
+ struct TALER_RevealWithdrawMasterSeedsP disclosed_batch_seeds;
+
+ /**
+ * The data from the original withdraw. Will be retrieved from
+ * the DB via @a wch.
+ */
+ struct TALER_EXCHANGEDB_Withdraw withdraw;
+};
+
+
+/**
+ * Parse the json body of an '/reveal-withdraw' request. It extracts
+ * the denomination hashes, blinded coins and disclosed coins and allocates
+ * memory for those.
+ *
+ * @param connection The MHD connection to handle
+ * @param j_disclosed_batch_seeds The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from
+ * @param[out] actx The context of the operation, only partially built at call time
+ * @param[out] mhd_ret The result if a reply is queued for MHD
+ * @return true on success, false on failure, with a reply already queued for MHD.
+ */
+static enum GNUNET_GenericReturnValue
+parse_withdraw_reveal_json (
+ struct MHD_Connection *connection,
+ const json_t *j_disclosed_batch_seeds,
+ struct WithdrawRevealContext *actx,
+ MHD_RESULT *mhd_ret)
+{
+ size_t num_entries;
+ const char *error;
+ struct GNUNET_JSON_Specification tuple[] = {
+ GNUNET_JSON_spec_fixed (NULL,
+ &actx->disclosed_batch_seeds.tuple[0],
+ sizeof(actx->disclosed_batch_seeds.tuple[0])),
+ GNUNET_JSON_spec_fixed (NULL,
+ &actx->disclosed_batch_seeds.tuple[1],
+ sizeof(actx->disclosed_batch_seeds.tuple[1])),
+ GNUNET_JSON_spec_end ()
+ };
+ struct GNUNET_JSON_Specification spec[] = {
+ TALER_JSON_spec_tuple_of (NULL,
+ tuple),
+ GNUNET_JSON_spec_end ()
+ };
+
+ /**
+ * Note that above, in tuple[], we have hard-wired
+ * the size of TALER_CNC_KAPPA.
+ * Let's make sure we keep this in sync.
+ */
+ _Static_assert ((TALER_CNC_KAPPA - 1) == 2);
+
+ num_entries = json_array_size (j_disclosed_batch_seeds); /* 0, if not an array */
+ if (! json_is_array (j_disclosed_batch_seeds))
+ error = "disclosed_batch_seeds must be an array";
+ else if (num_entries == 0)
+ error = "disclosed_batch_seeds must not be empty";
+ else if (num_entries != TALER_CNC_KAPPA - 1)
+ error =
+ "disclosed_batch_seeds must be an array of size "
+ TALER_CNC_KAPPA_MINUS_ONE_STR;
+ else
+ error = NULL;
+
+ if ( (NULL != error) ||
+ (GNUNET_OK !=
+ GNUNET_JSON_parse (j_disclosed_batch_seeds,
+ spec,
+ &error,
+ NULL)) )
+ {
+ GNUNET_break_op (0);
+ *mhd_ret = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ error);
+ return GNUNET_SYSERR;
+ }
+
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check if the request belongs to an existing withdraw request.
+ * If so, sets the withdraw object with the request data.
+ * Otherwise, it queues an appropriate MHD response.
+ *
+ * @param connection The HTTP connection to the client
+ * @param planchets_h Original commitment value sent with the withdraw request
+ * @param[out] withdraw Data from the original withdraw request
+ * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
+ * @return #GNUNET_OK if the withdraw request has been found,
+ * #GNUNET_SYSERR if we did not find the request in the DB
+ */
+static enum GNUNET_GenericReturnValue
+find_original_withdraw (
+ struct MHD_Connection *connection,
+ const struct TALER_HashBlindedPlanchetsP *planchets_h,
+ struct TALER_EXCHANGEDB_Withdraw *withdraw,
+ MHD_RESULT *result)
+{
+ enum GNUNET_DB_QueryStatus qs;
+
+ for (unsigned int try = 0; try < 3; try++)
+ {
+ qs = TEH_plugin->get_withdraw (TEH_plugin->cls,
+ planchets_h,
+ withdraw);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return GNUNET_OK; /* Only happy case */
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *result = TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_EXCHANGE_WITHDRAW_COMMITMENT_UNKNOWN,
+ NULL);
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_withdraw");
+ return GNUNET_SYSERR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ break; /* try again */
+ default:
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+ }
+ /* after unsuccessful retries*/
+ GNUNET_break (0);
+ *result = TALER_MHD_reply_with_ec (connection,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_withdraw");
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * @brief Derives an age-restricted planchet from a given secret and calculates the hash
+ *
+ * @param connection Connection to the client
+ * @param denom_key The denomination key
+ * @param secret The secret to a planchet
+ * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL
+ * @param nonce The derived nonce needed for CS denomination
+ * @param max_age The maximum age allowed
+ * @param[out] bch Hashcode to write
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message
+ * written to the client and @e result set.
+ */
+static enum GNUNET_GenericReturnValue
+calculate_blinded_hash (
+ struct MHD_Connection *connection,
+ struct TEH_DenominationKey *denom_key,
+ const struct TALER_PlanchetMasterSecretP *secret,
+ const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub,
+ union GNUNET_CRYPTO_BlindSessionNonce *nonce,
+ uint8_t max_age,
+ struct TALER_BlindedCoinHashP *bch,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret;
+ struct TALER_AgeCommitmentHashP ach;
+
+ /* calculate age commitment hash */
+ {
+ struct TALER_AgeCommitmentProof acp;
+
+ TALER_age_restriction_from_secret (secret,
+ &denom_key->denom_pub.age_mask,
+ max_age,
+ &acp);
+ TALER_age_commitment_hash (&acp.commitment,
+ &ach);
+ TALER_age_commitment_proof_free (&acp);
+ }
+
+ /* Next: calculate planchet */
+ {
+ struct TALER_CoinPubHashP c_hash;
+ struct TALER_PlanchetDetail detail = {0};
+ struct TALER_CoinSpendPrivateKeyP coin_priv;
+ union GNUNET_CRYPTO_BlindingSecretP bks;
+ struct GNUNET_CRYPTO_BlindingInputValues bi = {
+ .cipher = denom_key->denom_pub.bsign_pub_key->cipher
+ };
+ struct TALER_ExchangeBlindingValues alg_values = {
+ .blinding_inputs = &bi
+ };
+
+ if (GNUNET_CRYPTO_BSA_CS == bi.cipher)
+ {
+ GNUNET_assert (NULL != r_pub);
+ GNUNET_assert (NULL != nonce);
+ bi.details.cs_values = *r_pub;
+ }
+ TALER_planchet_blinding_secret_create (secret,
+ &alg_values,
+ &bks);
+ TALER_planchet_setup_coin_priv (secret,
+ &alg_values,
+ &coin_priv);
+ ret = TALER_planchet_prepare (&denom_key->denom_pub,
+ &alg_values,
+ &bks,
+ nonce,
+ &coin_priv,
+ &ach,
+ &c_hash,
+ &detail);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break (0);
+ *result = TALER_MHD_REPLY_JSON_PACK (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ GNUNET_JSON_pack_string (
+ "details",
+ "failed to prepare planchet from base key"));
+ return ret;
+ }
+
+ TALER_coin_ev_hash (&detail.blinded_planchet,
+ &denom_key->h_denom_pub,
+ bch);
+ TALER_blinded_planchet_free (&detail.blinded_planchet);
+ }
+
+ return ret;
+}
+
+
+/**
+ * @brief Checks the validity of the disclosed coins as follows:
+ * - Derives and calculates the disclosed coins'
+ * - public keys,
+ * - nonces (if applicable),
+ * - age commitments,
+ * - blindings
+ * - blinded hashes
+ * - Computes planchets_h with those calculated and the undisclosed hashes
+ * - Compares planchets_h with the value from the original commitment
+ * - Verifies that all public keys in indices larger than the age group
+ * corresponding to max_age are derived from the constant public key.
+ *
+ * The derivation of the blindings, (potential) nonces and age-commitment from
+ * a coin's private keys is defined in
+ * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw
+ *
+ * @param con HTTP-connection to the client
+ * @param wd Original withdraw request
+ * @param disclosed_batch_seeds The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many
+ * @param[out] result On error, a HTTP-response will be queued and result set accordingly
+ * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
+ */
+static enum GNUNET_GenericReturnValue
+verify_commitment_and_max_age (
+ struct MHD_Connection *con,
+ const struct TALER_EXCHANGEDB_Withdraw *wd,
+ const struct TALER_RevealWithdrawMasterSeedsP *disclosed_batch_seeds,
+ MHD_RESULT *result)
+{
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct GNUNET_HashContext *hash_context;
+ struct TEH_KeyStateHandle *keys;
+ struct TEH_DenominationKey *denom_keys[wd->num_coins];
+ struct TALER_Amount total_amount;
+ struct TALER_Amount total_fee;
+ struct TALER_AgeMask mask;
+ struct TALER_PlanchetMasterSecretP secrets[
+ TALER_CNC_KAPPA - 1][wd->num_coins];
+ bool is_cs[wd->num_coins];
+ size_t cs_count = 0;
+ uint8_t secrets_idx = 0; /* first index into secrets */
+
+ GNUNET_assert (wd->noreveal_index < TALER_CNC_KAPPA);
+
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &total_fee));
+
+ memset (denom_keys,
+ 0,
+ sizeof(denom_keys));
+ memset (is_cs,
+ 0,
+ sizeof(is_cs));
+
+ /* We need the current keys in memory for the meta-data of the denominations */
+ keys = TEH_keys_get_state ();
+ if (NULL == keys)
+ {
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* Find the denomination keys */
+ for (size_t i = 0; i < wd->num_coins; i++)
+ {
+ denom_keys[i] =
+ TEH_keys_denomination_by_serial_from_state (
+ keys,
+ wd->denom_serials[i]);
+ if (NULL == denom_keys[i])
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ /* Accumulate amount and fees */
+ GNUNET_assert (0 <= TALER_amount_add (&total_amount,
+ &total_amount,
+ &denom_keys[i]->meta.value));
+ GNUNET_assert (0 <= TALER_amount_add (&total_fee,
+ &total_fee,
+ &denom_keys[i]->meta.fees.withdraw));
+
+ if (i == 0)
+ mask = denom_keys[i]->meta.age_mask;
+ GNUNET_assert (mask.bits == denom_keys[i]->meta.age_mask.bits);
+
+ if (GNUNET_CRYPTO_BSA_CS ==
+ denom_keys[i]->denom_pub.bsign_pub_key->cipher)
+ {
+ is_cs[i] = true;
+ cs_count++;
+ }
+ }
+
+ hash_context = GNUNET_CRYPTO_hash_context_start ();
+
+ {
+ uint32_t cs_indices[cs_count];
+ union GNUNET_CRYPTO_BlindSessionNonce nonces[cs_count];
+ size_t cs_idx = 0; /* [0...cs_count) */
+
+ for (size_t i = 0; i < wd->num_coins; i++)
+ if (is_cs[i])
+ cs_indices[cs_idx++] = i;
+
+ TALER_cs_derive_only_cs_blind_nonces_from_seed (&wd->blinding_seed,
+ false, /* not for melt */
+ cs_count,
+ cs_indices,
+ nonces);
+
+ for (uint8_t gamma = 0; gamma<TALER_CNC_KAPPA; gamma++)
+ {
+ if (gamma == wd->noreveal_index)
+ {
+ /**
+ * For the disclosed index, all we have to do is to accumulate the hash
+ * of the selected coins
+ */
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ &wd->selected_h,
+ sizeof(wd->selected_h));
+ }
+ else
+ {
+ /**
+ * For the non-disclosed index, we have to generate the planchets in
+ * this batch and calculate their hash
+ */
+ struct GNUNET_HashContext *batch_ctx;
+ struct TALER_BlindedCoinHashP batch_h;
+ cs_idx = 0;
+
+ batch_ctx = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != batch_ctx);
+
+ /* Expand the secrets for a disclosed batch */
+ GNUNET_assert (secrets_idx < (TALER_CNC_KAPPA - 1));
+ TALER_withdraw_expand_secrets (
+ wd->num_coins,
+ &disclosed_batch_seeds->tuple[secrets_idx],
+ secrets[secrets_idx]);
+
+ /**
+ * Now individually create each coin in this batch and calculate
+ * its hash, and accumulate the hash of the batch with it
+ */
+ for (size_t coin_idx = 0; coin_idx < wd->num_coins; coin_idx++)
+ {
+ struct TALER_BlindedCoinHashP bch;
+ struct GNUNET_CRYPTO_CSPublicRPairP *rp;
+ union GNUNET_CRYPTO_BlindSessionNonce *np;
+
+ if (is_cs[coin_idx])
+ {
+ GNUNET_assert (cs_idx < cs_count);
+ np = &nonces[cs_idx];
+ rp = &wd->cs_r_values[cs_idx];
+ cs_idx++;
+ }
+ else
+ {
+ np = NULL;
+ rp = NULL;
+ }
+ ret = calculate_blinded_hash (con,
+ denom_keys[coin_idx],
+ &secrets[secrets_idx][coin_idx],
+ rp,
+ np,
+ wd->max_age,
+ &bch,
+ result);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_CRYPTO_hash_context_abort (hash_context);
+ return GNUNET_SYSERR;
+ }
+ /**
+ * Continue the running hash of all coin hashes in the batch
+ * with the calculated hash-value of the current, disclosed coin
+ */
+ GNUNET_CRYPTO_hash_context_read (batch_ctx,
+ &bch,
+ sizeof(bch));
+ }
+ /**
+ * Finalize the hash of this batch and add it to
+ * the total hash
+ */
+ GNUNET_CRYPTO_hash_context_finish (
+ batch_ctx,
+ &batch_h.hash);
+ GNUNET_CRYPTO_hash_context_read (
+ hash_context,
+ &batch_h,
+ sizeof(batch_h));
+
+ secrets_idx++;
+ }
+ }
+ }
+
+ /* Finally, compare the calculated hash with the original wd */
+ {
+ struct TALER_HashBlindedPlanchetsP planchets_h;
+
+ GNUNET_CRYPTO_hash_context_finish (
+ hash_context,
+ &planchets_h.hash);
+
+ if (0 != GNUNET_CRYPTO_hash_cmp (
+ &wd->planchets_h.hash,
+ &planchets_h.hash))
+ {
+ GNUNET_break_op (0);
+ *result = TALER_MHD_reply_with_ec (con,
+ TALER_EC_EXCHANGE_WITHDRAW_REVEAL_INVALID_HASH,
+ NULL);
+ return GNUNET_SYSERR;
+ }
+
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+ * @brief Send a response for "/reveal-withdraw"
+ *
+ * @param connection The http connection to the client to send the response to
+ * @param commitment The data from the commitment with signatures
+ * @return a MHD result code
+ */
+static MHD_RESULT
+reply_withdraw_reveal_success (
+ struct MHD_Connection *connection,
+ const struct TALER_EXCHANGEDB_Withdraw *commitment)
+{
+ json_t *list = json_array ();
+
+ GNUNET_assert (NULL != list);
+ for (size_t i = 0; i < commitment->num_coins; i++)
+ {
+ json_t *obj = GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (NULL,
+ &commitment->denom_sigs[i]));
+ GNUNET_assert (0 ==
+ json_array_append_new (list,
+ obj));
+ }
+
+ return TALER_MHD_REPLY_JSON_PACK (
+ connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ list));
+}
+
+
+MHD_RESULT
+TEH_handler_reveal_withdraw (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0])
+{
+ MHD_RESULT result = MHD_NO;
+ enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
+ struct WithdrawRevealContext actx = {0};
+ const json_t *j_disclosed_batch_seeds;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("planchets_h",
+ &actx.planchets_h),
+ GNUNET_JSON_spec_array_const ("disclosed_batch_seeds",
+ &j_disclosed_batch_seeds),
+ GNUNET_JSON_spec_end ()
+ };
+
+ (void) args;
+ /* Parse JSON body*/
+ ret = TALER_MHD_parse_json_data (rc->connection,
+ root,
+ spec);
+ if (GNUNET_OK != ret)
+ {
+ GNUNET_break_op (0);
+ return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
+ }
+
+ do {
+ /* Extract denominations, blinded and disclosed coins */
+ if (GNUNET_OK !=
+ parse_withdraw_reveal_json (
+ rc->connection,
+ j_disclosed_batch_seeds,
+ &actx,
+ &result))
+ break;
+
+ /* Find original commitment */
+ if (GNUNET_OK !=
+ find_original_withdraw (
+ rc->connection,
+ &actx.planchets_h,
+ &actx.withdraw,
+ &result))
+ break;
+
+ /* Verify the computed planchets_h equals the committed one and that coins
+ * have a maximum age group corresponding max_age (age-mask dependent) */
+ if (GNUNET_OK !=
+ verify_commitment_and_max_age (
+ rc->connection,
+ &actx.withdraw,
+ &actx.disclosed_batch_seeds,
+ &result))
+ break;
+
+ /* Finally, return the signatures */
+ result = reply_withdraw_reveal_success (rc->connection,
+ &actx.withdraw);
+
+ } while (0);
+
+ GNUNET_JSON_parse_free (spec);
+ if (NULL != actx.withdraw.denom_sigs)
+ {
+ for (size_t i = 0; i<actx.withdraw.num_coins; i++)
+ TALER_blinded_denom_sig_free (&actx.withdraw.denom_sigs[i]);
+ GNUNET_free (actx.withdraw.denom_sigs);
+ }
+ GNUNET_free (actx.withdraw.cs_r_values);
+ GNUNET_free (actx.withdraw.denom_pub_hashes);
+ GNUNET_free (actx.withdraw.denom_serials);
+ return result;
+}
+
+
+/* end of taler-exchange-httpd_reveal_withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_post-reveal-withdraw.h b/src/exchange/taler-exchange-httpd_post-reveal-withdraw.h
@@ -0,0 +1,56 @@
+/*
+ This file is part of TALER
+ Copyright (C) 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_post-reveal-withdraw.h
+ * @brief Handle /reveal-withdraw/$ACH requests
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_REVEAL_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_POST_REVEAL_WITHDRAW_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+
+/**
+ * Handle a "/reveal-withdraw" request.
+ *
+ * The client got a noreveal_index in response to a previous request
+ * /withdraw. It now has to reveal all n*(kappa-1)
+ * coin's private keys (except for the noreveal_index), from which all other
+ * coin-relevant data (blinding, age restriction, nonce) is derived from.
+ *
+ * The exchange computes those values, ensures that the maximum age is
+ * correctly applied, calculates the hash of the blinded envelopes, and -
+ * together with the non-disclosed blinded envelopes - compares the hash of
+ * the calculated withdraw commitment against the original.
+ *
+ * If all those checks and the used denominations turn out to be correct, the
+ * exchange signs all blinded envelopes with their appropriate denomination
+ * keys.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args not used
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_reveal_withdraw (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_post-withdraw.c b/src/exchange/taler-exchange-httpd_post-withdraw.c
@@ -0,0 +1,1818 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-withdraw.c
+ * @brief Code to handle /withdraw requests
+ * @note This endpoint is active since v26 of the protocol API
+ * @author Özgür Kesim
+ */
+
+#include "taler/platform.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <jansson.h>
+#include "taler-exchange-httpd.h"
+#include "taler/taler_json_lib.h"
+#include "taler/taler_kyclogic_lib.h"
+#include "taler/taler_mhd_lib.h"
+#include "taler-exchange-httpd_post-withdraw.h"
+#include "taler-exchange-httpd_common_kyc.h"
+#include "taler-exchange-httpd_responses.h"
+#include "taler-exchange-httpd_get-keys.h"
+#include "taler/taler_util.h"
+
+/**
+ * The different type of errors that might occur, sorted by name.
+ * Some of them require idempotency checks, which are marked
+ * in @e idempotency_check_required below.
+ */
+enum WithdrawError
+{
+ WITHDRAW_ERROR_NONE,
+ WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED,
+ WITHDRAW_ERROR_AMOUNT_OVERFLOW,
+ WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
+ WITHDRAW_ERROR_BLINDING_SEED_REQUIRED,
+ WITHDRAW_ERROR_CIPHER_MISMATCH,
+ WITHDRAW_ERROR_CONFIRMATION_SIGN,
+ WITHDRAW_ERROR_DB_FETCH_FAILED,
+ WITHDRAW_ERROR_DB_INVARIANT_FAILURE,
+ WITHDRAW_ERROR_DENOMINATION_EXPIRED,
+ WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
+ WITHDRAW_ERROR_DENOMINATION_REVOKED,
+ WITHDRAW_ERROR_DENOMINATION_SIGN,
+ WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ WITHDRAW_ERROR_FEE_OVERFLOW,
+ WITHDRAW_ERROR_IDEMPOTENT_PLANCHET,
+ WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
+ WITHDRAW_ERROR_CRYPTO_HELPER,
+ WITHDRAW_ERROR_KEYS_MISSING,
+ WITHDRAW_ERROR_KYC_REQUIRED,
+ WITHDRAW_ERROR_LEGITIMIZATION_RESULT,
+ WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE,
+ WITHDRAW_ERROR_NONCE_REUSE,
+ WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
+ WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
+ WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID,
+ WITHDRAW_ERROR_RESERVE_UNKNOWN,
+};
+
+/**
+ * With the bits set in this value will be mark the errors
+ * that require a check for idempotency before actually
+ * returning an error.
+ */
+static const uint64_t idempotency_check_required =
+ 0
+ | (1LLU << WITHDRAW_ERROR_DENOMINATION_EXPIRED)
+ | (1LLU << WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN)
+ | (1LLU << WITHDRAW_ERROR_DENOMINATION_REVOKED)
+ | (1LLU << WITHDRAW_ERROR_INSUFFICIENT_FUNDS)
+ | (1LLU << WITHDRAW_ERROR_KEYS_MISSING)
+ | (1LLU << WITHDRAW_ERROR_KYC_REQUIRED);
+
+#define IDEMPOTENCY_CHECK_REQUIRED(ec) \
+ (0LLU != (idempotency_check_required & (1LLU << (ec))))
+
+
+/**
+ * Context for a /withdraw requests
+ */
+struct WithdrawContext
+{
+
+ /**
+ * This struct is kept in a DLL.
+ */
+ struct WithdrawContext *prev;
+ struct WithdrawContext *next;
+
+ /**
+ * Processing phase we are in.
+ * The ordering here partially matters, as we progress through
+ * them by incrementing the phase in the happy path.
+ */
+ enum
+ {
+ WITHDRAW_PHASE_PARSE = 0,
+ WITHDRAW_PHASE_CHECK_KEYS,
+ WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE,
+ WITHDRAW_PHASE_RUN_LEGI_CHECK,
+ WITHDRAW_PHASE_SUSPENDED,
+ WITHDRAW_PHASE_CHECK_KYC_RESULT,
+ WITHDRAW_PHASE_PREPARE_TRANSACTION,
+ WITHDRAW_PHASE_RUN_TRANSACTION,
+ WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS,
+ WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
+ WITHDRAW_PHASE_RETURN_NO,
+ WITHDRAW_PHASE_RETURN_YES,
+ } phase;
+
+
+ /**
+ * Handle for the legitimization check.
+ */
+ struct TEH_LegitimizationCheckHandle *lch;
+
+ /**
+ * Request context
+ */
+ const struct TEH_RequestContext *rc;
+
+ /**
+ * KYC status for the operation.
+ */
+ struct TALER_EXCHANGEDB_KycStatus kyc;
+
+ /**
+ * Current time for the DB transaction.
+ */
+ struct GNUNET_TIME_Timestamp now;
+
+ /**
+ * Set to the hash of the normalized payto URI that established
+ * the reserve.
+ */
+ struct TALER_NormalizedPaytoHashP h_normalized_payto;
+
+ /**
+ * Captures all parameters provided in the JSON request
+ */
+ struct
+ {
+ /**
+ * All fields (from the request or computed)
+ * that we persist in the database.
+ */
+ struct TALER_EXCHANGEDB_Withdraw withdraw;
+
+ /**
+ * In some error cases we check for idempotency.
+ * If we find an entry in the database, we mark this here.
+ */
+ bool is_idempotent;
+
+ /**
+ * In some error conditions the request is checked
+ * for idempotency and the result from the database
+ * is stored here.
+ */
+ struct TALER_EXCHANGEDB_Withdraw withdraw_idem;
+
+ /**
+ * Array of ``withdraw.num_coins`` hashes of the public keys
+ * of the denominations to withdraw.
+ */
+ struct TALER_DenominationHashP *denoms_h;
+
+ /**
+ * Number of planchets. If ``withdraw.max_age`` was _not_ set, this is equal to ``num_coins``.
+ * Otherwise (``withdraw.max_age`` was set) it is ``withdraw.num_coins * kappa``.
+ */
+ size_t num_planchets;
+
+ /**
+ * Array of ``withdraw.num_planchets`` coin planchets.
+ * Note that the size depends on the age restriction:
+ * If ``withdraw.age_proof_required`` is false,
+ * this is an array of length ``withdraw.num_coins``.
+ * Otherwise it is an array of length ``kappa*withdraw.num_coins``,
+ * arranged in runs of ``num_coins`` coins,
+ * [0..num_coins)..[0..num_coins),
+ * one for each #TALER_CNC_KAPPA value.
+ */
+ struct TALER_BlindedPlanchet *planchets;
+
+ /**
+ * If proof of age-restriction is required, the #TALER_CNC_KAPPA hashes
+ * of the batches of ``withdraw.num_coins`` coins.
+ */
+ struct TALER_HashBlindedPlanchetsP kappa_planchets_h[TALER_CNC_KAPPA];
+
+ /**
+ * Total (over all coins) amount (excluding fee) committed to withdraw
+ */
+ struct TALER_Amount amount;
+
+ /**
+ * Total fees for the withdraw
+ */
+ struct TALER_Amount fee;
+
+ /**
+ * Array of length ``withdraw.num_cs_r_values`` of indices into
+ * @e denoms_h of CS denominations.
+ */
+ uint32_t *cs_indices;
+
+ } request;
+
+
+ /**
+ * Errors occurring during evaluation of the request are captured in this
+ * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
+ * message is prepared and sent to the client.
+ */
+ struct
+ {
+ /* The (internal) error code */
+ enum WithdrawError code;
+
+ /**
+ * Some errors require details to be sent to the client.
+ * These are captured in this union.
+ * Each field is named according to the error that is using it, except
+ * commented otherwise.
+ */
+ union
+ {
+ const char *request_parameter_malformed;
+
+ const char *reserve_cipher_unknown;
+
+ /**
+ * For all errors related to a particular denomination, i.e.
+ * WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
+ * WITHDRAW_ERROR_DENOMINATION_EXPIRED,
+ * WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ * WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ * we use this one field.
+ */
+ const struct TALER_DenominationHashP *denom_h;
+
+ const char *db_fetch_context;
+
+ struct
+ {
+ uint16_t max_allowed;
+ uint32_t birthday;
+ } maximum_age_too_large;
+
+ /**
+ * The lowest age required
+ */
+ uint16_t age_restriction_required;
+
+ /**
+ * Balance of the reserve
+ */
+ struct TALER_Amount insufficient_funds;
+
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ enum TALER_ErrorCode ec_denomination_sign;
+
+ struct
+ {
+ struct MHD_Response *response;
+ unsigned int http_status;
+ } legitimization_result;
+
+ } details;
+ } error;
+};
+
+/**
+ * The following macros set the given error code,
+ * set the phase to WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
+ * and optionally set the given field (with an optionally given value).
+ */
+#define SET_ERROR(wc, ec) \
+ do \
+ { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
+ (wc)->error.code = (ec); \
+ (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_FIELD(wc, ec, field) \
+ do \
+ { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
+ (wc)->error.code = (ec); \
+ (wc)->error.details.field = (field); \
+ (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+#define SET_ERROR_WITH_DETAIL(wc, ec, field, value) \
+ do \
+ { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
+ (wc)->error.code = (ec); \
+ (wc)->error.details.field = (value); \
+ (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
+
+
+/**
+ * All withdraw context is kept in a DLL.
+ */
+static struct WithdrawContext *wc_head;
+static struct WithdrawContext *wc_tail;
+
+
+void
+TEH_withdraw_cleanup ()
+{
+ struct WithdrawContext *wc;
+
+ while (NULL != (wc = wc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (wc_head,
+ wc_tail,
+ wc);
+ wc->phase = WITHDRAW_PHASE_RETURN_NO;
+ MHD_resume_connection (wc->rc->connection);
+ }
+}
+
+
+/**
+ * Terminate the main loop by returning the final
+ * result.
+ *
+ * @param[in,out] wc context to update phase for
+ * @param mres MHD status to return
+ */
+static void
+finish_loop (struct WithdrawContext *wc,
+ MHD_RESULT mres)
+{
+ wc->phase = (MHD_YES == mres)
+ ? WITHDRAW_PHASE_RETURN_YES
+ : WITHDRAW_PHASE_RETURN_NO;
+}
+
+
+/**
+ * Check if the withdraw request is replayed
+ * and we already have an answer.
+ * If so, replay the existing answer and return the HTTP response.
+ *
+ * @param[in,out] wc parsed request data
+ * @return true if the request is idempotent with an existing request
+ * false if we did not find the request in the DB and did not set @a mret
+ */
+static bool
+withdraw_is_idempotent (
+ struct WithdrawContext *wc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint8_t max_retries = 3;
+
+ /* We should at most be called once */
+ GNUNET_assert (! wc->request.is_idempotent);
+ while (0 < max_retries--)
+ {
+ qs = TEH_plugin->get_withdraw (
+ TEH_plugin->cls,
+ &wc->request.withdraw.planchets_h,
+ &wc->request.withdraw_idem);
+ if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
+ break;
+ }
+
+ if (0 > qs)
+ {
+ GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
+ GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
+ SET_ERROR_WITH_DETAIL (wc,
+ WITHDRAW_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "get_withdraw");
+ return true; /* Well, kind-of. */
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return false;
+
+ wc->request.is_idempotent = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "request is idempotent\n");
+
+ /* Generate idempotent reply */
+ TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
+ wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
+ return true;
+}
+
+
+/**
+ * Function implementing withdraw transaction. Runs the
+ * transaction logic; IF it returns a non-error code, the transaction
+ * logic MUST NOT queue a MHD response. IF it returns an hard error,
+ * the transaction logic MUST queue a MHD response and set @a mhd_ret.
+ * IF it returns the soft error code, the function MAY be called again
+ * to retry and MUST not queue a MHD response.
+ *
+ * @param cls a `struct WithdrawContext *`
+ * @param connection MHD request which triggered the transaction
+ * @param[out] mhd_ret set to MHD response status for @a connection,
+ * if transaction failed (!)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+withdraw_transaction (
+ void *cls,
+ struct MHD_Connection *connection,
+ MHD_RESULT *mhd_ret)
+{
+ struct WithdrawContext *wc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ bool balance_ok;
+ bool age_ok;
+ bool found;
+ uint16_t noreveal_index;
+ bool nonce_reuse;
+ uint16_t allowed_maximum_age;
+ uint32_t reserve_birthday;
+ struct TALER_Amount insufficient_funds;
+
+ qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
+ &wc->request.withdraw,
+ &wc->now,
+ &balance_ok,
+ &insufficient_funds,
+ &age_ok,
+ &allowed_maximum_age,
+ &reserve_birthday,
+ &found,
+ &noreveal_index,
+ &nonce_reuse);
+ if (0 > qs)
+ {
+ if (GNUNET_DB_STATUS_HARD_ERROR == qs)
+ SET_ERROR_WITH_DETAIL (wc,
+ WITHDRAW_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "do_withdraw");
+ return qs;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_RESERVE_UNKNOWN);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (found)
+ {
+ /**
+ * The request was idempotent and we got the previous noreveal_index.
+ * We simply overwrite that value in our current withdraw object and
+ * move on to reply success.
+ */
+ wc->request.withdraw.noreveal_index = noreveal_index;
+ wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+ }
+
+ if (! age_ok)
+ {
+ if (wc->request.withdraw.age_proof_required)
+ {
+ wc->error.details.maximum_age_too_large.max_allowed = allowed_maximum_age;
+ wc->error.details.maximum_age_too_large.birthday = reserve_birthday;
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE);
+ }
+ else
+ {
+ wc->error.details.age_restriction_required = allowed_maximum_age;
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED);
+ }
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (! balance_ok)
+ {
+ TEH_plugin->rollback (TEH_plugin->cls);
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
+ insufficient_funds);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (nonce_reuse)
+ {
+ GNUNET_break (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_NONCE_REUSE);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
+ return qs;
+}
+
+
+/**
+ * The request was prepared successfully.
+ * Run the main DB transaction.
+ *
+ * @param wc The context for the current withdraw request
+ */
+static void
+phase_run_transaction (
+ struct WithdrawContext *wc)
+{
+ MHD_RESULT mhd_ret;
+ enum GNUNET_GenericReturnValue qs;
+
+ GNUNET_assert (WITHDRAW_PHASE_RUN_TRANSACTION ==
+ wc->phase);
+ qs = TEH_DB_run_transaction (wc->rc->connection,
+ "run withdraw",
+ TEH_MT_REQUEST_WITHDRAW,
+ &mhd_ret,
+ &withdraw_transaction,
+ wc);
+ if (WITHDRAW_PHASE_RUN_TRANSACTION != wc->phase)
+ return;
+ GNUNET_break (GNUNET_OK == qs);
+ /* If the transaction has changed the phase, we don't alter it and return.*/
+ wc->phase++;
+}
+
+
+/**
+ * The request for withdraw was parsed successfully.
+ * Sign and persist the chosen blinded coins for the reveal step.
+ *
+ * @param wc The context for the current withdraw request
+ */
+static void
+phase_prepare_transaction (
+ struct WithdrawContext *wc)
+{
+ size_t offset = 0;
+
+ wc->request.withdraw.denom_sigs
+ = GNUNET_new_array (
+ wc->request.withdraw.num_coins,
+ struct TALER_BlindedDenominationSignature);
+ /* Pick the challenge in case of age restriction */
+ if (wc->request.withdraw.age_proof_required)
+ {
+ wc->request.withdraw.noreveal_index =
+ GNUNET_CRYPTO_random_u32 (
+ GNUNET_CRYPTO_QUALITY_STRONG,
+ TALER_CNC_KAPPA);
+ /**
+ * In case of age restriction, we use the corresponding offset in the planchet
+ * array to the beginning of the coins corresponding to the noreveal_index.
+ */
+ offset = wc->request.withdraw.noreveal_index
+ * wc->request.withdraw.num_coins;
+ GNUNET_assert (offset + wc->request.withdraw.num_coins <=
+ wc->request.num_planchets);
+ }
+
+ /* Choose and sign the coins */
+ {
+ struct TEH_CoinSignData csds[wc->request.withdraw.num_coins];
+ enum TALER_ErrorCode ec_denomination_sign;
+
+ memset (csds,
+ 0,
+ sizeof(csds));
+
+ /* Pick the chosen blinded coins */
+ for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++)
+ {
+ csds[i].bp = &wc->request.planchets[i + offset];
+ csds[i].h_denom_pub = &wc->request.denoms_h[i];
+ }
+
+ ec_denomination_sign = TEH_keys_denomination_batch_sign (
+ wc->request.withdraw.num_coins,
+ csds,
+ false,
+ wc->request.withdraw.denom_sigs);
+ if (TALER_EC_NONE != ec_denomination_sign)
+ {
+ GNUNET_break (0);
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_DENOMINATION_SIGN,
+ ec_denomination_sign);
+ return;
+ }
+
+ /* Save the hash value of the selected batch of coins */
+ wc->request.withdraw.selected_h =
+ wc->request.kappa_planchets_h[wc->request.withdraw.noreveal_index];
+ }
+
+ /**
+ * For the denominations with cipher CS, calculate the R-values
+ * and save the choices we made now, as at a later point, the
+ * private keys for the denominations might now be available anymore
+ * to make the same choice again.
+ */
+ if (0 < wc->request.withdraw.num_cs_r_values)
+ {
+ size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values;
+ struct TEH_CsDeriveData cdds[num_cs_r_values];
+ struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
+
+ memset (nonces,
+ 0,
+ sizeof(nonces));
+ wc->request.withdraw.cs_r_values
+ = GNUNET_new_array (
+ num_cs_r_values,
+ struct GNUNET_CRYPTO_CSPublicRPairP);
+ wc->request.withdraw.cs_r_choices = 0;
+
+ GNUNET_assert (! wc->request.withdraw.no_blinding_seed);
+ TALER_cs_derive_nonces_from_seed (
+ &wc->request.withdraw.blinding_seed,
+ false, /* not for melt */
+ num_cs_r_values,
+ wc->request.cs_indices,
+ nonces);
+
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = wc->request.cs_indices[i];
+
+ GNUNET_assert (idx < wc->request.withdraw.num_coins);
+ cdds[i].h_denom_pub = &wc->request.denoms_h[idx];
+ cdds[i].nonce = &nonces[i];
+ }
+
+ /**
+ * Let the crypto helper generate the R-values and make the choices.
+ */
+ if (TALER_EC_NONE !=
+ TEH_keys_denomination_cs_batch_r_pub_simple (
+ wc->request.withdraw.num_cs_r_values,
+ cdds,
+ false,
+ wc->request.withdraw.cs_r_values))
+ {
+ GNUNET_break (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_CRYPTO_HELPER);
+ return;
+ }
+
+ /* Now save the choices for the selected bits */
+ for (size_t i = 0; i < num_cs_r_values; i++)
+ {
+ size_t idx = wc->request.cs_indices[i];
+
+ struct TALER_BlindedDenominationSignature *sig =
+ &wc->request.withdraw.denom_sigs[idx];
+ uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
+
+ wc->request.withdraw.cs_r_choices |= bit << i;
+ GNUNET_static_assert (
+ TALER_MAX_COINS <=
+ sizeof(wc->request.withdraw.cs_r_choices) * 8);
+ }
+ }
+ wc->phase++;
+}
+
+
+/**
+ * Check the KYC result.
+ *
+ * @param wc context for request processing
+ */
+static void
+phase_check_kyc_result (struct WithdrawContext *wc)
+{
+ /* return final positive response */
+ if (! wc->kyc.ok)
+ {
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_KYC_REQUIRED);
+ return;
+ }
+ wc->phase++;
+}
+
+
+/**
+ * Function called with the result of a legitimization
+ * check.
+ *
+ * @param cls closure
+ * @param lcr legitimization check result
+ */
+static void
+withdraw_legi_cb (
+ void *cls,
+ const struct TEH_LegitimizationCheckResult *lcr)
+{
+ struct WithdrawContext *wc = cls;
+
+ wc->lch = NULL;
+ GNUNET_assert (WITHDRAW_PHASE_SUSPENDED ==
+ wc->phase);
+ MHD_resume_connection (wc->rc->connection);
+ GNUNET_CONTAINER_DLL_remove (wc_head,
+ wc_tail,
+ wc);
+ TALER_MHD_daemon_trigger ();
+ if (NULL != lcr->response)
+ {
+ wc->error.details.legitimization_result.response = lcr->response;
+ wc->error.details.legitimization_result.http_status = lcr->http_status;
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_LEGITIMIZATION_RESULT);
+ return;
+ }
+ wc->kyc = lcr->kyc;
+ wc->phase = WITHDRAW_PHASE_CHECK_KYC_RESULT;
+}
+
+
+/**
+ * Function called to iterate over KYC-relevant transaction amounts for a
+ * particular time range. Called within a database transaction, so must
+ * not start a new one.
+ *
+ * @param cls closure, identifies the event type and account to iterate
+ * over events for
+ * @param limit maximum time-range for which events should be fetched
+ * (timestamp in the past)
+ * @param cb function to call on each event found, events must be returned
+ * in reverse chronological order
+ * @param cb_cls closure for @a cb, of type struct WithdrawContext
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+withdraw_amount_cb (
+ void *cls,
+ struct GNUNET_TIME_Absolute limit,
+ TALER_EXCHANGEDB_KycAmountCallback cb,
+ void *cb_cls)
+{
+ struct WithdrawContext *wc = cls;
+ enum GNUNET_GenericReturnValue ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Signaling amount %s for KYC check during witdrawal\n",
+ TALER_amount2s (&wc->request.withdraw.amount_with_fee));
+
+ ret = cb (cb_cls,
+ &wc->request.withdraw.amount_with_fee,
+ wc->now.abs_time);
+ GNUNET_break (GNUNET_SYSERR != ret);
+ if (GNUNET_OK != ret)
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+
+ qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
+ TEH_plugin->cls,
+ &wc->h_normalized_payto,
+ limit,
+ cb,
+ cb_cls);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Got %d additional transactions for this withdrawal and limit %llu\n",
+ qs,
+ (unsigned long long) limit.abs_value_us);
+ GNUNET_break (qs >= 0);
+ return qs;
+}
+
+
+/**
+ * Do legitimization check.
+ *
+ * @param wc operation context
+ */
+static void
+phase_run_legi_check (struct WithdrawContext *wc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct TALER_FullPayto payto_uri;
+ struct TALER_FullPaytoHashP h_full_payto;
+
+ /* Check if the money came from a wire transfer */
+ qs = TEH_plugin->reserves_get_origin (
+ TEH_plugin->cls,
+ &wc->request.withdraw.reserve_pub,
+ &h_full_payto,
+ &payto_uri);
+ if (qs < 0)
+ {
+ SET_ERROR_WITH_DETAIL (wc,
+ WITHDRAW_ERROR_DB_FETCH_FAILED,
+ db_fetch_context,
+ "reserves_get_origin");
+ return;
+ }
+ /* If _no_ results, reserve was created by merge,
+ in which case no KYC check is required as the
+ merge already did that. */
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION;
+ return;
+ }
+ TALER_full_payto_normalize_and_hash (payto_uri,
+ &wc->h_normalized_payto);
+ wc->lch = TEH_legitimization_check (
+ &wc->rc->async_scope_id,
+ TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
+ payto_uri,
+ &wc->h_normalized_payto,
+ NULL, /* no account pub: this is about the origin account */
+ &withdraw_amount_cb,
+ wc,
+ &withdraw_legi_cb,
+ wc);
+ GNUNET_assert (NULL != wc->lch);
+ GNUNET_free (payto_uri.full_payto);
+ GNUNET_CONTAINER_DLL_insert (wc_head,
+ wc_tail,
+ wc);
+ MHD_suspend_connection (wc->rc->connection);
+ wc->phase = WITHDRAW_PHASE_SUSPENDED;
+}
+
+
+/**
+ * Check if the given denomination is still or already valid, has not been
+ * revoked and potentically supports age restriction.
+ *
+ * @param[in,out] wc context for the withdraw operation
+ * @param ksh The handle to the current state of (denomination) keys in the exchange
+ * @param denom_h Hash of the denomination key to check
+ * @param[out] pdk denomination key found, might be NULL
+ * @return true when denomation was found and valid,
+ * false when denomination was not valid and the state machine was advanced
+ */
+static enum GNUNET_GenericReturnValue
+find_denomination (
+ struct WithdrawContext *wc,
+ struct TEH_KeyStateHandle *ksh,
+ const struct TALER_DenominationHashP *denom_h,
+ struct TEH_DenominationKey **pdk)
+{
+ struct TEH_DenominationKey *dk;
+
+ *pdk = NULL;
+ dk = TEH_keys_denomination_by_hash_from_state (
+ ksh,
+ denom_h,
+ NULL,
+ NULL);
+ if (NULL == dk)
+ {
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
+ denom_h);
+ return false;
+ }
+ if (GNUNET_TIME_absolute_is_past (
+ dk->meta.expire_withdraw.abs_time))
+ {
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_DENOMINATION_EXPIRED,
+ denom_h);
+ return false;
+ }
+ if (GNUNET_TIME_absolute_is_future (
+ dk->meta.start.abs_time))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
+ denom_h);
+ return false;
+ }
+ if (dk->recoup_possible)
+ {
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_DENOMINATION_REVOKED);
+ return false;
+ }
+ /* In case of age withdraw, make sure that the denomination supports age restriction */
+ if (wc->request.withdraw.age_proof_required)
+ {
+ if (0 == dk->denom_pub.age_mask.bits)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
+ denom_h);
+ return false;
+ }
+ }
+ *pdk = dk;
+ return true;
+}
+
+
+/**
+ * Check if the given array of hashes of denomination_keys
+ * a) belong to valid denominations
+ * b) those are marked as age restricted, if the request is age restricted
+ * c) calculate the total amount of the denominations including fees
+ * for withdraw.
+ *
+ * @param wc context of the age withdrawal to check keys for
+ */
+static void
+phase_check_keys (
+ struct WithdrawContext *wc)
+{
+ struct TEH_KeyStateHandle *ksh;
+ bool is_cs_denom[wc->request.withdraw.num_coins];
+
+ memset (is_cs_denom,
+ 0,
+ sizeof(is_cs_denom));
+ ksh = TEH_keys_get_state ();
+ if (NULL == ksh)
+ {
+ GNUNET_break (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_KEYS_MISSING);
+ return;
+ }
+ wc->request.withdraw.denom_serials =
+ GNUNET_new_array (wc->request.withdraw.num_coins,
+ uint64_t);
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &wc->request.amount));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &wc->request.fee));
+ GNUNET_assert (GNUNET_OK ==
+ TALER_amount_set_zero (TEH_currency,
+ &wc->request.withdraw.amount_with_fee));
+
+ for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++)
+ {
+ struct TEH_DenominationKey *dk;
+
+ if (! find_denomination (wc,
+ ksh,
+ &wc->request.denoms_h[i],
+ &dk))
+ return;
+ switch (dk->denom_pub.bsign_pub_key->cipher)
+ {
+ case GNUNET_CRYPTO_BSA_INVALID:
+ /* This should never happen (memory corruption?) */
+ GNUNET_assert (0);
+ case GNUNET_CRYPTO_BSA_RSA:
+ /* nothing to do here */
+ break;
+ case GNUNET_CRYPTO_BSA_CS:
+ if (wc->request.withdraw.no_blinding_seed)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_BLINDING_SEED_REQUIRED);
+ return;
+ }
+ wc->request.withdraw.num_cs_r_values++;
+ is_cs_denom[i] = true;
+ break;
+ }
+
+ /* Ensure the ciphers from the planchets match the denominations'. */
+ if (wc->request.withdraw.age_proof_required)
+ {
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ size_t off = k * wc->request.withdraw.num_coins;
+
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ wc->request.planchets[i + off].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_CIPHER_MISMATCH);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (dk->denom_pub.bsign_pub_key->cipher !=
+ wc->request.planchets[i].blinded_message->cipher)
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_CIPHER_MISMATCH);
+ return;
+ }
+ }
+
+ /* Accumulate the values */
+ if (0 > TALER_amount_add (&wc->request.amount,
+ &wc->request.amount,
+ &dk->meta.value))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_AMOUNT_OVERFLOW);
+ return;
+ }
+
+ /* Accumulate the withdraw fees */
+ if (0 > TALER_amount_add (&wc->request.fee,
+ &wc->request.fee,
+ &dk->meta.fees.withdraw))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_FEE_OVERFLOW);
+ return;
+ }
+ wc->request.withdraw.denom_serials[i] = dk->meta.serial;
+ }
+
+ /* Save the hash of the batch of planchets */
+ if (! wc->request.withdraw.age_proof_required)
+ {
+ TALER_wallet_blinded_planchets_hash (
+ wc->request.withdraw.num_coins,
+ wc->request.planchets,
+ wc->request.denoms_h,
+ &wc->request.withdraw.planchets_h);
+ }
+ else
+ {
+ struct GNUNET_HashContext *ctx;
+
+ /**
+ * The age-proof-required case is a bit more involved,
+ * because we need to calculate and remember kappa hashes
+ * for each batch of coins.
+ */
+ ctx = GNUNET_CRYPTO_hash_context_start ();
+ GNUNET_assert (NULL != ctx);
+
+ for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
+ {
+ size_t off = k * wc->request.withdraw.num_coins;
+
+ TALER_wallet_blinded_planchets_hash (
+ wc->request.withdraw.num_coins,
+ &wc->request.planchets[off],
+ wc->request.denoms_h,
+ &wc->request.kappa_planchets_h[k]);
+ GNUNET_CRYPTO_hash_context_read (
+ ctx,
+ &wc->request.kappa_planchets_h[k],
+ sizeof(wc->request.kappa_planchets_h[k]));
+ }
+ GNUNET_CRYPTO_hash_context_finish (
+ ctx,
+ &wc->request.withdraw.planchets_h.hash);
+ }
+
+ /* Save the total amount including fees */
+ if (0 > TALER_amount_add (
+ &wc->request.withdraw.amount_with_fee,
+ &wc->request.amount,
+ &wc->request.fee))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
+ return;
+ }
+
+ /* Save the indices of CS denominations */
+ if (0 < wc->request.withdraw.num_cs_r_values)
+ {
+ size_t j = 0;
+
+ wc->request.cs_indices = GNUNET_new_array (
+ wc->request.withdraw.num_cs_r_values,
+ uint32_t);
+
+ for (size_t i = 0; i < wc->request.withdraw.num_coins; i++)
+ {
+ if (is_cs_denom[i])
+ wc->request.cs_indices[j++] = i;
+ }
+ }
+
+ wc->phase++;
+}
+
+
+/**
+ * Check that the client signature authorizing the withdrawal is valid.
+ *
+ * @param[in,out] wc request context to check
+ */
+static void
+phase_check_reserve_signature (
+ struct WithdrawContext *wc)
+{
+ TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
+ if (GNUNET_OK !=
+ TALER_wallet_withdraw_verify (
+ &wc->request.amount,
+ &wc->request.fee,
+ &wc->request.withdraw.planchets_h,
+ wc->request.withdraw.no_blinding_seed
+ ? NULL
+ : &wc->request.withdraw.blinding_seed,
+ (wc->request.withdraw.age_proof_required)
+ ? &TEH_age_restriction_config.mask
+ : NULL,
+ (wc->request.withdraw.age_proof_required)
+ ? wc->request.withdraw.max_age
+ : 0,
+ &wc->request.withdraw.reserve_pub,
+ &wc->request.withdraw.reserve_sig))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID);
+ return;
+ }
+ wc->phase++;
+}
+
+
+/**
+ * Free data inside of @a wd, but not @a wd itself.
+ *
+ * @param[in] wd withdraw data to free
+ */
+static void
+free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd)
+{
+ if (NULL != wd->denom_sigs)
+ {
+ for (unsigned int i = 0; i<wd->num_coins; i++)
+ TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
+ GNUNET_free (wd->denom_sigs);
+ }
+ GNUNET_free (wd->denom_serials);
+ GNUNET_free (wd->cs_r_values);
+}
+
+
+/**
+ * Cleanup routine for withdraw request.
+ * The function is called upon completion of the request
+ * that should clean up @a rh_ctx. Can be NULL.
+ *
+ * @param rc request context to clean up
+ */
+static void
+clean_withdraw_rc (struct TEH_RequestContext *rc)
+{
+ struct WithdrawContext *wc = rc->rh_ctx;
+
+ if (NULL != wc->lch)
+ {
+ TEH_legitimization_check_cancel (wc->lch);
+ wc->lch = NULL;
+ }
+ GNUNET_free (wc->request.denoms_h);
+ for (unsigned int i = 0; i<wc->request.num_planchets; i++)
+ TALER_blinded_planchet_free (&wc->request.planchets[i]);
+ GNUNET_free (wc->request.planchets);
+ free_db_withdraw_data (&wc->request.withdraw);
+ GNUNET_free (wc->request.cs_indices);
+ if (wc->request.is_idempotent)
+ free_db_withdraw_data (&wc->request.withdraw_idem);
+ if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) &&
+ (NULL != wc->error.details.legitimization_result.response) )
+ {
+ MHD_destroy_response (wc->error.details.legitimization_result.response);
+ wc->error.details.legitimization_result.response = NULL;
+ }
+ GNUNET_free (wc);
+}
+
+
+/**
+ * Generates response for the withdraw request.
+ *
+ * @param wc withdraw operation context
+ */
+static void
+phase_generate_reply_success (struct WithdrawContext *wc)
+{
+ struct TALER_EXCHANGEDB_Withdraw *db_obj;
+
+ db_obj = wc->request.is_idempotent
+ ? &wc->request.withdraw_idem
+ : &wc->request.withdraw;
+
+ if (wc->request.withdraw.age_proof_required)
+ {
+ struct TALER_ExchangePublicKeyP pub;
+ struct TALER_ExchangeSignatureP sig;
+ enum TALER_ErrorCode ec_confirmation_sign;
+
+ ec_confirmation_sign =
+ TALER_exchange_online_withdraw_age_confirmation_sign (
+ &TEH_keys_exchange_sign_,
+ &db_obj->planchets_h,
+ db_obj->noreveal_index,
+ &pub,
+ &sig);
+ if (TALER_EC_NONE != ec_confirmation_sign)
+ {
+ SET_ERROR_WITH_FIELD (wc,
+ WITHDRAW_ERROR_CONFIRMATION_SIGN,
+ ec_confirmation_sign);
+ return;
+ }
+
+ finish_loop (wc,
+ TALER_MHD_REPLY_JSON_PACK (
+ wc->rc->connection,
+ MHD_HTTP_CREATED,
+ GNUNET_JSON_pack_uint64 ("noreveal_index",
+ db_obj->noreveal_index),
+ GNUNET_JSON_pack_data_auto ("exchange_sig",
+ &sig),
+ GNUNET_JSON_pack_data_auto ("exchange_pub",
+ &pub)));
+ }
+ else /* not age restricted */
+ {
+ json_t *sigs;
+
+ sigs = json_array ();
+ GNUNET_assert (NULL != sigs);
+ for (unsigned int i = 0; i<db_obj->num_coins; i++)
+ {
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (
+ sigs,
+ GNUNET_JSON_PACK (
+ TALER_JSON_pack_blinded_denom_sig (
+ NULL,
+ &db_obj->denom_sigs[i]))));
+ }
+ finish_loop (wc,
+ TALER_MHD_REPLY_JSON_PACK (
+ wc->rc->connection,
+ MHD_HTTP_OK,
+ GNUNET_JSON_pack_array_steal ("ev_sigs",
+ sigs)));
+ }
+
+ TEH_METRICS_withdraw_num_coins += db_obj->num_coins;
+}
+
+
+/**
+ * Reports an error, potentially with details.
+ * That is, it puts a error-type specific response into the MHD queue.
+ * It will do a idempotency check first, if needed for the error type.
+ *
+ * @param wc withdraw context
+ */
+static void
+phase_generate_reply_error (
+ struct WithdrawContext *wc)
+{
+ GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase);
+ if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) &&
+ withdraw_is_idempotent (wc))
+ {
+ return;
+ }
+
+ switch (wc->error.code)
+ {
+ case WITHDRAW_ERROR_NONE:
+ break;
+ case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ wc->error.details.request_parameter_malformed));
+ return;
+ case WITHDRAW_ERROR_KEYS_MISSING:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_DB_FETCH_FAILED:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ wc->error.details.db_fetch_context));
+ return;
+ case WITHDRAW_ERROR_DB_INVARIANT_FAILURE:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_RESERVE_UNKNOWN:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_DENOMINATION_SIGN:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ wc->error.details.ec_denomination_sign,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_KYC_REQUIRED:
+ finish_loop (wc,
+ TEH_RESPONSE_reply_kyc_required (
+ wc->rc->connection,
+ &wc->h_normalized_payto,
+ &wc->kyc,
+ false));
+ return;
+ case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN:
+ GNUNET_break_op (0);
+ finish_loop (wc,
+ TEH_RESPONSE_reply_unknown_denom_pub_hash (
+ wc->rc->connection,
+ wc->error.details.denom_h));
+ return;
+ case WITHDRAW_ERROR_DENOMINATION_EXPIRED:
+ GNUNET_break_op (0);
+ finish_loop (wc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ wc->rc->connection,
+ wc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
+ "WITHDRAW"));
+ return;
+ case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
+ finish_loop (wc,
+ TEH_RESPONSE_reply_expired_denom_pub_hash (
+ wc->rc->connection,
+ wc->error.details.denom_h,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
+ "WITHDRAW"));
+ return;
+ case WITHDRAW_ERROR_DENOMINATION_REVOKED:
+ GNUNET_break_op (0);
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_CIPHER_MISMATCH:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_GENERIC_PARAMETER_MISSING,
+ "blinding_seed"));
+ return;
+ case WITHDRAW_ERROR_CRYPTO_HELPER:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
+ "cipher"));
+ return;
+ case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
+ {
+ char msg[256];
+
+ GNUNET_snprintf (msg,
+ sizeof(msg),
+ "denomination %s does not support age restriction",
+ GNUNET_h2s (&wc->error.details.denom_h->hash));
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
+ msg));
+ return;
+ }
+ case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Generating JSON response with code %d\n",
+ (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE);
+ finish_loop (wc,
+ TALER_MHD_REPLY_JSON_PACK (
+ wc->rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_MHD_PACK_EC (
+ TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE),
+ GNUNET_JSON_pack_uint64 (
+ "allowed_maximum_age",
+ wc->error.details.maximum_age_too_large.max_allowed),
+ GNUNET_JSON_pack_uint64 (
+ "reserve_birthday",
+ wc->error.details.maximum_age_too_large.birthday)));
+ return;
+ case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED:
+ finish_loop (wc,
+ TEH_RESPONSE_reply_reserve_age_restriction_required (
+ wc->rc->connection,
+ wc->error.details.age_restriction_required));
+ return;
+ case WITHDRAW_ERROR_AMOUNT_OVERFLOW:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "amount"));
+ return;
+ case WITHDRAW_ERROR_FEE_OVERFLOW:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
+ "fee"));
+ return;
+ case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
+ "amount+fee"));
+ return;
+ case WITHDRAW_ERROR_CONFIRMATION_SIGN:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ wc->error.details.ec_confirmation_sign,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_INSUFFICIENT_FUNDS:
+ finish_loop (wc,
+ TEH_RESPONSE_reply_reserve_insufficient_balance (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
+ &wc->error.details.insufficient_funds,
+ &wc->request.withdraw.amount_with_fee,
+ &wc->request.withdraw.reserve_pub));
+ return;
+ case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_NONCE_REUSE:
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID:
+ finish_loop (wc,
+ TALER_MHD_reply_with_ec (
+ wc->rc->connection,
+ TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
+ NULL));
+ return;
+ case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: {
+ finish_loop (
+ wc,
+ MHD_queue_response (wc->rc->connection,
+ wc->error.details.legitimization_result.http_status,
+ wc->error.details.legitimization_result.response));
+ return;
+ }
+ }
+ GNUNET_break (0);
+ finish_loop (wc,
+ TALER_MHD_reply_with_error (
+ wc->rc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "error phase without error"));
+}
+
+
+/**
+ * Initializes the new context for the incoming withdraw request
+ *
+ * @param[in,out] wc withdraw request context
+ * @param root json body of the request
+ */
+static void
+withdraw_phase_parse (
+ struct WithdrawContext *wc,
+ const json_t *root)
+{
+ const json_t *j_denoms_h;
+ const json_t *j_coin_evs;
+ const char *cipher;
+ bool no_max_age;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_string ("cipher",
+ &cipher),
+ GNUNET_JSON_spec_fixed_auto ("reserve_pub",
+ &wc->request.withdraw.reserve_pub),
+ GNUNET_JSON_spec_array_const ("denoms_h",
+ &j_denoms_h),
+ GNUNET_JSON_spec_array_const ("coin_evs",
+ &j_coin_evs),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_uint16 ("max_age",
+ &wc->request.withdraw.max_age),
+ &no_max_age),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_fixed_auto ("blinding_seed",
+ &wc->request.withdraw.blinding_seed),
+ &wc->request.withdraw.no_blinding_seed),
+ GNUNET_JSON_spec_fixed_auto ("reserve_sig",
+ &wc->request.withdraw.reserve_sig),
+ GNUNET_JSON_spec_end ()
+ };
+ enum GNUNET_GenericReturnValue res;
+
+ res = TALER_MHD_parse_json_data (wc->rc->connection,
+ root,
+ spec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ wc->phase = (GNUNET_SYSERR == res)
+ ? WITHDRAW_PHASE_RETURN_NO
+ : WITHDRAW_PHASE_RETURN_YES;
+ return;
+ }
+
+ /* For now, we only support cipher "ED25519" for signatures by the reserve */
+ if (0 != strcmp ("ED25519",
+ cipher))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (wc,
+ WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
+ reserve_cipher_unknown,
+ cipher);
+ return;
+ }
+
+ wc->request.withdraw.age_proof_required = ! no_max_age;
+
+ if (wc->request.withdraw.age_proof_required)
+ {
+ /* The age value MUST be on the beginning of an age group */
+ if (wc->request.withdraw.max_age !=
+ TALER_get_lowest_age (&TEH_age_restriction_config.mask,
+ wc->request.withdraw.max_age))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR_WITH_DETAIL (
+ wc,
+ WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ "max_age must be the lower edge of an age group");
+ return;
+ }
+ }
+
+ /* validate array size */
+ {
+ size_t num_coins = json_array_size (j_denoms_h);
+ size_t array_size = json_array_size (j_coin_evs);
+ const char *error;
+
+ GNUNET_static_assert (
+ TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
+
+#define BAIL_IF(cond, msg) \
+ if ((cond)) { \
+ GNUNET_break_op (0); \
+ error = (msg); break; \
+ }
+
+ do {
+ BAIL_IF (0 == num_coins,
+ "denoms_h must not be empty")
+
+ /**
+ * The wallet had committed to more than the maximum coins allowed, the
+ * reserve has been charged, but now the user can not withdraw any money
+ * from it. Note that the user can't get their money back in this case!
+ */
+ BAIL_IF (num_coins > TALER_MAX_COINS,
+ "maximum number of coins that can be withdrawn has been exceeded")
+
+ BAIL_IF ((! wc->request.withdraw.age_proof_required) &&
+ (num_coins != array_size),
+ "denoms_h and coin_evs must be arrays of the same size")
+
+ BAIL_IF (wc->request.withdraw.age_proof_required &&
+ ((TALER_CNC_KAPPA * num_coins) != array_size),
+ "coin_evs must be an array of length "
+ TALER_CNC_KAPPA_STR
+ "*len(denoms_h)")
+
+ wc->request.withdraw.num_coins = num_coins;
+ wc->request.num_planchets = array_size;
+ error = NULL;
+
+ } while (0);
+#undef BAIL_IF
+
+ if (NULL != error)
+ {
+ SET_ERROR_WITH_DETAIL (wc,
+ WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
+ request_parameter_malformed,
+ error);
+ return;
+ }
+ }
+ /* extract the denomination hashes */
+ {
+ size_t idx;
+ json_t *value;
+
+ wc->request.denoms_h
+ = GNUNET_new_array (wc->request.withdraw.num_coins,
+ struct TALER_DenominationHashP);
+
+ json_array_foreach (j_denoms_h, idx, value) {
+ struct GNUNET_JSON_Specification ispec[] = {
+ GNUNET_JSON_spec_fixed_auto (NULL,
+ &wc->request.denoms_h[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (wc->rc->connection,
+ value,
+ ispec);
+ if (GNUNET_YES != res)
+ {
+ GNUNET_break_op (0);
+ wc->phase = (GNUNET_SYSERR == res)
+ ? WITHDRAW_PHASE_RETURN_NO
+ : WITHDRAW_PHASE_RETURN_YES;
+ return;
+ }
+ }
+ }
+ /* Parse the blinded coin envelopes */
+ {
+ json_t *j_cev;
+ size_t idx;
+
+ wc->request.planchets =
+ GNUNET_new_array (wc->request.num_planchets,
+ struct TALER_BlindedPlanchet);
+ json_array_foreach (j_coin_evs, idx, j_cev)
+ {
+ /* Now parse the individual envelopes and calculate the hash of
+ * the commitment along the way. */
+ struct GNUNET_JSON_Specification kspec[] = {
+ TALER_JSON_spec_blinded_planchet (NULL,
+ &wc->request.planchets[idx]),
+ GNUNET_JSON_spec_end ()
+ };
+
+ res = TALER_MHD_parse_json_data (wc->rc->connection,
+ j_cev,
+ kspec);
+ if (GNUNET_OK != res)
+ {
+ GNUNET_break_op (0);
+ wc->phase = (GNUNET_SYSERR == res)
+ ? WITHDRAW_PHASE_RETURN_NO
+ : WITHDRAW_PHASE_RETURN_YES;
+ return;
+ }
+
+ /* Check for duplicate planchets. Technically a bug on
+ * the client side that is harmless for us, but still
+ * not allowed per protocol */
+ for (size_t i = 0; i < idx; i++)
+ {
+ if (0 ==
+ TALER_blinded_planchet_cmp (
+ &wc->request.planchets[idx],
+ &wc->request.planchets[i]))
+ {
+ GNUNET_break_op (0);
+ SET_ERROR (wc,
+ WITHDRAW_ERROR_IDEMPOTENT_PLANCHET);
+ return;
+ }
+ } /* end duplicate check */
+ } /* json_array_foreach over j_coin_evs */
+ } /* scope of j_kappa_planchets, idx */
+ wc->phase = WITHDRAW_PHASE_CHECK_KEYS;
+}
+
+
+MHD_RESULT
+TEH_handler_withdraw (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0])
+{
+ struct WithdrawContext *wc = rc->rh_ctx;
+
+ (void) args;
+ if (NULL == wc)
+ {
+ wc = GNUNET_new (struct WithdrawContext);
+ rc->rh_ctx = wc;
+ rc->rh_cleaner = &clean_withdraw_rc;
+ wc->rc = rc;
+ wc->now = GNUNET_TIME_timestamp_get ();
+ }
+ while (true)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "withdrawal%s processing in phase %d\n",
+ wc->request.withdraw.age_proof_required
+ ? " (with required age proof)"
+ : "",
+ wc->phase);
+ switch (wc->phase)
+ {
+ case WITHDRAW_PHASE_PARSE:
+ withdraw_phase_parse (wc,
+ root);
+ break;
+ case WITHDRAW_PHASE_CHECK_KEYS:
+ phase_check_keys (wc);
+ break;
+ case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE:
+ phase_check_reserve_signature (wc);
+ break;
+ case WITHDRAW_PHASE_RUN_LEGI_CHECK:
+ phase_run_legi_check (wc);
+ break;
+ case WITHDRAW_PHASE_SUSPENDED:
+ return MHD_YES;
+ case WITHDRAW_PHASE_CHECK_KYC_RESULT:
+ phase_check_kyc_result (wc);
+ break;
+ case WITHDRAW_PHASE_PREPARE_TRANSACTION:
+ phase_prepare_transaction (wc);
+ break;
+ case WITHDRAW_PHASE_RUN_TRANSACTION:
+ phase_run_transaction (wc);
+ break;
+ case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS:
+ phase_generate_reply_success (wc);
+ break;
+ case WITHDRAW_PHASE_GENERATE_REPLY_ERROR:
+ phase_generate_reply_error (wc);
+ break;
+ case WITHDRAW_PHASE_RETURN_YES:
+ return MHD_YES;
+ case WITHDRAW_PHASE_RETURN_NO:
+ return MHD_NO;
+ }
+ }
+}
diff --git a/src/exchange/taler-exchange-httpd_post-withdraw.h b/src/exchange/taler-exchange-httpd_post-withdraw.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2025 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_post-withdraw.h
+ * @brief Handle /withdraw requests
+ * @note This endpoint was introduced in v26 of the protocol.
+ * @author Özgür Kesim
+ */
+#ifndef TALER_EXCHANGE_HTTPD_POST_WITHDRAW_H
+#define TALER_EXCHANGE_HTTPD_POST_WITHDRAW_H
+
+#include <microhttpd.h>
+#include "taler-exchange-httpd.h"
+
+/**
+ * Resume suspended connections, we are shutting down.
+ */
+void
+TEH_withdraw_cleanup (void);
+
+/**
+ * @brief Handle a "/withdraw" request.
+ * @note This endpoint was introduced in v24 of the protocol.
+ *
+ * Parses the batch of requested "denom_pub" which specifies
+ * the key/value of the respective coin to be withdrawn,
+ * and checks the signature "reserve_sig" for given "reserve_pub"
+ * makes this a valid withdrawal request from the specific reserve.
+ * If the "max_age" value is set in the request,
+ * it is considered a commitment to withdraw age restricted coins.
+ * If the request is valid, the response contains a noreveal_index
+ * which the client has to use for the subsequent call to /reveal-withdraw.
+ * If "max_age" value is not set, and the request is valid, the envelopes
+ * with the blinded coins "blinded_coin_evs" is processed
+ * and the client receives the blinded signatures as response.
+ *
+ * @param rc request context
+ * @param root uploaded JSON data
+ * @param args array of additional options, not used.
+ * @return MHD result code
+ */
+MHD_RESULT
+TEH_handler_withdraw (
+ struct TEH_RequestContext *rc,
+ const json_t *root,
+ const char *const args[0]);
+
+#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_create.c b/src/exchange/taler-exchange-httpd_purses_create.c
@@ -1,664 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_create.c
- * @brief Handle /purses/$PID/create requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_deposit.h"
-#include "taler-exchange-httpd_purses_create.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for #create_transaction.
- */
-struct PurseCreateContext
-{
-
- /**
- * Total actually deposited by all the coins.
- */
- struct TALER_Amount deposit_total;
-
- /**
- * Our current time.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Merge key for the purse.
- */
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- /**
- * Encrypted contract of for the purse.
- */
- struct TALER_EncryptedContract econtract;
-
- /**
- * Signature of the client affiming this request.
- */
- struct TALER_PurseContractSignatureP purse_sig;
-
- /**
- * Fundamental details about the purse.
- */
- struct TEH_PurseDetails pd;
-
- /**
- * Array of coins being deposited.
- */
- struct TEH_PurseDepositedCoin *coins;
-
- /**
- * Length of the @e coins array.
- */
- unsigned int num_coins;
-
- /**
- * Minimum age for deposits into this purse.
- */
- uint32_t min_age;
-
- /**
- * Do we have an @e econtract?
- */
- bool no_econtract;
-
-};
-
-
-/**
- * Execute database transaction for /purses/$PID/create. Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
- * a MHD response. IF it returns an hard error, the transaction logic MUST
- * queue a MHD response and set @a mhd_ret. IF it returns the soft error
- * code, the function MAY be called again to retry and MUST not queue a MHD
- * response.
- *
- * @param cls a `struct PurseCreateContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-create_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct PurseCreateContext *pcc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount purse_fee;
- bool in_conflict = true;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &purse_fee));
- /* 1) create purse */
- qs = TEH_plugin->insert_purse_request (
- TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &pcc->merge_pub,
- pcc->pd.purse_expiration,
- &pcc->pd.h_contract_terms,
- pcc->min_age,
- TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
- &purse_fee,
- &pcc->pd.target_amount,
- &pcc->purse_sig,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING (
- "Failed to store create purse information in database\n");
- *mhd_ret =
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse create");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (in_conflict)
- {
- struct TALER_PurseMergePublicKeyP merge_pub;
- struct GNUNET_TIME_Timestamp purse_expiration;
- struct TALER_PrivateContractHashP h_contract_terms;
- struct TALER_Amount target_amount;
- struct TALER_Amount balance;
- struct TALER_PurseContractSignatureP purse_sig;
- uint32_t min_age;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- qs = TEH_plugin->get_purse_request (TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &merge_pub,
- &purse_expiration,
- &h_contract_terms,
- &min_age,
- &target_amount,
- &balance,
- &purse_sig);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse request");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_PURSE_CREATE_CONFLICTING_META_DATA),
- TALER_JSON_pack_amount ("amount",
- &target_amount),
- GNUNET_JSON_pack_uint64 ("min_age",
- min_age),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- purse_expiration),
- GNUNET_JSON_pack_data_auto ("purse_sig",
- &purse_sig),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &merge_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* 2) deposit all coins */
- for (unsigned int i = 0; i<pcc->num_coins; i++)
- {
- struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
- bool balance_ok = false;
- bool conflict = true;
- bool too_late = true;
-
- qs = TEH_make_coin_known (&coin->cpi,
- connection,
- &coin->known_coin_id,
- mhd_ret);
- if (qs < 0)
- return qs;
- qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &coin->cpi.coin_pub,
- &coin->amount,
- &coin->coin_sig,
- &coin->amount_minus_fee,
- &balance_ok,
- &too_late,
- &conflict);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING (
- "Failed to store purse deposit information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse create deposit");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Coin %s has insufficient balance for purse deposit of amount %s\n",
- TALER_B2S (&coin->cpi.coin_pub),
- TALER_amount2s (&coin->amount));
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &coin->cpi.denom_pub_hash,
- &coin->cpi.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (too_late)
- {
- *mhd_ret
- = TALER_MHD_reply_with_ec (
- connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "too late to deposit on purse creation");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (conflict)
- {
- struct TALER_Amount amount;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_AgeCommitmentHashP phac;
- char *partner_url = NULL;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &coin->cpi.coin_pub,
- &amount,
- &h_denom_pub,
- &phac,
- &coin_sig,
- &partner_url);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- TALER_LOG_WARNING (
- "Failed to fetch purse deposit information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get purse deposit");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &coin_pub),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_pack_data_auto ("h_age_restrictions",
- &phac),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("partner_url",
- partner_url)),
- TALER_JSON_pack_amount ("amount",
- &amount));
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- /* 3) if present, persist contract */
- if (! pcc->no_econtract)
- {
- in_conflict = true;
- qs = TEH_plugin->insert_contract (TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &pcc->econtract,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING ("Failed to store purse information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse create contract");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (in_conflict)
- {
- struct TALER_EncryptedContract econtract;
- struct GNUNET_HashCode h_econtract;
-
- qs = TEH_plugin->select_contract_by_purse (
- TEH_plugin->cls,
- &pcc->pd.purse_pub,
- &econtract);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING (
- "Failed to store fetch contract information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select contract");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_CRYPTO_hash (econtract.econtract,
- econtract.econtract_size,
- &h_econtract);
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
- GNUNET_JSON_pack_data_auto ("h_econtract",
- &h_econtract),
- GNUNET_JSON_pack_data_auto ("econtract_sig",
- &econtract.econtract_sig),
- GNUNET_JSON_pack_data_auto ("contract_pub",
- &econtract.contract_pub));
- GNUNET_free (econtract.econtract);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return qs;
-}
-
-
-/**
- * Parse a coin and check signature of the coin and the denomination
- * signature over the coin.
- *
- * @param[in,out] connection our HTTP connection
- * @param[in,out] pcc request context
- * @param[out] coin coin to initialize
- * @param jcoin coin to parse
- * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
- * #GNUNET_SYSERR on failure and no error could be returned
- */
-static enum GNUNET_GenericReturnValue
-parse_coin (struct MHD_Connection *connection,
- struct PurseCreateContext *pcc,
- struct TEH_PurseDepositedCoin *coin,
- const json_t *jcoin)
-{
- enum GNUNET_GenericReturnValue iret;
-
- if (GNUNET_OK !=
- (iret = TEH_common_purse_deposit_parse_coin (connection,
- coin,
- jcoin)))
- return iret;
- if (GNUNET_OK !=
- (iret = TEH_common_deposit_check_purse_deposit (
- connection,
- coin,
- &pcc->pd.purse_pub,
- pcc->min_age)))
- return iret;
- if (0 >
- TALER_amount_add (&pcc->deposit_total,
- &pcc->deposit_total,
- &coin->amount_minus_fee))
- {
- GNUNET_break (0);
- return (MHD_YES ==
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "total deposit contribution"))
- ? GNUNET_NO
- : GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-MHD_RESULT
-TEH_handler_purses_create (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root)
-{
- struct MHD_Connection *connection = rc->connection;
- struct PurseCreateContext pcc = {
- .pd.purse_pub = *purse_pub,
- .exchange_timestamp = GNUNET_TIME_timestamp_get ()
- };
- const json_t *deposits;
- json_t *deposit;
- unsigned int idx;
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("amount",
- TEH_currency,
- &pcc.pd.target_amount),
- GNUNET_JSON_spec_uint32 ("min_age",
- &pcc.min_age),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_econtract ("econtract",
- &pcc.econtract),
- &pcc.no_econtract),
- GNUNET_JSON_spec_fixed_auto ("merge_pub",
- &pcc.merge_pub),
- GNUNET_JSON_spec_fixed_auto ("purse_sig",
- &pcc.purse_sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &pcc.pd.h_contract_terms),
- GNUNET_JSON_spec_array_const ("deposits",
- &deposits),
- GNUNET_JSON_spec_timestamp ("purse_expiration",
- &pcc.pd.purse_expiration),
- GNUNET_JSON_spec_end ()
- };
- const struct TEH_GlobalFee *gf;
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &pcc.deposit_total));
- if (GNUNET_TIME_timestamp_cmp (pcc.pd.purse_expiration,
- <,
- pcc.exchange_timestamp))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_BEFORE_NOW,
- NULL);
- }
- if (GNUNET_TIME_absolute_is_never (pcc.pd.purse_expiration.abs_time))
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_PURSE_CREATE_EXPIRATION_IS_NEVER,
- NULL);
- }
- pcc.num_coins = json_array_size (deposits);
- if ( (0 == pcc.num_coins) ||
- (pcc.num_coins > TALER_MAX_COINS) )
- {
- GNUNET_break_op (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "deposits");
- }
- {
- struct TEH_KeyStateHandle *keys;
-
- keys = TEH_keys_get_state ();
- if (NULL == keys)
- {
- GNUNET_break (0);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- }
- gf = TEH_keys_global_fee_by_time (keys,
- pcc.exchange_timestamp);
- }
- if (NULL == gf)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot create purse: global fees not configured!\n");
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
- NULL);
- }
- /* parse deposits */
- pcc.coins = GNUNET_new_array (pcc.num_coins,
- struct TEH_PurseDepositedCoin);
- json_array_foreach (deposits, idx, deposit)
- {
- enum GNUNET_GenericReturnValue res;
- struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
-
- res = parse_coin (connection,
- &pcc,
- coin,
- deposit);
- if (GNUNET_OK != res)
- {
- for (unsigned int i = 0; i<idx; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- }
-
- if (0 < TALER_amount_cmp (&gf->fees.purse,
- &pcc.deposit_total))
- {
- GNUNET_break_op (0);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_CREATE_PURSE_NEGATIVE_VALUE_AFTER_FEE,
- NULL);
- }
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
-
- if (GNUNET_OK !=
- TALER_wallet_purse_create_verify (
- pcc.pd.purse_expiration,
- &pcc.pd.h_contract_terms,
- &pcc.merge_pub,
- pcc.min_age,
- &pcc.pd.target_amount,
- &pcc.pd.purse_pub,
- &pcc.purse_sig))
- {
- TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
- NULL);
- }
- if ( (! pcc.no_econtract) &&
- (GNUNET_OK !=
- TALER_wallet_econtract_upload_verify (pcc.econtract.econtract,
- pcc.econtract.econtract_size,
- &pcc.econtract.contract_pub,
- purse_pub,
- &pcc.econtract.econtract_sig)) )
- {
- TALER_LOG_WARNING ("Invalid signature on /purses/$PID/create request\n");
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
- NULL);
- }
-
-
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure");
- }
-
- /* execute transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute purse create",
- TEH_MT_REQUEST_PURSE_CREATE,
- &mhd_ret,
- &create_transaction,
- &pcc))
- {
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return mhd_ret;
- }
- }
-
- /* generate regular response */
- {
- MHD_RESULT res;
-
- res = TEH_RESPONSE_reply_purse_created (connection,
- pcc.exchange_timestamp,
- &pcc.deposit_total,
- &pcc.pd);
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_purses_create.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_create.h b/src/exchange/taler-exchange-httpd_purses_create.h
@@ -1,47 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_create.h
- * @brief Handle /purses/$PID/create requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_PURSES_CREATE_H
-#define TALER_EXCHANGE_HTTPD_PURSES_CREATE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/purses/$PURSE_PUB/create" request. Parses the JSON, and, if
- * successful, passes the JSON data to #create_transaction() to further check
- * the details of the operation specified. If everything checks out, this
- * will ultimately lead to the "purses create" being executed, or rejected.
- *
- * @param rc connection to handle
- * @param purse_pub public key of the purse
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_purses_create (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_delete.c b/src/exchange/taler-exchange-httpd_purses_delete.c
@@ -1,149 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_delete.c
- * @brief Handle DELETE /purses/$PID requests; parses the request and
- * verifies the signature before handing deletion to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_dbevents.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_deposit.h"
-#include "taler-exchange-httpd_purses_delete.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-MHD_RESULT
-TEH_handler_purses_delete (
- struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct MHD_Connection *connection = rc->connection;
- struct TALER_PurseContractPublicKeyP purse_pub;
- struct TALER_PurseContractSignatureP purse_sig;
- bool found;
- bool decided;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &purse_pub,
- sizeof (purse_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
- args[0]);
- }
- TALER_MHD_parse_request_header_auto_t (connection,
- "Taler-Purse-Signature",
- &purse_sig);
- if (GNUNET_OK !=
- TALER_wallet_purse_delete_verify (&purse_pub,
- &purse_sig))
- {
- TALER_LOG_WARNING ("Invalid signature on /purses/$PID/delete request\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_DELETE_SIGNATURE_INVALID,
- NULL);
- }
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure");
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->do_purse_delete (TEH_plugin->cls,
- &purse_pub,
- &purse_sig,
- &decided,
- &found);
- if (qs <= 0)
- {
- TALER_LOG_WARNING (
- "Failed to store delete purse information in database\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse delete");
- }
- }
- if (! found)
- {
- return TALER_MHD_reply_with_ec (
- connection,
- TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
- NULL);
- }
- if (decided)
- {
- return TALER_MHD_reply_with_ec (
- connection,
- TALER_EC_EXCHANGE_PURSE_DELETE_ALREADY_DECIDED,
- NULL);
- }
- {
- /* Possible minor optimization: integrate notification with
- transaction above... */
- struct TALER_PurseEventP rep_deposited = {
- .header.size = htons (sizeof (rep_deposited)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
- .purse_pub = purse_pub
- };
- struct TALER_PurseEventP rep_merged = {
- .header.size = htons (sizeof (rep_merged)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
- .purse_pub = purse_pub
- };
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying about purse deletion %s\n",
- TALER_B2S (&purse_pub));
- TEH_plugin->event_notify (TEH_plugin->cls,
- &rep_deposited.header,
- NULL,
- 0);
- TEH_plugin->event_notify (TEH_plugin->cls,
- &rep_merged.header,
- NULL,
- 0);
- }
- /* success */
- return TALER_MHD_reply_static (connection,
- MHD_HTTP_NO_CONTENT,
- NULL,
- NULL,
- 0);
-}
-
-
-/* end of taler-exchange-httpd_purses_delete.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_delete.h b/src/exchange/taler-exchange-httpd_purses_delete.h
@@ -1,42 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_delete.h
- * @brief Handle DELETE /purses/$PID requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
-#define TALER_EXCHANGE_HTTPD_PURSES_DELETE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a DELETE "/purses/$PURSE_PUB" request.
- *
- * @param rc request details about the request to handle
- * @param args argument with the public key of the purse
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_purses_delete (
- struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.c b/src/exchange/taler-exchange-httpd_purses_deposit.c
@@ -1,509 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_deposit.c
- * @brief Handle /purses/$PID/deposit requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_dbevents.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_deposit.h"
-#include "taler-exchange-httpd_purses_deposit.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for #deposit_transaction.
- */
-struct PurseDepositContext
-{
- /**
- * Public key of the purse we are creating.
- */
- const struct TALER_PurseContractPublicKeyP *purse_pub;
-
- /**
- * Total amount to be put into the purse.
- */
- struct TALER_Amount amount;
-
- /**
- * Total actually deposited by all the coins.
- */
- struct TALER_Amount deposit_total;
-
- /**
- * When should the purse expire.
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * Hash of the contract (needed for signing).
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Our current time.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Array of coins being deposited.
- */
- struct TEH_PurseDepositedCoin *coins;
-
- /**
- * Length of the @e coins array.
- */
- unsigned int num_coins;
-
- /**
- * Minimum age for deposits into this purse.
- */
- uint32_t min_age;
-};
-
-
-/**
- * Send confirmation of purse creation success to client.
- *
- * @param connection connection to the client
- * @param pcc details about the request that succeeded
- * @return MHD result code
- */
-static MHD_RESULT
-reply_deposit_success (struct MHD_Connection *connection,
- const struct PurseDepositContext *pcc)
-{
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_purse_created_sign (
- &TEH_keys_exchange_sign_,
- pcc->exchange_timestamp,
- pcc->purse_expiration,
- &pcc->amount,
- &pcc->deposit_total,
- pcc->purse_pub,
- &pcc->h_contract_terms,
- &pub,
- &sig)))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("total_deposited",
- &pcc->deposit_total),
- TALER_JSON_pack_amount ("purse_value_after_fees",
- &pcc->amount),
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- pcc->exchange_timestamp),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- pcc->purse_expiration),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &pcc->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Execute database transaction for /purses/$PID/deposit. Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
- * a MHD response. IF it returns an hard error, the transaction logic MUST
- * queue a MHD response and set @a mhd_ret. IF it returns the soft error
- * code, the function MAY be called again to retry and MUST not queue a MHD
- * response.
- *
- * @param cls a `struct PurseDepositContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-deposit_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct PurseDepositContext *pcc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- qs = GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- for (unsigned int i = 0; i<pcc->num_coins; i++)
- {
- struct TEH_PurseDepositedCoin *coin = &pcc->coins[i];
- bool balance_ok = false;
- bool conflict = true;
- bool too_late = true;
-
- qs = TEH_make_coin_known (&coin->cpi,
- connection,
- &coin->known_coin_id,
- mhd_ret);
- if (qs < 0)
- return qs;
- qs = TEH_plugin->do_purse_deposit (TEH_plugin->cls,
- pcc->purse_pub,
- &coin->cpi.coin_pub,
- &coin->amount,
- &coin->coin_sig,
- &coin->amount_minus_fee,
- &balance_ok,
- &too_late,
- &conflict);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING (
- "Failed to store purse deposit information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "do purse deposit");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! balance_ok)
- {
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &coin->cpi.denom_pub_hash,
- &coin->cpi.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (too_late)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret
- = TALER_MHD_reply_with_ec (
- connection,
- TALER_EC_EXCHANGE_PURSE_DEPOSIT_DECIDED_ALREADY,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (conflict)
- {
- struct TALER_Amount amount;
- struct TALER_CoinSpendPublicKeyP coin_pub;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_DenominationHashP h_denom_pub;
- struct TALER_AgeCommitmentHashP phac;
- char *partner_url = NULL;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- qs = TEH_plugin->get_purse_deposit (TEH_plugin->cls,
- pcc->purse_pub,
- &coin->cpi.coin_pub,
- &amount,
- &h_denom_pub,
- &phac,
- &coin_sig,
- &partner_url);
- if (qs < 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- TALER_LOG_WARNING (
- "Failed to fetch purse deposit information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get purse deposit");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_PURSE_DEPOSIT_CONFLICTING_META_DATA),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &coin_pub),
- GNUNET_JSON_pack_data_auto ("h_denom_pub",
- &h_denom_pub),
- GNUNET_JSON_pack_data_auto ("h_age_commitment",
- &phac),
- GNUNET_JSON_pack_data_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("partner_url",
- partner_url)),
- TALER_JSON_pack_amount ("amount",
- &amount));
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return qs;
-}
-
-
-/**
- * Parse a coin and check signature of the coin and the denomination
- * signature over the coin.
- *
- * @param[in,out] connection our HTTP connection
- * @param[in,out] pcc request context
- * @param[out] coin coin to initialize
- * @param jcoin coin to parse
- * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
- * #GNUNET_SYSERR on failure and no error could be returned
- */
-static enum GNUNET_GenericReturnValue
-parse_coin (struct MHD_Connection *connection,
- struct PurseDepositContext *pcc,
- struct TEH_PurseDepositedCoin *coin,
- const json_t *jcoin)
-{
- enum GNUNET_GenericReturnValue iret;
-
- if (GNUNET_OK !=
- (iret = TEH_common_purse_deposit_parse_coin (connection,
- coin,
- jcoin)))
- return iret;
- if (GNUNET_OK !=
- (iret = TEH_common_deposit_check_purse_deposit (
- connection,
- coin,
- pcc->purse_pub,
- pcc->min_age)))
- return iret;
- if (0 >
- TALER_amount_add (&pcc->deposit_total,
- &pcc->deposit_total,
- &coin->amount_minus_fee))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- "total deposit contribution");
- }
- return GNUNET_OK;
-}
-
-
-MHD_RESULT
-TEH_handler_purses_deposit (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root)
-{
- struct MHD_Connection *connection = rc->connection;
- struct PurseDepositContext pcc = {
- .purse_pub = purse_pub,
- .exchange_timestamp = GNUNET_TIME_timestamp_get ()
- };
- const json_t *deposits;
- json_t *deposit;
- unsigned int idx;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_array_const ("deposits",
- &deposits),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &pcc.deposit_total));
- pcc.num_coins = (unsigned int) json_array_size (deposits);
- if ( (0 == pcc.num_coins) ||
- (((size_t) pcc.num_coins) != json_array_size (deposits)) ||
- (pcc.num_coins > TALER_MAX_COINS) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "deposits");
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp create_timestamp;
- struct GNUNET_TIME_Timestamp merge_timestamp;
- bool was_deleted;
- bool was_refunded;
-
- qs = TEH_plugin->select_purse (
- TEH_plugin->cls,
- pcc.purse_pub,
- &create_timestamp,
- &pcc.purse_expiration,
- &pcc.amount,
- &pcc.deposit_total,
- &pcc.h_contract_terms,
- &merge_timestamp,
- &was_deleted,
- &was_refunded);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break; /* handled below */
- }
- if (was_refunded ||
- was_deleted)
- {
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_GONE,
- was_deleted
- ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
- : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
- GNUNET_TIME_timestamp2s (pcc.purse_expiration));
- }
- }
-
- /* parse deposits */
- pcc.coins = GNUNET_new_array (pcc.num_coins,
- struct TEH_PurseDepositedCoin);
- json_array_foreach (deposits, idx, deposit)
- {
- enum GNUNET_GenericReturnValue res;
- struct TEH_PurseDepositedCoin *coin = &pcc.coins[idx];
-
- res = parse_coin (connection,
- &pcc,
- coin,
- deposit);
- if (GNUNET_OK != res)
- {
- for (unsigned int i = 0; i<idx; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- }
-
- if (GNUNET_SYSERR ==
- TEH_plugin->preflight (TEH_plugin->cls))
- {
- GNUNET_break (0);
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_START_FAILED,
- "preflight failure");
- }
-
- /* execute transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute purse deposit",
- TEH_MT_REQUEST_PURSE_DEPOSIT,
- &mhd_ret,
- &deposit_transaction,
- &pcc))
- {
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- return mhd_ret;
- }
- }
- {
- struct TALER_PurseEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
- .purse_pub = *pcc.purse_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying about purse deposit %s\n",
- TALER_B2S (pcc.purse_pub));
- TEH_plugin->event_notify (TEH_plugin->cls,
- &rep.header,
- NULL,
- 0);
- }
-
- /* generate regular response */
- {
- MHD_RESULT res;
-
- res = reply_deposit_success (connection,
- &pcc);
- for (unsigned int i = 0; i<pcc.num_coins; i++)
- TEH_common_purse_deposit_free_coin (&pcc.coins[i]);
- GNUNET_free (pcc.coins);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_purses_deposit.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_deposit.h b/src/exchange/taler-exchange-httpd_purses_deposit.h
@@ -1,47 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_deposit.h
- * @brief Handle /purses/$PID/deposit requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_PURSES_DEPOSIT_H
-#define TALER_EXCHANGE_HTTPD_PURSES_DEPOSIT_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/purses/$PURSE_PUB/deposit" request. Parses the JSON, and, if
- * successful, passes the JSON data to #deposit_transaction() to further check
- * the details of the operation specified. If everything checks out, this
- * will ultimately lead to the "purses deposit" being executed, or rejected.
- *
- * @param rc request to handle
- * @param purse_pub public key of the purse
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_purses_deposit (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_get.c b/src/exchange/taler-exchange-httpd_purses_get.c
@@ -1,445 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_get.c
- * @brief Handle GET /purses/$PID/$TARGET requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_purses_get.h"
-#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Information about an ongoing /purses GET operation.
- */
-struct GetContext
-{
- /**
- * Kept in a DLL.
- */
- struct GetContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct GetContext *prev;
-
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Subscription for the database event we are
- * waiting for.
- */
- struct GNUNET_DB_EventHandler *eh;
-
- /**
- * Subscription for refund event we are
- * waiting for.
- */
- struct GNUNET_DB_EventHandler *ehr;
-
- /**
- * Public key of our purse.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * When does this purse expire?
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * When was this purse merged?
- */
- struct GNUNET_TIME_Timestamp merge_timestamp;
-
- /**
- * How much is the purse (supposed) to be worth?
- */
- struct TALER_Amount amount;
-
- /**
- * How much was deposited into the purse so far?
- */
- struct TALER_Amount deposited;
-
- /**
- * Hash over the contract of the purse.
- */
- struct TALER_PrivateContractHashP h_contract;
-
- /**
- * When will this request time out?
- */
- struct GNUNET_TIME_Absolute timeout;
-
- /**
- * true to wait for merge, false to wait for deposit.
- */
- bool wait_for_merge;
-
- /**
- * True if we are still suspended.
- */
- bool suspended;
-};
-
-
-/**
- * Head of DLL of suspended GET requests.
- */
-static struct GetContext *gc_head;
-
-/**
- * Tail of DLL of suspended GET requests.
- */
-static struct GetContext *gc_tail;
-
-
-void
-TEH_purses_get_cleanup ()
-{
- struct GetContext *gc;
-
- while (NULL != (gc = gc_head))
- {
- GNUNET_CONTAINER_DLL_remove (gc_head,
- gc_tail,
- gc);
- if (gc->suspended)
- {
- gc->suspended = false;
- MHD_resume_connection (gc->connection);
- }
- }
-}
-
-
-/**
- * Function called once a connection is done to
- * clean up the `struct GetContext` state.
- *
- * @param rc context to clean up for
- */
-static void
-gc_cleanup (struct TEH_RequestContext *rc)
-{
- struct GetContext *gc = rc->rh_ctx;
-
- GNUNET_assert (! gc->suspended);
- if (NULL != gc->eh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening\n");
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- gc->eh);
- gc->eh = NULL;
- }
- if (NULL != gc->ehr)
- {
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- gc->ehr);
- gc->ehr = NULL;
- }
- GNUNET_free (gc);
-}
-
-
-/**
- * Function called on events received from Postgres.
- * Wakes up long pollers.
- *
- * @param cls the `struct TEH_RequestContext *`
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-db_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- struct TEH_RequestContext *rc = cls;
- struct GetContext *gc = rc->rh_ctx;
- struct GNUNET_AsyncScopeSave old_scope;
-
- (void) extra;
- (void) extra_size;
- if (NULL == gc)
- return; /* event triggered while main transaction
- was still running */
- if (! gc->suspended)
- return; /* might get multiple wake-up events */
- gc->suspended = false;
- GNUNET_async_scope_enter (&rc->async_scope_id,
- &old_scope);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Waking up on %p - %p - %s\n",
- rc,
- gc,
- gc->suspended ? "suspended" : "active");
- TEH_check_invariants ();
- GNUNET_CONTAINER_DLL_remove (gc_head,
- gc_tail,
- gc);
- MHD_resume_connection (gc->connection);
- TALER_MHD_daemon_trigger ();
- TEH_check_invariants ();
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-MHD_RESULT
-TEH_handler_purses_get (struct TEH_RequestContext *rc,
- const char *const args[2])
-{
- struct GetContext *gc = rc->rh_ctx;
- bool purse_deleted;
- bool purse_refunded;
- MHD_RESULT res;
-
- if (NULL == gc)
- {
- gc = GNUNET_new (struct GetContext);
- rc->rh_ctx = gc;
- rc->rh_cleaner = &gc_cleanup;
- gc->connection = rc->connection;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &gc->purse_pub,
- sizeof (gc->purse_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
- args[0]);
- }
- if (0 == strcmp (args[1],
- "merge"))
- gc->wait_for_merge = true;
- else if (0 == strcmp (args[1],
- "deposit"))
- gc->wait_for_merge = false;
- else
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_PURSES_INVALID_WAIT_TARGET,
- args[1]);
- }
-
- TALER_MHD_parse_request_timeout (rc->connection,
- &gc->timeout);
- if ( (GNUNET_TIME_absolute_is_future (gc->timeout)) &&
- (NULL == gc->eh) )
- {
- struct TALER_PurseEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (
- gc->wait_for_merge
- ? TALER_DBEVENT_EXCHANGE_PURSE_MERGED
- : TALER_DBEVENT_EXCHANGE_PURSE_DEPOSITED),
- .purse_pub = gc->purse_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening on purse %s (%s)\n",
- TALER_B2S (&gc->purse_pub),
- gc->wait_for_merge
- ? "waiting for merge"
- : "waiting for deposit");
- gc->eh = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (gc->timeout),
- &rep.header,
- &db_event_cb,
- rc);
- if (NULL == gc->eh)
- {
- GNUNET_break (0);
- gc->timeout = GNUNET_TIME_UNIT_ZERO_ABS;
- }
- else
- {
- struct GNUNET_DB_EventHeaderP repr = {
- .size = htons (sizeof (repr)),
- .type = htons (TALER_DBEVENT_EXCHANGE_PURSE_REFUNDED),
- };
-
- gc->ehr = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (gc->timeout),
- &repr,
- &db_event_cb,
- rc);
- }
- }
- } /* end first-time initialization */
-
- {
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp create_timestamp;
-
- qs = TEH_plugin->select_purse (TEH_plugin->cls,
- &gc->purse_pub,
- &create_timestamp,
- &gc->purse_expiration,
- &gc->amount,
- &gc->deposited,
- &gc->h_contract,
- &gc->merge_timestamp,
- &purse_deleted,
- &purse_refunded);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "select_purse %s returned %d (%s)\n",
- args[0],
- (int) qs,
- GNUNET_TIME_timestamp2s (gc->merge_timestamp));
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_purse");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_purse");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break; /* handled below */
- }
- }
- if (purse_refunded ||
- purse_deleted)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Purse refunded or deleted\n");
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_GONE,
- purse_deleted
- ? TALER_EC_EXCHANGE_GENERIC_PURSE_DELETED
- : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED
- ,
- GNUNET_TIME_timestamp2s (
- gc->purse_expiration));
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Deposited amount is %s (%d/%d/%d)\n",
- TALER_amount2s (&gc->deposited),
- GNUNET_TIME_absolute_is_future (gc->timeout),
- GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time),
- (0 <
- TALER_amount_cmp (&gc->amount,
- &gc->deposited)));
- if (GNUNET_TIME_absolute_is_future (gc->timeout) &&
- ( ((gc->wait_for_merge) &&
- GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time)) ||
- ((! gc->wait_for_merge) &&
- (0 <
- TALER_amount_cmp (&gc->amount,
- &gc->deposited))) ) )
- {
- gc->suspended = true;
- GNUNET_CONTAINER_DLL_insert (gc_head,
- gc_tail,
- gc);
- MHD_suspend_connection (gc->connection);
- return MHD_YES;
- }
-
- {
- struct GNUNET_TIME_Timestamp dt = GNUNET_TIME_timestamp_get ();
- struct TALER_ExchangePublicKeyP exchange_pub;
- struct TALER_ExchangeSignatureP exchange_sig;
- enum TALER_ErrorCode ec;
-
- if (GNUNET_TIME_timestamp_cmp (dt,
- >,
- gc->purse_expiration))
- dt = gc->purse_expiration;
- if (0 <
- TALER_amount_cmp (&gc->amount,
- &gc->deposited))
- {
- /* amount > deposited: not yet fully paid */
- dt = GNUNET_TIME_UNIT_ZERO_TS;
- }
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_purse_status_sign (
- &TEH_keys_exchange_sign_,
- gc->merge_timestamp,
- dt,
- &gc->deposited,
- &exchange_pub,
- &exchange_sig)))
- {
- res = TALER_MHD_reply_with_ec (rc->connection,
- ec,
- NULL);
- }
- else
- {
- /* Make sure merge_timestamp is omitted if not yet merged */
- if (GNUNET_TIME_absolute_is_never (gc->merge_timestamp.abs_time))
- gc->merge_timestamp = GNUNET_TIME_UNIT_ZERO_TS;
- res = TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("balance",
- &gc->deposited),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- gc->purse_expiration),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- gc->merge_timestamp)),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_timestamp ("deposit_timestamp",
- dt))
- );
- }
- }
- return res;
-}
-
-
-/* end of taler-exchange-httpd_purses_get.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_get.h b/src/exchange/taler-exchange-httpd_purses_get.h
@@ -1,51 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_get.h
- * @brief Handle /purses/$PURSE_PUB/$TARGET GET requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_PURSES_GET_H
-#define TALER_EXCHANGE_HTTPD_PURSES_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Shutdown purses-get subsystem. Resumes all
- * suspended long-polling clients and cleans up
- * data structures.
- */
-void
-TEH_purses_get_cleanup (void);
-
-
-/**
- * Handle a GET "/purses/$PID/$TARGET" request. Parses the
- * given "purse_pub" in @a args (which should contain the
- * EdDSA public key of a purse) and then respond with the
- * status of the purse.
- *
- * @param rc request context
- * @param args array of additional options (length: 2, the purse_pub and a target)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_purses_get (struct TEH_RequestContext *rc,
- const char *const args[2]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.c b/src/exchange/taler-exchange-httpd_purses_merge.c
@@ -1,815 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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 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_purses_merge.c
- * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
- * verifies the reserve signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_dbevents.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_purses_merge.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for #merge_transaction.
- */
-struct PurseMergeContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct PurseMergeContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct PurseMergeContext *prev;
-
- /**
- * Our request.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for the legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Fees that apply to this operation.
- */
- const struct TALER_WireFeeSet *wf;
-
- /**
- * Base URL of the exchange provider hosting the reserve.
- */
- char *provider_url;
-
- /**
- * URI of the account the purse is to be merged into.
- * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
- */
- struct TALER_NormalizedPayto payto_uri;
-
- /**
- * Response to return, if set.
- */
- struct MHD_Response *response;
-
- /**
- * Public key of the purse we are creating.
- */
- struct TALER_PurseContractPublicKeyP purse_pub;
-
- /**
- * Total amount to be put into the purse.
- */
- struct TALER_Amount target_amount;
-
- /**
- * Current amount in the purse.
- */
- struct TALER_Amount balance;
-
- /**
- * When should the purse expire.
- */
- struct GNUNET_TIME_Timestamp purse_expiration;
-
- /**
- * When the client signed the merge.
- */
- struct GNUNET_TIME_Timestamp merge_timestamp;
-
- /**
- * Our current time.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Merge key for the purse.
- */
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- /**
- * Signature of the reservce affiming this request.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Signature of the client affiming the merge.
- */
- struct TALER_PurseMergeSignatureP merge_sig;
-
- /**
- * Public key of the reserve (account), as extracted from @e payto_uri.
- */
- union TALER_AccountPublicKeyP account_pub;
-
- /**
- * Hash of the contract terms of the purse.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Hash of the @e payto_uri.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * KYC status of the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * HTTP status to return with @e response, or 0.
- */
- unsigned int http_status;
-
- /**
- * Minimum age for deposits into this purse.
- */
- uint32_t min_age;
-
- /**
- * Set to true if this request was suspended.
- */
- bool suspended;
-};
-
-
-/**
- * Kept in a DLL.
- */
-static struct PurseMergeContext *pmc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct PurseMergeContext *pmc_tail;
-
-
-void
-TEH_purses_merge_cleanup ()
-{
- struct PurseMergeContext *pmc;
-
- while (NULL != (pmc = pmc_head))
- {
- GNUNET_CONTAINER_DLL_remove (pmc_head,
- pmc_tail,
- pmc);
- MHD_resume_connection (pmc->rc->connection);
- }
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls closure
- * @param lcr legitimization check result
- */
-static void
-legi_result_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct PurseMergeContext *pmc = cls;
-
- pmc->lch = NULL;
- MHD_resume_connection (pmc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (pmc_head,
- pmc_tail,
- pmc);
- TALER_MHD_daemon_trigger ();
- if (NULL != lcr->response)
- {
- pmc->response = lcr->response;
- pmc->http_status = lcr->http_status;
- return;
- }
- pmc->kyc = lcr->kyc;
-}
-
-
-/**
- * Send confirmation of purse creation success to client.
- *
- * @param pmc details about the request that succeeded
- * @return MHD result code
- */
-static MHD_RESULT
-reply_merge_success (const struct PurseMergeContext *pmc)
-{
- struct MHD_Connection *connection = pmc->rc->connection;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
- struct TALER_Amount merge_amount;
-
- if (0 <
- TALER_amount_cmp (&pmc->balance,
- &pmc->target_amount))
- {
- GNUNET_break (0);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_JSON_pack_amount ("balance",
- &pmc->balance),
- TALER_JSON_pack_amount ("target_amount",
- &pmc->target_amount));
- }
- if ( (NULL == pmc->provider_url) ||
- (0 == strcmp (pmc->provider_url,
- TEH_base_url)) )
- {
- /* wad fee is always zero if we stay at our own exchange */
- merge_amount = pmc->target_amount;
- }
- else
- {
-#if WAD_NOT_IMPLEMENTED /* #7271 */
- /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
- if (0 >
- TALER_amount_subtract (&merge_amount,
- &pmc->target_amount,
- &wad_fee))
- {
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &merge_amount));
- }
-#else
- merge_amount = pmc->target_amount;
-#endif
- }
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_purse_merged_sign (
- &TEH_keys_exchange_sign_,
- pmc->exchange_timestamp,
- pmc->purse_expiration,
- &merge_amount,
- &pmc->purse_pub,
- &pmc->h_contract_terms,
- &pmc->account_pub.reserve_pub,
- (NULL != pmc->provider_url)
- ? pmc->provider_url
- : TEH_base_url,
- &pub,
- &sig)))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("merge_amount",
- &merge_amount),
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- pmc->exchange_timestamp),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls a `struct PurseMergeContext`
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-amount_iterator (void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct PurseMergeContext *pmc = cls;
- enum GNUNET_GenericReturnValue ret;
- enum GNUNET_DB_QueryStatus qs;
-
- ret = cb (cb_cls,
- &pmc->target_amount,
- GNUNET_TIME_absolute_get ());
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- qs = TEH_plugin->select_merge_amounts_for_kyc_check (
- TEH_plugin->cls,
- &pmc->h_payto,
- limit,
- cb,
- cb_cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got %d additional transactions for this merge and limit %llu\n",
- qs,
- (unsigned long long) limit.abs_value_us);
- GNUNET_break (qs >= 0);
- return qs;
-}
-
-
-/**
- * Execute database transaction for /purses/$PID/merge. Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
- * a MHD response. IF it returns an hard error, the transaction logic MUST
- * queue a MHD response and set @a mhd_ret. IF it returns the soft error
- * code, the function MAY be called again to retry and MUST not queue a MHD
- * response.
- *
- * @param cls a `struct PurseMergeContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-merge_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct PurseMergeContext *pmc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool in_conflict = true;
- bool no_balance = true;
- bool no_partner = true;
-
- qs = TEH_plugin->do_purse_merge (
- TEH_plugin->cls,
- &pmc->purse_pub,
- &pmc->merge_sig,
- pmc->merge_timestamp,
- &pmc->reserve_sig,
- pmc->provider_url,
- &pmc->account_pub.reserve_pub,
- &no_partner,
- &no_balance,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret =
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse merge");
- return qs;
- }
- if (no_partner)
- {
- *mhd_ret =
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
- pmc->provider_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (no_balance)
- {
- *mhd_ret =
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PAYMENT_REQUIRED,
- TALER_EC_EXCHANGE_PURSE_NOT_FULL,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (in_conflict)
- {
- struct TALER_PurseMergeSignatureP merge_sig;
- struct GNUNET_TIME_Timestamp merge_timestamp;
- char *partner_url = NULL;
- struct TALER_ReservePublicKeyP reserve_pub;
- bool refunded;
-
- qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
- &pmc->purse_pub,
- &merge_sig,
- &merge_timestamp,
- &partner_url,
- &reserve_pub,
- &refunded);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING (
- "Failed to fetch merge purse information from database\n");
- *mhd_ret =
- TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse merge");
- return qs;
- }
- if (refunded)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Purse was already refunded\n");
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
- NULL);
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (0 !=
- GNUNET_memcmp (&merge_sig,
- &pmc->merge_sig))
- {
- *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &merge_sig),
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("partner_url",
- partner_url)),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub));
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- /* idempotent! */
- *mhd_ret = reply_merge_success (pmc);
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- return qs;
-}
-
-
-/**
- * Purse-merge-specific cleanup routine. Function called
- * upon completion of the request that should
- * clean up @a rh_ctx. Can be NULL.
- *
- * @param rc request context to clean up
- */
-static void
-clean_purse_merge_rc (struct TEH_RequestContext *rc)
-{
- struct PurseMergeContext *pmc = rc->rh_ctx;
-
- if (NULL != pmc->lch)
- {
- TEH_legitimization_check_cancel (pmc->lch);
- pmc->lch = NULL;
- }
- GNUNET_free (pmc->provider_url);
- GNUNET_free (pmc);
-}
-
-
-MHD_RESULT
-TEH_handler_purses_merge (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root)
-{
- struct PurseMergeContext *pmc = rc->rh_ctx;
-
- if (NULL == pmc)
- {
- pmc = GNUNET_new (struct PurseMergeContext);
- rc->rh_ctx = pmc;
- rc->rh_cleaner = &clean_purse_merge_rc;
- pmc->rc = rc;
- pmc->purse_pub = *purse_pub;
- pmc->exchange_timestamp
- = GNUNET_TIME_timestamp_get ();
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_normalized_payto_uri ("payto_uri",
- &pmc->payto_uri),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &pmc->reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("merge_sig",
- &pmc->merge_sig),
- GNUNET_JSON_spec_timestamp ("merge_timestamp",
- &pmc->merge_timestamp),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
-
- {
- struct TALER_PurseContractSignatureP purse_sig;
- enum GNUNET_DB_QueryStatus qs;
-
- /* Fetch purse details */
- qs = TEH_plugin->get_purse_request (
- TEH_plugin->cls,
- &pmc->purse_pub,
- &pmc->merge_pub,
- &pmc->purse_expiration,
- &pmc->h_contract_terms,
- &pmc->min_age,
- &pmc->target_amount,
- &pmc->balance,
- &purse_sig);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse request");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse request");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* continued below */
- break;
- }
- }
-
- /* check signatures */
- if (GNUNET_OK !=
- TALER_wallet_purse_merge_verify (
- pmc->payto_uri,
- pmc->merge_timestamp,
- &pmc->purse_pub,
- &pmc->merge_pub,
- &pmc->merge_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
- NULL);
- }
-
- /* parse 'payto_uri' into pmc->account_pub and provider_url */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Received payto: `%s'\n",
- pmc->payto_uri.normalized_payto);
- if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
- "payto://taler-reserve/",
- strlen ("payto://taler-reserve/"))) &&
- (0 != strncmp (pmc->payto_uri.normalized_payto,
- "payto://taler-reserve-http/",
- strlen ("payto://taler-reserve-http/"))) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payto_uri");
- }
-
- {
- bool http;
- const char *host;
- const char *slash;
-
- http = (0 == strncmp (pmc->payto_uri.normalized_payto,
- "payto://taler-reserve-http/",
- strlen ("payto://taler-reserve-http/")));
- host = &pmc->payto_uri.normalized_payto[http
- ? strlen ("payto://taler-reserve-http/")
- : strlen ("payto://taler-reserve/")];
- slash = strchr (host,
- '/');
- if (NULL == slash)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payto_uri");
- }
- GNUNET_asprintf (&pmc->provider_url,
- "%s://%.*s/",
- http ? "http" : "https",
- (int) (slash - host),
- host);
- slash++;
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (
- slash,
- strlen (slash),
- &pmc->account_pub.reserve_pub,
- sizeof (pmc->account_pub.reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "payto_uri");
- }
- }
- TALER_normalized_payto_hash (pmc->payto_uri,
- &pmc->h_payto);
- if (0 == strcmp (pmc->provider_url,
- TEH_base_url))
- {
- /* we use NULL to represent 'self' as the provider */
- GNUNET_free (pmc->provider_url);
- }
- else
- {
- char *method = GNUNET_strdup ("FIXME-WAD #7271");
-
- /* FIXME-#7271: lookup wire method by pmc.provider_url! */
- pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
- method);
- if (NULL == pmc->wf)
- {
- MHD_RESULT res;
-
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot merge purse: wire fees not configured!\n");
- res = TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
- method);
- GNUNET_free (method);
- return res;
- }
- GNUNET_free (method);
- }
-
- {
- struct TALER_Amount zero_purse_fee;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (
- pmc->target_amount.currency,
- &zero_purse_fee));
- if (GNUNET_OK !=
- TALER_wallet_account_merge_verify (
- pmc->merge_timestamp,
- &pmc->purse_pub,
- pmc->purse_expiration,
- &pmc->h_contract_terms,
- &pmc->target_amount,
- &zero_purse_fee,
- pmc->min_age,
- TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
- &pmc->account_pub.reserve_pub,
- &pmc->reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
- NULL);
- }
- }
- {
- struct TALER_FullPayto fake_full_payto;
-
- GNUNET_asprintf (&fake_full_payto.full_payto,
- "%s?receiver-name=wallet",
- pmc->payto_uri.normalized_payto);
- pmc->lch = TEH_legitimization_check (
- &rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
- fake_full_payto,
- &pmc->h_payto,
- &pmc->account_pub,
- &amount_iterator,
- pmc,
- &legi_result_cb,
- pmc);
- GNUNET_free (fake_full_payto.full_payto);
- }
- GNUNET_assert (NULL != pmc->lch);
- MHD_suspend_connection (rc->connection);
- GNUNET_CONTAINER_DLL_insert (pmc_head,
- pmc_tail,
- pmc);
- return MHD_YES;
- }
- if (NULL != pmc->response)
- {
- return MHD_queue_response (rc->connection,
- pmc->http_status,
- pmc->response);
- }
- if (! pmc->kyc.ok)
- return TEH_RESPONSE_reply_kyc_required (
- rc->connection,
- &pmc->h_payto,
- &pmc->kyc,
- false);
-
- /* execute merge transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "execute purse merge",
- TEH_MT_REQUEST_PURSE_MERGE,
- &mhd_ret,
- &merge_transaction,
- pmc))
- {
- return mhd_ret;
- }
- }
-
- {
- struct TALER_PurseEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
- .purse_pub = pmc->purse_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Notifying about purse merge\n");
- TEH_plugin->event_notify (TEH_plugin->cls,
- &rep.header,
- NULL,
- 0);
- }
-
- /* generate regular response */
- return reply_merge_success (pmc);
-}
-
-
-/* end of taler-exchange-httpd_purses_merge.c */
diff --git a/src/exchange/taler-exchange-httpd_purses_merge.h b/src/exchange/taler-exchange-httpd_purses_merge.h
@@ -1,54 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_purses_merge.h
- * @brief Handle /purses/$PID/merge requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_PURSES_MERGE_H
-#define TALER_EXCHANGE_HTTPD_PURSES_MERGE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_purses_merge_cleanup (void);
-
-
-/**
- * Handle a "/purses/$PURSE_PUB/merge" request. Parses the JSON, and, if
- * successful, passes the JSON data to #merge_transaction() to further check
- * the details of the operation specified. If everything checks out, this
- * will ultimately lead to the "purses merge" being executed, or rejected.
- *
- * @param rc request to handle
- * @param purse_pub public key of the purse
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_purses_merge (
- struct TEH_RequestContext *rc,
- const struct TALER_PurseContractPublicKeyP *purse_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.c b/src/exchange/taler-exchange-httpd_recoup-refresh.c
@@ -1,431 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017-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_recoup-refresh.c
- * @brief Handle /recoup-refresh requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_db.h"
-#include "taler-exchange-httpd_recoup-refresh.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler/taler_exchangedb_lib.h"
-
-
-/**
- * Closure for #recoup_refresh_transaction().
- */
-struct RecoupContext
-{
-
- /**
- * Set by #recoup_transaction() to the old coin that will
- * receive the recoup.
- */
- struct TALER_CoinSpendPublicKeyP old_coin_pub;
-
- /**
- * Details about the coin.
- */
- const struct TALER_CoinPublicInfo *coin;
-
- /**
- * Key used to blind the coin.
- */
- const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
-
- /**
- * Signature of the coin requesting recoup.
- */
- const struct TALER_CoinSpendSignatureP *coin_sig;
-
- /**
- * Unique ID of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
- /**
- * Unique ID of the refresh reveal context of the melt for the new coin.
- */
- uint64_t rrc_serial;
-
- /**
- * Set by #recoup_transaction to the timestamp when the recoup
- * was accepted.
- */
- struct GNUNET_TIME_Timestamp now;
-
-};
-
-
-/**
- * Execute a "recoup-refresh". The validity of the coin and signature have
- * already been checked. The database must now check that the coin is not
- * (double) spent, and execute the transaction.
- *
- * IF it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls the `struct RecoupContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-recoup_refresh_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct RecoupContext *pc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool recoup_ok;
- bool internal_failure;
-
- /* Finally, store new refund data */
- pc->now = GNUNET_TIME_timestamp_get ();
- qs = TEH_plugin->do_recoup_refresh (TEH_plugin->cls,
- &pc->old_coin_pub,
- pc->rrc_serial,
- pc->coin_bks,
- &pc->coin->coin_pub,
- pc->known_coin_id,
- pc->coin_sig,
- &pc->now,
- &recoup_ok,
- &internal_failure);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_recoup_refresh");
- return qs;
- }
-
- if (internal_failure)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "coin transaction history");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! recoup_ok)
- {
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &pc->coin->denom_pub_hash,
- &pc->coin->coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * We have parsed the JSON information about the recoup request. Do
- * some basic sanity checks (especially that the signature on the
- * request and coin is valid) and then execute the recoup operation.
- * Note that we need the DB to check the fee structure, so this is not
- * done here but during the recoup_transaction().
- *
- * @param connection the MHD connection to handle
- * @param coin information about the coin
- * @param exchange_vals values contributed by the exchange
- * during refresh
- * @param coin_bks blinding data of the coin (to be checked)
- * @param nonce withdraw nonce (if CS is used)
- * @param coin_sig signature of the coin
- * @return MHD result code
- */
-static MHD_RESULT
-verify_and_execute_recoup_refresh (
- struct MHD_Connection *connection,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
- const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
- const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- struct RecoupContext pc;
- const struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
- struct TALER_BlindedCoinHashP h_blind;
-
- /* check denomination exists and is in recoup mode */
- dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
- connection,
- &mret);
- if (NULL == dk)
- return mret;
- if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
- {
- /* This denomination is past the expiration time for recoup */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "RECOUP-REFRESH");
- }
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "RECOUP-REFRESH");
- }
- if (! dk->recoup_possible)
- {
- /* This denomination is not eligible for recoup */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_RECOUP_REFRESH_NOT_ELIGIBLE,
- "RECOUP-REFRESH");
- }
-
- /* check denomination signature */
- switch (dk->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
- break;
- case GNUNET_CRYPTO_BSA_CS:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
- break;
- default:
- break;
- }
- if (GNUNET_YES !=
- TALER_test_coin_valid (coin,
- &dk->denom_pub))
- {
- TALER_LOG_WARNING ("Invalid coin passed for recoup\n");
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL);
- }
-
- /* check recoup request signature */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_recoup_refresh_verify (&coin->denom_pub_hash,
- coin_bks,
- &coin->coin_pub,
- coin_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RECOUP_REFRESH_SIGNATURE_INVALID,
- NULL);
- }
-
- {
- struct TALER_CoinPubHashP c_hash;
- struct TALER_BlindedPlanchet blinded_planchet;
-
- if (GNUNET_OK !=
- TALER_denom_blind (&dk->denom_pub,
- coin_bks,
- nonce,
- &coin->h_age_commitment,
- &coin->coin_pub,
- exchange_vals,
- &c_hash,
- &blinded_planchet))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_RECOUP_REFRESH_BLINDING_FAILED,
- NULL);
- }
- TALER_coin_ev_hash (&blinded_planchet,
- &coin->denom_pub_hash,
- &h_blind);
- TALER_blinded_planchet_free (&blinded_planchet);
- }
-
- pc.coin_sig = coin_sig;
- pc.coin_bks = coin_bks;
- pc.coin = coin;
-
- {
- MHD_RESULT mhd_ret = MHD_NO;
- enum GNUNET_DB_QueryStatus qs;
-
- /* make sure coin is 'known' in database */
- qs = TEH_make_coin_known (coin,
- connection,
- &pc.known_coin_id,
- &mhd_ret);
- /* no transaction => no serialization failures should be possible */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- if (qs < 0)
- return mhd_ret;
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_old_coin_by_h_blind (TEH_plugin->cls,
- &h_blind,
- &pc.old_coin_pub,
- &pc.rrc_serial);
- if (0 > qs)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_old_coin_by_h_blind");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Recoup-refresh requested for unknown envelope %s\n",
- GNUNET_h2s (&h_blind.hash));
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RECOUP_REFRESH_MELT_NOT_FOUND,
- NULL);
- }
- }
-
- /* Perform actual recoup transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "run recoup-refresh",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &recoup_refresh_transaction,
- &pc))
- return mhd_ret;
- }
- /* Recoup succeeded, return result */
- return TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto (
- "old_coin_pub",
- &pc.old_coin_pub));
-}
-
-
-/**
- * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_recoup_refresh (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_CoinPublicInfo coin = {0};
- union GNUNET_CRYPTO_BlindingSecretP coin_bks;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_ExchangeBlindingValues exchange_vals;
- union GNUNET_CRYPTO_BlindSessionNonce nonce;
- bool no_nonce;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &coin.denom_pub_hash),
- TALER_JSON_spec_denom_sig ("denom_sig",
- &coin.denom_sig),
- TALER_JSON_spec_exchange_blinding_values ("ewv",
- &exchange_vals),
- GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
- &coin_bks),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &coin.h_age_commitment),
- &coin.no_age_commitment),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("nonce",
- &nonce),
- &no_nonce),
- GNUNET_JSON_spec_end ()
- };
-
- memset (&coin,
- 0,
- sizeof (coin));
- coin.coin_pub = *coin_pub;
- ret = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
- {
- MHD_RESULT res;
-
- res = verify_and_execute_recoup_refresh (connection,
- &coin,
- &exchange_vals,
- &coin_bks,
- no_nonce
- ? NULL
- : &nonce,
- &coin_sig);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_recoup-refresh.c */
diff --git a/src/exchange/taler-exchange-httpd_recoup-refresh.h b/src/exchange/taler-exchange-httpd_recoup-refresh.h
@@ -1,46 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017, 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_recoup-refresh.h
- * @brief Handle /recoup-refresh requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
-#define TALER_EXCHANGE_HTTPD_RECOUP_REFRESH_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/coins/$COIN_PUB/recoup-refresh" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_recoup_refresh() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_recoup_refresh (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_recoup.c b/src/exchange/taler-exchange-httpd_recoup.c
@@ -1,443 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017-2022 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_recoup.c
- * @brief Handle /recoup requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_db.h"
-#include "taler-exchange-httpd_recoup.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler/taler_exchangedb_lib.h"
-
-/**
- * Closure for #recoup_transaction.
- */
-struct RecoupContext
-{
- /**
- * Hash identifying the withdraw request.
- */
- struct TALER_BlindedCoinHashP h_coin_ev;
-
- /**
- * Set by #recoup_transaction() to the reserve that will
- * receive the recoup, if #refreshed is #GNUNET_NO.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Details about the coin.
- */
- const struct TALER_CoinPublicInfo *coin;
-
- /**
- * Key used to blind the coin.
- */
- const union GNUNET_CRYPTO_BlindingSecretP *coin_bks;
-
- /**
- * Signature of the coin requesting recoup.
- */
- const struct TALER_CoinSpendSignatureP *coin_sig;
-
- /**
- * Unique ID of the withdraw operation in the withdraw table.
- */
- uint64_t withdraw_serial_id;
-
- /**
- * Unique ID of the coin in the known_coins table.
- */
- uint64_t known_coin_id;
-
- /**
- * Set by #recoup_transaction to the timestamp when the recoup
- * was accepted.
- */
- struct GNUNET_TIME_Timestamp now;
-
-};
-
-
-/**
- * Execute a "recoup". The validity of the coin and signature have
- * already been checked. The database must now check that the coin is
- * not (double) spent, and execute the transaction.
- *
- * IF it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls the `struct RecoupContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status code
- */
-static enum GNUNET_DB_QueryStatus
-recoup_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct RecoupContext *pc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool recoup_ok;
- bool internal_failure;
-
- /* Finally, store new refund data */
- pc->now = GNUNET_TIME_timestamp_get ();
- qs = TEH_plugin->do_recoup (TEH_plugin->cls,
- &pc->reserve_pub,
- pc->withdraw_serial_id,
- pc->coin_bks,
- &pc->coin->coin_pub,
- pc->known_coin_id,
- pc->coin_sig,
- &pc->now,
- &recoup_ok,
- &internal_failure);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_recoup");
- return qs;
- }
-
- if (internal_failure)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "do_recoup");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! recoup_ok)
- {
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &pc->coin->denom_pub_hash,
- &pc->coin->coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * We have parsed the JSON information about the recoup request. Do
- * some basic sanity checks (especially that the signature on the
- * request and coin is valid) and then execute the recoup operation.
- * Note that we need the DB to check the fee structure, so this is not
- * done here but during the recoup_transaction().
- *
- * @param connection the MHD connection to handle
- * @param coin information about the coin
- * @param exchange_vals values contributed by the exchange
- * during withdrawal
- * @param coin_bks blinding data of the coin (to be checked)
- * @param h_planchets The hash of the commitment of the original withdraw request
- * @param nonce coin's nonce if CS is used
- * @param coin_sig signature of the coin
- * @return MHD result code
- */
-static MHD_RESULT
-verify_and_execute_recoup (
- struct MHD_Connection *connection,
- const struct TALER_CoinPublicInfo *coin,
- const struct TALER_ExchangeBlindingValues *exchange_vals,
- const union GNUNET_CRYPTO_BlindingSecretP *coin_bks,
- const struct TALER_HashBlindedPlanchetsP *h_planchets,
- const union GNUNET_CRYPTO_BlindSessionNonce *nonce,
- const struct TALER_CoinSpendSignatureP *coin_sig)
-{
- struct RecoupContext pc;
- const struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
-
- /* check denomination exists and is in recoup mode */
- dk = TEH_keys_denomination_by_hash (&coin->denom_pub_hash,
- connection,
- &mret);
- if (NULL == dk)
- return mret;
- if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
- {
- /* This denomination is past the expiration time for recoup */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "RECOUP");
- }
- if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
- {
- /* This denomination is not yet valid */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "RECOUP");
- }
- if (! dk->recoup_possible)
- {
- /* This denomination is not eligible for recoup */
- return TEH_RESPONSE_reply_expired_denom_pub_hash (
- connection,
- &coin->denom_pub_hash,
- TALER_EC_EXCHANGE_RECOUP_NOT_ELIGIBLE,
- "RECOUP");
- }
-
- /* check denomination signature */
- switch (dk->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_RSA:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
- break;
- case GNUNET_CRYPTO_BSA_CS:
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
- break;
- default:
- break;
- }
- if (GNUNET_YES !=
- TALER_test_coin_valid (coin,
- &dk->denom_pub))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
- NULL);
- }
-
- /* check recoup request signature */
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_recoup_verify (&coin->denom_pub_hash,
- coin_bks,
- &coin->coin_pub,
- coin_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RECOUP_SIGNATURE_INVALID,
- NULL);
- }
-
- /* re-compute client-side blinding so we can
- (a bit later) check that this coin was indeed
- signed by us. */
- {
- struct TALER_CoinPubHashP c_hash;
- struct TALER_BlindedPlanchet blinded_planchet;
-
- if (GNUNET_OK !=
- TALER_denom_blind (&dk->denom_pub,
- coin_bks,
- nonce,
- &coin->h_age_commitment,
- &coin->coin_pub,
- exchange_vals,
- &c_hash,
- &blinded_planchet))
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_RECOUP_BLINDING_FAILED,
- NULL);
- }
- TALER_coin_ev_hash (&blinded_planchet,
- &coin->denom_pub_hash,
- &pc.h_coin_ev);
- TALER_blinded_planchet_free (&blinded_planchet);
- }
-
- pc.coin_sig = coin_sig;
- pc.coin_bks = coin_bks;
- pc.coin = coin;
-
- {
- MHD_RESULT mhd_ret = MHD_NO;
- enum GNUNET_DB_QueryStatus qs;
-
- /* make sure coin is 'known' in database */
- qs = TEH_make_coin_known (coin,
- connection,
- &pc.known_coin_id,
- &mhd_ret);
- /* no transaction => no serialization failures should be possible */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- if (qs < 0)
- return mhd_ret;
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_reserve_by_h_planchets (
- TEH_plugin->cls,
- h_planchets,
- &pc.reserve_pub,
- &pc.withdraw_serial_id);
- if (0 > qs)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_reserve_by_commitment");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Recoup requested for unknown envelope %s\n",
- GNUNET_h2s (&pc.h_coin_ev.hash));
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_RECOUP_WITHDRAW_NOT_FOUND,
- NULL);
- }
- }
-
- /* Perform actual recoup transaction */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "run recoup",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &recoup_transaction,
- &pc))
- return mhd_ret;
- }
- /* Recoup succeeded, return result */
- return TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto (
- "reserve_pub",
- &pc.reserve_pub));
-}
-
-
-/**
- * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_recoup() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_recoup (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_CoinPublicInfo coin;
- union GNUNET_CRYPTO_BlindingSecretP coin_bks;
- struct TALER_CoinSpendSignatureP coin_sig;
- struct TALER_ExchangeBlindingValues exchange_vals;
- struct TALER_HashBlindedPlanchetsP h_planchets;
- union GNUNET_CRYPTO_BlindSessionNonce nonce;
- bool no_nonce;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &coin.denom_pub_hash),
- TALER_JSON_spec_denom_sig ("denom_sig",
- &coin.denom_sig),
- GNUNET_JSON_spec_fixed_auto ("h_planchets",
- &h_planchets),
- TALER_JSON_spec_exchange_blinding_values ("ewv",
- &exchange_vals),
- GNUNET_JSON_spec_fixed_auto ("coin_blind_key_secret",
- &coin_bks),
- GNUNET_JSON_spec_fixed_auto ("coin_sig",
- &coin_sig),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
- &coin.h_age_commitment),
- &coin.no_age_commitment),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("nonce",
- &nonce),
- &no_nonce),
- GNUNET_JSON_spec_end ()
- };
-
- memset (&coin,
- 0,
- sizeof (coin));
- coin.coin_pub = *coin_pub;
- ret = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == ret)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == ret)
- return MHD_YES; /* failure */
- {
- MHD_RESULT res;
-
- res = verify_and_execute_recoup (connection,
- &coin,
- &exchange_vals,
- &coin_bks,
- &h_planchets,
- no_nonce
- ? NULL
- : &nonce,
- &coin_sig);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_recoup.c */
diff --git a/src/exchange/taler-exchange-httpd_recoup.h b/src/exchange/taler-exchange-httpd_recoup.h
@@ -1,46 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2017 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_recoup.h
- * @brief Handle /recoup requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RECOUP_H
-#define TALER_EXCHANGE_HTTPD_RECOUP_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/coins/$COIN_PUB/recoup" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_recoup() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_recoup (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c
@@ -1,369 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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_refund.c
- * @brief Handle refund requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_refund.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * How often do we retry after soft database errors?
- */
-#define MAX_RETRIES 3
-
-
-/**
- * Generate successful refund confirmation message.
- *
- * @param connection connection to the client
- * @param coin_pub public key of the coin
- * @param refund details about the successful refund
- * @return MHD result code
- */
-static MHD_RESULT
-reply_refund_success (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_EXCHANGEDB_RefundListEntry *refund)
-{
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_refund_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &refund->h_contract_terms,
- coin_pub,
- &refund->merchant_pub,
- refund->rtransaction_id,
- &refund->refund_amount,
- &pub,
- &sig)))
- {
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Closure for refund_transaction().
- */
-struct RefundContext
-{
- /**
- * Details about the deposit operation.
- */
- const struct TALER_EXCHANGEDB_Refund *refund;
-
- /**
- * Deposit fee of the coin.
- */
- struct TALER_Amount deposit_fee;
-
- /**
- * Unique ID of the coin in known_coins.
- */
- uint64_t known_coin_id;
-};
-
-
-/**
- * Execute a "/refund" transaction. Returns a confirmation that the
- * refund was successful, or a failure if we are not aware of a
- * matching /deposit or if it is too late to do the refund.
- *
- * IF it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls closure with a `const struct TALER_EXCHANGEDB_Refund *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-refund_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct RefundContext *rctx = cls;
- const struct TALER_EXCHANGEDB_Refund *refund = rctx->refund;
- enum GNUNET_DB_QueryStatus qs;
- bool not_found;
- bool refund_ok;
- bool conflict;
- bool gone;
-
- /* Finally, store new refund data */
- qs = TEH_plugin->do_refund (TEH_plugin->cls,
- refund,
- &rctx->deposit_fee,
- rctx->known_coin_id,
- ¬_found,
- &refund_ok,
- &gone,
- &conflict);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do refund");
- return qs;
- }
-
- if (gone)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_REFUND_MERCHANT_ALREADY_PAID,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (conflict)
- {
- GNUNET_break_op (0);
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_REFUND_INCONSISTENT_AMOUNT,
- &refund->coin.denom_pub_hash,
- &refund->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (not_found)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_REFUND_DEPOSIT_NOT_FOUND,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (! refund_ok)
- {
- *mhd_ret = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_REFUND_CONFLICT_DEPOSIT_INSUFFICIENT,
- &refund->coin.denom_pub_hash,
- &refund->coin.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-/**
- * We have parsed the JSON information about the refund, do some basic
- * sanity checks (especially that the signature on the coin is valid)
- * and then execute the refund. Note that we need the DB to check
- * the fee structure, so this is not done here.
- *
- * @param connection the MHD connection to handle
- * @param[in,out] refund information about the refund
- * @return MHD result code
- */
-static MHD_RESULT
-verify_and_execute_refund (struct MHD_Connection *connection,
- struct TALER_EXCHANGEDB_Refund *refund)
-{
- struct RefundContext rctx = {
- .refund = refund
- };
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_merchant_refund_verify (&refund->coin.coin_pub,
- &refund->details.h_contract_terms,
- refund->details.rtransaction_id,
- &refund->details.refund_amount,
- &refund->details.merchant_pub,
- &refund->details.merchant_sig))
- {
- TALER_LOG_WARNING ("Invalid signature on refund request\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_REFUND_MERCHANT_SIGNATURE_INVALID,
- NULL);
- }
-
- /* Fetch the coin's denomination (hash) */
- for (unsigned int i = 0; i < MAX_RETRIES; i++)
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_coin_denomination (TEH_plugin->cls,
- &refund->coin.coin_pub,
- &rctx.known_coin_id,
- &refund->coin.denom_pub_hash);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- if (i < MAX_RETRIES - 1)
- continue;
- /* otherwise: fall-through */
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_coin_denomination");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- {
- MHD_RESULT res;
- char *dhs;
-
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- dhs = GNUNET_STRINGS_data_to_string_alloc (
- &refund->coin.denom_pub_hash,
- sizeof (refund->coin.denom_pub_hash));
- res = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_REFUND_COIN_NOT_FOUND,
- dhs);
- GNUNET_free (dhs);
- return res;
- }
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
-
- {
- /* Obtain information about the coin's denomination! */
- struct TEH_DenominationKey *dk;
- MHD_RESULT mret;
-
- dk = TEH_keys_denomination_by_hash (&refund->coin.denom_pub_hash,
- connection,
- &mret);
- if (NULL == dk)
- {
- /* DKI not found, but we do have a coin with this DK in our database;
- not good... */
- GNUNET_break (0);
- return mret;
- }
- refund->details.refund_fee = dk->meta.fees.refund;
- rctx.deposit_fee = dk->meta.fees.deposit;
- }
-
- /* Finally run the actual transaction logic */
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "run refund",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &refund_transaction,
- &rctx))
- {
- return mhd_ret;
- }
- }
- return reply_refund_success (connection,
- &refund->coin.coin_pub,
- &refund->details);
-}
-
-
-/**
- * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_refund() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_refund (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root)
-{
- struct TALER_EXCHANGEDB_Refund refund = {
- .details.refund_fee.currency = {0} /* set to invalid, just to be sure */
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("refund_amount",
- TEH_currency,
- &refund.details.refund_amount),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &refund.details.h_contract_terms),
- GNUNET_JSON_spec_fixed_auto ("merchant_pub",
- &refund.details.merchant_pub),
- GNUNET_JSON_spec_uint64 ("rtransaction_id",
- &refund.details.rtransaction_id),
- GNUNET_JSON_spec_fixed_auto ("merchant_sig",
- &refund.details.merchant_sig),
- GNUNET_JSON_spec_end ()
- };
-
- refund.coin.coin_pub = *coin_pub;
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- return MHD_NO; /* hard failure */
- if (GNUNET_NO == res)
- return MHD_YES; /* failure */
- }
- {
- MHD_RESULT res;
-
- res = verify_and_execute_refund (connection,
- &refund);
- GNUNET_JSON_parse_free (spec);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_refund.c */
diff --git a/src/exchange/taler-exchange-httpd_refund.h b/src/exchange/taler-exchange-httpd_refund.h
@@ -1,47 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014, 2015, 2016 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_refund.h
- * @brief Handle /refund requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_REFUND_H
-#define TALER_EXCHANGE_HTTPD_REFUND_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/coins/$COIN_PUB/refund" request. Parses the JSON, and, if
- * successful, passes the JSON data to #verify_and_execute_refund() to further
- * check the details of the operation specified. If everything checks out,
- * this will ultimately lead to the refund being executed, or rejected.
- *
- * @param connection the MHD connection to handle
- * @param coin_pub public key of the coin
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_refund (struct MHD_Connection *connection,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const json_t *root);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.c b/src/exchange/taler-exchange-httpd_reserves_attest.c
@@ -1,391 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2022, 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 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_reserves_attest.c
- * @brief Handle /reserves/$RESERVE_PUB/attest requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_dbevents.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_attest.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * How far do we allow a client's time to be off when
- * checking the request timestamp?
- */
-#define TIMESTAMP_TOLERANCE \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
-
-
-/**
- * Closure for #reserve_attest_transaction.
- */
-struct ReserveAttestContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Hash of the payto URI of this reserve.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * Timestamp of the request.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Expiration time for the attestation.
- */
- struct GNUNET_TIME_Timestamp etime;
-
- /**
- * List of requested details.
- */
- const json_t *details;
-
- /**
- * Client signature approving the request.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Attributes we are affirming. JSON object.
- */
- json_t *json_attest;
-
- /**
- * Database error codes encountered.
- */
- enum GNUNET_DB_QueryStatus qs;
-
- /**
- * Set to true if we did not find the reserve.
- */
- bool not_found;
-
-};
-
-
-/**
- * Send reserve attest to client.
- *
- * @param connection connection to the client
- * @param rhc reserve attest to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_attest_success (struct MHD_Connection *connection,
- const struct ReserveAttestContext *rhc)
-{
- struct TALER_ExchangeSignatureP exchange_sig;
- struct TALER_ExchangePublicKeyP exchange_pub;
- enum TALER_ErrorCode ec;
- struct GNUNET_TIME_Timestamp now;
-
- if (NULL == rhc->json_attest)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- }
- now = GNUNET_TIME_timestamp_get ();
- ec = TALER_exchange_online_reserve_attest_details_sign (
- &TEH_keys_exchange_sign_,
- now,
- rhc->etime,
- &rhc->reserve_pub,
- rhc->json_attest,
- &exchange_pub,
- &exchange_sig);
- if (TALER_EC_NONE != ec)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &exchange_sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &exchange_pub),
- GNUNET_JSON_pack_timestamp ("exchange_timestamp",
- now),
- GNUNET_JSON_pack_timestamp ("expiration_time",
- rhc->etime),
- GNUNET_JSON_pack_object_steal ("attributes",
- rhc->json_attest));
-}
-
-
-/**
- * Function called with information about all applicable
- * legitimization processes for the given user. Finds the
- * available attributes and merges them into our result
- * set based on the details requested by the client.
- *
- * @param cls our `struct ReserveAttestContext *`
- * @param h_payto account for which the attribute data is stored
- * @param provider_name provider that must be checked
- * @param collection_time when was the data collected
- * @param expiration_time when does the data expire
- * @param enc_attributes_size number of bytes in @a enc_attributes
- * @param enc_attributes encrypted attribute data
- */
-static void
-kyc_process_cb (void *cls,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- const char *provider_name,
- struct GNUNET_TIME_Timestamp collection_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- size_t enc_attributes_size,
- const void *enc_attributes)
-{
- struct ReserveAttestContext *rsc = cls;
- json_t *attrs;
- json_t *val;
- const char *name;
- bool match = false;
-
- if (GNUNET_TIME_absolute_is_past (expiration_time.abs_time))
- return;
- attrs = TALER_CRYPTO_kyc_attributes_decrypt (&TEH_attribute_key,
- enc_attributes,
- enc_attributes_size);
- if (NULL == attrs)
- {
- GNUNET_break (0);
- return;
- }
- json_object_foreach (attrs, name, val)
- {
- bool requested = strcmp (name,
- "FORM_ID"); /* we always return the FORM_ID */
- size_t idx;
- json_t *str;
-
- if (NULL != json_object_get (rsc->json_attest,
- name))
- continue; /* duplicate */
- json_array_foreach (rsc->details, idx, str)
- {
- if (0 == strcmp (json_string_value (str),
- name))
- {
- requested = true;
- break;
- }
- }
- if (! requested)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Skipping attribute `%s': not requested\n",
- name);
- continue;
- }
- match = true;
- GNUNET_assert (0 ==
- json_object_set (rsc->json_attest, /* NOT set_new! */
- name,
- val));
- }
- json_decref (attrs);
- if (! match)
- return;
- rsc->etime = GNUNET_TIME_timestamp_min (expiration_time,
- rsc->etime);
-}
-
-
-/**
- * Function implementing /reserves/$RID/attest transaction. Given the public
- * key of a reserve, return the associated transaction attest. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls a `struct ReserveAttestContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserve_attest_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveAttestContext *rsc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- rsc->json_attest = json_object ();
- GNUNET_assert (NULL != rsc->json_attest);
- qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
- &rsc->h_payto,
- &kyc_process_cb,
- rsc);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret
- = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_kyc_attributes");
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- rsc->not_found = true;
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- rsc->not_found = false;
- break;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1])
-{
- struct ReserveAttestContext rsc = {
- .etime = GNUNET_TIME_UNIT_FOREVER_TS
- };
- MHD_RESULT mhd_ret;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rsc.timestamp),
- GNUNET_JSON_spec_array_const ("details",
- &rsc.details),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rsc.reserve_sig),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_TIME_Timestamp now;
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &rsc.reserve_pub,
- sizeof (rsc.reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
- args[0]);
- }
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- now = GNUNET_TIME_timestamp_get ();
- if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
- rsc.timestamp.abs_time,
- TIMESTAMP_TOLERANCE))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
- NULL);
- }
-
- if (GNUNET_OK !=
- TALER_wallet_reserve_attest_request_verify (rsc.timestamp,
- rsc.details,
- &rsc.reserve_pub,
- &rsc.reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_ATTEST_BAD_SIGNATURE,
- NULL);
- }
-
- {
- struct TALER_NormalizedPayto payto_uri;
-
- payto_uri = TALER_reserve_make_payto (TEH_base_url,
- &rsc.reserve_pub);
- TALER_normalized_payto_hash (payto_uri,
- &rsc.h_payto);
- GNUNET_free (payto_uri.normalized_payto);
- }
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "post reserve attest",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_attest_transaction,
- &rsc))
- {
- return mhd_ret;
- }
- if (rsc.not_found)
- {
- json_decref (rsc.json_attest);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- args[0]);
- }
- return reply_reserve_attest_success (rc->connection,
- &rsc);
-}
-
-
-/* end of taler-exchange-httpd_reserves_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_attest.h b/src/exchange/taler-exchange-httpd_reserves_attest.h
@@ -1,41 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_attest.h
- * @brief Handle /reserves/$RESERVE_PUB/attest requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_ATTEST_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a POST "/reserves-attest/$RID" request.
- *
- * @param rc request context
- * @param root uploaded body from the client
- * @param args args[0] has public key of the reserve
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_attest (struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[1]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.c b/src/exchange/taler-exchange-httpd_reserves_close.c
@@ -1,575 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2022 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_reserves_close.c
- * @brief Handle /reserves/$RESERVE_PUB/close requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_close.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * How far do we allow a client's time to be off when
- * checking the request timestamp?
- */
-#define TIMESTAMP_TOLERANCE \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
-
-
-/**
- * Closure for #reserve_close_transaction.
- */
-struct ReserveCloseContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct ReserveCloseContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct ReserveCloseContext *prev;
-
- /**
- * Our request context.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Where to wire the funds, may be NULL.
- */
- struct TALER_FullPayto payto_uri;
-
- /**
- * Response to return. Note that the response must
- * be queued or destroyed by the callee. NULL
- * if the legitimization check was successful and the handler should return
- * a handler-specific result.
- */
- struct MHD_Response *response;
-
- /**
- * Public key of the reserve the inquiry is about.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Timestamp of the request.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Client signature approving the request.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Amount that will be wired (after closing fees).
- */
- struct TALER_Amount wire_amount;
-
- /**
- * Current balance of the reserve.
- */
- struct TALER_Amount balance;
-
- /**
- * Hash of the @e payto_uri, if given (otherwise zero).
- */
- struct TALER_FullPaytoHashP h_payto;
-
- /**
- * KYC status for the request.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Hash of the payto-URI that was used for the KYC decision.
- */
- struct TALER_NormalizedPaytoHashP kyc_payto;
-
- /**
- * Query status from the amount_it() helper function.
- */
- enum GNUNET_DB_QueryStatus qs;
-
- /**
- * HTTP status code for @a response, or 0
- */
- unsigned int http_status;
-
- /**
- * Set to true if the request was suspended.
- */
- bool suspended;
-
- /**
- * Set to true if the request was suspended.
- */
- bool resumed;
-};
-
-
-/**
- * Kept in a DLL.
- */
-static struct ReserveCloseContext *rcc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct ReserveCloseContext *rcc_tail;
-
-
-void
-TEH_reserves_close_cleanup ()
-{
- struct ReserveCloseContext *rcc;
-
- while (NULL != (rcc = rcc_head))
- {
- GNUNET_CONTAINER_DLL_remove (rcc_head,
- rcc_tail,
- rcc);
- MHD_resume_connection (rcc->rc->connection);
- }
-}
-
-
-/**
- * Send reserve close to client.
- *
- * @param rhc reserve close to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_close_success (
- const struct ReserveCloseContext *rhc)
-{
- struct MHD_Connection *connection = rhc->rc->connection;
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("wire_amount",
- &rhc->wire_amount));
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls closure
- * @param lcr legitimization check result
- */
-static void
-reserve_close_legi_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct ReserveCloseContext *rcc = cls;
-
- rcc->lch = NULL;
- rcc->http_status = lcr->http_status;
- rcc->response = lcr->response;
- rcc->kyc = lcr->kyc;
- GNUNET_CONTAINER_DLL_remove (rcc_head,
- rcc_tail,
- rcc);
- MHD_resume_connection (rcc->rc->connection);
- rcc->resumed = true;
- rcc->suspended = false;
- TALER_MHD_daemon_trigger ();
-}
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls closure, identifies the event type and
- * account to iterate over events for
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-amount_it (void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct ReserveCloseContext *rcc = cls;
- enum GNUNET_GenericReturnValue ret;
-
- ret = cb (cb_cls,
- &rcc->balance,
- GNUNET_TIME_absolute_get ());
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- rcc->qs
- = TEH_plugin->iterate_reserve_close_info (
- TEH_plugin->cls,
- &rcc->kyc_payto,
- limit,
- cb,
- cb_cls);
- return rcc->qs;
-}
-
-
-/**
- * Function implementing /reserves/$RID/close transaction. Given the public
- * key of a reserve, return the associated transaction close. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls a `struct ReserveCloseContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!); unused
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserve_close_transaction (
- void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveCloseContext *rcc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_FullPayto payto_uri = {
- .full_payto = NULL
- };
- const struct TALER_WireFeeSet *wf;
-
- qs = TEH_plugin->select_reserve_close_info (
- TEH_plugin->cls,
- &rcc->reserve_pub,
- &rcc->balance,
- &payto_uri);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret
- = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_reserve_close_info");
- return qs;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *mhd_ret
- = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
-
- if ( (NULL == rcc->payto_uri.full_payto) &&
- (NULL == payto_uri.full_payto) )
- {
- *mhd_ret
- = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if ( (! rcc->resumed) &&
- (NULL != rcc->payto_uri.full_payto) &&
- ( (NULL == payto_uri.full_payto) ||
- (0 != TALER_full_payto_cmp (payto_uri,
- rcc->payto_uri)) ) )
- {
- /* KYC check may be needed: we're not returning
- the money to the account that funded the reserve
- in the first place. */
-
- TALER_full_payto_normalize_and_hash (rcc->payto_uri,
- &rcc->kyc_payto);
- rcc->lch = TEH_legitimization_check (
- &rcc->rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
- rcc->payto_uri,
- &rcc->kyc_payto,
- NULL, /* no account_pub: this is about the origin/destination account */
- &amount_it,
- rcc,
- &reserve_close_legi_cb,
- rcc);
- GNUNET_assert (NULL != rcc->lch);
- GNUNET_CONTAINER_DLL_insert (rcc_head,
- rcc_tail,
- rcc);
- MHD_suspend_connection (rcc->rc->connection);
- rcc->suspended = true;
- GNUNET_free (payto_uri.full_payto);
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- }
- rcc->kyc.ok = true;
- if (NULL == rcc->payto_uri.full_payto)
- rcc->payto_uri = payto_uri;
-
- {
- char *method;
-
- method = TALER_payto_get_method (rcc->payto_uri.full_payto);
- wf = TEH_wire_fees_by_time (rcc->timestamp,
- method);
- if (NULL == wf)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
- method);
- GNUNET_free (method);
- GNUNET_free (payto_uri.full_payto);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_free (method);
- }
-
- if (0 >
- TALER_amount_subtract (&rcc->wire_amount,
- &rcc->balance,
- &wf->closing))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Client attempted to close reserve with insufficient balance.\n");
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &rcc->wire_amount));
- *mhd_ret = reply_reserve_close_success (rcc);
- GNUNET_free (payto_uri.full_payto);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- qs = TEH_plugin->insert_close_request (
- TEH_plugin->cls,
- &rcc->reserve_pub,
- payto_uri,
- &rcc->reserve_sig,
- rcc->timestamp,
- &rcc->balance,
- &wf->closing);
- GNUNET_free (payto_uri.full_payto);
- rcc->payto_uri.full_payto = NULL;
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- *mhd_ret
- = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "insert_close_request");
- return qs;
- }
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- return qs;
- }
- return qs;
-}
-
-
-/**
- * Cleanup routine. Function called
- * upon completion of the request that should
- * clean up @a rh_ctx. Can be NULL.
- *
- * @param rc request to clean up context for
- */
-static void
-reserve_close_cleanup (struct TEH_RequestContext *rc)
-{
- struct ReserveCloseContext *rcc = rc->rh_ctx;
-
- if (NULL != rcc->lch)
- {
- TEH_legitimization_check_cancel (rcc->lch);
- rcc->lch = NULL;
- }
- GNUNET_free (rcc);
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_close (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root)
-{
- struct ReserveCloseContext *rcc = rc->rh_ctx;
- MHD_RESULT mhd_ret;
-
- if (NULL == rcc)
- {
- rcc = GNUNET_new (struct ReserveCloseContext);
- rc->rh_ctx = rcc;
- rc->rh_cleaner = &reserve_close_cleanup;
- rcc->reserve_pub = *reserve_pub;
- rcc->rc = rc;
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rcc->timestamp),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_full_payto_uri ("payto_uri",
- &rcc->payto_uri),
- NULL),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rcc->reserve_sig),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- }
-
- {
- struct GNUNET_TIME_Timestamp now;
-
- now = GNUNET_TIME_timestamp_get ();
- if (! GNUNET_TIME_absolute_approx_eq (
- now.abs_time,
- rcc->timestamp.abs_time,
- TIMESTAMP_TOLERANCE))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
- NULL);
- }
- }
-
- if (NULL != rcc->payto_uri.full_payto)
- TALER_full_payto_hash (rcc->payto_uri,
- &rcc->h_payto);
- if (GNUNET_OK !=
- TALER_wallet_reserve_close_verify (
- rcc->timestamp,
- &rcc->h_payto,
- &rcc->reserve_pub,
- &rcc->reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
- NULL);
- }
- }
- if (NULL != rcc->response)
- return MHD_queue_response (rc->connection,
- rcc->http_status,
- rcc->response);
- if (rcc->resumed &&
- (! rcc->kyc.ok) )
- {
- if (0 == rcc->kyc.requirement_row)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "requirement row not set");
- }
- return TEH_RESPONSE_reply_kyc_required (
- rc->connection,
- &rcc->kyc_payto,
- &rcc->kyc,
- false);
- }
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "reserve close",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_close_transaction,
- rcc))
- {
- return mhd_ret;
- }
- if (rcc->suspended)
- return MHD_YES;
- return reply_reserve_close_success (rcc);
-}
-
-
-/* end of taler-exchange-httpd_reserves_close.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_close.h b/src/exchange/taler-exchange-httpd_reserves_close.h
@@ -1,48 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_close.h
- * @brief Handle /reserves/$RESERVE_PUB/close requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_CLOSE_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_reserves_close_cleanup (void);
-
-/**
- * Handle a POST "/reserves/$RID/close" request.
- *
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @param root uploaded body from the client
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_close (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.c b/src/exchange/taler-exchange-httpd_reserves_get.c
@@ -1,278 +0,0 @@
-/*
- This file is part of TALER
- Copyright (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 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_reserves_get.c
- * @brief Handle /reserves/$RESERVE_PUB GET requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_get.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Reserve GET request that is long-polling.
- */
-struct ReservePoller
-{
- /**
- * Kept in a DLL.
- */
- struct ReservePoller *next;
-
- /**
- * Kept in a DLL.
- */
- struct ReservePoller *prev;
-
- /**
- * Connection we are handling.
- */
- struct MHD_Connection *connection;
-
- /**
- * Our request context.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Subscription for the database event we are waiting for.
- */
- struct GNUNET_DB_EventHandler *eh;
-
- /**
- * When will this request time out?
- */
- struct GNUNET_TIME_Absolute timeout;
-
- /**
- * Public key of the reserve the inquiry is about.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Balance of the reserve, set in the callback.
- */
- struct TALER_Amount balance;
-
- /**
- * Last origin account of the reserve, NULL if only
- * P2P payments were made.
- */
- struct TALER_FullPayto origin_account;
-
- /**
- * True if we are still suspended.
- */
- bool suspended;
-
-};
-
-
-/**
- * Head of list of requests in long polling.
- */
-static struct ReservePoller *rp_head;
-
-/**
- * Tail of list of requests in long polling.
- */
-static struct ReservePoller *rp_tail;
-
-
-void
-TEH_reserves_get_cleanup ()
-{
- for (struct ReservePoller *rp = rp_head;
- NULL != rp;
- rp = rp->next)
- {
- if (rp->suspended)
- {
- rp->suspended = false;
- MHD_resume_connection (rp->connection);
- }
- }
-}
-
-
-/**
- * Function called once a connection is done to
- * clean up the `struct ReservePoller` state.
- *
- * @param rc context to clean up for
- */
-static void
-rp_cleanup (struct TEH_RequestContext *rc)
-{
- struct ReservePoller *rp = rc->rh_ctx;
-
- GNUNET_assert (! rp->suspended);
- if (NULL != rp->eh)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Cancelling DB event listening on cleanup (odd unless during shutdown)\n");
- TEH_plugin->event_listen_cancel (TEH_plugin->cls,
- rp->eh);
- rp->eh = NULL;
- }
- GNUNET_CONTAINER_DLL_remove (rp_head,
- rp_tail,
- rp);
- GNUNET_free (rp->origin_account.full_payto);
- GNUNET_free (rp);
-}
-
-
-/**
- * Function called on events received from Postgres.
- * Wakes up long pollers.
- *
- * @param cls the `struct TEH_RequestContext *`
- * @param extra additional event data provided
- * @param extra_size number of bytes in @a extra
- */
-static void
-db_event_cb (void *cls,
- const void *extra,
- size_t extra_size)
-{
- struct ReservePoller *rp = cls;
- struct GNUNET_AsyncScopeSave old_scope;
-
- (void) extra;
- (void) extra_size;
- if (! rp->suspended)
- return; /* might get multiple wake-up events */
- GNUNET_async_scope_enter (&rp->rc->async_scope_id,
- &old_scope);
- TEH_check_invariants ();
- rp->suspended = false;
- MHD_resume_connection (rp->connection);
- TALER_MHD_daemon_trigger ();
- TEH_check_invariants ();
- GNUNET_async_scope_restore (&old_scope);
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_get (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct ReservePoller *rp = rc->rh_ctx;
-
- if (NULL == rp)
- {
- rp = GNUNET_new (struct ReservePoller);
- rp->connection = rc->connection;
- rp->rc = rc;
- rc->rh_ctx = rp;
- rc->rh_cleaner = &rp_cleanup;
- GNUNET_CONTAINER_DLL_insert (rp_head,
- rp_tail,
- rp);
- rp->reserve_pub = *reserve_pub;
- TALER_MHD_parse_request_timeout (rc->connection,
- &rp->timeout);
- }
-
- if ( (GNUNET_TIME_absolute_is_future (rp->timeout)) &&
- (NULL == rp->eh) )
- {
- struct TALER_ReserveEventP rep = {
- .header.size = htons (sizeof (rep)),
- .header.type = htons (TALER_DBEVENT_EXCHANGE_RESERVE_INCOMING),
- .reserve_pub = rp->reserve_pub
- };
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Starting DB event listening until %s\n",
- GNUNET_TIME_absolute2s (rp->timeout));
- rp->eh = TEH_plugin->event_listen (
- TEH_plugin->cls,
- GNUNET_TIME_absolute_get_remaining (rp->timeout),
- &rep.header,
- &db_event_cb,
- rp);
- }
- {
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_free (rp->origin_account.full_payto);
- qs = TEH_plugin->get_reserve_balance (TEH_plugin->cls,
- &rp->reserve_pub,
- &rp->balance,
- &rp->origin_account);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0); /* single-shot query should never have soft-errors */
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- "get_reserve_balance");
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_reserve_balance");
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got reserve balance of %s\n",
- TALER_amount2s (&rp->balance));
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string (
- "last_origin",
- rp->origin_account.full_payto)),
- TALER_JSON_pack_amount ("balance",
- &rp->balance));
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- if (! GNUNET_TIME_absolute_is_future (rp->timeout))
- {
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- }
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Long-polling on reserve for %s\n",
- GNUNET_STRINGS_relative_time_to_string (
- GNUNET_TIME_absolute_get_remaining (rp->timeout),
- true));
- rp->suspended = true;
- MHD_suspend_connection (rc->connection);
- return MHD_YES;
- }
- }
- GNUNET_break (0);
- return MHD_NO;
-}
-
-
-/* end of taler-exchange-httpd_reserves_get.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_get.h b/src/exchange/taler-exchange-httpd_reserves_get.h
@@ -1,54 +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 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_reserves_get.h
- * @brief Handle /reserves/$RESERVE_PUB GET requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_GET_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_GET_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Shutdown reserves-get subsystem. Resumes all
- * suspended long-polling clients and cleans up
- * data structures.
- */
-void
-TEH_reserves_get_cleanup (void);
-
-
-/**
- * Handle a GET "/reserves/" request. Parses the
- * given "reserve_pub" in @a args (which should contain the
- * EdDSA public key of a reserve) and then respond with the
- * status of the reserve.
- *
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_get (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.c b/src/exchange/taler-exchange-httpd_reserves_get_attest.c
@@ -1,195 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_get_attest.c
- * @brief Handle GET /reserves/$RESERVE_PUB/attest requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_get_attest.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Closure for #reserve_attest_transaction.
- */
-struct ReserveAttestContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- struct TALER_ReservePublicKeyP reserve_pub;
-
- /**
- * Hash of the payto URI of this reserve.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * Available attributes.
- */
- json_t *attributes;
-
-};
-
-
-/**
- * Function called with information about all applicable
- * legitimization processes for the given user.
- *
- * @param cls our `struct ReserveAttestContext *`
- * @param h_payto account for which the attribute data is stored
- * @param provider_name provider that must be checked
- * @param collection_time when was the data collected
- * @param expiration_time when does the data expire
- * @param enc_attributes_size number of bytes in @a enc_attributes
- * @param enc_attributes encrypted attribute data
- */
-static void
-kyc_process_cb (void *cls,
- const struct TALER_NormalizedPaytoHashP *h_payto,
- const char *provider_name,
- struct GNUNET_TIME_Timestamp collection_time,
- struct GNUNET_TIME_Timestamp expiration_time,
- size_t enc_attributes_size,
- const void *enc_attributes)
-{
- struct ReserveAttestContext *rsc = cls;
- json_t *attrs;
- json_t *val;
- const char *name;
-
- if (GNUNET_TIME_absolute_is_past (
- expiration_time.abs_time))
- return;
- attrs = TALER_CRYPTO_kyc_attributes_decrypt (
- &TEH_attribute_key,
- enc_attributes,
- enc_attributes_size);
- json_object_foreach (attrs, name, val)
- {
- bool duplicate = false;
- size_t idx;
- json_t *str;
-
- json_array_foreach (rsc->attributes, idx, str)
- {
- if (0 == strcmp (json_string_value (str),
- name))
- {
- duplicate = true;
- break;
- }
- }
- if (duplicate)
- continue;
- GNUNET_assert (0 ==
- json_array_append_new (rsc->attributes,
- json_string (name)));
- }
- json_decref (attrs);
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_get_attest (
- struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct ReserveAttestContext rsc = {
- .attributes = NULL
- };
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (
- args[0],
- strlen (args[0]),
- &rsc.reserve_pub,
- sizeof (rsc.reserve_pub)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_RESERVE_PUB_MALFORMED,
- args[0]);
- }
- {
- struct TALER_NormalizedPayto payto_uri;
-
- payto_uri
- = TALER_reserve_make_payto (TEH_base_url,
- &rsc.reserve_pub);
- TALER_normalized_payto_hash (payto_uri,
- &rsc.h_payto);
- GNUNET_free (payto_uri.normalized_payto);
- }
- {
- enum GNUNET_DB_QueryStatus qs;
-
- rsc.attributes = json_array ();
- GNUNET_assert (NULL != rsc.attributes);
- qs = TEH_plugin->select_kyc_attributes (TEH_plugin->cls,
- &rsc.h_payto,
- &kyc_process_cb,
- &rsc);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- json_decref (rsc.attributes);
- rsc.attributes = NULL;
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_kyc_attributes");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- GNUNET_break (0);
- json_decref (rsc.attributes);
- rsc.attributes = NULL;
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select_kyc_attributes");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- GNUNET_break_op (0);
- json_decref (rsc.attributes);
- rsc.attributes = NULL;
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- }
- return TALER_MHD_REPLY_JSON_PACK (
- rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("details",
- rsc.attributes));
-}
-
-
-/* end of taler-exchange-httpd_reserves_get_attest.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_get_attest.h b/src/exchange/taler-exchange-httpd_reserves_get_attest.h
@@ -1,44 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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_reserves_get_attest.h
- * @brief Handle /reserves/$RESERVE_PUB GET_ATTEST requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_GET_ATTEST_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/reserves/$RID/attest" request. Parses the
- * given "reserve_pub" in @a args (which should contain the
- * EdDSA public key of a reserve) and then responds with the
- * available attestations for the reserve.
- *
- * @param rc request context
- * @param args array of additional options (length: 1, just the reserve_pub)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_get_attest (struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.c b/src/exchange/taler-exchange-httpd_reserves_history.c
@@ -1,633 +0,0 @@
-/*
- This file is part of TALER
- Copyright (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 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_reserves_history.c
- * @brief Handle /reserves/$RESERVE_PUB HISTORY requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_history.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Compile the history of a reserve into a JSON object.
- *
- * @param rh reserve history to JSON-ify
- * @return json representation of the @a rh, NULL on error
- */
-static json_t *
-compile_reserve_history (
- const struct TALER_EXCHANGEDB_ReserveHistory *rh)
-{
- json_t *json_history;
-
- json_history = json_array ();
- GNUNET_assert (NULL != json_history);
- for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
- NULL != pos;
- pos = pos->next)
- {
- switch (pos->type)
- {
- case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
- {
- const struct TALER_EXCHANGEDB_BankTransfer *bank =
- pos->details.bank;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CREDIT"),
- GNUNET_JSON_pack_timestamp ("timestamp",
- bank->execution_date),
- TALER_JSON_pack_full_payto ("sender_account_url",
- bank->sender_account_details),
- GNUNET_JSON_pack_uint64 ("wire_reference",
- bank->wire_reference),
- TALER_JSON_pack_amount ("amount",
- &bank->amount))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- break;
- }
- case TALER_EXCHANGEDB_RO_WITHDRAW_COINS:
- {
- const struct TALER_EXCHANGEDB_Withdraw *withdraw
- = pos->details.withdraw;
- struct TALER_Amount withdraw_fee;
- struct TEH_KeyStateHandle *ksh;
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (
- TEH_currency,
- &withdraw_fee));
-
- /*
- * We need to calculate the fees for the withdraw.
- * Therefore, we need to get access to the key state.
- */
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
-
- for (size_t i = 0; i < withdraw->num_coins; i++)
- {
- /* Find the denomination and accumulate the fee */
- {
- struct TEH_DenominationKey *dk;
- dk = TEH_keys_denomination_by_hash_from_state (
- ksh,
- &withdraw->denom_pub_hashes[i],
- NULL,
- NULL);
-
- if (NULL == dk)
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
-
- if (0 > TALER_amount_add (&withdraw_fee,
- &withdraw_fee,
- &dk->meta.fees.withdraw))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- }
-
- /* Prepare the entry for the history */
- {
- json_t *j_entry = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string (
- "type",
- "WITHDRAW"),
- GNUNET_JSON_pack_data_auto (
- "reserve_sig",
- &withdraw->reserve_sig),
- GNUNET_JSON_pack_data_auto (
- "planchets_h",
- &withdraw->planchets_h),
- GNUNET_JSON_pack_uint64 (
- "num_coins",
- withdraw->num_coins),
- TALER_JSON_pack_array_of_data (
- "denom_pub_hashes",
- withdraw->num_coins,
- withdraw->denom_pub_hashes,
- sizeof(withdraw->denom_pub_hashes[0])),
- TALER_JSON_pack_amount (
- "withdraw_fee",
- &withdraw_fee),
- TALER_JSON_pack_amount (
- "amount",
- &withdraw->amount_with_fee)
- );
-
- if (! withdraw->no_blinding_seed)
- {
- if (0!=
- json_object_update_new (
- j_entry,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto (
- "blinding_seed",
- &withdraw->blinding_seed))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- if (0!=
- json_object_update_new (
- j_entry,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_array_of_data (
- "cs_r_values",
- withdraw->num_cs_r_values,
- withdraw->cs_r_values,
- sizeof(withdraw->cs_r_values[0])))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- if (withdraw->age_proof_required)
- {
- if (0 !=
- json_object_update_new (
- j_entry,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_uint64 (
- "noreveal_index",
- withdraw->noreveal_index),
- GNUNET_JSON_pack_data_auto (
- "selected_h",
- &withdraw->selected_h),
- GNUNET_JSON_pack_uint64 (
- "max_age",
- withdraw->max_age))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
-
- if (0 !=
- json_array_append_new (
- json_history,
- j_entry))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- }
-
- break;
- case TALER_EXCHANGEDB_RO_RECOUP_COIN:
- {
- const struct TALER_EXCHANGEDB_Recoup *recoup
- = pos->details.recoup;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_confirm_recoup_sign (
- &TEH_keys_exchange_sign_,
- recoup->timestamp,
- &recoup->value,
- &recoup->coin.coin_pub,
- &recoup->reserve_pub,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "RECOUP"),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_timestamp ("timestamp",
- recoup->timestamp),
- TALER_JSON_pack_amount ("amount",
- &recoup->value),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &recoup->coin.coin_pub))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
- {
- const struct TALER_EXCHANGEDB_ClosingTransfer *closing =
- pos->details.closing;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
-
- if (TALER_EC_NONE !=
- TALER_exchange_online_reserve_closed_sign (
- &TEH_keys_exchange_sign_,
- closing->execution_date,
- &closing->amount,
- &closing->closing_fee,
- closing->receiver_account_details,
- &closing->wtid,
- &pos->details.closing->reserve_pub,
- &pub,
- &sig))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CLOSING"),
- TALER_JSON_pack_full_payto ("receiver_account_details",
- closing->receiver_account_details),
- GNUNET_JSON_pack_data_auto ("wtid",
- &closing->wtid),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_timestamp ("timestamp",
- closing->execution_date),
- TALER_JSON_pack_amount ("amount",
- &closing->amount),
- TALER_JSON_pack_amount ("closing_fee",
- &closing->closing_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_PURSE_MERGE:
- {
- const struct TALER_EXCHANGEDB_PurseMerge *merge =
- pos->details.merge;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "MERGE"),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &merge->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &merge->merge_pub),
- GNUNET_JSON_pack_uint64 ("min_age",
- merge->min_age),
- GNUNET_JSON_pack_uint64 ("flags",
- merge->flags),
- GNUNET_JSON_pack_data_auto ("purse_pub",
- &merge->purse_pub),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &merge->reserve_sig),
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge->merge_timestamp),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- merge->purse_expiration),
- TALER_JSON_pack_amount ("purse_fee",
- &merge->purse_fee),
- TALER_JSON_pack_amount ("amount",
- &merge->amount_with_fee),
- GNUNET_JSON_pack_bool ("merged",
- merge->merged))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- case TALER_EXCHANGEDB_RO_HISTORY_REQUEST:
- {
- const struct TALER_EXCHANGEDB_HistoryRequest *history =
- pos->details.history;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "HISTORY"),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &history->reserve_sig),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- history->request_timestamp),
- TALER_JSON_pack_amount ("amount",
- &history->history_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
-
- case TALER_EXCHANGEDB_RO_OPEN_REQUEST:
- {
- const struct TALER_EXCHANGEDB_OpenRequest *orq =
- pos->details.open_request;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "OPEN"),
- GNUNET_JSON_pack_uint64 ("requested_min_purses",
- orq->purse_limit),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &orq->reserve_sig),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- orq->request_timestamp),
- GNUNET_JSON_pack_timestamp ("requested_expiration",
- orq->reserve_expiration),
- TALER_JSON_pack_amount ("open_fee",
- &orq->open_fee))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
-
- case TALER_EXCHANGEDB_RO_CLOSE_REQUEST:
- {
- const struct TALER_EXCHANGEDB_CloseRequest *crq =
- pos->details.close_request;
-
- if (0 !=
- json_array_append_new (
- json_history,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "CLOSE"),
- GNUNET_JSON_pack_data_auto ("reserve_sig",
- &crq->reserve_sig),
- GNUNET_is_zero (&crq->target_account_h_payto)
- ? GNUNET_JSON_pack_allow_null (
- GNUNET_JSON_pack_string ("h_payto",
- NULL))
- : GNUNET_JSON_pack_data_auto ("h_payto",
- &crq->target_account_h_payto),
- GNUNET_JSON_pack_timestamp ("request_timestamp",
- crq->request_timestamp))))
- {
- GNUNET_break (0);
- json_decref (json_history);
- return NULL;
- }
- }
- break;
- }
- }
- return json_history;
-}
-
-
-/**
- * Add the headers we want to set for every /keys response.
- *
- * @param cls the key state to use
- * @param[in,out] response the response to modify
- */
-static void
-add_response_headers (void *cls,
- struct MHD_Response *response)
-{
- (void) cls;
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (response,
- MHD_HTTP_HEADER_CACHE_CONTROL,
- "no-cache"));
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_history (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub)
-{
- struct TALER_EXCHANGEDB_ReserveHistory *rh = NULL;
- uint64_t start_off = 0;
- struct TALER_Amount balance;
- uint64_t etag_in;
- uint64_t etag_out;
- char etagp[24];
- struct MHD_Response *resp;
- unsigned int http_status;
-
- TALER_MHD_parse_request_number (rc->connection,
- "start",
- &start_off);
- {
- struct TALER_ReserveSignatureP reserve_sig;
- bool required = true;
-
- TALER_MHD_parse_request_header_auto (rc->connection,
- TALER_RESERVE_HISTORY_SIGNATURE_HEADER,
- &reserve_sig,
- required);
-
- if (GNUNET_OK !=
- TALER_wallet_reserve_history_verify (start_off,
- reserve_pub,
- &reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVE_HISTORY_BAD_SIGNATURE,
- NULL);
- }
- }
-
- /* Get etag */
- {
- const char *etags;
-
- etags = MHD_lookup_connection_value (rc->connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_IF_NONE_MATCH);
- if (NULL != etags)
- {
- char dummy;
- unsigned long long ev;
-
- if (1 != sscanf (etags,
- "\"%llu\"%c",
- &ev,
- &dummy))
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Client send malformed `If-None-Match' header `%s'\n",
- etags);
- etag_in = 0;
- }
- else
- {
- etag_in = (uint64_t) ev;
- }
- }
- else
- {
- etag_in = start_off;
- }
- }
-
- {
- enum GNUNET_DB_QueryStatus qs;
-
- qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
- reserve_pub,
- start_off,
- etag_in,
- &etag_out,
- &balance,
- &rh);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_reserve_history");
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_SOFT_FAILURE,
- "get_reserve_history");
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- /* Handled below */
- break;
- }
- }
-
- GNUNET_snprintf (etagp,
- sizeof (etagp),
- "\"%llu\"",
- (unsigned long long) etag_out);
- if (etag_in == etag_out)
- {
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rh);
- return TEH_RESPONSE_reply_not_modified (rc->connection,
- etagp,
- &add_response_headers,
- NULL);
- }
- if (NULL == rh)
- {
- /* 204: empty history */
- resp = MHD_create_response_from_buffer_static (0,
- "");
- http_status = MHD_HTTP_NO_CONTENT;
- }
- else
- {
- json_t *history;
-
- http_status = MHD_HTTP_OK;
- history = compile_reserve_history (rh);
- TEH_plugin->free_reserve_history (TEH_plugin->cls,
- rh);
- rh = NULL;
- if (NULL == history)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- NULL);
- }
- resp = TALER_MHD_MAKE_JSON_PACK (
- TALER_JSON_pack_amount ("balance",
- &balance),
- GNUNET_JSON_pack_array_steal ("history",
- history));
- }
- add_response_headers (NULL,
- resp);
- GNUNET_break (MHD_YES ==
- MHD_add_response_header (resp,
- MHD_HTTP_HEADER_ETAG,
- etagp));
- {
- MHD_RESULT ret;
-
- ret = MHD_queue_response (rc->connection,
- http_status,
- resp);
- GNUNET_break (MHD_YES == ret);
- MHD_destroy_response (resp);
- return ret;
- }
-}
-
-
-/* end of taler-exchange-httpd_reserves_history.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_history.h b/src/exchange/taler-exchange-httpd_reserves_history.h
@@ -1,43 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2020 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_reserves_history.h
- * @brief Handle /reserves/$RESERVE_PUB/history requests
- * @author Florian Dold
- * @author Benedikt Mueller
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_HISTORY_H
-
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/reserves/$RID/history" request.
- *
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_history (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.c b/src/exchange/taler-exchange-httpd_reserves_open.c
@@ -1,471 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_open.c
- * @brief Handle /reserves/$RESERVE_PUB/open requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_dbevents.h"
-#include "taler-exchange-httpd_common_deposit.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_reserves_open.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * How far do we allow a client's time to be off when
- * checking the request timestamp?
- */
-#define TIMESTAMP_TOLERANCE \
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
-
-
-/**
- * Closure for #reserve_open_transaction.
- */
-struct ReserveOpenContext
-{
- /**
- * Public key of the reserve the inquiry is about.
- */
- const struct TALER_ReservePublicKeyP *reserve_pub;
-
- /**
- * Desired (minimum) expiration time for the reserve.
- */
- struct GNUNET_TIME_Timestamp desired_expiration;
-
- /**
- * Actual expiration time for the reserve.
- */
- struct GNUNET_TIME_Timestamp reserve_expiration;
-
- /**
- * Timestamp of the request.
- */
- struct GNUNET_TIME_Timestamp timestamp;
-
- /**
- * Client signature approving the request.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Global fees applying to the request.
- */
- const struct TEH_GlobalFee *gf;
-
- /**
- * Amount to be paid from the reserve.
- */
- struct TALER_Amount reserve_payment;
-
- /**
- * Actual cost to open the reserve.
- */
- struct TALER_Amount open_cost;
-
- /**
- * Total amount that was deposited.
- */
- struct TALER_Amount total;
-
- /**
- * Information about payments by coin.
- */
- struct TEH_PurseDepositedCoin *payments;
-
- /**
- * Length of the @e payments array.
- */
- unsigned int payments_len;
-
- /**
- * Desired minimum purse limit.
- */
- uint32_t purse_limit;
-
- /**
- * Set to true if the reserve balance is too low
- * for the operation.
- */
- bool no_funds;
-
-};
-
-
-/**
- * Send reserve open to client.
- *
- * @param connection connection to the client
- * @param rsc reserve open data to return
- * @return MHD result code
- */
-static MHD_RESULT
-reply_reserve_open_success (struct MHD_Connection *connection,
- const struct ReserveOpenContext *rsc)
-{
- struct GNUNET_TIME_Timestamp now;
- struct GNUNET_TIME_Timestamp re;
- unsigned int status;
-
- status = MHD_HTTP_OK;
- if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
- <,
- rsc->desired_expiration))
- status = MHD_HTTP_PAYMENT_REQUIRED;
- now = GNUNET_TIME_timestamp_get ();
- if (GNUNET_TIME_timestamp_cmp (rsc->reserve_expiration,
- <,
- now))
- re = now;
- else
- re = rsc->reserve_expiration;
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- status,
- GNUNET_JSON_pack_timestamp ("reserve_expiration",
- re),
- TALER_JSON_pack_amount ("open_cost",
- &rsc->open_cost));
-}
-
-
-/**
- * Cleans up information in @a rsc, but does not
- * free @a rsc itself (allocated on the stack!).
- *
- * @param[in] rsc struct with information to clean up
- */
-static void
-cleanup_rsc (struct ReserveOpenContext *rsc)
-{
- for (unsigned int i = 0; i<rsc->payments_len; i++)
- {
- TEH_common_purse_deposit_free_coin (&rsc->payments[i]);
- }
- GNUNET_free (rsc->payments);
-}
-
-
-/**
- * Function implementing /reserves/$RID/open transaction. Given the public
- * key of a reserve, return the associated transaction open. Runs the
- * transaction logic; IF it returns a non-error code, the transaction logic
- * MUST NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
- * returns the soft error code, the function MAY be called again to retry and
- * MUST not queue a MHD response.
- *
- * @param cls a `struct ReserveOpenContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-reserve_open_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReserveOpenContext *rsc = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_Amount reserve_balance;
-
- for (unsigned int i = 0; i<rsc->payments_len; i++)
- {
- struct TEH_PurseDepositedCoin *coin = &rsc->payments[i];
- bool insufficient_funds = true;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Make coin %u known\n",
- i);
- qs = TEH_make_coin_known (&coin->cpi,
- connection,
- &coin->known_coin_id,
- mhd_ret);
- if (qs < 0)
- return qs;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Insert open deposit %u known\n",
- i);
- qs = TEH_plugin->insert_reserve_open_deposit (
- TEH_plugin->cls,
- &coin->cpi,
- &coin->coin_sig,
- coin->known_coin_id,
- &coin->amount,
- &rsc->reserve_sig,
- rsc->reserve_pub,
- &insufficient_funds);
- /* 0 == qs is fine, then the coin was already
- spent for this very operation as identified
- by reserve_sig! */
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_reserve_open_deposit");
- return qs;
- }
- if (insufficient_funds)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handle insufficient funds\n");
- *mhd_ret
- = TEH_RESPONSE_reply_coin_insufficient_funds (
- connection,
- TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
- &coin->cpi.denom_pub_hash,
- &coin->cpi.coin_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Do reserve open with reserve payment of %s\n",
- TALER_amount2s (&rsc->total));
- qs = TEH_plugin->do_reserve_open (TEH_plugin->cls,
- /* inputs */
- rsc->reserve_pub,
- &rsc->total,
- &rsc->reserve_payment,
- rsc->purse_limit,
- &rsc->reserve_sig,
- rsc->desired_expiration,
- rsc->timestamp,
- &rsc->gf->fees.account,
- /* outputs */
- &rsc->no_funds,
- &reserve_balance,
- &rsc->open_cost,
- &rsc->reserve_expiration);
- switch (qs)
- {
- case GNUNET_DB_STATUS_HARD_ERROR:
- GNUNET_break (0);
- *mhd_ret
- = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "do_reserve_open");
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- return qs;
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *mhd_ret
- = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- break;
- }
- if (rsc->no_funds)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- *mhd_ret
- = TEH_RESPONSE_reply_reserve_insufficient_balance (
- connection,
- TALER_EC_EXCHANGE_RESERVES_OPEN_INSUFFICIENT_FUNDS,
- &reserve_balance,
- &rsc->reserve_payment,
- rsc->reserve_pub);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return qs;
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_open (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root)
-{
- struct ReserveOpenContext rsc;
- const json_t *payments;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_timestamp ("request_timestamp",
- &rsc.timestamp),
- GNUNET_JSON_spec_timestamp ("reserve_expiration",
- &rsc.desired_expiration),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rsc.reserve_sig),
- GNUNET_JSON_spec_uint32 ("purse_limit",
- &rsc.purse_limit),
- GNUNET_JSON_spec_array_const ("payments",
- &payments),
- TALER_JSON_spec_amount ("reserve_payment",
- TEH_currency,
- &rsc.reserve_payment),
- GNUNET_JSON_spec_end ()
- };
-
- rsc.reserve_pub = reserve_pub;
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
-
- {
- struct GNUNET_TIME_Timestamp now;
-
- now = GNUNET_TIME_timestamp_get ();
- if (! GNUNET_TIME_absolute_approx_eq (now.abs_time,
- rsc.timestamp.abs_time,
- TIMESTAMP_TOLERANCE))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
- NULL);
- }
- }
-
- rsc.payments_len = json_array_size (payments);
- rsc.payments = GNUNET_new_array (rsc.payments_len,
- struct TEH_PurseDepositedCoin);
- rsc.total = rsc.reserve_payment;
- for (unsigned int i = 0; i<rsc.payments_len; i++)
- {
- struct TEH_PurseDepositedCoin *coin = &rsc.payments[i];
- enum GNUNET_GenericReturnValue res;
-
- res = TEH_common_purse_deposit_parse_coin (
- rc->connection,
- coin,
- json_array_get (payments,
- i));
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- cleanup_rsc (&rsc);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- cleanup_rsc (&rsc);
- return MHD_YES; /* failure */
- }
- if (0 >
- TALER_amount_add (&rsc.total,
- &rsc.total,
- &coin->amount_minus_fee))
- {
- GNUNET_break (0);
- cleanup_rsc (&rsc);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_FAILED_COMPUTE_AMOUNT,
- NULL);
- }
- }
-
- {
- struct TEH_KeyStateHandle *keys;
-
- keys = TEH_keys_get_state ();
- if (NULL == keys)
- {
- GNUNET_break (0);
- cleanup_rsc (&rsc);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- }
- rsc.gf = TEH_keys_global_fee_by_time (keys,
- rsc.timestamp);
- }
- if (NULL == rsc.gf)
- {
- GNUNET_break (0);
- cleanup_rsc (&rsc);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
- NULL);
- }
-
- if (GNUNET_OK !=
- TALER_wallet_reserve_open_verify (&rsc.reserve_payment,
- rsc.timestamp,
- rsc.desired_expiration,
- rsc.purse_limit,
- reserve_pub,
- &rsc.reserve_sig))
- {
- GNUNET_break_op (0);
- cleanup_rsc (&rsc);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_OPEN_BAD_SIGNATURE,
- NULL);
- }
-
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "reserve open",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &reserve_open_transaction,
- &rsc))
- {
- cleanup_rsc (&rsc);
- return mhd_ret;
- }
- }
-
- {
- MHD_RESULT mhd_ret;
-
- mhd_ret = reply_reserve_open_success (rc->connection,
- &rsc);
- cleanup_rsc (&rsc);
- return mhd_ret;
- }
-}
-
-
-/* end of taler-exchange-httpd_reserves_open.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_open.h b/src/exchange/taler-exchange-httpd_reserves_open.h
@@ -1,41 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_open.h
- * @brief Handle /reserves/$RESERVE_PUB/open requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_OPEN_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a POST "/reserves/$RID/open" request.
- *
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @param root uploaded body from the client
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_open (struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.c b/src/exchange/taler-exchange-httpd_reserves_purse.c
@@ -1,894 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022-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 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_reserves_purse.c
- * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
- * verifies the coin signature before handing things off
- * to the database.
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_reserves_purse.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_exchangedb_lib.h"
-#include "taler-exchange-httpd_keys.h"
-
-
-/**
- * Closure for #purse_transaction.
- */
-struct ReservePurseContext
-{
-
- /**
- * Kept in a DLL.
- */
- struct ReservePurseContext *next;
-
- /**
- * Kept in a DLL.
- */
- struct ReservePurseContext *prev;
-
- /**
- * Our request context.
- */
- struct TEH_RequestContext *rc;
-
- /**
- * Handle for legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Response to return. Note that the response must
- * be queued or destroyed by the callee. NULL
- * if the legitimization check was successful and the handler should return
- * a handler-specific result.
- */
- struct MHD_Response *response;
-
- /**
- * Payto URI for the reserve.
- */
- struct TALER_NormalizedPayto payto_uri;
-
- /**
- * Public key of the account (reserve) we are creating a purse for.
- */
- union TALER_AccountPublicKeyP account_pub;
-
- /**
- * Signature of the reserve affirming the merge.
- */
- struct TALER_ReserveSignatureP reserve_sig;
-
- /**
- * Purse fee the client is willing to pay.
- */
- struct TALER_Amount purse_fee;
-
- /**
- * Total amount already put into the purse.
- */
- struct TALER_Amount deposit_total;
-
- /**
- * Merge time.
- */
- struct GNUNET_TIME_Timestamp merge_timestamp;
-
- /**
- * Our current time.
- */
- struct GNUNET_TIME_Timestamp exchange_timestamp;
-
- /**
- * Details about an encrypted contract, if any.
- */
- struct TALER_EncryptedContract econtract;
-
- /**
- * Merge key for the purse.
- */
- struct TALER_PurseMergePublicKeyP merge_pub;
-
- /**
- * Merge affirmation by the @e merge_pub.
- */
- struct TALER_PurseMergeSignatureP merge_sig;
-
- /**
- * Signature of the client affiming this request.
- */
- struct TALER_PurseContractSignatureP purse_sig;
-
- /**
- * Fundamental details about the purse.
- */
- struct TEH_PurseDetails pd;
-
- /**
- * Hash of the @e payto_uri.
- */
- struct TALER_NormalizedPaytoHashP h_payto;
-
- /**
- * KYC status of the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Minimum age for deposits into this purse.
- */
- uint32_t min_age;
-
- /**
- * Flags for the operation.
- */
- enum TALER_WalletAccountMergeFlags flags;
-
- /**
- * HTTP status code for @a response, or 0
- */
- unsigned int http_status;
-
- /**
- * Do we lack an @e econtract?
- */
- bool no_econtract;
-
- /**
- * Set to true if the purse_fee was not given in the REST request.
- */
- bool no_purse_fee;
-
-};
-
-/**
- * Kept in a DLL.
- */
-static struct ReservePurseContext *rpc_head;
-
-/**
- * Kept in a DLL.
- */
-static struct ReservePurseContext *rpc_tail;
-
-
-void
-TEH_reserves_purse_cleanup ()
-{
- struct ReservePurseContext *rpc;
-
- while (NULL != (rpc = rpc_head))
- {
- GNUNET_CONTAINER_DLL_remove (rpc_head,
- rpc_tail,
- rpc);
- MHD_resume_connection (rpc->rc->connection);
- }
-}
-
-
-/**
- * Function called to iterate over KYC-relevant
- * transaction amounts for a particular time range.
- * Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls a `struct ReservePurseContext`
- * @param limit maximum time-range for which events
- * should be fetched (timestamp in the past)
- * @param cb function to call on each event found,
- * events must be returned in reverse chronological
- * order
- * @param cb_cls closure for @a cb
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-amount_iterator (void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct ReservePurseContext *rpc = cls;
- enum GNUNET_DB_QueryStatus qs;
- enum GNUNET_GenericReturnValue ret;
-
- ret = cb (cb_cls,
- &rpc->pd.target_amount,
- GNUNET_TIME_absolute_get ());
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
- qs = TEH_plugin->select_merge_amounts_for_kyc_check (
- TEH_plugin->cls,
- &rpc->h_payto,
- limit,
- cb,
- cb_cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got %d additional transactions for this merge and limit %llu\n",
- qs,
- (unsigned long long) limit.abs_value_us);
- GNUNET_break (qs >= 0);
- return qs;
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls closure
- * @param lcr legitimization check result
- */
-static void
-reserve_purse_legi_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct ReservePurseContext *rpc = cls;
-
- rpc->lch = NULL;
- rpc->http_status = lcr->http_status;
- rpc->response = lcr->response;
- rpc->kyc = lcr->kyc;
- GNUNET_CONTAINER_DLL_remove (rpc_head,
- rpc_tail,
- rpc);
- MHD_resume_connection (rpc->rc->connection);
- TALER_MHD_daemon_trigger ();
-}
-
-
-/**
- * Execute database transaction for /reserves/$PID/purse. Runs the transaction
- * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
- * a MHD response. IF it returns an hard error, the transaction logic MUST
- * queue a MHD response and set @a mhd_ret. IF it returns the soft error
- * code, the function MAY be called again to retry and MUST not queue a MHD
- * response.
- *
- * @param cls a `struct ReservePurseContext`
- * @param connection MHD request context
- * @param[out] mhd_ret set to MHD status on error
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-purse_transaction (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct ReservePurseContext *rpc = cls;
- enum GNUNET_DB_QueryStatus qs;
-
- {
- bool in_conflict = true;
-
- /* 1) store purse */
- qs = TEH_plugin->insert_purse_request (
- TEH_plugin->cls,
- &rpc->pd.purse_pub,
- &rpc->merge_pub,
- rpc->pd.purse_expiration,
- &rpc->pd.h_contract_terms,
- rpc->min_age,
- rpc->flags,
- &rpc->purse_fee,
- &rpc->pd.target_amount,
- &rpc->purse_sig,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0);
- *mhd_ret =
- TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert purse request");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return qs;
- if (in_conflict)
- {
- struct TALER_PurseMergePublicKeyP merge_pub;
- struct GNUNET_TIME_Timestamp purse_expiration;
- struct TALER_PrivateContractHashP h_contract_terms;
- struct TALER_Amount target_amount;
- struct TALER_Amount balance;
- struct TALER_PurseContractSignatureP purse_sig;
- uint32_t min_age;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- qs = TEH_plugin->get_purse_request (
- TEH_plugin->cls,
- &rpc->pd.purse_pub,
- &merge_pub,
- &purse_expiration,
- &h_contract_terms,
- &min_age,
- &target_amount,
- &balance,
- &purse_sig);
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse request");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
- TALER_JSON_pack_amount ("amount",
- &target_amount),
- GNUNET_JSON_pack_uint64 ("min_age",
- min_age),
- GNUNET_JSON_pack_timestamp ("purse_expiration",
- purse_expiration),
- GNUNET_JSON_pack_data_auto ("purse_sig",
- &purse_sig),
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &h_contract_terms),
- GNUNET_JSON_pack_data_auto ("merge_pub",
- &merge_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- }
-
- /* 2) create purse with reserve (and debit reserve for purse creation!) */
- {
- bool in_conflict = true;
- bool insufficient_funds = true;
- bool no_reserve = true;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Creating purse with flags %d\n",
- rpc->flags);
- qs = TEH_plugin->do_reserve_purse (
- TEH_plugin->cls,
- &rpc->pd.purse_pub,
- &rpc->merge_sig,
- rpc->merge_timestamp,
- &rpc->reserve_sig,
- (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
- == rpc->flags)
- ? NULL
- : &rpc->purse_fee,
- &rpc->account_pub.reserve_pub,
- &in_conflict,
- &no_reserve,
- &insufficient_funds);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING (
- "Failed to store purse merge information in database\n");
- *mhd_ret =
- TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "do reserve purse");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (in_conflict)
- {
- /* same purse already merged into a different reserve!? */
- struct TALER_PurseContractPublicKeyP purse_pub;
- struct TALER_PurseMergeSignatureP merge_sig;
- struct GNUNET_TIME_Timestamp merge_timestamp;
- char *partner_url;
- struct TALER_ReservePublicKeyP reserve_pub;
- bool refunded;
-
- TEH_plugin->rollback (TEH_plugin->cls);
- qs = TEH_plugin->select_purse_merge (
- TEH_plugin->cls,
- &purse_pub,
- &merge_sig,
- &merge_timestamp,
- &partner_url,
- &reserve_pub,
- &refunded);
- if (qs <= 0)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING (
- "Failed to fetch purse merge information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select purse merge");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (refunded)
- {
- /* This is a bit of a strange case ... */
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Purse was already refunded\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_GONE,
- TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
- NULL);
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
- GNUNET_JSON_pack_string ("partner_url",
- NULL == partner_url
- ? TEH_base_url
- : partner_url),
- GNUNET_JSON_pack_timestamp ("merge_timestamp",
- merge_timestamp),
- GNUNET_JSON_pack_data_auto ("merge_sig",
- &merge_sig),
- GNUNET_JSON_pack_data_auto ("reserve_pub",
- &reserve_pub));
- GNUNET_free (partner_url);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if ( (no_reserve) &&
- ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
- == rpc->flags) ||
- (! TALER_amount_is_zero (&rpc->purse_fee)) ) )
- {
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_NOT_FOUND,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (insufficient_funds)
- {
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- /* 3) if present, persist contract */
- if (! rpc->no_econtract)
- {
- bool in_conflict = true;
-
- qs = TEH_plugin->insert_contract (TEH_plugin->cls,
- &rpc->pd.purse_pub,
- &rpc->econtract,
- &in_conflict);
- if (qs < 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- TALER_LOG_WARNING ("Failed to store purse information in database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "purse purse contract");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (in_conflict)
- {
- struct TALER_EncryptedContract econtract;
- struct GNUNET_HashCode h_econtract;
-
- qs = TEH_plugin->select_contract_by_purse (
- TEH_plugin->cls,
- &rpc->pd.purse_pub,
- &econtract);
- if (qs <= 0)
- {
- if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
- return qs;
- GNUNET_break (0 != qs);
- TALER_LOG_WARNING (
- "Failed to store fetch contract information from database\n");
- *mhd_ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "select contract");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- GNUNET_CRYPTO_hash (econtract.econtract,
- econtract.econtract_size,
- &h_econtract);
- *mhd_ret
- = TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_CONFLICT,
- TALER_JSON_pack_ec (
- TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
- GNUNET_JSON_pack_data_auto ("h_econtract",
- &h_econtract),
- GNUNET_JSON_pack_data_auto ("econtract_sig",
- &econtract.econtract_sig),
- GNUNET_JSON_pack_data_auto ("contract_pub",
- &econtract.contract_pub));
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- }
- return qs;
-}
-
-
-/**
- * Function to clean up our rh_ctx in @a rc
- *
- * @param[in,out] rc context to clean up
- */
-static void
-rpc_cleaner (struct TEH_RequestContext *rc)
-{
- struct ReservePurseContext *rpc = rc->rh_ctx;
-
- if (NULL != rpc->lch)
- {
- TEH_legitimization_check_cancel (rpc->lch);
- rpc->lch = NULL;
- }
- GNUNET_free (rpc->econtract.econtract);
- GNUNET_free (rpc->payto_uri.normalized_payto);
- GNUNET_free (rpc);
-}
-
-
-MHD_RESULT
-TEH_handler_reserves_purse (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root)
-{
- struct ReservePurseContext *rpc = rc->rh_ctx;
- struct MHD_Connection *connection = rc->connection;
-
- if (NULL == rpc)
- {
- rpc = GNUNET_new (struct ReservePurseContext);
- rc->rh_ctx = rpc;
- rc->rh_cleaner = &rpc_cleaner;
- rpc->rc = rc;
- rpc->account_pub.reserve_pub = *reserve_pub;
- rpc->exchange_timestamp
- = GNUNET_TIME_timestamp_get ();
- rpc->no_purse_fee = true;
-
-
- {
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_amount ("purse_value",
- TEH_currency,
- &rpc->pd.target_amount),
- GNUNET_JSON_spec_uint32 ("min_age",
- &rpc->min_age),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_amount ("purse_fee",
- TEH_currency,
- &rpc->purse_fee),
- &rpc->no_purse_fee),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_econtract ("econtract",
- &rpc->econtract),
- &rpc->no_econtract),
- GNUNET_JSON_spec_fixed_auto ("merge_pub",
- &rpc->merge_pub),
- GNUNET_JSON_spec_fixed_auto ("merge_sig",
- &rpc->merge_sig),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &rpc->reserve_sig),
- GNUNET_JSON_spec_fixed_auto ("purse_pub",
- &rpc->pd.purse_pub),
- GNUNET_JSON_spec_fixed_auto ("purse_sig",
- &rpc->purse_sig),
- GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
- &rpc->pd.h_contract_terms),
- GNUNET_JSON_spec_timestamp ("merge_timestamp",
- &rpc->merge_timestamp),
- GNUNET_JSON_spec_timestamp ("purse_expiration",
- &rpc->pd.purse_expiration),
- GNUNET_JSON_spec_end ()
- };
-
- {
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_SYSERR == res)
- {
- GNUNET_break (0);
- return MHD_NO; /* hard failure */
- }
- if (GNUNET_NO == res)
- {
- GNUNET_break_op (0);
- return MHD_YES; /* failure */
- }
- }
- }
-
- rpc->payto_uri
- = TALER_reserve_make_payto (TEH_base_url,
- &rpc->account_pub.reserve_pub);
- TALER_normalized_payto_hash (rpc->payto_uri,
- &rpc->h_payto);
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
-
-
- if (GNUNET_OK !=
- TALER_wallet_purse_merge_verify (rpc->payto_uri,
- rpc->merge_timestamp,
- &rpc->pd.purse_pub,
- &rpc->merge_pub,
- &rpc->merge_sig))
- {
- MHD_RESULT ret;
-
- GNUNET_break_op (0);
- ret = TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
- rpc->payto_uri.normalized_payto);
- return ret;
- }
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &rpc->deposit_total));
- if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration,
- <,
- rpc->exchange_timestamp))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
- NULL);
- }
- if (GNUNET_TIME_absolute_is_never (
- rpc->pd.purse_expiration.abs_time))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
- NULL);
- }
-
- {
- struct TEH_KeyStateHandle *keys;
- const struct TEH_GlobalFee *gf;
-
- keys = TEH_keys_get_state ();
- if (NULL == keys)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- }
- gf = TEH_keys_global_fee_by_time (keys,
- rpc->exchange_timestamp);
- if (NULL == gf)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Cannot create purse: global fees not configured!\n");
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
- NULL);
- }
- if (rpc->no_purse_fee)
- {
- rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &rpc->purse_fee));
- }
- else
- {
- rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
- if (-1 ==
- TALER_amount_cmp (&rpc->purse_fee,
- &gf->fees.purse))
- {
- /* rpc->purse_fee is below gf.fees.purse! */
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
- TALER_amount2s (&gf->fees.purse));
- }
- }
- }
-
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_purse_create_verify (
- rpc->pd.purse_expiration,
- &rpc->pd.h_contract_terms,
- &rpc->merge_pub,
- rpc->min_age,
- &rpc->pd.target_amount,
- &rpc->pd.purse_pub,
- &rpc->purse_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
- NULL);
- }
- if (GNUNET_OK !=
- TALER_wallet_account_merge_verify (
- rpc->merge_timestamp,
- &rpc->pd.purse_pub,
- rpc->pd.purse_expiration,
- &rpc->pd.h_contract_terms,
- &rpc->pd.target_amount,
- &rpc->purse_fee,
- rpc->min_age,
- rpc->flags,
- &rpc->account_pub.reserve_pub,
- &rpc->reserve_sig))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
- NULL);
- }
- if ( (! rpc->no_econtract) &&
- (GNUNET_OK !=
- TALER_wallet_econtract_upload_verify (
- rpc->econtract.econtract,
- rpc->econtract.econtract_size,
- &rpc->econtract.contract_pub,
- &rpc->pd.purse_pub,
- &rpc->econtract.econtract_sig))
- )
- {
- TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
- NULL);
- }
- {
- struct TALER_FullPayto fake_full_payto;
-
- GNUNET_asprintf (&fake_full_payto.full_payto,
- "%s?receiver-name=wallet",
- rpc->payto_uri.normalized_payto);
- rpc->lch = TEH_legitimization_check (
- &rpc->rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
- fake_full_payto,
- &rpc->h_payto,
- &rpc->account_pub,
- &amount_iterator,
- rpc,
- &reserve_purse_legi_cb,
- rpc);
- GNUNET_free (fake_full_payto.full_payto);
- }
- GNUNET_assert (NULL != rpc->lch);
- MHD_suspend_connection (rc->connection);
- GNUNET_CONTAINER_DLL_insert (rpc_head,
- rpc_tail,
- rpc);
- return MHD_YES;
- }
- if (NULL != rpc->response)
- return MHD_queue_response (connection,
- rpc->http_status,
- rpc->response);
- if (! rpc->kyc.ok)
- {
- if (0 == rpc->kyc.requirement_row)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "requirement row not set");
- }
- return TEH_RESPONSE_reply_kyc_required (
- connection,
- &rpc->h_payto,
- &rpc->kyc,
- false);
- }
-
- {
- MHD_RESULT mhd_ret;
-
- if (GNUNET_OK !=
- TEH_DB_run_transaction (connection,
- "execute reserve purse",
- TEH_MT_REQUEST_RESERVE_PURSE,
- &mhd_ret,
- &purse_transaction,
- rpc))
- {
- return mhd_ret;
- }
- }
-
- /* generate regular response */
- {
- MHD_RESULT res;
-
- res = TEH_RESPONSE_reply_purse_created (
- connection,
- rpc->exchange_timestamp,
- &rpc->deposit_total,
- &rpc->pd);
- return res;
- }
-}
-
-
-/* end of taler-exchange-httpd_reserves_purse.c */
diff --git a/src/exchange/taler-exchange-httpd_reserves_purse.h b/src/exchange/taler-exchange-httpd_reserves_purse.h
@@ -1,53 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2022 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_reserves_purse.h
- * @brief Handle /reserves/$RID/purse requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_RESERVES_PURSE_H
-#define TALER_EXCHANGE_HTTPD_RESERVES_PURSE_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_reserves_purse_cleanup (void);
-
-
-/**
- * Handle a "/reserves/$RESERVE_PUB/purse" request. Parses the JSON, and, if
- * successful, passes the JSON data to #create_transaction() to further check
- * the details of the operation specified. If everything checks out, this
- * will ultimately lead to the "purses create" being executed, or rejected.
- *
- * @param rc request context
- * @param reserve_pub public key of the reserve
- * @param root uploaded JSON data
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reserves_purse (
- struct TEH_RequestContext *rc,
- const struct TALER_ReservePublicKeyP *reserve_pub,
- const json_t *root);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_responses.c b/src/exchange/taler-exchange-httpd_responses.c
@@ -31,7 +31,7 @@
#include "taler/taler_util.h"
#include "taler/taler_json_lib.h"
#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_keys.h"
+#include "taler-exchange-httpd_get-keys.h"
MHD_RESULT
diff --git a/src/exchange/taler-exchange-httpd_reveal-melt.c b/src/exchange/taler-exchange-httpd_reveal-melt.c
@@ -1,877 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023,2025 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_reveal-melt.c
- * @brief Handle /reveal-melt requests
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_common.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd_metrics.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_reveal-melt.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-#define KAPPA_MINUS_1 (TALER_CNC_KAPPA - 1)
-
-
-/**
- * State for an /reveal-melt operation.
- */
-struct MeltRevealContext
-{
-
- /**
- * Commitment for the melt operation, previously called by the
- * client.
- */
- struct TALER_RefreshCommitmentP rc;
-
- /**
- * The data from the original melt. Will be retrieved from
- * the DB via @a rc.
- */
- struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS refresh;
-
- /**
- * True, if @e signatures were not provided in the request.
- */
- bool no_signatures;
-
- /**
- * @since v27
- * @deprecated after vDOLDPLUS
- *
- * TALER_CNC_KAPPA-1 disclosed signatures for public refresh nonces.
- */
- struct TALER_PrivateRefreshNonceSignatureP signatures[KAPPA_MINUS_1];
-
- /**
- * True, if @e transfer_secret_seeds were not provided in the request.
- */
- bool no_transfer_secret_seeds;
-
- /**
- * @since vDOLDPLUS
- *
- * The transfer secret seeds for the revealed batches of coins.
- */
- struct TALER_PrivateRefreshBatchSeedP transfer_secret_seeds[KAPPA_MINUS_1];
-
- /**
- * True, if no @e age_commitment was provided
- */
- bool no_age_commitment;
-
- /**
- * If @e no_age_commitment is false, the age commitment of
- * the old coin. Needed to ensure that the age commitment
- * is applied correctly to the fresh coins.
- */
- struct TALER_AgeCommitment age_commitment;
-};
-
-
-/**
- * Check if the request belongs to an existing refresh request.
- * If so, sets the refresh object with the request data.
- * Otherwise, it queues an appropriate MHD response.
- *
- * @param connection The HTTP connection to the client
- * @param rc Original commitment value sent with the melt request
- * @param[out] refresh Data from the original refresh request
- * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
- * @return #GNUNET_OK if the refresh request has been found,
- * #GNUNET_SYSERR if we did not find the request in the DB
- */
-static enum GNUNET_GenericReturnValue
-find_original_refresh (
- struct MHD_Connection *connection,
- const struct TALER_RefreshCommitmentP *rc,
- struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh,
- MHD_RESULT *result)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- for (unsigned int retry = 0; retry < 3; retry++)
- {
- qs = TEH_plugin->get_refresh (TEH_plugin->cls,
- rc,
- refresh);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_OK; /* Only happy case */
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
- NULL);
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_HARD_ERROR:
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_refresh");
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- break; /* try again */
- default:
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- return GNUNET_SYSERR;
- }
- }
- /* after unsuccessful retries*/
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_refresh");
- return GNUNET_SYSERR;
-}
-
-
-/**
- * Verify that the age commitment is sound, that is, if the
- * previous /melt provided a hash, ensure we have the corresponding
- * age commitment. Or not, if it wasn't provided.
- *
- * @param connection The MHD connection to handle
- * @param actx The context of the operation, only partially built at this time
- * @param[out] mhd_ret The result if a reply is queued for MHD
- * @return #GNUNET_OK on success, otherwise a reply is queued for MHD and @a mhd_ret is set
- */
-static enum GNUNET_GenericReturnValue
-compare_age_commitment (
- struct MHD_Connection *connection,
- struct MeltRevealContext *actx,
- MHD_RESULT *mhd_ret)
-{
- if (actx->no_age_commitment !=
- actx->refresh.coin.no_age_commitment)
- {
- *mhd_ret = TALER_MHD_reply_with_ec (connection,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
- NULL);
- return GNUNET_SYSERR;
- }
- if (! actx->no_age_commitment)
- {
- struct TALER_AgeCommitmentHashP ach;
-
- actx->age_commitment.mask = TEH_age_restriction_config.mask;
- TALER_age_commitment_hash (
- &actx->age_commitment,
- &ach);
- if (0 != GNUNET_memcmp (
- &actx->refresh.coin.h_age_commitment,
- &ach))
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_ec (connection,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_AGE_RESTRICTION_COMMITMENT_INVALID,
- NULL);
- return GNUNET_SYSERR;
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * @brief Derives an planchet from a given input and returns
- * blinded planchets detail
- *
- * @param connection Connection to the client
- * @param denom_key The denomination key
- * @param secret The secret to a planchet
- * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL
- * @param nonce The derived nonce needed for CS denomination
- * @param old_age_commitment The age commitment of the old coin, might be NULL
- * @param[out] detail planchet detail to write to write
- * @param[out] result On error, a HTTP-response will be queued and result set accordingly
- * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message
- * written to the client and @e result set.
- */
-static enum GNUNET_GenericReturnValue
-calculate_blinded_detail (
- struct MHD_Connection *connection,
- struct TEH_DenominationKey *denom_key,
- const struct TALER_PlanchetMasterSecretP *secret,
- const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub,
- union GNUNET_CRYPTO_BlindSessionNonce *nonce,
- const struct TALER_AgeCommitment *old_age_commitment,
- struct TALER_PlanchetDetail *detail,
- MHD_RESULT *result)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_AgeCommitmentHashP ach;
- bool no_age_commitment = (NULL == old_age_commitment);
-
- /* calculate age commitment hash */
- if (! no_age_commitment)
- {
- struct TALER_AgeCommitment nac;
-
- TALER_age_commitment_derive_from_secret (old_age_commitment,
- secret,
- &nac);
- TALER_age_commitment_hash (&nac,
- &ach);
- TALER_age_commitment_free (&nac);
- }
-
- /* Next: calculate planchet */
- {
- struct TALER_CoinPubHashP c_hash;
- struct TALER_CoinSpendPrivateKeyP coin_priv;
- union GNUNET_CRYPTO_BlindingSecretP bks;
- struct GNUNET_CRYPTO_BlindingInputValues bi = {
- .cipher = denom_key->denom_pub.bsign_pub_key->cipher
- };
- struct TALER_ExchangeBlindingValues blinding_values = {
- .blinding_inputs = &bi
- };
-
- switch (bi.cipher)
- {
- case GNUNET_CRYPTO_BSA_CS:
- GNUNET_assert (NULL != r_pub);
- GNUNET_assert (NULL != nonce);
- bi.details.cs_values = *r_pub;
- break;
- case GNUNET_CRYPTO_BSA_RSA:
- break;
- default:
- GNUNET_assert (0);
- }
-
- TALER_planchet_blinding_secret_create (secret,
- &blinding_values,
- &bks);
- TALER_planchet_setup_coin_priv (secret,
- &blinding_values,
- &coin_priv);
- ret = TALER_planchet_prepare (&denom_key->denom_pub,
- &blinding_values,
- &bks,
- nonce,
- &coin_priv,
- no_age_commitment
- ? NULL
- : &ach,
- &c_hash,
- detail);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- *result = TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- GNUNET_JSON_pack_string (
- "details",
- "failed to prepare planchet from base key"));
- return ret;
- }
- }
- return ret;
-}
-
-
-/**
- * @brief Checks the validity of the disclosed signatures as follows:
- * - Verifies the validity of the disclosed signatures with the old coin's public key
- * - Derives the seeds for disclosed fresh coins
- * - Derives the fresh coins from the seeds
- * - Derives new age commitment
- * - Calculates the blinded coin planchet hashes
- * - Calculates the refresh commitment from above data
- * - Compares the calculated commitment with existing one
- *
- * The derivation of a fresh coin from the old coin is defined in
- * https://docs.taler.net/design-documents/062-pq-refresh.html
- *
- * The derivation of age-commitment from a coin's age-commitment
- * https://docs.taler.net/design-documents/024-age-restriction.html#melt
- *
- * @param con HTTP-connection to the client
- * @param rf Original refresh object from the previous /melt request
- * @param old_age_commitment The age commitment of the original coin
- * @param signatures The secrets of the disclosed coins, KAPPA_MINUS_1*num_coins many, maybe NULL
- * @param rev_batch_seeds The seeds for the transfer secrets of the disclosed coins, KAPPA_MINUS_1 many, maybe NULL
- * @param[out] result On error, a HTTP-response will be queued and result set accordingly
- * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-verify_commitment (
- struct MHD_Connection *con,
- const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
- const struct TALER_AgeCommitment *old_age_commitment,
- const struct TALER_PrivateRefreshNonceSignatureP (*signatures)[KAPPA_MINUS_1],
- const struct TALER_PrivateRefreshBatchSeedP (*rev_batch_seeds)[KAPPA_MINUS_1],
- MHD_RESULT *result)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TEH_KeyStateHandle *keys;
- struct TEH_DenominationKey *denom_keys[rf->num_coins];
- struct TALER_DenominationHashP *denoms_h[rf->num_coins];
- struct TALER_Amount total_amount;
- struct TALER_Amount total_fee;
- bool is_cs[rf->num_coins];
- size_t cs_count = 0;
-
- GNUNET_assert (rf->noreveal_index < TALER_CNC_KAPPA);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &total_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &total_fee));
- memset (denom_keys,
- 0,
- sizeof(denom_keys));
- memset (is_cs,
- 0,
- sizeof(is_cs));
-
- /**
- * Consistency check:
- * If the refresh was for v27, signatures must not be NULL,
- * otherwise the revealed batch seeds must not be NULL.
- */
- if (rf->is_v27_refresh)
- {
- if (NULL == signatures)
- {
- *result = TALER_MHD_reply_with_error (con,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "signatures missing");
- return GNUNET_SYSERR;
- }
- }
- else /* vDOLDPLUS */
- {
- if (NULL == rev_batch_seeds)
- {
- *result = TALER_MHD_reply_with_error (con,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "batch_seeds missing");
- return GNUNET_SYSERR;
- }
- }
- /**
- * We need the current keys in memory for the meta-data of the denominations
- */
- keys = TEH_keys_get_state ();
- if (NULL == keys)
- {
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return GNUNET_SYSERR;
- }
-
- /**
- * Find the denomination keys from the original request to /melt
- * and keep track of those of type CS.
- */
- for (size_t i = 0; i < rf->num_coins; i++)
- {
- denom_keys[i] =
- TEH_keys_denomination_by_serial_from_state (
- keys,
- rf->denom_serials[i]);
- if (NULL == denom_keys[i])
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return GNUNET_SYSERR;
- }
-
- /* Accumulate amount and fees */
- GNUNET_assert (0 <= TALER_amount_add (&total_amount,
- &total_amount,
- &denom_keys[i]->meta.value));
- GNUNET_assert (0 <= TALER_amount_add (&total_fee,
- &total_fee,
- &denom_keys[i]->meta.fees.refresh));
-
- if (GNUNET_CRYPTO_BSA_CS ==
- denom_keys[i]->denom_pub.bsign_pub_key->cipher)
- {
- is_cs[i] = true;
- cs_count++;
- }
-
- /* Remember the hash of the public key of the denomination for later */
- denoms_h[i] = &denom_keys[i]->h_denom_pub;
- }
-
- /**
- * Sanity check:
- * The number CS denominations must match those from the /melt request
- */
- if (cs_count != rf->num_cs_r_values)
- {
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- return GNUNET_SYSERR;
- }
- /**
- * First things first for v27 clients: Verify the signature of the old coin
- * over the refresh nonce. This proves the ownership
- * for the fresh coin.
- */
- if (rf->is_v27_refresh)
- {
- size_t sig_idx = 0;
- struct TALER_KappaPublicRefreshNoncesP kappa_nonces;
-
- GNUNET_assert (NULL != signatures);
-
- /**
- * We expand the provided refresh_seed from the original call to /melt,
- * into kappa many batch seeds, from which we will later use all except the
- * noreveal_index one.
- */
- TALER_refresh_expand_kappa_nonces_v27 (
- &rf->refresh_seed,
- &kappa_nonces);
-
- for (uint8_t k=0; k < TALER_CNC_KAPPA; k++)
- {
- if (rf->noreveal_index == k)
- continue;
- if (GNUNET_OK !=
- TALER_wallet_refresh_nonce_verify (
- &rf->coin.coin_pub,
- &kappa_nonces.tuple[k],
- rf->num_coins,
- denoms_h,
- k,
- &(*signatures)[sig_idx++]))
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_LINK_SIGNATURE_INVALID,
- NULL);
- return GNUNET_SYSERR;
- }
- }
- }
- /**
- * In the following scope, we start collecting blinded coin planchet hashes,
- * either those persisted from the original request to /melt, or we
- * derive and calculate them from the provided signatures, after having
- * verified that each of them was signed by the old coin's private key.
- *
- * After collecting the blinded coin planchet hashes, we can then get
- * the commitment for the calculated values and compare the result with
- * the commitment from the /melt request.
- */
- {
- struct TALER_KappaHashBlindedPlanchetsP kappa_planchets_h;
- union GNUNET_CRYPTO_BlindSessionNonce b_nonces[GNUNET_NZL (cs_count)];
- size_t cs_idx = 0; /* [0...cs_count) */
- uint8_t sig_idx = 0; /* [0..KAPPA_MINUS_1) */
- /* These two are only necessary for non-v27 clients, but this is the common case. */
- struct TALER_TransferPublicKeyP k_tpbs[TALER_CNC_KAPPA][rf->num_coins];
- struct TALER_KappaTransferPublicKeys kappa_transfer_pubs = {
- .num_transfer_pubs = rf->num_coins
- };
- /**
- * First, derive the blinding nonces for the CS denominations all at once.
- */
- if (0 < cs_count)
- {
- uint32_t cs_indices[cs_count];
- size_t idx = 0; /* [0...cs_count) */
-
- for (size_t i = 0; i < rf->num_coins; i++)
- if (is_cs[i])
- cs_indices[idx++] = i;
-
- TALER_cs_derive_only_cs_blind_nonces_from_seed (&rf->blinding_seed,
- true, /* for melt */
- cs_count,
- cs_indices,
- b_nonces);
- }
- /**
- * We handle the kappa batches of rf->num_coins depths first.
- */
- for (uint8_t k = 0; k<TALER_CNC_KAPPA; k++)
- {
- if (k == rf->noreveal_index)
- {
- /**
- * We take the stored value for the hash of selected batch
- */
- kappa_planchets_h.tuple[k] = rf->selected_h;
- }
- else
- {
- /**
- * We have to generate all the planchets' details from
- * the disclosed input material and generate the
- * hashes of them.
- */
- struct TALER_PlanchetMasterSecretP planchet_secrets[rf->num_coins];
- struct TALER_PlanchetDetail details[rf->num_coins];
-
- memset (planchet_secrets,
- 0,
- sizeof(planchet_secrets));
- memset (details,
- 0,
- sizeof(details));
-
- GNUNET_assert (sig_idx < KAPPA_MINUS_1);
-
- /**
- * Expand from the k-th signature all num_coin planchet secrets,
- * except for the noreveal_index.
- */
- if (rf->is_v27_refresh)
- {
- TALER_refresh_signature_to_secrets_v27 (
- &(*signatures)[sig_idx++],
- rf->num_coins,
- planchet_secrets);
- }
- else /* vDOLDPLUS */
- {
- TALER_refresh_expand_batch_seed_to_transfer_data (
- &(*rev_batch_seeds)[sig_idx++],
- &rf->coin.coin_pub,
- rf->num_coins,
- planchet_secrets,
- k_tpbs[k]);
-
- kappa_transfer_pubs.batch[k] = k_tpbs[k];
- }
- /**
- * Reset the index for the CS denominations.
- */
- cs_idx = 0;
-
- for (size_t coin_idx = 0; coin_idx < rf->num_coins; coin_idx++)
- {
- struct GNUNET_CRYPTO_CSPublicRPairP *rp;
- union GNUNET_CRYPTO_BlindSessionNonce *np;
-
- if (is_cs[coin_idx])
- {
- GNUNET_assert (cs_idx < cs_count);
- np = &b_nonces[cs_idx];
- rp = &rf->cs_r_values[cs_idx];
- cs_idx++;
- }
- else
- {
- np = NULL;
- rp = NULL;
- }
- ret = calculate_blinded_detail (con,
- denom_keys[coin_idx],
- &planchet_secrets[coin_idx],
- rp,
- np,
- old_age_commitment,
- &details[coin_idx],
- result);
- if (GNUNET_OK != ret)
- return GNUNET_SYSERR;
- }
- /**
- * Now we can generate the hashes for the kappa-th batch of coins
- */
- TALER_wallet_blinded_planchet_details_hash (
- rf->num_coins,
- details,
- &kappa_planchets_h.tuple[k]);
-
- for (size_t i =0; i<rf->num_coins; i++)
- TALER_planchet_detail_free (&details[i]);
- }
- }
- /**
- * Finally, calculate the refresh commitment and compare it with the original.
- */
- {
- struct TALER_RefreshCommitmentP rc;
-
- if (rf->is_v27_refresh)
- {
- TALER_refresh_get_commitment_v27 (&rc,
- &rf->refresh_seed,
- rf->no_blinding_seed
- ? NULL
- : &rf->blinding_seed,
- &kappa_planchets_h,
- &rf->coin.coin_pub,
- &rf->amount_with_fee);
- }
- else
- {
- TALER_refresh_get_commitment (&rc,
- &rf->refresh_seed,
- rf->no_blinding_seed
- ? NULL
- : &rf->blinding_seed,
- &kappa_transfer_pubs,
- &kappa_planchets_h,
- &rf->coin.coin_pub,
- &rf->amount_with_fee);
- }
-
- if (0 != GNUNET_CRYPTO_hash_cmp (
- &rf->rc.session_hash,
- &rc.session_hash))
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_INVALID_RCH,
- "rc");
- return GNUNET_SYSERR;
- }
- }
- }
- return GNUNET_OK;
-}
-
-
-/**
- * @brief Commit the successful reveal to the database
- *
- * @param con HTTP-connection to the client
- * @param rc Original refresh commitment from the previous /melt request
- * @param[out] result On error, a HTTP-response will be queued and result set accordingly
- * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-commit_reveal (
- struct MHD_Connection *con,
- const struct TALER_RefreshCommitmentP *rc,
- MHD_RESULT *result)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- for (unsigned int retry = 0; retry < 3; retry++)
- {
- qs = TEH_plugin->mark_refresh_reveal_success (
- TEH_plugin->cls,
- rc);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_OK; /* Only happy case */
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *result = TALER_MHD_reply_with_error (con,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_REFRESHES_REVEAL_SESSION_UNKNOWN,
- NULL);
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_HARD_ERROR:
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "mark_refresh_reveal_success");
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- break; /* try again */
- default:
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- return GNUNET_SYSERR;
- }
- }
- /* after unsuccessful retries*/
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "mark_refresh_reveal_success");
- return GNUNET_SYSERR;
-
-}
-
-
-/**
- * @brief Send a response for "/reveal-melt"
- *
- * @param connection The http connection to the client to send the response to
- * @param refresh The data from the previous call to /melt with signatures
- * @return a MHD result code
- */
-static MHD_RESULT
-reply_melt_reveal_success (
- struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *refresh)
-{
- json_t *list = json_array ();
- GNUNET_assert (NULL != list);
-
- for (unsigned int i = 0; i < refresh->num_coins; i++)
- {
- json_t *obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_denom_sig (NULL,
- &refresh->denom_sigs[i]));
- GNUNET_assert (0 ==
- json_array_append_new (list,
- obj));
- }
-
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("ev_sigs",
- list));
-}
-
-
-MHD_RESULT
-TEH_handler_reveal_melt (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[2])
-{
- MHD_RESULT result = MHD_NO;
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
- struct MeltRevealContext actx = {0};
- struct GNUNET_JSON_Specification sig_tuple[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &actx.signatures[0]),
- GNUNET_JSON_spec_fixed_auto (NULL,
- &actx.signatures[1]),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification seeds_tuple[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &actx.transfer_secret_seeds[0]),
- GNUNET_JSON_spec_fixed_auto (NULL,
- &actx.transfer_secret_seeds[1]),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("rc",
- &actx.rc),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_tuple_of ("signatures",
- sig_tuple),
- &actx.no_signatures),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_tuple_of ("batch_seeds",
- seeds_tuple),
- &actx.no_transfer_secret_seeds),
- GNUNET_JSON_spec_mark_optional (
- TALER_JSON_spec_age_commitment ("age_commitment",
- &actx.age_commitment),
- &actx.no_age_commitment),
- GNUNET_JSON_spec_end ()
- };
-
- /**
- * Note that above, we have hard-wired
- * the size of TALER_CNC_KAPPA.
- * Let's make sure we keep this in sync.
- */
- _Static_assert (KAPPA_MINUS_1 == 2,
- "TALER_CNC_KAPPA isn't 3!?!?");
-
- /* Parse JSON body*/
- ret = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
- }
-
- (void) args;
-
- do {
- /* Find original commitment */
- if (GNUNET_OK !=
- find_original_refresh (
- rc->connection,
- &actx.rc,
- &actx.refresh,
- &result))
- break;
-
- /* Compare age commitment with the hash from the /melt request, if present */
- if (GNUNET_OK !=
- compare_age_commitment (
- rc->connection,
- &actx,
- &result))
- break;
-
- /* verify the commitment */
- if (GNUNET_OK !=
- verify_commitment (
- rc->connection,
- &actx.refresh,
- actx.no_age_commitment
- ? NULL
- : &actx.age_commitment,
- actx.no_signatures
- ? NULL
- : &actx.signatures,
- actx.no_transfer_secret_seeds
- ? NULL
- : &actx.transfer_secret_seeds,
- &result))
- break;
-
- if (GNUNET_OK !=
- commit_reveal (rc->connection,
- &actx.rc,
- &result))
- break;
-
- /* Finally, return the signatures */
- result = reply_melt_reveal_success (rc->connection,
- &actx.refresh);
-
- } while (0);
-
- GNUNET_JSON_parse_free (spec);
- if (NULL != actx.refresh.denom_sigs)
- for (unsigned int i = 0; i<actx.refresh.num_coins; i++)
- TALER_blinded_denom_sig_free (&actx.refresh.denom_sigs[i]);
- GNUNET_free (actx.refresh.denom_sigs);
- GNUNET_free (actx.refresh.denom_pub_hashes);
- GNUNET_free (actx.refresh.denom_serials);
- GNUNET_free (actx.refresh.cs_r_values);
- return result;
-}
-
-
-/* end of taler-exchange-httpd_reveal_melt.c */
diff --git a/src/exchange/taler-exchange-httpd_reveal-melt.h b/src/exchange/taler-exchange-httpd_reveal-melt.h
@@ -1,58 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_reveal-melt.h
- * @brief Handle /reveal-melt requests
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_REVEAL_MELT_H
-#define TALER_EXCHANGE_HTTPD_REVEAL_MELT_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/reveal-melt" request.
- *
- * The client got a noreveal_index in response to a previous request
- * /melt. It now has to reveal all n*(kappa-1) signatures (except for the
- * noreveal_index), signed by the old coin private key, over the coin's
- * specific nonce, which is derived per coin from the refresh_seed,
- * From that signature also all other coin-relevant data (blinding, age
- * restriction, nonce) are derived from.
- *
- * The exchange computes those values, potentially ensures that age restriction
- * is correctly applied, calculates the hash of the blinded envelopes, and -
- * together with the non-disclosed blinded envelopes - compares the hash of the
- * calculated melt commitment against the original.
- *
- * If all those checks and the used denominations turn out to be correct, the
- * exchange signs all blinded envelopes with their appropriate denomination
- * keys.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param args not used
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reveal_melt (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[2]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_reveal-withdraw.c b/src/exchange/taler-exchange-httpd_reveal-withdraw.c
@@ -1,637 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2023,2025 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_reveal-withdraw.c
- * @brief Handle /reveal-withdraw requests
- * @author Özgür Kesim
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_common.h>
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd_metrics.h"
-#include "taler/taler_error_codes.h"
-#include "taler/taler_exchangedb_plugin.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_mhd.h"
-#include "taler-exchange-httpd_reveal-withdraw.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-
-/**
- * State for an /reveal-withdraw operation.
- */
-struct WithdrawRevealContext
-{
-
- /**
- * Commitment for the withdraw operation, previously called by the
- * client.
- */
- struct TALER_HashBlindedPlanchetsP planchets_h;
-
- /**
- * TALER_CNC_KAPPA-1 secrets for disclosed coin batches.
- */
- struct TALER_RevealWithdrawMasterSeedsP disclosed_batch_seeds;
-
- /**
- * The data from the original withdraw. Will be retrieved from
- * the DB via @a wch.
- */
- struct TALER_EXCHANGEDB_Withdraw withdraw;
-};
-
-
-/**
- * Parse the json body of an '/reveal-withdraw' request. It extracts
- * the denomination hashes, blinded coins and disclosed coins and allocates
- * memory for those.
- *
- * @param connection The MHD connection to handle
- * @param j_disclosed_batch_seeds The n*(kappa-1) disclosed coins' private keys in JSON format, from which all other attributes (age restriction, blinding, nonce) will be derived from
- * @param[out] actx The context of the operation, only partially built at call time
- * @param[out] mhd_ret The result if a reply is queued for MHD
- * @return true on success, false on failure, with a reply already queued for MHD.
- */
-static enum GNUNET_GenericReturnValue
-parse_withdraw_reveal_json (
- struct MHD_Connection *connection,
- const json_t *j_disclosed_batch_seeds,
- struct WithdrawRevealContext *actx,
- MHD_RESULT *mhd_ret)
-{
- size_t num_entries;
- const char *error;
- struct GNUNET_JSON_Specification tuple[] = {
- GNUNET_JSON_spec_fixed (NULL,
- &actx->disclosed_batch_seeds.tuple[0],
- sizeof(actx->disclosed_batch_seeds.tuple[0])),
- GNUNET_JSON_spec_fixed (NULL,
- &actx->disclosed_batch_seeds.tuple[1],
- sizeof(actx->disclosed_batch_seeds.tuple[1])),
- GNUNET_JSON_spec_end ()
- };
- struct GNUNET_JSON_Specification spec[] = {
- TALER_JSON_spec_tuple_of (NULL,
- tuple),
- GNUNET_JSON_spec_end ()
- };
-
- /**
- * Note that above, in tuple[], we have hard-wired
- * the size of TALER_CNC_KAPPA.
- * Let's make sure we keep this in sync.
- */
- _Static_assert ((TALER_CNC_KAPPA - 1) == 2);
-
- num_entries = json_array_size (j_disclosed_batch_seeds); /* 0, if not an array */
- if (! json_is_array (j_disclosed_batch_seeds))
- error = "disclosed_batch_seeds must be an array";
- else if (num_entries == 0)
- error = "disclosed_batch_seeds must not be empty";
- else if (num_entries != TALER_CNC_KAPPA - 1)
- error =
- "disclosed_batch_seeds must be an array of size "
- TALER_CNC_KAPPA_MINUS_ONE_STR;
- else
- error = NULL;
-
- if ( (NULL != error) ||
- (GNUNET_OK !=
- GNUNET_JSON_parse (j_disclosed_batch_seeds,
- spec,
- &error,
- NULL)) )
- {
- GNUNET_break_op (0);
- *mhd_ret = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- error);
- return GNUNET_SYSERR;
- }
-
- return GNUNET_OK;
-}
-
-
-/**
- * Check if the request belongs to an existing withdraw request.
- * If so, sets the withdraw object with the request data.
- * Otherwise, it queues an appropriate MHD response.
- *
- * @param connection The HTTP connection to the client
- * @param planchets_h Original commitment value sent with the withdraw request
- * @param[out] withdraw Data from the original withdraw request
- * @param[out] result In the error cases, a response will be queued with MHD and this will be the result.
- * @return #GNUNET_OK if the withdraw request has been found,
- * #GNUNET_SYSERR if we did not find the request in the DB
- */
-static enum GNUNET_GenericReturnValue
-find_original_withdraw (
- struct MHD_Connection *connection,
- const struct TALER_HashBlindedPlanchetsP *planchets_h,
- struct TALER_EXCHANGEDB_Withdraw *withdraw,
- MHD_RESULT *result)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- for (unsigned int try = 0; try < 3; try++)
- {
- qs = TEH_plugin->get_withdraw (TEH_plugin->cls,
- planchets_h,
- withdraw);
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
- return GNUNET_OK; /* Only happy case */
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- *result = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_WITHDRAW_COMMITMENT_UNKNOWN,
- NULL);
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_HARD_ERROR:
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_withdraw");
- return GNUNET_SYSERR;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- break; /* try again */
- default:
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL);
- return GNUNET_SYSERR;
- }
- }
- /* after unsuccessful retries*/
- GNUNET_break (0);
- *result = TALER_MHD_reply_with_ec (connection,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_withdraw");
- return GNUNET_SYSERR;
-}
-
-
-/**
- * @brief Derives an age-restricted planchet from a given secret and calculates the hash
- *
- * @param connection Connection to the client
- * @param denom_key The denomination key
- * @param secret The secret to a planchet
- * @param r_pub The public R-values from the exchange in case of a CS denomination; might be NULL
- * @param nonce The derived nonce needed for CS denomination
- * @param max_age The maximum age allowed
- * @param[out] bch Hashcode to write
- * @param[out] result On error, a HTTP-response will be queued and result set accordingly
- * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise, with an error message
- * written to the client and @e result set.
- */
-static enum GNUNET_GenericReturnValue
-calculate_blinded_hash (
- struct MHD_Connection *connection,
- struct TEH_DenominationKey *denom_key,
- const struct TALER_PlanchetMasterSecretP *secret,
- const struct GNUNET_CRYPTO_CSPublicRPairP *r_pub,
- union GNUNET_CRYPTO_BlindSessionNonce *nonce,
- uint8_t max_age,
- struct TALER_BlindedCoinHashP *bch,
- MHD_RESULT *result)
-{
- enum GNUNET_GenericReturnValue ret;
- struct TALER_AgeCommitmentHashP ach;
-
- /* calculate age commitment hash */
- {
- struct TALER_AgeCommitmentProof acp;
-
- TALER_age_restriction_from_secret (secret,
- &denom_key->denom_pub.age_mask,
- max_age,
- &acp);
- TALER_age_commitment_hash (&acp.commitment,
- &ach);
- TALER_age_commitment_proof_free (&acp);
- }
-
- /* Next: calculate planchet */
- {
- struct TALER_CoinPubHashP c_hash;
- struct TALER_PlanchetDetail detail = {0};
- struct TALER_CoinSpendPrivateKeyP coin_priv;
- union GNUNET_CRYPTO_BlindingSecretP bks;
- struct GNUNET_CRYPTO_BlindingInputValues bi = {
- .cipher = denom_key->denom_pub.bsign_pub_key->cipher
- };
- struct TALER_ExchangeBlindingValues alg_values = {
- .blinding_inputs = &bi
- };
-
- if (GNUNET_CRYPTO_BSA_CS == bi.cipher)
- {
- GNUNET_assert (NULL != r_pub);
- GNUNET_assert (NULL != nonce);
- bi.details.cs_values = *r_pub;
- }
- TALER_planchet_blinding_secret_create (secret,
- &alg_values,
- &bks);
- TALER_planchet_setup_coin_priv (secret,
- &alg_values,
- &coin_priv);
- ret = TALER_planchet_prepare (&denom_key->denom_pub,
- &alg_values,
- &bks,
- nonce,
- &coin_priv,
- &ach,
- &c_hash,
- &detail);
- if (GNUNET_OK != ret)
- {
- GNUNET_break (0);
- *result = TALER_MHD_REPLY_JSON_PACK (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- GNUNET_JSON_pack_string (
- "details",
- "failed to prepare planchet from base key"));
- return ret;
- }
-
- TALER_coin_ev_hash (&detail.blinded_planchet,
- &denom_key->h_denom_pub,
- bch);
- TALER_blinded_planchet_free (&detail.blinded_planchet);
- }
-
- return ret;
-}
-
-
-/**
- * @brief Checks the validity of the disclosed coins as follows:
- * - Derives and calculates the disclosed coins'
- * - public keys,
- * - nonces (if applicable),
- * - age commitments,
- * - blindings
- * - blinded hashes
- * - Computes planchets_h with those calculated and the undisclosed hashes
- * - Compares planchets_h with the value from the original commitment
- * - Verifies that all public keys in indices larger than the age group
- * corresponding to max_age are derived from the constant public key.
- *
- * The derivation of the blindings, (potential) nonces and age-commitment from
- * a coin's private keys is defined in
- * https://docs.taler.net/design-documents/024-age-restriction.html#withdraw
- *
- * @param con HTTP-connection to the client
- * @param wd Original withdraw request
- * @param disclosed_batch_seeds The secrets of the disclosed coins, (TALER_CNC_KAPPA - 1)*num_coins many
- * @param[out] result On error, a HTTP-response will be queued and result set accordingly
- * @return #GNUNET_OK on success, #GNUNET_SYSERR otherwise
- */
-static enum GNUNET_GenericReturnValue
-verify_commitment_and_max_age (
- struct MHD_Connection *con,
- const struct TALER_EXCHANGEDB_Withdraw *wd,
- const struct TALER_RevealWithdrawMasterSeedsP *disclosed_batch_seeds,
- MHD_RESULT *result)
-{
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
- struct GNUNET_HashContext *hash_context;
- struct TEH_KeyStateHandle *keys;
- struct TEH_DenominationKey *denom_keys[wd->num_coins];
- struct TALER_Amount total_amount;
- struct TALER_Amount total_fee;
- struct TALER_AgeMask mask;
- struct TALER_PlanchetMasterSecretP secrets[
- TALER_CNC_KAPPA - 1][wd->num_coins];
- bool is_cs[wd->num_coins];
- size_t cs_count = 0;
- uint8_t secrets_idx = 0; /* first index into secrets */
-
- GNUNET_assert (wd->noreveal_index < TALER_CNC_KAPPA);
-
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &total_amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &total_fee));
-
- memset (denom_keys,
- 0,
- sizeof(denom_keys));
- memset (is_cs,
- 0,
- sizeof(is_cs));
-
- /* We need the current keys in memory for the meta-data of the denominations */
- keys = TEH_keys_get_state ();
- if (NULL == keys)
- {
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return GNUNET_SYSERR;
- }
-
- /* Find the denomination keys */
- for (size_t i = 0; i < wd->num_coins; i++)
- {
- denom_keys[i] =
- TEH_keys_denomination_by_serial_from_state (
- keys,
- wd->denom_serials[i]);
- if (NULL == denom_keys[i])
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL);
- return GNUNET_SYSERR;
- }
-
- /* Accumulate amount and fees */
- GNUNET_assert (0 <= TALER_amount_add (&total_amount,
- &total_amount,
- &denom_keys[i]->meta.value));
- GNUNET_assert (0 <= TALER_amount_add (&total_fee,
- &total_fee,
- &denom_keys[i]->meta.fees.withdraw));
-
- if (i == 0)
- mask = denom_keys[i]->meta.age_mask;
- GNUNET_assert (mask.bits == denom_keys[i]->meta.age_mask.bits);
-
- if (GNUNET_CRYPTO_BSA_CS ==
- denom_keys[i]->denom_pub.bsign_pub_key->cipher)
- {
- is_cs[i] = true;
- cs_count++;
- }
- }
-
- hash_context = GNUNET_CRYPTO_hash_context_start ();
-
- {
- uint32_t cs_indices[cs_count];
- union GNUNET_CRYPTO_BlindSessionNonce nonces[cs_count];
- size_t cs_idx = 0; /* [0...cs_count) */
-
- for (size_t i = 0; i < wd->num_coins; i++)
- if (is_cs[i])
- cs_indices[cs_idx++] = i;
-
- TALER_cs_derive_only_cs_blind_nonces_from_seed (&wd->blinding_seed,
- false, /* not for melt */
- cs_count,
- cs_indices,
- nonces);
-
- for (uint8_t gamma = 0; gamma<TALER_CNC_KAPPA; gamma++)
- {
- if (gamma == wd->noreveal_index)
- {
- /**
- * For the disclosed index, all we have to do is to accumulate the hash
- * of the selected coins
- */
- GNUNET_CRYPTO_hash_context_read (
- hash_context,
- &wd->selected_h,
- sizeof(wd->selected_h));
- }
- else
- {
- /**
- * For the non-disclosed index, we have to generate the planchets in
- * this batch and calculate their hash
- */
- struct GNUNET_HashContext *batch_ctx;
- struct TALER_BlindedCoinHashP batch_h;
- cs_idx = 0;
-
- batch_ctx = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_assert (NULL != batch_ctx);
-
- /* Expand the secrets for a disclosed batch */
- GNUNET_assert (secrets_idx < (TALER_CNC_KAPPA - 1));
- TALER_withdraw_expand_secrets (
- wd->num_coins,
- &disclosed_batch_seeds->tuple[secrets_idx],
- secrets[secrets_idx]);
-
- /**
- * Now individually create each coin in this batch and calculate
- * its hash, and accumulate the hash of the batch with it
- */
- for (size_t coin_idx = 0; coin_idx < wd->num_coins; coin_idx++)
- {
- struct TALER_BlindedCoinHashP bch;
- struct GNUNET_CRYPTO_CSPublicRPairP *rp;
- union GNUNET_CRYPTO_BlindSessionNonce *np;
-
- if (is_cs[coin_idx])
- {
- GNUNET_assert (cs_idx < cs_count);
- np = &nonces[cs_idx];
- rp = &wd->cs_r_values[cs_idx];
- cs_idx++;
- }
- else
- {
- np = NULL;
- rp = NULL;
- }
- ret = calculate_blinded_hash (con,
- denom_keys[coin_idx],
- &secrets[secrets_idx][coin_idx],
- rp,
- np,
- wd->max_age,
- &bch,
- result);
- if (GNUNET_OK != ret)
- {
- GNUNET_CRYPTO_hash_context_abort (hash_context);
- return GNUNET_SYSERR;
- }
- /**
- * Continue the running hash of all coin hashes in the batch
- * with the calculated hash-value of the current, disclosed coin
- */
- GNUNET_CRYPTO_hash_context_read (batch_ctx,
- &bch,
- sizeof(bch));
- }
- /**
- * Finalize the hash of this batch and add it to
- * the total hash
- */
- GNUNET_CRYPTO_hash_context_finish (
- batch_ctx,
- &batch_h.hash);
- GNUNET_CRYPTO_hash_context_read (
- hash_context,
- &batch_h,
- sizeof(batch_h));
-
- secrets_idx++;
- }
- }
- }
-
- /* Finally, compare the calculated hash with the original wd */
- {
- struct TALER_HashBlindedPlanchetsP planchets_h;
-
- GNUNET_CRYPTO_hash_context_finish (
- hash_context,
- &planchets_h.hash);
-
- if (0 != GNUNET_CRYPTO_hash_cmp (
- &wd->planchets_h.hash,
- &planchets_h.hash))
- {
- GNUNET_break_op (0);
- *result = TALER_MHD_reply_with_ec (con,
- TALER_EC_EXCHANGE_WITHDRAW_REVEAL_INVALID_HASH,
- NULL);
- return GNUNET_SYSERR;
- }
-
- }
- return GNUNET_OK;
-}
-
-
-/**
- * @brief Send a response for "/reveal-withdraw"
- *
- * @param connection The http connection to the client to send the response to
- * @param commitment The data from the commitment with signatures
- * @return a MHD result code
- */
-static MHD_RESULT
-reply_withdraw_reveal_success (
- struct MHD_Connection *connection,
- const struct TALER_EXCHANGEDB_Withdraw *commitment)
-{
- json_t *list = json_array ();
-
- GNUNET_assert (NULL != list);
- for (size_t i = 0; i < commitment->num_coins; i++)
- {
- json_t *obj = GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_denom_sig (NULL,
- &commitment->denom_sigs[i]));
- GNUNET_assert (0 ==
- json_array_append_new (list,
- obj));
- }
-
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("ev_sigs",
- list));
-}
-
-
-MHD_RESULT
-TEH_handler_reveal_withdraw (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0])
-{
- MHD_RESULT result = MHD_NO;
- enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
- struct WithdrawRevealContext actx = {0};
- const json_t *j_disclosed_batch_seeds;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("planchets_h",
- &actx.planchets_h),
- GNUNET_JSON_spec_array_const ("disclosed_batch_seeds",
- &j_disclosed_batch_seeds),
- GNUNET_JSON_spec_end ()
- };
-
- (void) args;
- /* Parse JSON body*/
- ret = TALER_MHD_parse_json_data (rc->connection,
- root,
- spec);
- if (GNUNET_OK != ret)
- {
- GNUNET_break_op (0);
- return (GNUNET_SYSERR == ret) ? MHD_NO : MHD_YES;
- }
-
- do {
- /* Extract denominations, blinded and disclosed coins */
- if (GNUNET_OK !=
- parse_withdraw_reveal_json (
- rc->connection,
- j_disclosed_batch_seeds,
- &actx,
- &result))
- break;
-
- /* Find original commitment */
- if (GNUNET_OK !=
- find_original_withdraw (
- rc->connection,
- &actx.planchets_h,
- &actx.withdraw,
- &result))
- break;
-
- /* Verify the computed planchets_h equals the committed one and that coins
- * have a maximum age group corresponding max_age (age-mask dependent) */
- if (GNUNET_OK !=
- verify_commitment_and_max_age (
- rc->connection,
- &actx.withdraw,
- &actx.disclosed_batch_seeds,
- &result))
- break;
-
- /* Finally, return the signatures */
- result = reply_withdraw_reveal_success (rc->connection,
- &actx.withdraw);
-
- } while (0);
-
- GNUNET_JSON_parse_free (spec);
- if (NULL != actx.withdraw.denom_sigs)
- {
- for (size_t i = 0; i<actx.withdraw.num_coins; i++)
- TALER_blinded_denom_sig_free (&actx.withdraw.denom_sigs[i]);
- GNUNET_free (actx.withdraw.denom_sigs);
- }
- GNUNET_free (actx.withdraw.cs_r_values);
- GNUNET_free (actx.withdraw.denom_pub_hashes);
- GNUNET_free (actx.withdraw.denom_serials);
- return result;
-}
-
-
-/* end of taler-exchange-httpd_reveal_withdraw.c */
diff --git a/src/exchange/taler-exchange-httpd_reveal-withdraw.h b/src/exchange/taler-exchange-httpd_reveal-withdraw.h
@@ -1,56 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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_reveal-withdraw.h
- * @brief Handle /reveal-withdraw/$ACH requests
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_REVEAL_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_REVEAL_WITHDRAW_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a "/reveal-withdraw" request.
- *
- * The client got a noreveal_index in response to a previous request
- * /withdraw. It now has to reveal all n*(kappa-1)
- * coin's private keys (except for the noreveal_index), from which all other
- * coin-relevant data (blinding, age restriction, nonce) is derived from.
- *
- * The exchange computes those values, ensures that the maximum age is
- * correctly applied, calculates the hash of the blinded envelopes, and -
- * together with the non-disclosed blinded envelopes - compares the hash of
- * the calculated withdraw commitment against the original.
- *
- * If all those checks and the used denominations turn out to be correct, the
- * exchange signs all blinded envelopes with their appropriate denomination
- * keys.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param args not used
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_reveal_withdraw (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0]);
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_spa.c b/src/exchange/taler-exchange-httpd_spa.c
@@ -1,128 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2020, 2023, 2024 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 EXCHANGEABILITY 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 taler-exchange-httpd_spa.c
- * @brief logic to load single page apps (/)
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include "taler/taler_util.h"
-#include "taler/taler_mhd_lib.h"
-#include <gnunet/gnunet_mhd_compat.h>
-#include "taler-exchange-httpd.h"
-#include "taler-exchange-httpd_spa.h"
-
-
-/**
- * Resources of the AML SPA.
- */
-static struct TALER_MHD_Spa *aml_spa;
-
-/**
- * Resources of the KYC SPA.
- */
-static struct TALER_MHD_Spa *kyc_spa;
-
-
-MHD_RESULT
-TEH_handler_aml_spa (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- const char *path = args[0];
-
- return TALER_MHD_spa_handler (aml_spa,
- rc->connection,
- path);
-}
-
-
-MHD_RESULT
-TEH_handler_kyc_spa (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- const char *path = args[0];
- struct TALER_AccountAccessTokenP tok;
-
- if (NULL == path)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_GENERIC_TOKEN_PERMISSION_INSUFFICIENT,
- "no account access token specified");
- }
- if (GNUNET_OK ==
- GNUNET_STRINGS_string_to_data (path,
- strlen (path),
- &tok,
- sizeof (tok)))
- {
- /* The access token is used internally by the SPA,
- we simply map all access tokens to "index.html" */
- path = "index.html";
- }
- return TALER_MHD_spa_handler (kyc_spa,
- rc->connection,
- path);
-}
-
-
-enum GNUNET_GenericReturnValue
-TEH_spa_init ()
-{
- aml_spa = TALER_MHD_spa_load (TALER_EXCHANGE_project_data (),
- "aml-spa/");
- if (NULL == aml_spa)
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- kyc_spa = TALER_MHD_spa_load (TALER_EXCHANGE_project_data (),
- "kyc-spa/");
- if (NULL == kyc_spa)
- {
- GNUNET_break (0);
- TALER_MHD_spa_free (aml_spa);
- aml_spa = NULL;
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/* Suppresses warning */
-void __attribute__ ((destructor))
-get_spa_fini (void);
-
-/**
- * Nicely shut down.
- */
-void __attribute__ ((destructor))
-get_spa_fini (void)
-{
- if (NULL != kyc_spa)
- {
- TALER_MHD_spa_free (kyc_spa);
- kyc_spa = NULL;
- }
- if (NULL != aml_spa)
- {
- TALER_MHD_spa_free (aml_spa);
- aml_spa = NULL;
- }
-}
diff --git a/src/exchange/taler-exchange-httpd_spa.h b/src/exchange/taler-exchange-httpd_spa.h
@@ -1,63 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 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 EXCHANGEABILITY 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 taler-exchange-httpd_spa.h
- * @brief logic to preload and serve static files
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_SPA_H
-#define TALER_EXCHANGE_HTTPD_SPA_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Return our single-page-app user interface
- * for AML staff (see contrib/wallet-core/).
- *
- * @param rc context of the handler
- * @param[in,out] args remaining arguments (ignored)
- * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
- */
-MHD_RESULT
-TEH_handler_aml_spa (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-/**
- * Return our single-page-app user interface
- * for the KYC process (see contrib/wallet-core/).
- *
- * @param rc context of the handler
- * @param[in,out] args remaining arguments (ignored)
- * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection)
- */
-MHD_RESULT
-TEH_handler_kyc_spa (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-/**
- * Preload and compress SPA files.
- *
- * @return #GNUNET_OK on success
- */
-enum GNUNET_GenericReturnValue
-TEH_spa_init (void);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_terms.c b/src/exchange/taler-exchange-httpd_terms.c
@@ -1,82 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2019, 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_terms.c
- * @brief Handle /terms requests to return the terms of service
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_terms.h"
-
-/**
- * Our terms of service.
- */
-static struct TALER_MHD_Legal *tos;
-
-
-/**
- * Our privacy policy.
- */
-static struct TALER_MHD_Legal *pp;
-
-
-MHD_RESULT
-TEH_handler_terms (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- (void) args;
- return TALER_MHD_reply_legal (rc->connection,
- tos);
-}
-
-
-MHD_RESULT
-TEH_handler_privacy (struct TEH_RequestContext *rc,
- const char *const args[])
-{
- (void) args;
- return TALER_MHD_reply_legal (rc->connection,
- pp);
-}
-
-
-void
-TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
-{
- tos = TALER_MHD_legal_load (cfg,
- "exchange",
- "TERMS_DIR",
- "TERMS_ETAG");
- if (NULL == tos)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Terms of service not configured\n");
- pp = TALER_MHD_legal_load (cfg,
- "exchange",
- "PRIVACY_DIR",
- "PRIVACY_ETAG");
- if (NULL == pp)
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Privacy policy not configured\n");
-}
-
-
-/* end of taler-exchange-httpd_terms.c */
diff --git a/src/exchange/taler-exchange-httpd_terms.h b/src/exchange/taler-exchange-httpd_terms.h
@@ -1,65 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2019, 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_terms.h
- * @brief Handle /terms requests to return the terms of service
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_TERMS_H
-#define TALER_EXCHANGE_HTTPD_TERMS_H
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <gnunet/gnunet_json_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_responses.h"
-
-
-/**
- * Handle a "/terms" 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_terms (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-/**
- * Handle a "/privacy" 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_privacy (struct TEH_RequestContext *rc,
- const char *const args[]);
-
-
-/**
- * Load our terms of service as per configuration.
- *
- * @param cfg configuration to process
- */
-void
-TEH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_transfers_get.c b/src/exchange/taler-exchange-httpd_transfers_get.c
@@ -1,641 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2018, 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_transfers_get.c
- * @brief Handle wire transfer(s) GET requests
- * @author Christian Grothoff
- */
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include <microhttpd.h>
-#include <pthread.h>
-#include "taler/taler_signatures.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler-exchange-httpd_transfers_get.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_mhd_lib.h"
-
-
-/**
- * Information about one of the transactions that was
- * aggregated, to be returned in the /transfers response.
- */
-struct AggregatedDepositDetail
-{
-
- /**
- * We keep deposit details in a DLL.
- */
- struct AggregatedDepositDetail *next;
-
- /**
- * We keep deposit details in a DLL.
- */
- struct AggregatedDepositDetail *prev;
-
- /**
- * Hash of the contract terms.
- */
- struct TALER_PrivateContractHashP h_contract_terms;
-
- /**
- * Coin's public key of the deposited coin.
- */
- struct TALER_CoinSpendPublicKeyP coin_pub;
-
- /**
- * Total value of the coin in the deposit (after
- * refunds).
- */
- struct TALER_Amount deposit_value;
-
- /**
- * Fees charged by the exchange for the deposit of this coin (possibly after reduction due to refunds).
- */
- struct TALER_Amount deposit_fee;
-
- /**
- * Total amount refunded for this coin.
- */
- struct TALER_Amount refund_total;
-};
-
-
-/**
- * A merchant asked for transaction details about a wire transfer.
- * Provide them. Generates the 200 reply.
- *
- * @param connection connection to the client
- * @param total total amount that was transferred
- * @param merchant_pub public key of the merchant
- * @param payto_uri destination account
- * @param wire_fee wire fee that was charged
- * @param exec_time execution time of the wire transfer
- * @param wdd_head linked list with details about the combined deposits
- * @return MHD result code
- */
-static MHD_RESULT
-reply_transfer_details (struct MHD_Connection *connection,
- const struct TALER_Amount *total,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_FullPayto payto_uri,
- const struct TALER_Amount *wire_fee,
- struct GNUNET_TIME_Timestamp exec_time,
- const struct AggregatedDepositDetail *wdd_head)
-{
- json_t *deposits;
- struct GNUNET_HashContext *hash_context;
- struct GNUNET_HashCode h_details;
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- struct TALER_FullPaytoHashP h_payto;
-
- deposits = json_array ();
- GNUNET_assert (NULL != deposits);
- hash_context = GNUNET_CRYPTO_hash_context_start ();
- for (const struct AggregatedDepositDetail *wdd_pos = wdd_head;
- NULL != wdd_pos;
- wdd_pos = wdd_pos->next)
- {
- TALER_exchange_online_wire_deposit_append (hash_context,
- &wdd_pos->h_contract_terms,
- exec_time,
- &wdd_pos->coin_pub,
- &wdd_pos->deposit_value,
- &wdd_pos->deposit_fee);
- if (0 !=
- json_array_append_new (
- deposits,
- GNUNET_JSON_PACK (
- GNUNET_JSON_pack_data_auto ("h_contract_terms",
- &wdd_pos->h_contract_terms),
- GNUNET_JSON_pack_data_auto ("coin_pub",
- &wdd_pos->coin_pub),
-
- GNUNET_JSON_pack_allow_null (
- TALER_JSON_pack_amount ("refund_total",
- TALER_amount_is_zero (
- &wdd_pos->refund_total)
- ? NULL
- : &wdd_pos->refund_total)),
- TALER_JSON_pack_amount ("deposit_value",
- &wdd_pos->deposit_value),
- TALER_JSON_pack_amount ("deposit_fee",
- &wdd_pos->deposit_fee))))
- {
- json_decref (deposits);
- GNUNET_CRYPTO_hash_context_abort (hash_context);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_JSON_ALLOCATION_FAILURE,
- "json_array_append_new() failed");
- }
- }
- GNUNET_CRYPTO_hash_context_finish (hash_context,
- &h_details);
- {
- enum TALER_ErrorCode ec;
-
- if (TALER_EC_NONE !=
- (ec = TALER_exchange_online_wire_deposit_sign (
- &TEH_keys_exchange_sign_,
- total,
- wire_fee,
- merchant_pub,
- payto_uri,
- &h_details,
- &pub,
- &sig)))
- {
- json_decref (deposits);
- return TALER_MHD_reply_with_ec (connection,
- ec,
- NULL);
- }
- }
-
- TALER_full_payto_hash (payto_uri,
- &h_payto);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- TALER_JSON_pack_amount ("total",
- total),
- TALER_JSON_pack_amount ("wire_fee",
- wire_fee),
- GNUNET_JSON_pack_data_auto ("merchant_pub",
- merchant_pub),
- GNUNET_JSON_pack_data_auto ("h_payto",
- &h_payto),
- GNUNET_JSON_pack_timestamp ("execution_time",
- exec_time),
- GNUNET_JSON_pack_array_steal ("deposits",
- deposits),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub));
-}
-
-
-/**
- * Closure for #handle_transaction_data.
- */
-struct WtidTransactionContext
-{
-
- /**
- * Identifier of the wire transfer to track.
- */
- struct TALER_WireTransferIdentifierRawP wtid;
-
- /**
- * Total amount of the wire transfer, as calculated by
- * summing up the individual amounts. To be rounded down
- * to calculate the real transfer amount at the end.
- * Only valid if @e is_valid is #GNUNET_YES.
- */
- struct TALER_Amount total;
-
- /**
- * Public key of the merchant, only valid if @e is_valid
- * is #GNUNET_YES.
- */
- struct TALER_MerchantPublicKeyP merchant_pub;
-
- /**
- * Wire fees applicable at @e exec_time.
- */
- struct TALER_WireFeeSet fees;
-
- /**
- * Execution time of the wire transfer
- */
- struct GNUNET_TIME_Timestamp exec_time;
-
- /**
- * Head of DLL with deposit details for transfers GET response.
- */
- struct AggregatedDepositDetail *wdd_head;
-
- /**
- * Tail of DLL with deposit details for transfers GET response.
- */
- struct AggregatedDepositDetail *wdd_tail;
-
- /**
- * Where were the funds wired?
- */
- struct TALER_FullPayto payto_uri;
-
- /**
- * JSON array with details about the individual deposits.
- */
- json_t *deposits;
-
- /**
- * Initially #GNUNET_NO, if we found no deposits so far. Set to
- * #GNUNET_YES if we got transaction data, and the database replies
- * remained consistent with respect to @e merchant_pub and @e h_wire
- * (as they should). Set to #GNUNET_SYSERR if we encountered an
- * internal error.
- */
- enum GNUNET_GenericReturnValue is_valid;
-
-};
-
-
-/**
- * Callback that totals up the applicable refunds.
- *
- * @param cls a `struct TALER_Amount` where we keep the total
- * @param amount_with_fee amount being refunded
- */
-static enum GNUNET_GenericReturnValue
-add_refunds (void *cls,
- const struct TALER_Amount *amount_with_fee)
-
-{
- struct TALER_Amount *total = cls;
-
- if (0 >
- TALER_amount_add (total,
- total,
- amount_with_fee))
- {
- GNUNET_break (0);
- return GNUNET_SYSERR;
- }
- return GNUNET_OK;
-}
-
-
-/**
- * Function called with the results of the lookup of the individual deposits
- * that were aggregated for the given wire transfer.
- *
- * @param cls our context for transmission
- * @param rowid which row in the DB is the information from (for diagnostics), ignored
- * @param merchant_pub public key of the merchant (should be same for all callbacks with the same @e cls)
- * @param account_payto_uri where the funds were sent
- * @param h_payto hash over @a account_payto_uri as it is in the DB
- * @param exec_time execution time of the wire transfer (should be same for all callbacks with the same @e cls)
- * @param h_contract_terms which proposal was this payment about
- * @param denom_pub denomination public key of the @a coin_pub (ignored)
- * @param coin_pub which public key was this payment about
- * @param deposit_value amount contributed by this coin in total (including fee)
- * @param deposit_fee deposit fee charged by exchange for this coin
- */
-static void
-handle_deposit_data (
- void *cls,
- uint64_t rowid,
- const struct TALER_MerchantPublicKeyP *merchant_pub,
- const struct TALER_FullPayto account_payto_uri,
- const struct TALER_FullPaytoHashP *h_payto,
- struct GNUNET_TIME_Timestamp exec_time,
- const struct TALER_PrivateContractHashP *h_contract_terms,
- const struct TALER_DenominationPublicKey *denom_pub,
- const struct TALER_CoinSpendPublicKeyP *coin_pub,
- const struct TALER_Amount *deposit_value,
- const struct TALER_Amount *deposit_fee)
-{
- struct WtidTransactionContext *ctx = cls;
- struct TALER_Amount total_refunds;
- struct TALER_Amount dval;
- struct TALER_Amount dfee;
- enum GNUNET_DB_QueryStatus qs;
-
- (void) rowid;
- (void) denom_pub;
- (void) h_payto;
- if (GNUNET_SYSERR == ctx->is_valid)
- return;
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (deposit_value->currency,
- &total_refunds));
- qs = TEH_plugin->select_refunds_by_coin (TEH_plugin->cls,
- coin_pub,
- merchant_pub,
- h_contract_terms,
- &add_refunds,
- &total_refunds);
- if (qs < 0)
- {
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- if (1 ==
- TALER_amount_cmp (&total_refunds,
- deposit_value))
- {
- /* Refunds exceeded total deposit? not OK! */
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- if (0 ==
- TALER_amount_cmp (&total_refunds,
- deposit_value))
- {
- /* total_refunds == deposit_value;
- in this case, the total contributed to the
- wire transfer is zero (as are fees) */
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (deposit_value->currency,
- &dval));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (deposit_value->currency,
- &dfee));
-
- }
- else
- {
- /* Compute deposit value by subtracting refunds */
- GNUNET_assert (0 <
- TALER_amount_subtract (&dval,
- deposit_value,
- &total_refunds));
- if (-1 ==
- TALER_amount_cmp (&dval,
- deposit_fee))
- {
- /* dval < deposit_fee, so after refunds less than
- the deposit fee remains; reduce deposit fee to
- the remaining value of the coin */
- dfee = dval;
- }
- else
- {
- /* Partial refund, deposit fee remains */
- dfee = *deposit_fee;
- }
- }
-
- if (GNUNET_NO == ctx->is_valid)
- {
- /* First one we encounter, setup general information in 'ctx' */
- ctx->merchant_pub = *merchant_pub;
- ctx->payto_uri.full_payto = GNUNET_strdup (account_payto_uri.full_payto);
- ctx->exec_time = exec_time;
- ctx->is_valid = GNUNET_YES;
- if (0 >
- TALER_amount_subtract (&ctx->total,
- &dval,
- &dfee))
- {
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- }
- else
- {
- struct TALER_Amount delta;
-
- /* Subsequent data, check general information matches that in 'ctx';
- (it should, otherwise the deposits should not have been aggregated) */
- if ( (0 != GNUNET_memcmp (&ctx->merchant_pub,
- merchant_pub)) ||
- (0 != TALER_full_payto_cmp (account_payto_uri,
- ctx->payto_uri)) )
- {
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- if (0 >
- TALER_amount_subtract (&delta,
- &dval,
- &dfee))
- {
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- if (0 >
- TALER_amount_add (&ctx->total,
- &ctx->total,
- &delta))
- {
- GNUNET_break (0);
- ctx->is_valid = GNUNET_SYSERR;
- return;
- }
- }
-
- {
- struct AggregatedDepositDetail *wdd;
-
- wdd = GNUNET_new (struct AggregatedDepositDetail);
- wdd->deposit_value = dval;
- wdd->deposit_fee = dfee;
- wdd->refund_total = total_refunds;
- wdd->h_contract_terms = *h_contract_terms;
- wdd->coin_pub = *coin_pub;
- GNUNET_CONTAINER_DLL_insert (ctx->wdd_head,
- ctx->wdd_tail,
- wdd);
- }
-}
-
-
-/**
- * Free data structure reachable from @a ctx, but not @a ctx itself.
- *
- * @param ctx context to free
- */
-static void
-free_ctx (struct WtidTransactionContext *ctx)
-{
- struct AggregatedDepositDetail *wdd;
-
- while (NULL != (wdd = ctx->wdd_head))
- {
- GNUNET_CONTAINER_DLL_remove (ctx->wdd_head,
- ctx->wdd_tail,
- wdd);
- GNUNET_free (wdd);
- }
- GNUNET_free (ctx->payto_uri.full_payto);
-}
-
-
-/**
- * Execute a "/transfers" GET operation. Returns the deposit details of the
- * deposits that were aggregated to create the given wire transfer.
- *
- * If it returns a non-error code, the transaction logic MUST
- * NOT queue a MHD response. IF it returns an hard error, the
- * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
- * it returns the soft error code, the function MAY be called again to
- * retry and MUST not queue a MHD response.
- *
- * @param cls closure
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-get_transfer_deposits (void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct WtidTransactionContext *ctx = cls;
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Timestamp wire_fee_start_date;
- struct GNUNET_TIME_Timestamp wire_fee_end_date;
- struct TALER_MasterSignatureP wire_fee_master_sig;
-
- /* resetting to NULL/0 in case transaction was repeated after
- serialization failure */
- free_ctx (ctx);
- qs = TEH_plugin->lookup_wire_transfer (TEH_plugin->cls,
- &ctx->wtid,
- &handle_deposit_data,
- ctx);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "wire transfer");
- }
- return qs;
- }
- if (GNUNET_SYSERR == ctx->is_valid)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "wire history malformed");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- if (GNUNET_NO == ctx->is_valid)
- {
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_NOT_FOUND,
- TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_NOT_FOUND,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- {
- char *wire_method;
- uint64_t rowid;
-
- wire_method = TALER_payto_get_method (ctx->payto_uri.full_payto);
- if (NULL == wire_method)
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- "payto:// without wire method encountered");
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- qs = TEH_plugin->get_wire_fee (TEH_plugin->cls,
- wire_method,
- ctx->exec_time,
- &rowid,
- &wire_fee_start_date,
- &wire_fee_end_date,
- &ctx->fees,
- &wire_fee_master_sig);
- GNUNET_free (wire_method);
- }
- if (0 >= qs)
- {
- if ( (GNUNET_DB_STATUS_HARD_ERROR == qs) ||
- (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) )
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_NOT_FOUND,
- NULL);
- }
- return qs;
- }
- if (0 >
- TALER_amount_subtract (&ctx->total,
- &ctx->total,
- &ctx->fees.wire))
- {
- GNUNET_break (0);
- *mhd_ret = TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_TRANSFERS_GET_WIRE_FEE_INCONSISTENT,
- NULL);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
-}
-
-
-MHD_RESULT
-TEH_handler_transfers_get (struct TEH_RequestContext *rc,
- const char *const args[1])
-{
- struct WtidTransactionContext ctx;
- MHD_RESULT mhd_ret;
-
- memset (&ctx,
- 0,
- sizeof (ctx));
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (args[0],
- strlen (args[0]),
- &ctx.wtid,
- sizeof (ctx.wtid)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_TRANSFERS_GET_WTID_MALFORMED,
- args[0]);
- }
- if (GNUNET_OK !=
- TEH_DB_run_transaction (rc->connection,
- "run transfers GET",
- TEH_MT_REQUEST_OTHER,
- &mhd_ret,
- &get_transfer_deposits,
- &ctx))
- {
- free_ctx (&ctx);
- return mhd_ret;
- }
- mhd_ret = reply_transfer_details (rc->connection,
- &ctx.total,
- &ctx.merchant_pub,
- ctx.payto_uri,
- &ctx.fees.wire,
- ctx.exec_time,
- ctx.wdd_head);
- free_ctx (&ctx);
- return mhd_ret;
-}
-
-
-/* end of taler-exchange-httpd_transfers_get.c */
diff --git a/src/exchange/taler-exchange-httpd_transfers_get.h b/src/exchange/taler-exchange-httpd_transfers_get.h
@@ -1,41 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2014-2017, 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_transfers_get.h
- * @brief Handle wire transfer tracking-related requests
- * @author Christian Grothoff
- */
-#ifndef TALER_EXCHANGE_HTTPD_TRANSFERS_GET_H
-#define TALER_EXCHANGE_HTTPD_TRANSFERS_GET_H
-
-#include <gnunet/gnunet_util_lib.h>
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-
-/**
- * Handle a GET "/transfers/$WTID" request.
- *
- * @param rc request context of the handler
- * @param args array of additional options (length: 1, just the wtid)
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_transfers_get (struct TEH_RequestContext *rc,
- const char *const args[1]);
-
-
-#endif
diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c
@@ -1,1818 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_withdraw.c
- * @brief Code to handle /withdraw requests
- * @note This endpoint is active since v26 of the protocol API
- * @author Özgür Kesim
- */
-
-#include "taler/platform.h"
-#include <gnunet/gnunet_util_lib.h>
-#include <jansson.h>
-#include "taler-exchange-httpd.h"
-#include "taler/taler_json_lib.h"
-#include "taler/taler_kyclogic_lib.h"
-#include "taler/taler_mhd_lib.h"
-#include "taler-exchange-httpd_withdraw.h"
-#include "taler-exchange-httpd_common_kyc.h"
-#include "taler-exchange-httpd_responses.h"
-#include "taler-exchange-httpd_keys.h"
-#include "taler/taler_util.h"
-
-/**
- * The different type of errors that might occur, sorted by name.
- * Some of them require idempotency checks, which are marked
- * in @e idempotency_check_required below.
- */
-enum WithdrawError
-{
- WITHDRAW_ERROR_NONE,
- WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED,
- WITHDRAW_ERROR_AMOUNT_OVERFLOW,
- WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW,
- WITHDRAW_ERROR_BLINDING_SEED_REQUIRED,
- WITHDRAW_ERROR_CIPHER_MISMATCH,
- WITHDRAW_ERROR_CONFIRMATION_SIGN,
- WITHDRAW_ERROR_DB_FETCH_FAILED,
- WITHDRAW_ERROR_DB_INVARIANT_FAILURE,
- WITHDRAW_ERROR_DENOMINATION_EXPIRED,
- WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
- WITHDRAW_ERROR_DENOMINATION_REVOKED,
- WITHDRAW_ERROR_DENOMINATION_SIGN,
- WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- WITHDRAW_ERROR_FEE_OVERFLOW,
- WITHDRAW_ERROR_IDEMPOTENT_PLANCHET,
- WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
- WITHDRAW_ERROR_CRYPTO_HELPER,
- WITHDRAW_ERROR_KEYS_MISSING,
- WITHDRAW_ERROR_KYC_REQUIRED,
- WITHDRAW_ERROR_LEGITIMIZATION_RESULT,
- WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE,
- WITHDRAW_ERROR_NONCE_REUSE,
- WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
- WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
- WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID,
- WITHDRAW_ERROR_RESERVE_UNKNOWN,
-};
-
-/**
- * With the bits set in this value will be mark the errors
- * that require a check for idempotency before actually
- * returning an error.
- */
-static const uint64_t idempotency_check_required =
- 0
- | (1LLU << WITHDRAW_ERROR_DENOMINATION_EXPIRED)
- | (1LLU << WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN)
- | (1LLU << WITHDRAW_ERROR_DENOMINATION_REVOKED)
- | (1LLU << WITHDRAW_ERROR_INSUFFICIENT_FUNDS)
- | (1LLU << WITHDRAW_ERROR_KEYS_MISSING)
- | (1LLU << WITHDRAW_ERROR_KYC_REQUIRED);
-
-#define IDEMPOTENCY_CHECK_REQUIRED(ec) \
- (0LLU != (idempotency_check_required & (1LLU << (ec))))
-
-
-/**
- * Context for a /withdraw requests
- */
-struct WithdrawContext
-{
-
- /**
- * This struct is kept in a DLL.
- */
- struct WithdrawContext *prev;
- struct WithdrawContext *next;
-
- /**
- * Processing phase we are in.
- * The ordering here partially matters, as we progress through
- * them by incrementing the phase in the happy path.
- */
- enum
- {
- WITHDRAW_PHASE_PARSE = 0,
- WITHDRAW_PHASE_CHECK_KEYS,
- WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE,
- WITHDRAW_PHASE_RUN_LEGI_CHECK,
- WITHDRAW_PHASE_SUSPENDED,
- WITHDRAW_PHASE_CHECK_KYC_RESULT,
- WITHDRAW_PHASE_PREPARE_TRANSACTION,
- WITHDRAW_PHASE_RUN_TRANSACTION,
- WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS,
- WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
- WITHDRAW_PHASE_RETURN_NO,
- WITHDRAW_PHASE_RETURN_YES,
- } phase;
-
-
- /**
- * Handle for the legitimization check.
- */
- struct TEH_LegitimizationCheckHandle *lch;
-
- /**
- * Request context
- */
- const struct TEH_RequestContext *rc;
-
- /**
- * KYC status for the operation.
- */
- struct TALER_EXCHANGEDB_KycStatus kyc;
-
- /**
- * Current time for the DB transaction.
- */
- struct GNUNET_TIME_Timestamp now;
-
- /**
- * Set to the hash of the normalized payto URI that established
- * the reserve.
- */
- struct TALER_NormalizedPaytoHashP h_normalized_payto;
-
- /**
- * Captures all parameters provided in the JSON request
- */
- struct
- {
- /**
- * All fields (from the request or computed)
- * that we persist in the database.
- */
- struct TALER_EXCHANGEDB_Withdraw withdraw;
-
- /**
- * In some error cases we check for idempotency.
- * If we find an entry in the database, we mark this here.
- */
- bool is_idempotent;
-
- /**
- * In some error conditions the request is checked
- * for idempotency and the result from the database
- * is stored here.
- */
- struct TALER_EXCHANGEDB_Withdraw withdraw_idem;
-
- /**
- * Array of ``withdraw.num_coins`` hashes of the public keys
- * of the denominations to withdraw.
- */
- struct TALER_DenominationHashP *denoms_h;
-
- /**
- * Number of planchets. If ``withdraw.max_age`` was _not_ set, this is equal to ``num_coins``.
- * Otherwise (``withdraw.max_age`` was set) it is ``withdraw.num_coins * kappa``.
- */
- size_t num_planchets;
-
- /**
- * Array of ``withdraw.num_planchets`` coin planchets.
- * Note that the size depends on the age restriction:
- * If ``withdraw.age_proof_required`` is false,
- * this is an array of length ``withdraw.num_coins``.
- * Otherwise it is an array of length ``kappa*withdraw.num_coins``,
- * arranged in runs of ``num_coins`` coins,
- * [0..num_coins)..[0..num_coins),
- * one for each #TALER_CNC_KAPPA value.
- */
- struct TALER_BlindedPlanchet *planchets;
-
- /**
- * If proof of age-restriction is required, the #TALER_CNC_KAPPA hashes
- * of the batches of ``withdraw.num_coins`` coins.
- */
- struct TALER_HashBlindedPlanchetsP kappa_planchets_h[TALER_CNC_KAPPA];
-
- /**
- * Total (over all coins) amount (excluding fee) committed to withdraw
- */
- struct TALER_Amount amount;
-
- /**
- * Total fees for the withdraw
- */
- struct TALER_Amount fee;
-
- /**
- * Array of length ``withdraw.num_cs_r_values`` of indices into
- * @e denoms_h of CS denominations.
- */
- uint32_t *cs_indices;
-
- } request;
-
-
- /**
- * Errors occurring during evaluation of the request are captured in this
- * struct. In phase WITHDRAW_PHASE_GENERATE_REPLY_ERROR an appropriate error
- * message is prepared and sent to the client.
- */
- struct
- {
- /* The (internal) error code */
- enum WithdrawError code;
-
- /**
- * Some errors require details to be sent to the client.
- * These are captured in this union.
- * Each field is named according to the error that is using it, except
- * commented otherwise.
- */
- union
- {
- const char *request_parameter_malformed;
-
- const char *reserve_cipher_unknown;
-
- /**
- * For all errors related to a particular denomination, i.e.
- * WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
- * WITHDRAW_ERROR_DENOMINATION_EXPIRED,
- * WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- * WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- * we use this one field.
- */
- const struct TALER_DenominationHashP *denom_h;
-
- const char *db_fetch_context;
-
- struct
- {
- uint16_t max_allowed;
- uint32_t birthday;
- } maximum_age_too_large;
-
- /**
- * The lowest age required
- */
- uint16_t age_restriction_required;
-
- /**
- * Balance of the reserve
- */
- struct TALER_Amount insufficient_funds;
-
- enum TALER_ErrorCode ec_confirmation_sign;
-
- enum TALER_ErrorCode ec_denomination_sign;
-
- struct
- {
- struct MHD_Response *response;
- unsigned int http_status;
- } legitimization_result;
-
- } details;
- } error;
-};
-
-/**
- * The following macros set the given error code,
- * set the phase to WITHDRAW_PHASE_GENERATE_REPLY_ERROR,
- * and optionally set the given field (with an optionally given value).
- */
-#define SET_ERROR(wc, ec) \
- do \
- { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
- (wc)->error.code = (ec); \
- (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_FIELD(wc, ec, field) \
- do \
- { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
- (wc)->error.code = (ec); \
- (wc)->error.details.field = (field); \
- (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-#define SET_ERROR_WITH_DETAIL(wc, ec, field, value) \
- do \
- { GNUNET_static_assert (WITHDRAW_ERROR_NONE != ec); \
- (wc)->error.code = (ec); \
- (wc)->error.details.field = (value); \
- (wc)->phase = WITHDRAW_PHASE_GENERATE_REPLY_ERROR; } while (0)
-
-
-/**
- * All withdraw context is kept in a DLL.
- */
-static struct WithdrawContext *wc_head;
-static struct WithdrawContext *wc_tail;
-
-
-void
-TEH_withdraw_cleanup ()
-{
- struct WithdrawContext *wc;
-
- while (NULL != (wc = wc_head))
- {
- GNUNET_CONTAINER_DLL_remove (wc_head,
- wc_tail,
- wc);
- wc->phase = WITHDRAW_PHASE_RETURN_NO;
- MHD_resume_connection (wc->rc->connection);
- }
-}
-
-
-/**
- * Terminate the main loop by returning the final
- * result.
- *
- * @param[in,out] wc context to update phase for
- * @param mres MHD status to return
- */
-static void
-finish_loop (struct WithdrawContext *wc,
- MHD_RESULT mres)
-{
- wc->phase = (MHD_YES == mres)
- ? WITHDRAW_PHASE_RETURN_YES
- : WITHDRAW_PHASE_RETURN_NO;
-}
-
-
-/**
- * Check if the withdraw request is replayed
- * and we already have an answer.
- * If so, replay the existing answer and return the HTTP response.
- *
- * @param[in,out] wc parsed request data
- * @return true if the request is idempotent with an existing request
- * false if we did not find the request in the DB and did not set @a mret
- */
-static bool
-withdraw_is_idempotent (
- struct WithdrawContext *wc)
-{
- enum GNUNET_DB_QueryStatus qs;
- uint8_t max_retries = 3;
-
- /* We should at most be called once */
- GNUNET_assert (! wc->request.is_idempotent);
- while (0 < max_retries--)
- {
- qs = TEH_plugin->get_withdraw (
- TEH_plugin->cls,
- &wc->request.withdraw.planchets_h,
- &wc->request.withdraw_idem);
- if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
- break;
- }
-
- if (0 > qs)
- {
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
- GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
- SET_ERROR_WITH_DETAIL (wc,
- WITHDRAW_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "get_withdraw");
- return true; /* Well, kind-of. */
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- return false;
-
- wc->request.is_idempotent = true;
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "request is idempotent\n");
-
- /* Generate idempotent reply */
- TEH_METRICS_num_requests[TEH_MT_REQUEST_IDEMPOTENT_WITHDRAW]++;
- wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
- return true;
-}
-
-
-/**
- * Function implementing withdraw transaction. Runs the
- * transaction logic; IF it returns a non-error code, the transaction
- * logic MUST NOT queue a MHD response. IF it returns an hard error,
- * the transaction logic MUST queue a MHD response and set @a mhd_ret.
- * IF it returns the soft error code, the function MAY be called again
- * to retry and MUST not queue a MHD response.
- *
- * @param cls a `struct WithdrawContext *`
- * @param connection MHD request which triggered the transaction
- * @param[out] mhd_ret set to MHD response status for @a connection,
- * if transaction failed (!)
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-withdraw_transaction (
- void *cls,
- struct MHD_Connection *connection,
- MHD_RESULT *mhd_ret)
-{
- struct WithdrawContext *wc = cls;
- enum GNUNET_DB_QueryStatus qs;
- bool balance_ok;
- bool age_ok;
- bool found;
- uint16_t noreveal_index;
- bool nonce_reuse;
- uint16_t allowed_maximum_age;
- uint32_t reserve_birthday;
- struct TALER_Amount insufficient_funds;
-
- qs = TEH_plugin->do_withdraw (TEH_plugin->cls,
- &wc->request.withdraw,
- &wc->now,
- &balance_ok,
- &insufficient_funds,
- &age_ok,
- &allowed_maximum_age,
- &reserve_birthday,
- &found,
- &noreveal_index,
- &nonce_reuse);
- if (0 > qs)
- {
- if (GNUNET_DB_STATUS_HARD_ERROR == qs)
- SET_ERROR_WITH_DETAIL (wc,
- WITHDRAW_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "do_withdraw");
- return qs;
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- SET_ERROR (wc,
- WITHDRAW_ERROR_RESERVE_UNKNOWN);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (found)
- {
- /**
- * The request was idempotent and we got the previous noreveal_index.
- * We simply overwrite that value in our current withdraw object and
- * move on to reply success.
- */
- wc->request.withdraw.noreveal_index = noreveal_index;
- wc->phase = WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS;
- return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
- }
-
- if (! age_ok)
- {
- if (wc->request.withdraw.age_proof_required)
- {
- wc->error.details.maximum_age_too_large.max_allowed = allowed_maximum_age;
- wc->error.details.maximum_age_too_large.birthday = reserve_birthday;
- SET_ERROR (wc,
- WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE);
- }
- else
- {
- wc->error.details.age_restriction_required = allowed_maximum_age;
- SET_ERROR (wc,
- WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED);
- }
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (! balance_ok)
- {
- TEH_plugin->rollback (TEH_plugin->cls);
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_INSUFFICIENT_FUNDS,
- insufficient_funds);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (nonce_reuse)
- {
- GNUNET_break (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_NONCE_REUSE);
- return GNUNET_DB_STATUS_HARD_ERROR;
- }
-
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
- TEH_METRICS_num_success[TEH_MT_SUCCESS_WITHDRAW]++;
- return qs;
-}
-
-
-/**
- * The request was prepared successfully.
- * Run the main DB transaction.
- *
- * @param wc The context for the current withdraw request
- */
-static void
-phase_run_transaction (
- struct WithdrawContext *wc)
-{
- MHD_RESULT mhd_ret;
- enum GNUNET_GenericReturnValue qs;
-
- GNUNET_assert (WITHDRAW_PHASE_RUN_TRANSACTION ==
- wc->phase);
- qs = TEH_DB_run_transaction (wc->rc->connection,
- "run withdraw",
- TEH_MT_REQUEST_WITHDRAW,
- &mhd_ret,
- &withdraw_transaction,
- wc);
- if (WITHDRAW_PHASE_RUN_TRANSACTION != wc->phase)
- return;
- GNUNET_break (GNUNET_OK == qs);
- /* If the transaction has changed the phase, we don't alter it and return.*/
- wc->phase++;
-}
-
-
-/**
- * The request for withdraw was parsed successfully.
- * Sign and persist the chosen blinded coins for the reveal step.
- *
- * @param wc The context for the current withdraw request
- */
-static void
-phase_prepare_transaction (
- struct WithdrawContext *wc)
-{
- size_t offset = 0;
-
- wc->request.withdraw.denom_sigs
- = GNUNET_new_array (
- wc->request.withdraw.num_coins,
- struct TALER_BlindedDenominationSignature);
- /* Pick the challenge in case of age restriction */
- if (wc->request.withdraw.age_proof_required)
- {
- wc->request.withdraw.noreveal_index =
- GNUNET_CRYPTO_random_u32 (
- GNUNET_CRYPTO_QUALITY_STRONG,
- TALER_CNC_KAPPA);
- /**
- * In case of age restriction, we use the corresponding offset in the planchet
- * array to the beginning of the coins corresponding to the noreveal_index.
- */
- offset = wc->request.withdraw.noreveal_index
- * wc->request.withdraw.num_coins;
- GNUNET_assert (offset + wc->request.withdraw.num_coins <=
- wc->request.num_planchets);
- }
-
- /* Choose and sign the coins */
- {
- struct TEH_CoinSignData csds[wc->request.withdraw.num_coins];
- enum TALER_ErrorCode ec_denomination_sign;
-
- memset (csds,
- 0,
- sizeof(csds));
-
- /* Pick the chosen blinded coins */
- for (uint32_t i = 0; i<wc->request.withdraw.num_coins; i++)
- {
- csds[i].bp = &wc->request.planchets[i + offset];
- csds[i].h_denom_pub = &wc->request.denoms_h[i];
- }
-
- ec_denomination_sign = TEH_keys_denomination_batch_sign (
- wc->request.withdraw.num_coins,
- csds,
- false,
- wc->request.withdraw.denom_sigs);
- if (TALER_EC_NONE != ec_denomination_sign)
- {
- GNUNET_break (0);
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_DENOMINATION_SIGN,
- ec_denomination_sign);
- return;
- }
-
- /* Save the hash value of the selected batch of coins */
- wc->request.withdraw.selected_h =
- wc->request.kappa_planchets_h[wc->request.withdraw.noreveal_index];
- }
-
- /**
- * For the denominations with cipher CS, calculate the R-values
- * and save the choices we made now, as at a later point, the
- * private keys for the denominations might now be available anymore
- * to make the same choice again.
- */
- if (0 < wc->request.withdraw.num_cs_r_values)
- {
- size_t num_cs_r_values = wc->request.withdraw.num_cs_r_values;
- struct TEH_CsDeriveData cdds[num_cs_r_values];
- struct GNUNET_CRYPTO_CsSessionNonce nonces[num_cs_r_values];
-
- memset (nonces,
- 0,
- sizeof(nonces));
- wc->request.withdraw.cs_r_values
- = GNUNET_new_array (
- num_cs_r_values,
- struct GNUNET_CRYPTO_CSPublicRPairP);
- wc->request.withdraw.cs_r_choices = 0;
-
- GNUNET_assert (! wc->request.withdraw.no_blinding_seed);
- TALER_cs_derive_nonces_from_seed (
- &wc->request.withdraw.blinding_seed,
- false, /* not for melt */
- num_cs_r_values,
- wc->request.cs_indices,
- nonces);
-
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = wc->request.cs_indices[i];
-
- GNUNET_assert (idx < wc->request.withdraw.num_coins);
- cdds[i].h_denom_pub = &wc->request.denoms_h[idx];
- cdds[i].nonce = &nonces[i];
- }
-
- /**
- * Let the crypto helper generate the R-values and make the choices.
- */
- if (TALER_EC_NONE !=
- TEH_keys_denomination_cs_batch_r_pub_simple (
- wc->request.withdraw.num_cs_r_values,
- cdds,
- false,
- wc->request.withdraw.cs_r_values))
- {
- GNUNET_break (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_CRYPTO_HELPER);
- return;
- }
-
- /* Now save the choices for the selected bits */
- for (size_t i = 0; i < num_cs_r_values; i++)
- {
- size_t idx = wc->request.cs_indices[i];
-
- struct TALER_BlindedDenominationSignature *sig =
- &wc->request.withdraw.denom_sigs[idx];
- uint8_t bit = sig->blinded_sig->details.blinded_cs_answer.b;
-
- wc->request.withdraw.cs_r_choices |= bit << i;
- GNUNET_static_assert (
- TALER_MAX_COINS <=
- sizeof(wc->request.withdraw.cs_r_choices) * 8);
- }
- }
- wc->phase++;
-}
-
-
-/**
- * Check the KYC result.
- *
- * @param wc context for request processing
- */
-static void
-phase_check_kyc_result (struct WithdrawContext *wc)
-{
- /* return final positive response */
- if (! wc->kyc.ok)
- {
- SET_ERROR (wc,
- WITHDRAW_ERROR_KYC_REQUIRED);
- return;
- }
- wc->phase++;
-}
-
-
-/**
- * Function called with the result of a legitimization
- * check.
- *
- * @param cls closure
- * @param lcr legitimization check result
- */
-static void
-withdraw_legi_cb (
- void *cls,
- const struct TEH_LegitimizationCheckResult *lcr)
-{
- struct WithdrawContext *wc = cls;
-
- wc->lch = NULL;
- GNUNET_assert (WITHDRAW_PHASE_SUSPENDED ==
- wc->phase);
- MHD_resume_connection (wc->rc->connection);
- GNUNET_CONTAINER_DLL_remove (wc_head,
- wc_tail,
- wc);
- TALER_MHD_daemon_trigger ();
- if (NULL != lcr->response)
- {
- wc->error.details.legitimization_result.response = lcr->response;
- wc->error.details.legitimization_result.http_status = lcr->http_status;
- SET_ERROR (wc,
- WITHDRAW_ERROR_LEGITIMIZATION_RESULT);
- return;
- }
- wc->kyc = lcr->kyc;
- wc->phase = WITHDRAW_PHASE_CHECK_KYC_RESULT;
-}
-
-
-/**
- * Function called to iterate over KYC-relevant transaction amounts for a
- * particular time range. Called within a database transaction, so must
- * not start a new one.
- *
- * @param cls closure, identifies the event type and account to iterate
- * over events for
- * @param limit maximum time-range for which events should be fetched
- * (timestamp in the past)
- * @param cb function to call on each event found, events must be returned
- * in reverse chronological order
- * @param cb_cls closure for @a cb, of type struct WithdrawContext
- * @return transaction status
- */
-static enum GNUNET_DB_QueryStatus
-withdraw_amount_cb (
- void *cls,
- struct GNUNET_TIME_Absolute limit,
- TALER_EXCHANGEDB_KycAmountCallback cb,
- void *cb_cls)
-{
- struct WithdrawContext *wc = cls;
- enum GNUNET_GenericReturnValue ret;
- enum GNUNET_DB_QueryStatus qs;
-
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Signaling amount %s for KYC check during witdrawal\n",
- TALER_amount2s (&wc->request.withdraw.amount_with_fee));
-
- ret = cb (cb_cls,
- &wc->request.withdraw.amount_with_fee,
- wc->now.abs_time);
- GNUNET_break (GNUNET_SYSERR != ret);
- if (GNUNET_OK != ret)
- return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
-
- qs = TEH_plugin->select_withdraw_amounts_for_kyc_check (
- TEH_plugin->cls,
- &wc->h_normalized_payto,
- limit,
- cb,
- cb_cls);
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Got %d additional transactions for this withdrawal and limit %llu\n",
- qs,
- (unsigned long long) limit.abs_value_us);
- GNUNET_break (qs >= 0);
- return qs;
-}
-
-
-/**
- * Do legitimization check.
- *
- * @param wc operation context
- */
-static void
-phase_run_legi_check (struct WithdrawContext *wc)
-{
- enum GNUNET_DB_QueryStatus qs;
- struct TALER_FullPayto payto_uri;
- struct TALER_FullPaytoHashP h_full_payto;
-
- /* Check if the money came from a wire transfer */
- qs = TEH_plugin->reserves_get_origin (
- TEH_plugin->cls,
- &wc->request.withdraw.reserve_pub,
- &h_full_payto,
- &payto_uri);
- if (qs < 0)
- {
- SET_ERROR_WITH_DETAIL (wc,
- WITHDRAW_ERROR_DB_FETCH_FAILED,
- db_fetch_context,
- "reserves_get_origin");
- return;
- }
- /* If _no_ results, reserve was created by merge,
- in which case no KYC check is required as the
- merge already did that. */
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- wc->phase = WITHDRAW_PHASE_PREPARE_TRANSACTION;
- return;
- }
- TALER_full_payto_normalize_and_hash (payto_uri,
- &wc->h_normalized_payto);
- wc->lch = TEH_legitimization_check (
- &wc->rc->async_scope_id,
- TALER_KYCLOGIC_KYC_TRIGGER_WITHDRAW,
- payto_uri,
- &wc->h_normalized_payto,
- NULL, /* no account pub: this is about the origin account */
- &withdraw_amount_cb,
- wc,
- &withdraw_legi_cb,
- wc);
- GNUNET_assert (NULL != wc->lch);
- GNUNET_free (payto_uri.full_payto);
- GNUNET_CONTAINER_DLL_insert (wc_head,
- wc_tail,
- wc);
- MHD_suspend_connection (wc->rc->connection);
- wc->phase = WITHDRAW_PHASE_SUSPENDED;
-}
-
-
-/**
- * Check if the given denomination is still or already valid, has not been
- * revoked and potentically supports age restriction.
- *
- * @param[in,out] wc context for the withdraw operation
- * @param ksh The handle to the current state of (denomination) keys in the exchange
- * @param denom_h Hash of the denomination key to check
- * @param[out] pdk denomination key found, might be NULL
- * @return true when denomation was found and valid,
- * false when denomination was not valid and the state machine was advanced
- */
-static enum GNUNET_GenericReturnValue
-find_denomination (
- struct WithdrawContext *wc,
- struct TEH_KeyStateHandle *ksh,
- const struct TALER_DenominationHashP *denom_h,
- struct TEH_DenominationKey **pdk)
-{
- struct TEH_DenominationKey *dk;
-
- *pdk = NULL;
- dk = TEH_keys_denomination_by_hash_from_state (
- ksh,
- denom_h,
- NULL,
- NULL);
- if (NULL == dk)
- {
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN,
- denom_h);
- return false;
- }
- if (GNUNET_TIME_absolute_is_past (
- dk->meta.expire_withdraw.abs_time))
- {
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_DENOMINATION_EXPIRED,
- denom_h);
- return false;
- }
- if (GNUNET_TIME_absolute_is_future (
- dk->meta.start.abs_time))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE,
- denom_h);
- return false;
- }
- if (dk->recoup_possible)
- {
- SET_ERROR (wc,
- WITHDRAW_ERROR_DENOMINATION_REVOKED);
- return false;
- }
- /* In case of age withdraw, make sure that the denomination supports age restriction */
- if (wc->request.withdraw.age_proof_required)
- {
- if (0 == dk->denom_pub.age_mask.bits)
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION,
- denom_h);
- return false;
- }
- }
- *pdk = dk;
- return true;
-}
-
-
-/**
- * Check if the given array of hashes of denomination_keys
- * a) belong to valid denominations
- * b) those are marked as age restricted, if the request is age restricted
- * c) calculate the total amount of the denominations including fees
- * for withdraw.
- *
- * @param wc context of the age withdrawal to check keys for
- */
-static void
-phase_check_keys (
- struct WithdrawContext *wc)
-{
- struct TEH_KeyStateHandle *ksh;
- bool is_cs_denom[wc->request.withdraw.num_coins];
-
- memset (is_cs_denom,
- 0,
- sizeof(is_cs_denom));
- ksh = TEH_keys_get_state ();
- if (NULL == ksh)
- {
- GNUNET_break (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_KEYS_MISSING);
- return;
- }
- wc->request.withdraw.denom_serials =
- GNUNET_new_array (wc->request.withdraw.num_coins,
- uint64_t);
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &wc->request.amount));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &wc->request.fee));
- GNUNET_assert (GNUNET_OK ==
- TALER_amount_set_zero (TEH_currency,
- &wc->request.withdraw.amount_with_fee));
-
- for (unsigned int i = 0; i < wc->request.withdraw.num_coins; i++)
- {
- struct TEH_DenominationKey *dk;
-
- if (! find_denomination (wc,
- ksh,
- &wc->request.denoms_h[i],
- &dk))
- return;
- switch (dk->denom_pub.bsign_pub_key->cipher)
- {
- case GNUNET_CRYPTO_BSA_INVALID:
- /* This should never happen (memory corruption?) */
- GNUNET_assert (0);
- case GNUNET_CRYPTO_BSA_RSA:
- /* nothing to do here */
- break;
- case GNUNET_CRYPTO_BSA_CS:
- if (wc->request.withdraw.no_blinding_seed)
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_BLINDING_SEED_REQUIRED);
- return;
- }
- wc->request.withdraw.num_cs_r_values++;
- is_cs_denom[i] = true;
- break;
- }
-
- /* Ensure the ciphers from the planchets match the denominations'. */
- if (wc->request.withdraw.age_proof_required)
- {
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- size_t off = k * wc->request.withdraw.num_coins;
-
- if (dk->denom_pub.bsign_pub_key->cipher !=
- wc->request.planchets[i + off].blinded_message->cipher)
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_CIPHER_MISMATCH);
- return;
- }
- }
- }
- else
- {
- if (dk->denom_pub.bsign_pub_key->cipher !=
- wc->request.planchets[i].blinded_message->cipher)
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_CIPHER_MISMATCH);
- return;
- }
- }
-
- /* Accumulate the values */
- if (0 > TALER_amount_add (&wc->request.amount,
- &wc->request.amount,
- &dk->meta.value))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_AMOUNT_OVERFLOW);
- return;
- }
-
- /* Accumulate the withdraw fees */
- if (0 > TALER_amount_add (&wc->request.fee,
- &wc->request.fee,
- &dk->meta.fees.withdraw))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_FEE_OVERFLOW);
- return;
- }
- wc->request.withdraw.denom_serials[i] = dk->meta.serial;
- }
-
- /* Save the hash of the batch of planchets */
- if (! wc->request.withdraw.age_proof_required)
- {
- TALER_wallet_blinded_planchets_hash (
- wc->request.withdraw.num_coins,
- wc->request.planchets,
- wc->request.denoms_h,
- &wc->request.withdraw.planchets_h);
- }
- else
- {
- struct GNUNET_HashContext *ctx;
-
- /**
- * The age-proof-required case is a bit more involved,
- * because we need to calculate and remember kappa hashes
- * for each batch of coins.
- */
- ctx = GNUNET_CRYPTO_hash_context_start ();
- GNUNET_assert (NULL != ctx);
-
- for (uint8_t k = 0; k < TALER_CNC_KAPPA; k++)
- {
- size_t off = k * wc->request.withdraw.num_coins;
-
- TALER_wallet_blinded_planchets_hash (
- wc->request.withdraw.num_coins,
- &wc->request.planchets[off],
- wc->request.denoms_h,
- &wc->request.kappa_planchets_h[k]);
- GNUNET_CRYPTO_hash_context_read (
- ctx,
- &wc->request.kappa_planchets_h[k],
- sizeof(wc->request.kappa_planchets_h[k]));
- }
- GNUNET_CRYPTO_hash_context_finish (
- ctx,
- &wc->request.withdraw.planchets_h.hash);
- }
-
- /* Save the total amount including fees */
- if (0 > TALER_amount_add (
- &wc->request.withdraw.amount_with_fee,
- &wc->request.amount,
- &wc->request.fee))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW);
- return;
- }
-
- /* Save the indices of CS denominations */
- if (0 < wc->request.withdraw.num_cs_r_values)
- {
- size_t j = 0;
-
- wc->request.cs_indices = GNUNET_new_array (
- wc->request.withdraw.num_cs_r_values,
- uint32_t);
-
- for (size_t i = 0; i < wc->request.withdraw.num_coins; i++)
- {
- if (is_cs_denom[i])
- wc->request.cs_indices[j++] = i;
- }
- }
-
- wc->phase++;
-}
-
-
-/**
- * Check that the client signature authorizing the withdrawal is valid.
- *
- * @param[in,out] wc request context to check
- */
-static void
-phase_check_reserve_signature (
- struct WithdrawContext *wc)
-{
- TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
- if (GNUNET_OK !=
- TALER_wallet_withdraw_verify (
- &wc->request.amount,
- &wc->request.fee,
- &wc->request.withdraw.planchets_h,
- wc->request.withdraw.no_blinding_seed
- ? NULL
- : &wc->request.withdraw.blinding_seed,
- (wc->request.withdraw.age_proof_required)
- ? &TEH_age_restriction_config.mask
- : NULL,
- (wc->request.withdraw.age_proof_required)
- ? wc->request.withdraw.max_age
- : 0,
- &wc->request.withdraw.reserve_pub,
- &wc->request.withdraw.reserve_sig))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID);
- return;
- }
- wc->phase++;
-}
-
-
-/**
- * Free data inside of @a wd, but not @a wd itself.
- *
- * @param[in] wd withdraw data to free
- */
-static void
-free_db_withdraw_data (struct TALER_EXCHANGEDB_Withdraw *wd)
-{
- if (NULL != wd->denom_sigs)
- {
- for (unsigned int i = 0; i<wd->num_coins; i++)
- TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
- GNUNET_free (wd->denom_sigs);
- }
- GNUNET_free (wd->denom_serials);
- GNUNET_free (wd->cs_r_values);
-}
-
-
-/**
- * Cleanup routine for withdraw request.
- * The function is called upon completion of the request
- * that should clean up @a rh_ctx. Can be NULL.
- *
- * @param rc request context to clean up
- */
-static void
-clean_withdraw_rc (struct TEH_RequestContext *rc)
-{
- struct WithdrawContext *wc = rc->rh_ctx;
-
- if (NULL != wc->lch)
- {
- TEH_legitimization_check_cancel (wc->lch);
- wc->lch = NULL;
- }
- GNUNET_free (wc->request.denoms_h);
- for (unsigned int i = 0; i<wc->request.num_planchets; i++)
- TALER_blinded_planchet_free (&wc->request.planchets[i]);
- GNUNET_free (wc->request.planchets);
- free_db_withdraw_data (&wc->request.withdraw);
- GNUNET_free (wc->request.cs_indices);
- if (wc->request.is_idempotent)
- free_db_withdraw_data (&wc->request.withdraw_idem);
- if ( (WITHDRAW_ERROR_LEGITIMIZATION_RESULT == wc->error.code) &&
- (NULL != wc->error.details.legitimization_result.response) )
- {
- MHD_destroy_response (wc->error.details.legitimization_result.response);
- wc->error.details.legitimization_result.response = NULL;
- }
- GNUNET_free (wc);
-}
-
-
-/**
- * Generates response for the withdraw request.
- *
- * @param wc withdraw operation context
- */
-static void
-phase_generate_reply_success (struct WithdrawContext *wc)
-{
- struct TALER_EXCHANGEDB_Withdraw *db_obj;
-
- db_obj = wc->request.is_idempotent
- ? &wc->request.withdraw_idem
- : &wc->request.withdraw;
-
- if (wc->request.withdraw.age_proof_required)
- {
- struct TALER_ExchangePublicKeyP pub;
- struct TALER_ExchangeSignatureP sig;
- enum TALER_ErrorCode ec_confirmation_sign;
-
- ec_confirmation_sign =
- TALER_exchange_online_withdraw_age_confirmation_sign (
- &TEH_keys_exchange_sign_,
- &db_obj->planchets_h,
- db_obj->noreveal_index,
- &pub,
- &sig);
- if (TALER_EC_NONE != ec_confirmation_sign)
- {
- SET_ERROR_WITH_FIELD (wc,
- WITHDRAW_ERROR_CONFIRMATION_SIGN,
- ec_confirmation_sign);
- return;
- }
-
- finish_loop (wc,
- TALER_MHD_REPLY_JSON_PACK (
- wc->rc->connection,
- MHD_HTTP_CREATED,
- GNUNET_JSON_pack_uint64 ("noreveal_index",
- db_obj->noreveal_index),
- GNUNET_JSON_pack_data_auto ("exchange_sig",
- &sig),
- GNUNET_JSON_pack_data_auto ("exchange_pub",
- &pub)));
- }
- else /* not age restricted */
- {
- json_t *sigs;
-
- sigs = json_array ();
- GNUNET_assert (NULL != sigs);
- for (unsigned int i = 0; i<db_obj->num_coins; i++)
- {
- GNUNET_assert (
- 0 ==
- json_array_append_new (
- sigs,
- GNUNET_JSON_PACK (
- TALER_JSON_pack_blinded_denom_sig (
- NULL,
- &db_obj->denom_sigs[i]))));
- }
- finish_loop (wc,
- TALER_MHD_REPLY_JSON_PACK (
- wc->rc->connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_array_steal ("ev_sigs",
- sigs)));
- }
-
- TEH_METRICS_withdraw_num_coins += db_obj->num_coins;
-}
-
-
-/**
- * Reports an error, potentially with details.
- * That is, it puts a error-type specific response into the MHD queue.
- * It will do a idempotency check first, if needed for the error type.
- *
- * @param wc withdraw context
- */
-static void
-phase_generate_reply_error (
- struct WithdrawContext *wc)
-{
- GNUNET_assert (WITHDRAW_PHASE_GENERATE_REPLY_ERROR == wc->phase);
- if (IDEMPOTENCY_CHECK_REQUIRED (wc->error.code) &&
- withdraw_is_idempotent (wc))
- {
- return;
- }
-
- switch (wc->error.code)
- {
- case WITHDRAW_ERROR_NONE:
- break;
- case WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- wc->error.details.request_parameter_malformed));
- return;
- case WITHDRAW_ERROR_KEYS_MISSING:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
- NULL));
- return;
- case WITHDRAW_ERROR_DB_FETCH_FAILED:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- wc->error.details.db_fetch_context));
- return;
- case WITHDRAW_ERROR_DB_INVARIANT_FAILURE:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
- NULL));
- return;
- case WITHDRAW_ERROR_RESERVE_UNKNOWN:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
- NULL));
- return;
- case WITHDRAW_ERROR_DENOMINATION_SIGN:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- wc->error.details.ec_denomination_sign,
- NULL));
- return;
- case WITHDRAW_ERROR_KYC_REQUIRED:
- finish_loop (wc,
- TEH_RESPONSE_reply_kyc_required (
- wc->rc->connection,
- &wc->h_normalized_payto,
- &wc->kyc,
- false));
- return;
- case WITHDRAW_ERROR_DENOMINATION_KEY_UNKNOWN:
- GNUNET_break_op (0);
- finish_loop (wc,
- TEH_RESPONSE_reply_unknown_denom_pub_hash (
- wc->rc->connection,
- wc->error.details.denom_h));
- return;
- case WITHDRAW_ERROR_DENOMINATION_EXPIRED:
- GNUNET_break_op (0);
- finish_loop (wc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- wc->rc->connection,
- wc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
- "WITHDRAW"));
- return;
- case WITHDRAW_ERROR_DENOMINATION_VALIDITY_IN_FUTURE:
- finish_loop (wc,
- TEH_RESPONSE_reply_expired_denom_pub_hash (
- wc->rc->connection,
- wc->error.details.denom_h,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
- "WITHDRAW"));
- return;
- case WITHDRAW_ERROR_DENOMINATION_REVOKED:
- GNUNET_break_op (0);
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
- NULL));
- return;
- case WITHDRAW_ERROR_CIPHER_MISMATCH:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- NULL));
- return;
- case WITHDRAW_ERROR_BLINDING_SEED_REQUIRED:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- "blinding_seed"));
- return;
- case WITHDRAW_ERROR_CRYPTO_HELPER:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- NULL));
- return;
- case WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
- "cipher"));
- return;
- case WITHDRAW_ERROR_AGE_RESTRICTION_NOT_SUPPORTED_BY_DENOMINATION:
- {
- char msg[256];
-
- GNUNET_snprintf (msg,
- sizeof(msg),
- "denomination %s does not support age restriction",
- GNUNET_h2s (&wc->error.details.denom_h->hash));
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN,
- msg));
- return;
- }
- case WITHDRAW_ERROR_MAXIMUM_AGE_TOO_LARGE:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Generating JSON response with code %d\n",
- (int) TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE);
- finish_loop (wc,
- TALER_MHD_REPLY_JSON_PACK (
- wc->rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_MHD_PACK_EC (
- TALER_EC_EXCHANGE_WITHDRAW_MAXIMUM_AGE_TOO_LARGE),
- GNUNET_JSON_pack_uint64 (
- "allowed_maximum_age",
- wc->error.details.maximum_age_too_large.max_allowed),
- GNUNET_JSON_pack_uint64 (
- "reserve_birthday",
- wc->error.details.maximum_age_too_large.birthday)));
- return;
- case WITHDRAW_ERROR_AGE_RESTRICTION_REQUIRED:
- finish_loop (wc,
- TEH_RESPONSE_reply_reserve_age_restriction_required (
- wc->rc->connection,
- wc->error.details.age_restriction_required));
- return;
- case WITHDRAW_ERROR_AMOUNT_OVERFLOW:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
- "amount"));
- return;
- case WITHDRAW_ERROR_FEE_OVERFLOW:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_OVERFLOW,
- "fee"));
- return;
- case WITHDRAW_ERROR_AMOUNT_PLUS_FEE_OVERFLOW:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_WITHDRAW_AMOUNT_FEE_OVERFLOW,
- "amount+fee"));
- return;
- case WITHDRAW_ERROR_CONFIRMATION_SIGN:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- wc->error.details.ec_confirmation_sign,
- NULL));
- return;
- case WITHDRAW_ERROR_INSUFFICIENT_FUNDS:
- finish_loop (wc,
- TEH_RESPONSE_reply_reserve_insufficient_balance (
- wc->rc->connection,
- TALER_EC_EXCHANGE_WITHDRAW_INSUFFICIENT_FUNDS,
- &wc->error.details.insufficient_funds,
- &wc->request.withdraw.amount_with_fee,
- &wc->request.withdraw.reserve_pub));
- return;
- case WITHDRAW_ERROR_IDEMPOTENT_PLANCHET:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_EXCHANGE_WITHDRAW_IDEMPOTENT_PLANCHET,
- NULL));
- return;
- case WITHDRAW_ERROR_NONCE_REUSE:
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_CONFLICT,
- TALER_EC_EXCHANGE_WITHDRAW_NONCE_REUSE,
- NULL));
- return;
- case WITHDRAW_ERROR_RESERVE_SIGNATURE_INVALID:
- finish_loop (wc,
- TALER_MHD_reply_with_ec (
- wc->rc->connection,
- TALER_EC_EXCHANGE_WITHDRAW_RESERVE_SIGNATURE_INVALID,
- NULL));
- return;
- case WITHDRAW_ERROR_LEGITIMIZATION_RESULT: {
- finish_loop (
- wc,
- MHD_queue_response (wc->rc->connection,
- wc->error.details.legitimization_result.http_status,
- wc->error.details.legitimization_result.response));
- return;
- }
- }
- GNUNET_break (0);
- finish_loop (wc,
- TALER_MHD_reply_with_error (
- wc->rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
- "error phase without error"));
-}
-
-
-/**
- * Initializes the new context for the incoming withdraw request
- *
- * @param[in,out] wc withdraw request context
- * @param root json body of the request
- */
-static void
-withdraw_phase_parse (
- struct WithdrawContext *wc,
- const json_t *root)
-{
- const json_t *j_denoms_h;
- const json_t *j_coin_evs;
- const char *cipher;
- bool no_max_age;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_string ("cipher",
- &cipher),
- GNUNET_JSON_spec_fixed_auto ("reserve_pub",
- &wc->request.withdraw.reserve_pub),
- GNUNET_JSON_spec_array_const ("denoms_h",
- &j_denoms_h),
- GNUNET_JSON_spec_array_const ("coin_evs",
- &j_coin_evs),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_uint16 ("max_age",
- &wc->request.withdraw.max_age),
- &no_max_age),
- GNUNET_JSON_spec_mark_optional (
- GNUNET_JSON_spec_fixed_auto ("blinding_seed",
- &wc->request.withdraw.blinding_seed),
- &wc->request.withdraw.no_blinding_seed),
- GNUNET_JSON_spec_fixed_auto ("reserve_sig",
- &wc->request.withdraw.reserve_sig),
- GNUNET_JSON_spec_end ()
- };
- enum GNUNET_GenericReturnValue res;
-
- res = TALER_MHD_parse_json_data (wc->rc->connection,
- root,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- wc->phase = (GNUNET_SYSERR == res)
- ? WITHDRAW_PHASE_RETURN_NO
- : WITHDRAW_PHASE_RETURN_YES;
- return;
- }
-
- /* For now, we only support cipher "ED25519" for signatures by the reserve */
- if (0 != strcmp ("ED25519",
- cipher))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (wc,
- WITHDRAW_ERROR_RESERVE_CIPHER_UNKNOWN,
- reserve_cipher_unknown,
- cipher);
- return;
- }
-
- wc->request.withdraw.age_proof_required = ! no_max_age;
-
- if (wc->request.withdraw.age_proof_required)
- {
- /* The age value MUST be on the beginning of an age group */
- if (wc->request.withdraw.max_age !=
- TALER_get_lowest_age (&TEH_age_restriction_config.mask,
- wc->request.withdraw.max_age))
- {
- GNUNET_break_op (0);
- SET_ERROR_WITH_DETAIL (
- wc,
- WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- "max_age must be the lower edge of an age group");
- return;
- }
- }
-
- /* validate array size */
- {
- size_t num_coins = json_array_size (j_denoms_h);
- size_t array_size = json_array_size (j_coin_evs);
- const char *error;
-
- GNUNET_static_assert (
- TALER_MAX_COINS < INT_MAX / TALER_CNC_KAPPA);
-
-#define BAIL_IF(cond, msg) \
- if ((cond)) { \
- GNUNET_break_op (0); \
- error = (msg); break; \
- }
-
- do {
- BAIL_IF (0 == num_coins,
- "denoms_h must not be empty")
-
- /**
- * The wallet had committed to more than the maximum coins allowed, the
- * reserve has been charged, but now the user can not withdraw any money
- * from it. Note that the user can't get their money back in this case!
- */
- BAIL_IF (num_coins > TALER_MAX_COINS,
- "maximum number of coins that can be withdrawn has been exceeded")
-
- BAIL_IF ((! wc->request.withdraw.age_proof_required) &&
- (num_coins != array_size),
- "denoms_h and coin_evs must be arrays of the same size")
-
- BAIL_IF (wc->request.withdraw.age_proof_required &&
- ((TALER_CNC_KAPPA * num_coins) != array_size),
- "coin_evs must be an array of length "
- TALER_CNC_KAPPA_STR
- "*len(denoms_h)")
-
- wc->request.withdraw.num_coins = num_coins;
- wc->request.num_planchets = array_size;
- error = NULL;
-
- } while (0);
-#undef BAIL_IF
-
- if (NULL != error)
- {
- SET_ERROR_WITH_DETAIL (wc,
- WITHDRAW_ERROR_REQUEST_PARAMETER_MALFORMED,
- request_parameter_malformed,
- error);
- return;
- }
- }
- /* extract the denomination hashes */
- {
- size_t idx;
- json_t *value;
-
- wc->request.denoms_h
- = GNUNET_new_array (wc->request.withdraw.num_coins,
- struct TALER_DenominationHashP);
-
- json_array_foreach (j_denoms_h, idx, value) {
- struct GNUNET_JSON_Specification ispec[] = {
- GNUNET_JSON_spec_fixed_auto (NULL,
- &wc->request.denoms_h[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (wc->rc->connection,
- value,
- ispec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- wc->phase = (GNUNET_SYSERR == res)
- ? WITHDRAW_PHASE_RETURN_NO
- : WITHDRAW_PHASE_RETURN_YES;
- return;
- }
- }
- }
- /* Parse the blinded coin envelopes */
- {
- json_t *j_cev;
- size_t idx;
-
- wc->request.planchets =
- GNUNET_new_array (wc->request.num_planchets,
- struct TALER_BlindedPlanchet);
- json_array_foreach (j_coin_evs, idx, j_cev)
- {
- /* Now parse the individual envelopes and calculate the hash of
- * the commitment along the way. */
- struct GNUNET_JSON_Specification kspec[] = {
- TALER_JSON_spec_blinded_planchet (NULL,
- &wc->request.planchets[idx]),
- GNUNET_JSON_spec_end ()
- };
-
- res = TALER_MHD_parse_json_data (wc->rc->connection,
- j_cev,
- kspec);
- if (GNUNET_OK != res)
- {
- GNUNET_break_op (0);
- wc->phase = (GNUNET_SYSERR == res)
- ? WITHDRAW_PHASE_RETURN_NO
- : WITHDRAW_PHASE_RETURN_YES;
- return;
- }
-
- /* Check for duplicate planchets. Technically a bug on
- * the client side that is harmless for us, but still
- * not allowed per protocol */
- for (size_t i = 0; i < idx; i++)
- {
- if (0 ==
- TALER_blinded_planchet_cmp (
- &wc->request.planchets[idx],
- &wc->request.planchets[i]))
- {
- GNUNET_break_op (0);
- SET_ERROR (wc,
- WITHDRAW_ERROR_IDEMPOTENT_PLANCHET);
- return;
- }
- } /* end duplicate check */
- } /* json_array_foreach over j_coin_evs */
- } /* scope of j_kappa_planchets, idx */
- wc->phase = WITHDRAW_PHASE_CHECK_KEYS;
-}
-
-
-MHD_RESULT
-TEH_handler_withdraw (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0])
-{
- struct WithdrawContext *wc = rc->rh_ctx;
-
- (void) args;
- if (NULL == wc)
- {
- wc = GNUNET_new (struct WithdrawContext);
- rc->rh_ctx = wc;
- rc->rh_cleaner = &clean_withdraw_rc;
- wc->rc = rc;
- wc->now = GNUNET_TIME_timestamp_get ();
- }
- while (true)
- {
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "withdrawal%s processing in phase %d\n",
- wc->request.withdraw.age_proof_required
- ? " (with required age proof)"
- : "",
- wc->phase);
- switch (wc->phase)
- {
- case WITHDRAW_PHASE_PARSE:
- withdraw_phase_parse (wc,
- root);
- break;
- case WITHDRAW_PHASE_CHECK_KEYS:
- phase_check_keys (wc);
- break;
- case WITHDRAW_PHASE_CHECK_RESERVE_SIGNATURE:
- phase_check_reserve_signature (wc);
- break;
- case WITHDRAW_PHASE_RUN_LEGI_CHECK:
- phase_run_legi_check (wc);
- break;
- case WITHDRAW_PHASE_SUSPENDED:
- return MHD_YES;
- case WITHDRAW_PHASE_CHECK_KYC_RESULT:
- phase_check_kyc_result (wc);
- break;
- case WITHDRAW_PHASE_PREPARE_TRANSACTION:
- phase_prepare_transaction (wc);
- break;
- case WITHDRAW_PHASE_RUN_TRANSACTION:
- phase_run_transaction (wc);
- break;
- case WITHDRAW_PHASE_GENERATE_REPLY_SUCCESS:
- phase_generate_reply_success (wc);
- break;
- case WITHDRAW_PHASE_GENERATE_REPLY_ERROR:
- phase_generate_reply_error (wc);
- break;
- case WITHDRAW_PHASE_RETURN_YES:
- return MHD_YES;
- case WITHDRAW_PHASE_RETURN_NO:
- return MHD_NO;
- }
- }
-}
diff --git a/src/exchange/taler-exchange-httpd_withdraw.h b/src/exchange/taler-exchange-httpd_withdraw.h
@@ -1,61 +0,0 @@
-/*
- This file is part of TALER
- Copyright (C) 2025 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_withdraw.h
- * @brief Handle /withdraw requests
- * @note This endpoint was introduced in v26 of the protocol.
- * @author Özgür Kesim
- */
-#ifndef TALER_EXCHANGE_HTTPD_WITHDRAW_H
-#define TALER_EXCHANGE_HTTPD_WITHDRAW_H
-
-#include <microhttpd.h>
-#include "taler-exchange-httpd.h"
-
-/**
- * Resume suspended connections, we are shutting down.
- */
-void
-TEH_withdraw_cleanup (void);
-
-/**
- * @brief Handle a "/withdraw" request.
- * @note This endpoint was introduced in v24 of the protocol.
- *
- * Parses the batch of requested "denom_pub" which specifies
- * the key/value of the respective coin to be withdrawn,
- * and checks the signature "reserve_sig" for given "reserve_pub"
- * makes this a valid withdrawal request from the specific reserve.
- * If the "max_age" value is set in the request,
- * it is considered a commitment to withdraw age restricted coins.
- * If the request is valid, the response contains a noreveal_index
- * which the client has to use for the subsequent call to /reveal-withdraw.
- * If "max_age" value is not set, and the request is valid, the envelopes
- * with the blinded coins "blinded_coin_evs" is processed
- * and the client receives the blinded signatures as response.
- *
- * @param rc request context
- * @param root uploaded JSON data
- * @param args array of additional options, not used.
- * @return MHD result code
- */
-MHD_RESULT
-TEH_handler_withdraw (
- struct TEH_RequestContext *rc,
- const json_t *root,
- const char *const args[0]);
-
-#endif
diff --git a/src/include/taler/taler_exchangedb_plugin.h b/src/include/taler/taler_exchangedb_plugin.h
@@ -1001,7 +1001,7 @@ struct TALER_EXCHANGEDB_Reserve
/**
* Meta data about a denomination public key.
* If this is changed, you must also adjust
- * taler-exchange-httpd_management_post_keys.c::denomination_meta_cmp().
+ * taler-exchange-httpd-post-management-keys.c::denomination_meta_cmp().
*/
struct TALER_EXCHANGEDB_DenominationKeyMetaData
{