/* This file is part of TALER (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 General Public License for more details. You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, see */ /** * @file taler-merchant-httpd_private-post-reserves.c * @brief implementing POST /reserves request handling * @author Christian Grothoff */ #include "platform.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_private-post-reserves.h" #include "taler-merchant-httpd_helper.h" #include "taler-merchant-httpd_reserves.h" #include /** * How long to wait before giving up processing with the exchange? */ #define EXCHANGE_GENERIC_TIMEOUT (GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_SECONDS, \ 15)) /** * Information we keep for an individual call to the POST /reserves handler. */ struct PostReserveContext { /** * Stored in a DLL. */ struct PostReserveContext *next; /** * Stored in a DLL. */ struct PostReserveContext *prev; /** * Array with @e coins_cnt coins we are despositing. */ struct DepositConfirmation *dc; /** * MHD connection to return to */ struct MHD_Connection *connection; /** * Details about the client's request. */ struct TMH_HandlerContext *hc; /** * URL of the exchange. */ const char *exchange_url; /** * Wire method the client wants to use for the payment. */ const char *wire_method; /** * Array of accounts that could be used. */ json_t *accounts; /** * Handle for contacting the exchange for /keys. */ struct TMH_EXCHANGES_KeysOperation *fo; /** * Master public key of the exchange matching * @e exchange_url. */ struct TALER_MasterPublicKeyP master_pub; /** * Initial balance of the reserve. */ struct TALER_Amount initial_balance; /** * When will the reserve expire. */ struct GNUNET_TIME_Timestamp reserve_expiration; /** * Which HTTP status should we return? */ unsigned int http_status; /** * Which error code should we return? */ enum TALER_ErrorCode ec; /** * Did we suspend @a connection and are thus in * the #rc_head DLL (#GNUNET_YES). Set to * #GNUNET_NO if we are not suspended, and to * #GNUNET_SYSERR if we should close the connection * without a response due to shutdown. */ enum GNUNET_GenericReturnValue suspended; /** * True if we already force reloaded /keys. */ bool force_reload; }; /** * Stored in a DLL. */ static struct PostReserveContext *rc_head; /** * Stored in a DLL. */ static struct PostReserveContext *rc_tail; /** * Force all post reserve contexts to be resumed as we are about * to shut down MHD. */ void TMH_force_rc_resume () { struct PostReserveContext *rcn; for (struct PostReserveContext *rc = rc_head; NULL != rc; rc = rcn) { rcn = rc->next; if (GNUNET_YES == rc->suspended) { rc->suspended = GNUNET_SYSERR; MHD_resume_connection (rc->connection); GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); } if (NULL != rc->fo) { TMH_EXCHANGES_keys4exchange_cancel (rc->fo); rc->fo = NULL; } } } /** * Custom cleanup routine for a `struct PostReserveContext`. * * @param cls the `struct PostReserveContext` to clean up. */ static void reserve_context_cleanup (void *cls) { struct PostReserveContext *rc = cls; if (NULL != rc->fo) { TMH_EXCHANGES_keys4exchange_cancel (rc->fo); rc->fo = NULL; } GNUNET_assert (GNUNET_YES != rc->suspended); json_decref (rc->accounts); GNUNET_free (rc); } /** * Resume request handling. * * @param[in,out] rc request to resume */ static void resume_request (struct PostReserveContext *rc) { rc->suspended = GNUNET_NO; MHD_resume_connection (rc->connection); GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); TALER_MHD_daemon_trigger (); /* we resumed, kick MHD */ } /** * Function called with the result of a #TMH_EXCHANGES_keys4exchange() * operation. * * @param cls closure with our `struct PostReserveContext *` * @param keys exchange keys * @param exchange representation of the exchange */ static void handle_exchange (void *cls, struct TALER_EXCHANGE_Keys *keys, struct TMH_Exchange *exchange) { struct PostReserveContext *rc = cls; rc->fo = NULL; if (NULL == keys) { GNUNET_break_op (0); rc->ec = TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE; rc->http_status = MHD_HTTP_GATEWAY_TIMEOUT; resume_request (rc); return; } if (! keys->rewards_allowed) { if (! rc->force_reload) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Forcing %s/keys reload (rewards not allowed)\n", rc->exchange_url); rc->force_reload = true; rc->fo = TMH_EXCHANGES_keys4exchange (rc->exchange_url, true, &handle_exchange, rc); return; } rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_REWARDS_NOT_ALLOWED; rc->http_status = MHD_HTTP_CONFLICT; resume_request (rc); return; } rc->master_pub = keys->master_pub; rc->reserve_expiration = GNUNET_TIME_relative_to_timestamp (keys->reserve_closing_delay); rc->accounts = TMH_exchange_accounts_by_method ( &rc->master_pub, rc->wire_method); if ( (NULL == rc->accounts) || (0 == json_array_size (rc->accounts)) ) { if (! rc->force_reload) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Forcing %s/keys reload (no accounts)\n", rc->exchange_url); rc->force_reload = true; rc->fo = TMH_EXCHANGES_keys4exchange (rc->exchange_url, true, &handle_exchange, rc); return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Wire method `%s' not supported\n", rc->wire_method); rc->ec = TALER_EC_MERCHANT_PRIVATE_POST_RESERVES_UNSUPPORTED_WIRE_METHOD; rc->http_status = MHD_HTTP_CONFLICT; resume_request (rc); return; } resume_request (rc); } MHD_RESULT TMH_private_post_reserves (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, struct TMH_HandlerContext *hc) { struct PostReserveContext *rc = hc->ctx; struct TMH_MerchantInstance *mi = hc->instance; GNUNET_assert (NULL != mi); if (NULL == rc) { rc = GNUNET_new (struct PostReserveContext); rc->connection = connection; rc->hc = hc; hc->ctx = rc; hc->cc = &reserve_context_cleanup; { struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_web_url ("exchange_url", &rc->exchange_url), GNUNET_JSON_spec_string ("wire_method", &rc->wire_method), TALER_JSON_spec_amount_any ("initial_balance", &rc->initial_balance), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, hc->request_body, spec); if (GNUNET_OK != res) return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } rc->fo = TMH_EXCHANGES_keys4exchange (rc->exchange_url, false, &handle_exchange, rc); rc->suspended = GNUNET_YES; GNUNET_CONTAINER_DLL_insert (rc_head, rc_tail, rc); MHD_suspend_connection (connection); return MHD_YES; } if (GNUNET_SYSERR == rc->suspended) return MHD_NO; /* we are in shutdown */ if (TALER_EC_NONE != rc->ec) { return TALER_MHD_reply_with_error (connection, rc->http_status, rc->ec, NULL); } GNUNET_assert (GNUNET_NO == rc->suspended); GNUNET_assert (! GNUNET_TIME_absolute_is_zero ( rc->reserve_expiration.abs_time)); { struct TALER_ReservePublicKeyP reserve_pub; struct TALER_ReservePrivateKeyP reserve_priv; enum GNUNET_DB_QueryStatus qs; GNUNET_CRYPTO_eddsa_key_create (&reserve_priv.eddsa_priv); GNUNET_CRYPTO_eddsa_key_get_public (&reserve_priv.eddsa_priv, &reserve_pub.eddsa_pub); qs = TMH_db->insert_reserve (TMH_db->cls, mi->settings.id, &reserve_priv, &reserve_pub, &rc->master_pub, rc->exchange_url, &rc->initial_balance, rc->reserve_expiration); GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs); TMH_RESERVES_check (mi->settings.id, rc->exchange_url, &reserve_pub, &rc->initial_balance); if (qs < 0) return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_STORE_FAILED, "reserve"); return TALER_MHD_REPLY_JSON_PACK ( connection, MHD_HTTP_OK, GNUNET_JSON_pack_data_auto ("reserve_pub", &reserve_pub), GNUNET_JSON_pack_array_incref ("accounts", rc->accounts)); } } /* end of taler-merchant-httpd_private-post-reserves.c */