/* This file is part of TALER (C) 2014, 2015, 2016 INRIA 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, If not, see */ /** * @file backend/taler-merchant-httpd_track-deposit.c * @brief implement API for tracking deposits and wire transfers * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler-merchant-httpd.h" #include "taler-merchant-httpd_mhd.h" #include "taler-merchant-httpd_parsing.h" #include "taler-merchant-httpd_auditors.h" #include "taler-merchant-httpd_exchanges.h" #include "taler-merchant-httpd_responses.h" #include "taler-merchant-httpd_track_deposit.h" /** * How long to wait before giving up processing with the exchange? */ #define TRACK_TIMEOUT (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30)) extern char *TMH_merchant_currency_string; /** * Context used for handing /track/deposit requests. */ struct DepositTrackContext { /** * This MUST be first! */ struct TM_HandlerContext hc; /** * Handle to the exchange. */ struct TALER_EXCHANGE_Handle *eh; /** * Handle for the /wire/deposits request. */ struct TALER_EXCHANGE_WireDepositsHandle *wdh; /** * */ struct TALER_WireDepositDetails *details; /** * Argument for the /wire/deposits request. */ struct TALER_WireTransferIdentifierRawP wtid; /** * Length of the @e details array. */ unsigned int details_length; /** * HTTP connection we are handling. */ struct MHD_Connection *connection; /** * Response code to return. */ unsigned int response_code; /** * Error message. */ const char *error; /** * Response to return upon resume. */ struct MHD_Response *response; /** * Handle for operation to lookup /keys (and auditors) from * the exchange used for this transaction; NULL if no operation is * pending. */ struct TMH_EXCHANGES_FindOperation *fo; /** * Task run on timeout. */ struct GNUNET_SCHEDULER_Task *timeout_task; /** * URI of the exchange. */ char *uri; }; /** * Free the @a rctx. * * @param rctx data to free */ static void free_deposit_track_context (struct DepositTrackContext *rctx) { if (NULL != rctx->fo) { TMH_EXCHANGES_find_exchange_cancel (rctx->fo); rctx->fo = NULL; } if (NULL != rctx->timeout_task) { GNUNET_SCHEDULER_cancel (rctx->timeout_task); rctx->timeout_task = NULL; } GNUNET_free (rctx); } /** * Resume the given /track/deposit operation and send the given response. * Stores the response in the @a rctx and signals MHD to resume * the connection. Also ensures MHD runs immediately. * * @param rctx deposit tracking context * @param response_code response code to use * @param response response data to send back */ static void resume_track_deposit_with_response (struct DepositTrackContext *rctx, unsigned int response_code, struct MHD_Response *response) { rctx->response_code = response_code; rctx->response = response; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /track/transaction handling as exchange interaction is done (%u)\n", response_code); if (NULL != rctx->timeout_task) { GNUNET_SCHEDULER_cancel (rctx->timeout_task); rctx->timeout_task = NULL; } MHD_resume_connection (rctx->connection); TMH_trigger_daemon (); /* we resumed, kick MHD */ } /** * Custom cleanup routine for a `struct DepositTrackContext`. * * @param hc the `struct DepositTrackContext` to clean up. */ static void track_deposit_cleanup (struct TM_HandlerContext *hc) { struct DepositTrackContext *rctx = (struct DepositTrackContext *) hc; free_deposit_track_context (rctx); } /** * Function called with information about a coin that was deposited. * Verify that it matches the information claimed by the exchange. * * @param cls closure FIXME! * @param transaction_id of the contract * @param coin_pub public key of the coin * @param amount_with_fee amount the exchange will deposit for this coin * @param deposit_fee fee the exchange will charge for this coin * @param exchange_proof proof from exchange that coin was accepted */ static void check_deposit (void *cls, uint64_t transaction_id, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_Amount *amount_with_fee, const struct TALER_Amount *deposit_fee, const json_t *exchange_proof) { GNUNET_break (0); // not implemented! } /** * Function called with detailed wire transfer data, including all * of the coin transactions that were combined into the wire transfer. * * @param cls closure * @param http_status HTTP status code we got, 0 on exchange protocol violation * @param json original json reply (may include signatures, those have then been * validated already) * @param wtid extracted wire transfer identifier, or NULL if the exchange could * not provide any (set only if @a http_status is #MHD_HTTP_OK) * @param total_amount total amount of the wire transfer, or NULL if the exchange could * not provide any @a wtid (set only if @a http_status is #MHD_HTTP_OK) * @param details_length length of the @a details array * @param details array with details about the combined transactions */ static void wire_deposit_cb (void *cls, unsigned int http_status, const json_t *json, const struct GNUNET_HashCode *h_wire, const struct TALER_Amount *total_amount, unsigned int details_length, const struct TALER_WireDepositDetails *details) { struct DepositTrackContext *rctx = cls; unsigned int i; int ret; rctx->wdh = NULL; if (MHD_HTTP_OK != http_status) { resume_track_deposit_with_response (rctx, MHD_HTTP_FAILED_DEPENDENCY, TMH_RESPONSE_make_json_pack ("{s:I, s:O}", "exchange_status", (json_int_t) http_status, "details", json)); return; } if (GNUNET_OK != db->store_transfer_to_proof (db->cls, rctx->uri, &rctx->wtid, json)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist wire transfer proof in DB\n"); } rctx->details_length = details_length; rctx->details = GNUNET_new_array (details_length, struct TALER_WireDepositDetails); memcpy (rctx->details, details, details_length * sizeof (struct TALER_WireDepositDetails)); for (i=0;ifind_payments_by_id (rctx, details[i].transaction_id, &check_deposit, rctx); if (GNUNET_SYSERR == ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to verify existing payment data in DB\n"); } if (GNUNET_NO == ret) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to find payment data in DB\n"); } /* FIXME: check result of "check_deposit"! */ ret = db->store_coin_to_transfer (db->cls, details[i].transaction_id, &details[i].coin_pub, &rctx->wtid); if (GNUNET_OK != ret) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to persist coin to wire transfer mapping in DB\n"); } } /* FIXME: might want a more custom response here... */ resume_track_deposit_with_response (rctx, MHD_HTTP_OK, TMH_RESPONSE_make_json_pack ("{s:I, s:O}", "exchange_status", (json_int_t) http_status, "details", json)); } /** * Function called with the result of our exchange lookup. * * @param cls the `struct DepositTrackContext` * @param eh NULL if exchange was not found to be acceptable * @param exchange_trusted #GNUNET_YES if this exchange is trusted by config */ static void process_track_deposit_with_exchange (void *cls, struct TALER_EXCHANGE_Handle *eh, int exchange_trusted) { struct DepositTrackContext *rctx = cls; rctx->fo = NULL; rctx->eh = eh; rctx->wdh = TALER_EXCHANGE_wire_deposits (eh, &rctx->wtid, &wire_deposit_cb, rctx); } /** * Handle a timeout for the processing of the track deposit request. * * @param cls closure */ static void handle_track_deposit_timeout (void *cls) { struct DepositTrackContext *rctx = cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Resuming /track/deposit with error after timeout\n"); rctx->timeout_task = NULL; if (NULL != rctx->fo) { TMH_EXCHANGES_find_exchange_cancel (rctx->fo); rctx->fo = NULL; } resume_track_deposit_with_response (rctx, MHD_HTTP_SERVICE_UNAVAILABLE, TMH_RESPONSE_make_internal_error ("exchange not reachable")); } /** * Function called with information about a wire transfer identifier. * Generate a response based on the given @a proof. * * @param cls closure * @param proof proof from exchange about what the wire transfer was for */ static void proof_cb (void *cls, const json_t *proof) { struct DepositTrackContext *rctx = cls; GNUNET_break (0); // not implemented! } /** * Manages a /track/wtid call, thus it calls the /track/deposit * offered by the exchange in order to return the set of deposits * (of coins) associated with a given wire transfer. * * @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 * @return MHD result code */ int MH_handler_track_deposit (struct TMH_RequestHandler *rh, struct MHD_Connection *connection, void **connection_cls, const char *upload_data, size_t *upload_data_size) { struct DepositTrackContext *rctx; const char *str; const char *uri; int ret; if (NULL == *connection_cls) { rctx = GNUNET_new (struct DepositTrackContext); rctx->hc.cc = &track_deposit_cleanup; rctx->connection = connection; *connection_cls = rctx; } else { /* not first call, recover state */ rctx = *connection_cls; } if (0 != rctx->response_code) { /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == rctx->response_code) { GNUNET_break (0); return MHD_NO; /* hard error */ } ret = MHD_queue_response (connection, rctx->response_code, rctx->response); if (NULL != rctx->response) { MHD_destroy_response (rctx->response); rctx->response = NULL; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Queueing response (%u) for /track/deposit (%s).\n", (unsigned int) rctx->response_code, ret ? "OK" : "FAILED"); return ret; } uri = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "exchange"); if (NULL == uri) return TMH_RESPONSE_reply_external_error (connection, "exchange argument missing"); rctx->uri = GNUNET_strdup (uri); str = MHD_lookup_connection_value (connection, MHD_GET_ARGUMENT_KIND, "wtid"); if (NULL == str) return TMH_RESPONSE_reply_external_error (connection, "wtid argument missing"); if (GNUNET_OK != GNUNET_STRINGS_string_to_data (str, strlen (str), &rctx->wtid, sizeof (rctx->wtid))) { return TMH_RESPONSE_reply_external_error (connection, "wtid argument malformed"); } /* FIXME: check if reply is already in database! */ ret = db->find_proof_by_wtid (db->cls, rctx->uri, &rctx->wtid, &proof_cb, rctx); if (0 != rctx->response_code) { ret = MHD_queue_response (connection, rctx->response_code, rctx->response); if (NULL != rctx->response) { MHD_destroy_response (rctx->response); rctx->response = NULL; } return ret; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Suspending /track/deposit handling while working with the exchange\n"); MHD_suspend_connection (connection); rctx->fo = TMH_EXCHANGES_find_exchange (uri, &process_track_deposit_with_exchange, rctx); rctx->timeout_task = GNUNET_SCHEDULER_add_delayed (TRACK_TIMEOUT, &handle_track_deposit_timeout, rctx); return MHD_NO; } /* end of taler-merchant-httpd_track-deposit.c */