/* This file is part of TALER (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 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 backend/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_reserves.h" #include /** * 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; /** * URI of the exchange where the payment needs to be made to. */ char *payto_uri; /** * Handle for contacting the exchange. */ struct TMH_EXCHANGES_FindOperation *fo; /** * Initial balance of the reserve. */ struct TALER_Amount initial_balance; /** * When will the reserve expire. */ struct GNUNET_TIME_Absolute reserve_expiration; /** * Which HTTP status should we return? */ unsigned int http_status; /** * Which error code should we return? */ enum TALER_ErrorCode ec; /** * Are we suspended? */ bool suspended; }; /** * 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 () { for (struct PostReserveContext *rc = rc_head; NULL != rc; rc = rc->next) { if (rc->suspended) { rc->suspended = false; MHD_resume_connection (rc->connection); GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); } if (NULL != rc->fo) { TMH_EXCHANGES_find_exchange_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_find_exchange_cancel (rc->fo); rc->fo = NULL; } GNUNET_assert (! rc->suspended); GNUNET_free (rc->payto_uri); GNUNET_free (rc); } /** * Function called with the result of a #TMH_EXCHANGES_find_exchange() * operation. * * @param cls closure with our `struct PostReserveContext *` * @param hr HTTP response details * @param payto_uri URI of the exchange for the wire transfer, NULL on errors * @param eh handle to the exchange context * @param wire_fee current applicable wire fee for dealing with @a eh, NULL if not available * @param exchange_trusted true if this exchange is trusted by config */ static void handle_exchange (void *cls, const struct TALER_EXCHANGE_HttpResponse *hr, struct TALER_EXCHANGE_Handle *eh, const char *payto_uri, const struct TALER_Amount *wire_fee, bool exchange_trusted) { struct PostReserveContext *rc = cls; const struct TALER_EXCHANGE_Keys *keys; rc->fo = NULL; rc->suspended = false; MHD_resume_connection (rc->connection); GNUNET_CONTAINER_DLL_remove (rc_head, rc_tail, rc); if (NULL == hr) { rc->ec = TALER_EC_TIMEOUT; rc->http_status = MHD_HTTP_REQUEST_TIMEOUT; TMH_trigger_daemon (); /* we resumed, kick MHD */ return; } keys = TALER_EXCHANGE_get_keys (eh); if (NULL == keys) { rc->ec = TALER_EC_KEYS_INVALID; rc->http_status = MHD_HTTP_FAILED_DEPENDENCY; TMH_trigger_daemon (); /* we resumed, kick MHD */ return; } if (NULL == payto_uri) { rc->ec = TALER_EC_RESERVES_POST_UNSUPPORTED_WIRE_METHOD; rc->http_status = MHD_HTTP_CONFLICT; TMH_trigger_daemon (); /* we resumed, kick MHD */ return; } rc->reserve_expiration = GNUNET_TIME_relative_to_absolute (keys->reserve_closing_delay); rc->payto_uri = GNUNET_strdup (payto_uri); TMH_trigger_daemon (); /* we resumed, kick MHD */ } /** * Generate a reserve, given its keys and balance. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] hc context with further information about the request * @return MHD result code */ 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) { const char *wire_method; rc = GNUNET_new (struct PostReserveContext); rc->connection = connection; rc->hc = hc; hc->ctx = rc; hc->cc = &reserve_context_cleanup; { enum GNUNET_GenericReturnValue res; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_string ("exchange_url", &rc->exchange_url), GNUNET_JSON_spec_string ("wire_method", &wire_method), TALER_JSON_spec_amount ("initial_balance", &rc->initial_balance), GNUNET_JSON_spec_end () }; 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_find_exchange (rc->exchange_url, wire_method, GNUNET_NO, &handle_exchange, rc); rc->suspended = true; GNUNET_CONTAINER_DLL_insert (rc_head, rc_tail, rc); MHD_suspend_connection (connection); return MHD_YES; } GNUNET_assert (! rc->suspended); if (NULL == rc->payto_uri) { return TALER_MHD_reply_with_error (connection, rc->http_status, rc->ec, "Exchange does not support wire method"); } { 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->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_RESERVES_POST_DB_COMMIT_HARD_ERROR, "Failed to commit transaction"); return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, "{s:o,s:s}", "reserve_pub", GNUNET_JSON_from_data_auto (&reserve_pub), "payto_uri", rc->payto_uri); } } /* end of taler-merchant-httpd_private-post-reserves.c */