/*
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 */