summaryrefslogtreecommitdiff
path: root/src/backend/taler-merchant-httpd_post-tips-tip-pickup.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/taler-merchant-httpd_post-tips-tip-pickup.c')
-rw-r--r--src/backend/taler-merchant-httpd_post-tips-tip-pickup.c771
1 files changed, 0 insertions, 771 deletions
diff --git a/src/backend/taler-merchant-httpd_post-tips-tip-pickup.c b/src/backend/taler-merchant-httpd_post-tips-tip-pickup.c
deleted file mode 100644
index 51dd8121..00000000
--- a/src/backend/taler-merchant-httpd_post-tips-tip-pickup.c
+++ /dev/null
@@ -1,771 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017-2020 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 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 <http://www.gnu.org/licenses/>
-*/
-/**
- * @file backend/taler-merchant-httpd_tip-pickup.c
- * @brief implementation of /tip-pickup handler
- * @author Christian Grothoff
- */
-#include "platform.h"
-#include <microhttpd.h>
-#include <jansson.h>
-#include <taler/taler_json_lib.h>
-#include <taler/taler_signatures.h>
-#include "taler-merchant-httpd.h"
-#include "taler-merchant-httpd_mhd.h"
-#include "taler-merchant-httpd_exchanges.h"
-#include "taler-merchant-httpd_tip-pickup.h"
-
-
-/**
- * Information we keep per tip pickup request.
- */
-struct PickupContext;
-
-
-/**
- * Details about a planchet that the customer wants to obtain
- * a withdrawal authorization. This is the information that
- * will need to be sent to the exchange to obtain the blind
- * signature required to turn a planchet into a coin.
- */
-struct PlanchetDetail
-{
-
- /**
- * Hash of the denomination public key requested for this planchet.
- */
- struct GNUNET_HashCode h_denom_pub;
-
- /**
- * Pickup context this planchet belongs to.
- */
- struct PickupContext *pc;
-
- /**
- * Handle to withdraw operation with the exchange.
- */
- struct TALER_EXCHANGE_Withdraw2Handle *wh;
-
- /**
- * Blind signature to return, or NULL if not available.
- */
- json_t *blind_sig;
-
- /**
- * Blinded coin (see GNUNET_CRYPTO_rsa_blind()). Note: is malloc()'ed!
- */
- char *coin_ev;
-
- /**
- * Number of bytes in @a coin_ev.
- */
- size_t coin_ev_size;
-
-};
-
-
-/**
- * Information we keep per tip pickup request.
- */
-struct PickupContext
-{
-
- /**
- * This field MUST be first for handle_mhd_completion_callback() to work
- * when it treats this struct as a `struct TM_HandlerContext`.
- */
- struct TM_HandlerContext hc;
-
- /**
- * Placeholder for #TALER_MHD_parse_post_json() to keep its internal state.
- */
- void *json_parse_context;
-
- /**
- * Kept in a DLL while suspended.
- */
- struct PickupContext *next;
-
- /**
- * Kept in a DLL while suspended.
- */
- struct PickupContext *prev;
-
- /**
- * URL of the exchange this tip uses.
- */
- char *exchange_url;
-
- /**
- * Operation we run to find the exchange (and get its /keys).
- */
- struct TMH_EXCHANGES_FindOperation *fo;
-
- /**
- * Handle to the exchange (set after exchange_found_cb()).
- */
- struct TALER_EXCHANGE_Handle *eh;
-
- /**
- * Array of planchets of length @e planchets_len.
- */
- struct PlanchetDetail *planchets;
-
- /**
- * The connection we are processing.
- */
- struct MHD_Connection *connection;
-
- /**
- * Tip ID that was supplied by the client.
- */
- struct GNUNET_HashCode tip_id;
-
- /**
- * Unique identifier for the pickup operation, used to detect
- * duplicate requests (retries).
- */
- struct GNUNET_HashCode pickup_id;
-
- /**
- * Total value of the coins we are withdrawing.
- */
- struct TALER_Amount total;
-
- /**
- * Length of @e planchets.
- */
- unsigned int planchets_len;
-
- /**
- * Message to return.
- */
- struct MHD_Response *response;
-
- /**
- * HTTP status code to return in combination with @e response.
- */
- unsigned int response_code;
-
- /**
- * Set to #GNUNET_YES if @e connection was suspended.
- */
- int suspended;
-
-};
-
-
-/**
- * Kept in a DLL while suspended.
- */
-static struct PickupContext *pc_head;
-
-/**
- * Kept in a DLL while suspended.
- */
-static struct PickupContext *pc_tail;
-
-
-/**
- * We are shutting down, force resuming all suspended pickup operations.
- */
-void
-MH_force_tip_pickup_resume ()
-{
- struct PickupContext *pc;
-
- while (NULL != (pc = pc_head))
- {
- GNUNET_assert (GNUNET_YES == pc->suspended);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- }
-}
-
-
-/**
- * Custom cleanup routine for a `struct PickupContext`.
- *
- * @param hc the `struct PickupContext` to clean up.
- */
-static void
-pickup_cleanup (struct TM_HandlerContext *hc)
-{
- struct PickupContext *pc = (struct PickupContext *) hc;
-
- if (NULL != pc->planchets)
- {
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- struct PlanchetDetail *pd = &pc->planchets[i];
- GNUNET_free_non_null (pd->coin_ev);
- if (NULL != pd->wh)
- {
- TALER_EXCHANGE_withdraw2_cancel (pd->wh);
- pd->wh = NULL;
- }
- if (NULL != pd->blind_sig)
- {
- json_decref (pd->blind_sig);
- pd->blind_sig = NULL;
- }
- }
- GNUNET_free (pc->planchets);
- pc->planchets = NULL;
- }
- if (NULL != pc->fo)
- {
- TMH_EXCHANGES_find_exchange_cancel (pc->fo);
- pc->fo = NULL;
- }
- TALER_MHD_parse_post_cleanup_callback (pc->json_parse_context);
- GNUNET_free_non_null (pc->exchange_url);
- if (NULL != pc->response)
- {
- MHD_destroy_response (pc->response);
- pc->response = NULL;
- }
- GNUNET_free (pc);
-}
-
-
-/**
- * Resume processing of a suspended @a pc.
- *
- * @param pc a suspended pickup operation
- */
-static void
-resume_pc (struct PickupContext *pc)
-{
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- struct PlanchetDetail *pd = &pc->planchets[i];
-
- if (NULL != pd->wh)
- {
- TALER_EXCHANGE_withdraw2_cancel (pc->planchets[i].wh);
- pc->planchets[i].wh = NULL;
- }
- }
- GNUNET_assert (GNUNET_YES == pc->suspended);
- GNUNET_CONTAINER_DLL_remove (pc_head,
- pc_tail,
- pc);
- MHD_resume_connection (pc->connection);
- TMH_trigger_daemon ();
-}
-
-
-/**
- * Function called with the result of our attempt to withdraw
- * the coin for a tip.
- *
- * @param cls closure
- * @param hr HTTP response data
- * @param blind_sig blind signature over the coin, NULL on error
- */
-static void
-withdraw_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- const struct GNUNET_CRYPTO_RsaSignature *blind_sig)
-{
- struct PlanchetDetail *pd = cls;
- struct PickupContext *pc = pd->pc;
- json_t *ja;
-
- pd->wh = NULL;
- if (NULL == blind_sig)
- {
- pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
- pc->response = TALER_MHD_make_json_pack (
- (NULL != hr->reply)
- ? "{s:s, s:I, s:I, s:I, s:O}"
- : "{s:s, s:I, s:I, s:I}",
- "hint", "failed to withdraw coin from exchange",
- "code", (json_int_t) TALER_EC_TIP_PICKUP_WITHDRAW_FAILED_AT_EXCHANGE,
- "exchange_http_status", (json_int_t) hr->http_status,
- "exchange_code", (json_int_t) hr->ec,
- "exchange_reply", hr->reply);
- resume_pc (pc);
- return;
- }
- /* FIXME: persisit blind_sig in our database!?
- (or at least _all_ of them once we have them all?) */
- pd->blind_sig = GNUNET_JSON_from_rsa_signature (blind_sig);
- GNUNET_assert (NULL != pd->blind_sig);
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- if (NULL != pc->planchets[i].wh)
- return;
- /* All done, build final response */
- ja = json_array ();
- GNUNET_assert (NULL != ja);
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- struct PlanchetDetail *pd = &pc->planchets[i];
-
- GNUNET_assert (0 ==
- json_array_append_new (ja,
- json_pack ("{s:o}",
- "blind_sig",
- pd->blind_sig)));
- pd->blind_sig = NULL;
- }
- pc->response_code = MHD_HTTP_OK;
- pc->response = TALER_MHD_make_json_pack ("{s:o}",
- "blind_sigs",
- ja);
- resume_pc (pc);
-}
-
-
-/**
- * Prepare (and eventually execute) a pickup. Computes
- * the "pickup ID" (by hashing the planchets and denomination keys),
- * resolves the denomination keys and calculates the total
- * amount to be picked up. Then runs the pick up execution logic.
- *
- * @param pc pickup context
- */
-static void
-run_pickup (struct PickupContext *pc)
-{
- struct TALER_ReservePrivateKeyP reserve_priv;
- enum TALER_ErrorCode ec;
-
- db->preflight (db->cls);
- ec = db->pickup_tip_TR (db->cls,
- &pc->total,
- &pc->tip_id,
- &pc->pickup_id,
- &reserve_priv);
- if (TALER_EC_NONE != ec)
- {
- const char *human;
-
- switch (ec)
- {
- case TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN:
- pc->response_code = MHD_HTTP_NOT_FOUND;
- human = "tip identifier not known to this service";
- break;
- case TALER_EC_TIP_PICKUP_NO_FUNDS:
- pc->response_code = MHD_HTTP_CONFLICT;
- human = "withdrawn funds exceed amounts approved for tip";
- break;
- default:
- pc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- human = "database failure";
- break;
- }
- pc->response = TALER_MHD_make_error (ec,
- human);
- resume_pc (pc);
- return;
- }
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- struct PlanchetDetail *pd = &pc->planchets[i];
- struct TALER_PlanchetDetail pdx = {
- .denom_pub_hash = pd->h_denom_pub,
- .coin_ev = pd->coin_ev,
- .coin_ev_size = pd->coin_ev_size,
- };
-
- pd->wh = TALER_EXCHANGE_withdraw2 (pc->eh,
- &pdx,
- &reserve_priv,
- &withdraw_cb,
- pd);
- if (NULL == pd->wh)
- {
- GNUNET_break (0);
- pc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- pc->response = TALER_MHD_make_error (TALER_EC_TIP_PICKUP_WITHDRAW_FAILED,
- "could not inititate withdrawal");
- resume_pc (pc);
- return;
- }
- }
-}
-
-
-/**
- * Function called with the result of a #TMH_EXCHANGES_find_exchange()
- * operation.
- *
- * @param cls closure with the `struct PickupContext`
- * @param hr HTTP response details
- * @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 #GNUNET_YES if this exchange is trusted by config
- */
-static void
-exchange_found_cb (void *cls,
- const struct TALER_EXCHANGE_HttpResponse *hr,
- struct TALER_EXCHANGE_Handle *eh,
- const struct TALER_Amount *wire_fee,
- int exchange_trusted)
-{
- struct PickupContext *pc = cls;
- const struct TALER_EXCHANGE_Keys *keys;
- struct TALER_Amount total;
- int ae;
-
- pc->fo = NULL;
- if (NULL == eh)
- {
- pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
- pc->response = TALER_MHD_make_json_pack (
- (NULL != hr->reply)
- ? "{s:s, s:I, s:I, s:I, s:O}"
- : "{s:s, s:I, s:I, s:I}",
- "hint", "failed to contact exchange, check URL",
- "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_DOWN,
- "exchange_http_status", (json_int_t) hr->http_status,
- "exchange_code", (json_int_t) hr->ec,
- "exchange_reply", hr->reply);
- resume_pc (pc);
- return;
- }
- keys = TALER_EXCHANGE_get_keys (eh);
- if (NULL == keys)
- {
- pc->response_code = MHD_HTTP_FAILED_DEPENDENCY;
- pc->response = TALER_MHD_make_json_pack (
- (NULL != hr->reply)
- ? "{s:s, s:I, s:I, s:I, s:O}"
- : "{s:s, s:I, s:I, s:I}",
- "hint", "could not obtain denomination keys from exchange, check URL",
- "code", (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEYS,
- "exchange_http_status", (json_int_t) hr->http_status,
- "exchange_code", (json_int_t) hr->ec,
- "exchange_reply", hr->reply);
- resume_pc (pc);
- return;
- }
- GNUNET_assert (0 != pc->planchets_len);
- ae = GNUNET_NO;
- memset (&total,
- 0,
- sizeof (total));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Calculating tip amount over %u planchets!\n",
- pc->planchets_len);
- {
- struct GNUNET_HashContext *hc;
-
- hc = GNUNET_CRYPTO_hash_context_start ();
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- struct PlanchetDetail *pd = &pc->planchets[i];
- struct TALER_Amount amount_with_fee;
- const struct TALER_EXCHANGE_DenomPublicKey *dk;
-
- dk = TALER_EXCHANGE_get_denomination_key_by_hash (keys,
- &pd->h_denom_pub);
- if (NULL == dk)
- {
- pc->response_code = MHD_HTTP_NOT_FOUND;
- pc->response
- = TALER_MHD_make_json_pack (
- "{s:s, s:I}"
- "hint",
- "could not find matching denomination key",
- "code",
- (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_LACKED_KEY);
- resume_pc (pc);
- GNUNET_CRYPTO_hash_context_abort (hc);
- return;
- }
- GNUNET_CRYPTO_hash_context_read (hc,
- &pd->h_denom_pub,
- sizeof (struct GNUNET_HashCode));
- GNUNET_CRYPTO_hash_context_read (hc,
- pd->coin_ev,
- pd->coin_ev_size);
- if (0 >
- TALER_amount_add (&amount_with_fee,
- &dk->value,
- &dk->fee_withdraw))
- {
- ae = GNUNET_YES;
- }
- if (0 == i)
- {
- total = amount_with_fee;
- }
- else
- {
- if (0 >
- TALER_amount_add (&total,
- &total,
- &amount_with_fee))
- {
- ae = GNUNET_YES;
- }
- }
- }
- GNUNET_CRYPTO_hash_context_finish (hc,
- &pc->pickup_id);
- }
- if (GNUNET_YES == ae)
- {
- pc->response_code = MHD_HTTP_BAD_REQUEST;
- pc->response
- = TALER_MHD_make_json_pack (
- "{s:s, s:I}"
- "hint",
- "error computing total value of the tip",
- "code",
- (json_int_t) TALER_EC_TIP_PICKUP_EXCHANGE_AMOUNT_OVERFLOW);
- resume_pc (pc);
- return;
- }
- pc->eh = eh;
- pc->total = total;
- run_pickup (pc);
-}
-
-
-/**
- * Prepare (and eventually execute) a pickup. Finds the exchange
- * handle we need for #run_pickup().
- *
- * @param pc pickup context
- * @return #MHD_YES upon success, #MHD_NO if
- * the connection ought to be dropped
- */
-static MHD_RESULT
-prepare_pickup (struct PickupContext *pc)
-{
- enum GNUNET_DB_QueryStatus qs;
-
- db->preflight (db->cls);
- /* FIXME: do not pass NULL's, *do* get the
- data from the DB, we may be able to avoid
- most of the processing if we already have
- a valid result! */
- qs = db->lookup_tip_by_id (db->cls,
- &pc->tip_id,
- &pc->exchange_url,
- NULL, NULL, NULL, NULL);
- if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
- {
- unsigned int response_code;
- enum TALER_ErrorCode ec;
-
- switch (qs)
- {
- case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
- ec = TALER_EC_TIP_PICKUP_TIP_ID_UNKNOWN;
- response_code = MHD_HTTP_NOT_FOUND;
- break;
- case GNUNET_DB_STATUS_SOFT_ERROR:
- ec = TALER_EC_TIP_PICKUP_DB_ERROR_SOFT;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- case GNUNET_DB_STATUS_HARD_ERROR:
- ec = TALER_EC_TIP_PICKUP_DB_ERROR_HARD;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- default:
- GNUNET_break (0);
- ec = TALER_EC_INTERNAL_LOGIC_ERROR;
- response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
- break;
- }
- return TALER_MHD_reply_with_error (pc->connection,
- response_code,
- ec,
- "Could not determine exchange URL for the given tip id");
-
- }
- pc->fo = TMH_EXCHANGES_find_exchange (pc->exchange_url,
- NULL,
- GNUNET_NO,
- &exchange_found_cb,
- pc);
- if (NULL == pc->fo)
- {
- return TALER_MHD_reply_with_error (pc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_INTERNAL_INVARIANT_FAILURE,
- "consult server logs");
- }
- /* continued asynchronously in exchange_found_cb() */
- GNUNET_assert (GNUNET_NO == pc->suspended);
- pc->suspended = GNUNET_YES;
- GNUNET_CONTAINER_DLL_insert (pc_head,
- pc_tail,
- pc);
- MHD_suspend_connection (pc->connection);
- return MHD_YES;
-}
-
-
-/**
- * Parse the given @a planchet into the @a pd.
- *
- * @param connection connection to use for error reporting
- * @param planchet planchet data in JSON format
- * @param[out] pd where to store the binary data
- * @return #GNUNET_OK upon success, #GNUNET_NO if a response
- * was generated, #GNUNET_SYSERR to drop the connection
- */
-static enum GNUNET_GenericReturnValue
-parse_planchet (struct MHD_Connection *connection,
- const json_t *planchet,
- struct PlanchetDetail *pd)
-{
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
- &pd->h_denom_pub),
- GNUNET_JSON_spec_varsize ("coin_ev",
- (void **) &pd->coin_ev,
- &pd->coin_ev_size),
- GNUNET_JSON_spec_end ()
- };
-
- return TALER_MHD_parse_json_data (connection,
- planchet,
- spec);
-}
-
-
-/**
- * Manages a POST /tip-pickup call, checking that the tip is authorized,
- * and if so, returning the withdrawal permissions.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @param[in,out] connection_cls the connection's closure (can be updated)
- * @param upload_data upload data
- * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
- * @param mi merchant backend instance, never NULL
- * @return MHD result code
- */
-MHD_RESULT
-MH_handler_tip_pickup (struct TMH_RequestHandler *rh,
- struct MHD_Connection *connection,
- void **connection_cls,
- const char *upload_data,
- size_t *upload_data_size,
- struct MerchantInstance *mi)
-{
- enum GNUNET_GenericReturnValue res;
- struct GNUNET_HashCode tip_id;
- json_t *planchets;
- struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("tip_id",
- &tip_id),
- GNUNET_JSON_spec_json ("planchets",
- &planchets),
- GNUNET_JSON_spec_end ()
- };
- struct PickupContext *pc;
- json_t *root;
-
- if (NULL == *connection_cls)
- {
- pc = GNUNET_new (struct PickupContext);
- pc->hc.cc = &pickup_cleanup;
- pc->connection = connection;
- *connection_cls = pc;
- }
- else
- {
- pc = *connection_cls;
- }
- if (NULL != pc->response)
- {
- MHD_RESULT ret;
-
- ret = MHD_queue_response (connection,
- pc->response_code,
- pc->response);
- MHD_destroy_response (pc->response);
- pc->response = NULL;
- return ret;
- }
- res = TALER_MHD_parse_post_json (connection,
- &pc->json_parse_context,
- upload_data,
- upload_data_size,
- &root);
- if (GNUNET_SYSERR == res)
- return MHD_NO;
- /* the POST's body has to be further fetched */
- if ( (GNUNET_NO == res) ||
- (NULL == root) )
- return MHD_YES;
-
- res = TALER_MHD_parse_json_data (connection,
- root,
- spec);
- if (GNUNET_YES != res)
- {
- GNUNET_break_op (0);
- json_decref (root);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- pc->planchets_len = json_array_size (planchets);
- if (pc->planchets_len > 1024)
- {
- GNUNET_JSON_parse_free (spec);
- json_decref (root);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_TIP_PICKUP_EXCHANGE_TOO_MANY_PLANCHETS,
- "per request limit of 1024 planchets exceeded");
- }
- if (0 == pc->planchets_len)
- {
- GNUNET_JSON_parse_free (spec);
- json_decref (root);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_PARAMETER_MALFORMED,
- "no planchets specified");
- }
- db->preflight (db->cls);
- pc->planchets = GNUNET_new_array (pc->planchets_len,
- struct PlanchetDetail);
- for (unsigned int i = 0; i<pc->planchets_len; i++)
- {
- pc->planchets[i].pc = pc;
- if (GNUNET_OK !=
- (res = parse_planchet (connection,
- json_array_get (planchets,
- i),
- &pc->planchets[i])))
- {
- GNUNET_JSON_parse_free (spec);
- json_decref (root);
- return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
- }
- }
- pc->tip_id = tip_id;
- {
- MHD_RESULT ret;
-
- ret = prepare_pickup (pc);
- GNUNET_JSON_parse_free (spec);
- json_decref (root);
- return ret;
- }
-}