From cd83daaeae915e0c1b6170cb11f40aa1cfbfece4 Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 16 Mar 2020 20:22:30 +0100 Subject: simplify refund processing, add additional checks for matching currency --- src/exchange/taler-exchange-httpd_deposit.c | 14 +- src/exchange/taler-exchange-httpd_refund.c | 259 +++++++++++++-------------- src/exchange/taler-exchange-httpd_withdraw.c | 222 +++++++++++++---------- src/include/taler_error_codes.h | 24 ++- 4 files changed, 281 insertions(+), 238 deletions(-) diff --git a/src/exchange/taler-exchange-httpd_deposit.c b/src/exchange/taler-exchange-httpd_deposit.c index 4008ed271..039ca9f9a 100644 --- a/src/exchange/taler-exchange-httpd_deposit.c +++ b/src/exchange/taler-exchange-httpd_deposit.c @@ -417,8 +417,8 @@ TEH_handler_deposit (struct MHD_Connection *connection, &hc); if (NULL == dki) { - TEH_KS_release (key_state); TALER_LOG_WARNING ("Unknown denomination key in /deposit request\n"); + TEH_KS_release (key_state); GNUNET_JSON_parse_free (spec); return TALER_MHD_reply_with_error (connection, hc, @@ -427,6 +427,18 @@ TEH_handler_deposit (struct MHD_Connection *connection, } TALER_amount_ntoh (&deposit.deposit_fee, &dki->issue.properties.fee_deposit); + if (GNUNET_YES != + TALER_amount_cmp_currency (&deposit.amount_with_fee, + &deposit.deposit_fee) ) + { + GNUNET_break_op (0); + TEH_KS_release (key_state); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_DEPOSIT_CURRENCY_MISSMATCH, + "contribution"); + } /* check coin signature */ if (GNUNET_YES != TALER_test_coin_valid (&deposit.coin, diff --git a/src/exchange/taler-exchange-httpd_refund.c b/src/exchange/taler-exchange-httpd_refund.c index d564a3619..e590703af 100644 --- a/src/exchange/taler-exchange-httpd_refund.c +++ b/src/exchange/taler-exchange-httpd_refund.c @@ -15,7 +15,7 @@ */ /** * @file taler-exchange-httpd_refund.c - * @brief Handle /refund requests; parses the POST and JSON and + * @brief Handle refund requests; parses the POST and JSON and * verifies the coin signature before handing things off * to the database. * @author Florian Dold @@ -48,16 +48,17 @@ reply_refund_success (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const struct TALER_EXCHANGEDB_RefundListEntry *refund) { - struct TALER_RefundConfirmationPS rc; struct TALER_ExchangePublicKeyP pub; struct TALER_ExchangeSignatureP sig; + struct TALER_RefundConfirmationPS rc = { + .purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND), + .purpose.size = htonl (sizeof (rc)), + .h_contract_terms = refund->h_contract_terms, + .coin_pub = *coin_pub, + .merchant = refund->merchant_pub, + .rtransaction_id = GNUNET_htonll (refund->rtransaction_id) + }; - rc.purpose.purpose = htonl (TALER_SIGNATURE_EXCHANGE_CONFIRM_REFUND); - rc.purpose.size = htonl (sizeof (struct TALER_RefundConfirmationPS)); - rc.h_contract_terms = refund->h_contract_terms; - rc.coin_pub = *coin_pub; - rc.merchant = refund->merchant_pub; - rc.rtransaction_id = GNUNET_htonll (refund->rtransaction_id); TALER_amount_hton (&rc.refund_amount, &refund->refund_amount); TALER_amount_hton (&rc.refund_fee, @@ -70,7 +71,7 @@ reply_refund_success (struct MHD_Connection *connection, return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_EXCHANGE_BAD_CONFIGURATION, - "no keys"); + "no online signing key"); } return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, @@ -81,51 +82,6 @@ reply_refund_success (struct MHD_Connection *connection, } -/** - * Generate refund conflict failure message. Returns the - * transaction list @a tl with the details about the conflict. - * - * @param connection connection to the client - * @param coin_pub public key this is about - * @param tl transaction list showing the conflict - * @return MHD result code - */ -static int -reply_refund_conflict (struct MHD_Connection *connection, - const struct TALER_CoinSpendPublicKeyP *coin_pub, - const struct TALER_EXCHANGEDB_TransactionList *tl) -{ - return TALER_MHD_reply_json_pack (connection, - MHD_HTTP_CONFLICT, - "{s:s, s:I, s:o}", - "hint", "conflicting refund", - "code", - (json_int_t) TALER_EC_REFUND_CONFLICT, - "history", - TEH_RESPONSE_compile_transaction_history ( - coin_pub, - tl)); -} - - -/** - * Closure for the transaction. - */ -struct TALER_EXCHANGEDB_RefundContext -{ - /** - * Information about the refund. - */ - const struct TALER_EXCHANGEDB_Refund *refund; - - /** - * Expected refund fee by the denomination of the coin. - */ - struct TALER_Amount expect_fee; - -}; - - /** * Execute a "/refund" transaction. Returns a confirmation that the * refund was successful, or a failure if we are not aware of a @@ -150,18 +106,14 @@ refund_transaction (void *cls, struct TALER_EXCHANGEDB_Session *session, int *mhd_ret) { - struct TALER_EXCHANGEDB_RefundContext *rc = cls; - const struct TALER_EXCHANGEDB_Refund *refund = rc->refund; + const struct TALER_EXCHANGEDB_Refund *refund = cls; struct TALER_EXCHANGEDB_TransactionList *tl; const struct TALER_EXCHANGEDB_DepositListEntry *dep; const struct TALER_EXCHANGEDB_RefundListEntry *ref; enum GNUNET_DB_QueryStatus qs; int deposit_found; int refund_found; - int fee_cmp; - dep = NULL; - ref = NULL; tl = NULL; qs = TEH_plugin->get_coin_transactions (TEH_plugin->cls, session, @@ -177,6 +129,8 @@ refund_transaction (void *cls, "database transaction failure"); return qs; } + dep = NULL; + ref = NULL; deposit_found = GNUNET_NO; refund_found = GNUNET_NO; for (struct TALER_EXCHANGEDB_TransactionList *tlp = tl; @@ -267,9 +221,15 @@ refund_transaction (void *cls, /* handle if conflicting refund found */ if (GNUNET_SYSERR == refund_found) { - *mhd_ret = reply_refund_conflict (connection, - &refund->coin.coin_pub, - tl); + *mhd_ret = TALER_MHD_reply_json_pack ( + connection, + MHD_HTTP_CONFLICT, + "{s:s, s:I, s:o}", + "hint", "conflicting refund", + "code", (json_int_t) TALER_EC_REFUND_CONFLICT, + "history", TEH_RESPONSE_compile_transaction_history ( + &refund->coin.coin_pub, + tl)); TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); return GNUNET_DB_STATUS_HARD_ERROR; @@ -321,7 +281,7 @@ refund_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_REFUND_DB_INCONSISTENT, - "database inconsistent"); + "database inconsistent (deposit data became inaccessible during transaction)"); return qs; } if (GNUNET_DB_STATUS_SOFT_ERROR == qs) @@ -352,24 +312,6 @@ refund_transaction (void *cls, "refund requested exceeds original value"); return GNUNET_DB_STATUS_HARD_ERROR; } - /* Check refund fee matches fee of denomination key! */ - fee_cmp = TALER_amount_cmp (&refund->details.refund_fee, - &rc->expect_fee); - if (-1 == fee_cmp) - { - TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, - tl); - *mhd_ret = TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_TOO_LOW, - "refund_fee"); - return GNUNET_DB_STATUS_HARD_ERROR; - } - if (1 == fee_cmp) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Refund fee proposed by merchant is higher than necessary.\n"); - } TEH_plugin->free_coin_transaction_list (TEH_plugin->cls, tl); @@ -405,50 +347,35 @@ static int verify_and_execute_refund (struct MHD_Connection *connection, const struct TALER_EXCHANGEDB_Refund *refund) { - struct TALER_EXCHANGEDB_RefundContext rc; - struct TALER_RefundRequestPS rr; struct GNUNET_HashCode denom_hash; + struct TALER_Amount expect_fee; - if (GNUNET_YES != - TALER_amount_cmp_currency (&refund->details.refund_amount, - &refund->details.refund_fee) ) { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH, - "refund_fee"); - } - if (-1 == TALER_amount_cmp (&refund->details.refund_amount, - &refund->details.refund_fee) ) - { - GNUNET_break_op (0); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_BAD_REQUEST, - TALER_EC_REFUND_FEE_ABOVE_AMOUNT, - "refund_amount"); - } - rr.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND); - rr.purpose.size = htonl (sizeof (struct TALER_RefundRequestPS)); - rr.h_contract_terms = refund->details.h_contract_terms; - rr.coin_pub = refund->coin.coin_pub; - rr.merchant = refund->details.merchant_pub; - rr.rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id); - TALER_amount_hton (&rr.refund_amount, - &refund->details.refund_amount); - TALER_amount_hton (&rr.refund_fee, - &refund->details.refund_fee); - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, - &rr.purpose, - &refund->details.merchant_sig.eddsa_sig, - &refund->details.merchant_pub.eddsa_pub)) - { - TALER_LOG_WARNING ("Invalid signature on /refund request\n"); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_FORBIDDEN, - TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID, - "merchant_sig"); + struct TALER_RefundRequestPS rr = { + .purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_REFUND), + .purpose.size = htonl (sizeof (rr)), + .h_contract_terms = refund->details.h_contract_terms, + .coin_pub = refund->coin.coin_pub, + .merchant = refund->details.merchant_pub, + .rtransaction_id = GNUNET_htonll (refund->details.rtransaction_id) + }; + + TALER_amount_hton (&rr.refund_amount, + &refund->details.refund_amount); + TALER_amount_hton (&rr.refund_fee, + &refund->details.refund_fee); + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MERCHANT_REFUND, + &rr.purpose, + &refund->details.merchant_sig.eddsa_sig, + &refund->details.merchant_pub.eddsa_pub)) + { + TALER_LOG_WARNING ("Invalid signature on refund request\n"); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_FORBIDDEN, + TALER_EC_REFUND_MERCHANT_SIGNATURE_INVALID, + "merchant_sig"); + } } /* Fetch the coin's denomination (hash) */ @@ -503,23 +430,53 @@ verify_and_execute_refund (struct MHD_Connection *connection, ec, "denomination not found, but coin known"); } - TALER_amount_ntoh (&rc.expect_fee, + TALER_amount_ntoh (&expect_fee, &dki->issue.properties.fee_refund); } TEH_KS_release (key_state); } + /* Check refund fee matches fee of denomination key! */ + if (GNUNET_YES != + TALER_amount_cmp_currency (&expect_fee, + &refund->details.refund_fee) ) + { + GNUNET_break_op (0); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH, + "refund_fee"); + } + { + int fee_cmp; + + fee_cmp = TALER_amount_cmp (&refund->details.refund_fee, + &expect_fee); + if (-1 == fee_cmp) + { + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFUND_FEE_TOO_LOW, + "refund_fee"); + } + if (1 == fee_cmp) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Refund fee proposed by merchant is higher than necessary.\n"); + } + } + + /* Finally run the actual transaction logic */ { int mhd_ret; - rc.refund = refund; if (GNUNET_OK != TEH_DB_run_transaction (connection, "run refund", &mhd_ret, &refund_transaction, - &rc)) + (void *) refund)) { return mhd_ret; } @@ -546,7 +503,6 @@ TEH_handler_refund (struct MHD_Connection *connection, const struct TALER_CoinSpendPublicKeyP *coin_pub, const json_t *root) { - int res; struct TALER_EXCHANGEDB_Refund refund; struct GNUNET_JSON_Specification spec[] = { TALER_JSON_spec_amount ("refund_amount", &refund.details.refund_amount), @@ -561,17 +517,46 @@ TEH_handler_refund (struct MHD_Connection *connection, }; refund.coin.coin_pub = *coin_pub; - res = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_SYSERR == res) - return MHD_NO; /* hard failure */ - if (GNUNET_NO == res) - return MHD_YES; /* failure */ - res = verify_and_execute_refund (connection, - &refund); - GNUNET_JSON_parse_free (spec); - return res; + { + int res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + } + if (GNUNET_YES != + TALER_amount_cmp_currency (&refund.details.refund_amount, + &refund.details.refund_fee) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFUND_FEE_CURRENCY_MISSMATCH, + "refund_amount or refund_fee"); + } + if (-1 == TALER_amount_cmp (&refund.details.refund_amount, + &refund.details.refund_fee) ) + { + GNUNET_break_op (0); + GNUNET_JSON_parse_free (spec); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_BAD_REQUEST, + TALER_EC_REFUND_FEE_ABOVE_AMOUNT, + "refund_amount"); + } + { + int res; + + res = verify_and_execute_refund (connection, + &refund); + GNUNET_JSON_parse_free (spec); + return res; + } } diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index 0a334ec4c..4575a8ab5 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -18,7 +18,7 @@ */ /** * @file taler-exchange-httpd_withdraw.c - * @brief Handle /reserve/withdraw requests + * @brief Handle /reserves/$RESERVE_PUB/withdraw requests * @author Florian Dold * @author Benedikt Mueller * @author Christian Grothoff @@ -42,7 +42,7 @@ /** - * Send reserve status information to client with the + * Send reserve history information to client with the * message that we have insufficient funds for the * requested withdraw operation. * @@ -52,10 +52,10 @@ * @return MHD result code */ static int -reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, - const struct TALER_Amount *ebalance, - const struct - TALER_EXCHANGEDB_ReserveHistory *rh) +reply_withdraw_insufficient_funds ( + struct MHD_Connection *connection, + const struct TALER_Amount *ebalance, + const struct TALER_EXCHANGEDB_ReserveHistory *rh) { json_t *json_history; struct TALER_Amount balance; @@ -66,7 +66,7 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS, - "balance calculation failure"); + "reserve balance calculation failure"); if (0 != TALER_amount_cmp (&balance, ebalance)) @@ -75,7 +75,7 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, json_decref (json_history); return TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_WITHDRAW_HISTORY_RESERVE_BALANCE_CORRUPT, + TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT, "internal balance inconsistency error"); } return TALER_MHD_reply_json_pack (connection, @@ -92,21 +92,25 @@ reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection, /** - * Send blinded coin information to client. + * Send blind signature to client. * * @param connection connection to the client * @param collectable blinded coin to return * @return MHD result code */ static int -reply_reserve_withdraw_success (struct MHD_Connection *connection, - const struct - TALER_EXCHANGEDB_CollectableBlindcoin * - collectable) +reply_withdraw_success ( + struct MHD_Connection *connection, + const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable) { json_t *sig_json; sig_json = GNUNET_JSON_from_rsa_signature (collectable->sig.rsa_signature); + if (NULL == sig_json) + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_JSON_ALLOCATION_FAILURE, + "GNUNET_JSON_from_rsa_signature() failed"); return TALER_MHD_reply_json_pack (connection, MHD_HTTP_OK, "{s:o}", @@ -168,7 +172,7 @@ struct WithdrawContext /** - * Function implementing /reserve/withdraw transaction. Runs the + * Function implementing withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction * logic MUST NOT queue a MHD response. IF it returns an hard error, * the transaction logic MUST queue a MHD response and set @a mhd_ret. @@ -180,9 +184,9 @@ struct WithdrawContext * before entering the transaction, or because this function is run * twice (!) by #TEH_DB_run_transaction() and the first time created * the signature and then failed to commit. Furthermore, we may get - * a 2nd correct signature briefly if "get_withdraw_info" suceeds and + * a 2nd correct signature briefly if "get_withdraw_info" succeeds and * finds one in the DB. To avoid signing twice, the function may - * return a valid signature in "wc->collectable.sig" even if it failed. + * return a valid signature in "wc->collectable.sig" **even if it failed**. * The caller must thus free the signature in either case. * * @param cls a `struct WithdrawContext *` @@ -201,7 +205,6 @@ withdraw_transaction (void *cls, struct WithdrawContext *wc = cls; struct TALER_EXCHANGEDB_Reserve r; enum GNUNET_DB_QueryStatus qs; - struct TALER_Amount fee_withdraw; struct TALER_DenominationSignature denom_sig; #if OPTIMISTIC_SIGN @@ -227,18 +230,23 @@ withdraw_transaction (void *cls, } /* Don't sign again if we have already signed the coin */ - if (1 == qs) + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { + /* Toss out the optimistic signature, we got another one from the DB; + optimization trade-off loses in this case: we unnecessarily computed + a signature :-( */ #if OPTIMISTIC_SIGN GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature); #endif return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; } - GNUNET_assert (0 == qs); - wc->collectable.sig = denom_sig; + /* We should never get more than one result, and we handled + the errors (negative case) above, so that leaves no results. */ + GNUNET_assert (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs); + wc->collectable.sig = denom_sig; /* Note: might still be NULL if we didn't do OPTIMISTIC_SIGN */ /* Check if balance is sufficient */ - r.pub = wc->wsrd.reserve_pub; + r.pub = wc->wsrd.reserve_pub; /* other fields of 'r' initialized in reserves_get (if successful) */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Trying to withdraw from reserve: %s\n", TALER_B2S (&r.pub)); @@ -265,20 +273,25 @@ withdraw_transaction (void *cls, if (0 < TALER_amount_cmp (&wc->amount_required, &r.balance)) { - char *amount_required; - char *r_balance; struct TALER_EXCHANGEDB_ReserveHistory *rh; /* The reserve does not have the required amount (actual * amount + withdraw fee) */ GNUNET_break_op (0); - amount_required = TALER_amount_to_string (&wc->amount_required); - r_balance = TALER_amount_to_string (&r.balance); - TALER_LOG_WARNING ("Asked %s over a reserve worth %s\n", - amount_required, - r_balance); - GNUNET_free (amount_required); - GNUNET_free (r_balance); +#if GNUNET_EXTRA_LOGGING + { + char *amount_required; + char *r_balance; + + amount_required = TALER_amount_to_string (&wc->amount_required); + r_balance = TALER_amount_to_string (&r.balance); + TALER_LOG_WARNING ("Asked %s over a reserve worth %s\n", + amount_required, + r_balance); + GNUNET_free (amount_required); + GNUNET_free (r_balance); + } +#endif qs = TEH_plugin->get_reserve_history (TEH_plugin->cls, session, &wc->wsrd.reserve_pub, @@ -292,9 +305,9 @@ withdraw_transaction (void *cls, "failed to fetch reserve history"); return GNUNET_DB_STATUS_HARD_ERROR; } - *mhd_ret = reply_reserve_withdraw_insufficient_funds (connection, - &r.balance, - rh); + *mhd_ret = reply_withdraw_insufficient_funds (connection, + &r.balance, + rh); TEH_plugin->free_reserve_history (TEH_plugin->cls, rh); return GNUNET_DB_STATUS_HARD_ERROR; @@ -314,16 +327,15 @@ withdraw_transaction (void *cls, *mhd_ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_WITHDRAW_SIGNATURE_FAILED, - "Failed to create signature"); + "Failed to create blind signature"); return GNUNET_DB_STATUS_HARD_ERROR; } } #endif - TALER_amount_ntoh (&fee_withdraw, - &wc->dki->issue.properties.fee_withdraw); wc->collectable.denom_pub_hash = wc->denom_pub_hash; wc->collectable.amount_with_fee = wc->amount_required; - wc->collectable.withdraw_fee = fee_withdraw; + TALER_amount_ntoh (&wc->collectable.withdraw_fee, + &wc->dki->issue.properties.fee_withdraw); wc->collectable.reserve_pub = wc->wsrd.reserve_pub; wc->collectable.h_coin_envelope = wc->wsrd.h_coin_envelope; wc->collectable.reserve_sig = wc->signature; @@ -366,12 +378,6 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh, const char *const args[2]) { struct WithdrawContext wc; - int res; - int mhd_ret; - unsigned int hc; - enum TALER_ErrorCode ec; - struct TALER_Amount amount; - struct TALER_Amount fee_withdraw; struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_varsize ("coin_ev", (void **) &wc.blinded_msg, @@ -397,11 +403,15 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh, "reserve public key malformed"); } - res = TALER_MHD_parse_json_data (connection, - root, - spec); - if (GNUNET_OK != res) - return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + { + int res; + + res = TALER_MHD_parse_json_data (connection, + root, + spec); + if (GNUNET_OK != res) + return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; + } wc.key_state = TEH_KS_acquire (GNUNET_TIME_absolute_get ()); if (NULL == wc.key_state) { @@ -412,41 +422,52 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh, TALER_EC_EXCHANGE_BAD_CONFIGURATION, "no keys"); } - wc.dki = TEH_KS_denomination_key_lookup_by_hash (wc.key_state, - &wc.denom_pub_hash, - TEH_KS_DKU_WITHDRAW, - &ec, - &hc); - if (NULL == wc.dki) { - GNUNET_JSON_parse_free (spec); - TEH_KS_release (wc.key_state); - return TALER_MHD_reply_with_error (connection, - hc, - ec, - "could not find denomination key"); + unsigned int hc; + enum TALER_ErrorCode ec; + + wc.dki = TEH_KS_denomination_key_lookup_by_hash (wc.key_state, + &wc.denom_pub_hash, + TEH_KS_DKU_WITHDRAW, + &ec, + &hc); + if (NULL == wc.dki) + { + GNUNET_JSON_parse_free (spec); + TEH_KS_release (wc.key_state); + return TALER_MHD_reply_with_error (connection, + hc, + ec, + "could not find denomination key"); + } } GNUNET_assert (NULL != wc.dki->denom_priv.rsa_private_key); - TALER_amount_ntoh (&amount, - &wc.dki->issue.properties.value); - TALER_amount_ntoh (&fee_withdraw, - &wc.dki->issue.properties.fee_withdraw); - if (GNUNET_OK != - TALER_amount_add (&wc.amount_required, - &amount, - &fee_withdraw)) { - GNUNET_JSON_parse_free (spec); - TEH_KS_release (wc.key_state); - return TALER_MHD_reply_with_error (connection, - MHD_HTTP_INTERNAL_SERVER_ERROR, - TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW, - "amount overflow for value plus withdraw fee"); + struct TALER_Amount amount; + struct TALER_Amount fee_withdraw; + + TALER_amount_ntoh (&amount, + &wc.dki->issue.properties.value); + TALER_amount_ntoh (&fee_withdraw, + &wc.dki->issue.properties.fee_withdraw); + if (GNUNET_OK != + TALER_amount_add (&wc.amount_required, + &amount, + &fee_withdraw)) + { + GNUNET_JSON_parse_free (spec); + TEH_KS_release (wc.key_state); + return TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW, + "amount overflow for value plus withdraw fee"); + } + TALER_amount_hton (&wc.wsrd.amount_with_fee, + &wc.amount_required); + TALER_amount_hton (&wc.wsrd.withdraw_fee, + &fee_withdraw); } - TALER_amount_hton (&wc.wsrd.amount_with_fee, - &wc.amount_required); - TALER_amount_hton (&wc.wsrd.withdraw_fee, - &fee_withdraw); + /* verify signature! */ wc.wsrd.purpose.size = htonl (sizeof (struct TALER_WithdrawRequestPS)); @@ -491,28 +512,39 @@ TEH_handler_withdraw (const struct TEH_RequestHandler *rh, } #endif - if (GNUNET_OK != - TEH_DB_run_transaction (connection, - "run withdraw", - &mhd_ret, - &withdraw_transaction, - &wc)) + /* run transaction and sign (if not optimistically signed before) */ { - TEH_KS_release (wc.key_state); - /* Even if #withdraw_transaction() failed, it may have created a signature - (or we might have done it optimistically above). */ - if (NULL != wc.collectable.sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); - GNUNET_JSON_parse_free (spec); - return mhd_ret; + int mhd_ret; + + if (GNUNET_OK != + TEH_DB_run_transaction (connection, + "run withdraw", + &mhd_ret, + &withdraw_transaction, + &wc)) + { + TEH_KS_release (wc.key_state); + /* Even if #withdraw_transaction() failed, it may have created a signature + (or we might have done it optimistically above). */ + if (NULL != wc.collectable.sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); + GNUNET_JSON_parse_free (spec); + return mhd_ret; + } } + + /* Clean up and send back final (positive) response */ TEH_KS_release (wc.key_state); GNUNET_JSON_parse_free (spec); - mhd_ret = reply_reserve_withdraw_success (connection, - &wc.collectable); - GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); - return mhd_ret; + { + int ret; + + ret = reply_withdraw_success (connection, + &wc.collectable); + GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); + return ret; + } } diff --git a/src/include/taler_error_codes.h b/src/include/taler_error_codes.h index e6bd73647..9a7c544b9 100644 --- a/src/include/taler_error_codes.h +++ b/src/include/taler_error_codes.h @@ -382,11 +382,11 @@ enum TALER_ErrorCode TALER_EC_DENOMINATION_KEY_LOST = 1116, /** - * The exchange's database entry with the reserve balance summary - * is inconsistent with its own history of the reserve. - * Returned with an HTTP status of #MHD_HTTP_INTERNAL_SERVER_ERROR. + * The exchange's database entry with the reserve balance summary is + * inconsistent with its own history of the reserve. Returned with an + * HTTP status of #MHD_HTTP_INTERNAL_SERVER_ERROR. */ - TALER_EC_WITHDRAW_HISTORY_RESERVE_BALANCE_CORRUPT = 1117, + TALER_EC_WITHDRAW_RESERVE_BALANCE_CORRUPT = 1117, /** * The exchange failed to obtain the transaction history of the given @@ -528,6 +528,13 @@ enum TALER_ErrorCode */ TALER_EC_DEPOSIT_INVALID_SIGNATURE_BY_EXCHANGE = 1221, + /** + * The currency specified for the deposit is different from the + * currency of the coin. This response is provided with HTTP status + * code MHD_HTTP_PRECONDITION_FAILED. + */ + TALER_EC_DEPOSIT_CURRENCY_MISSMATCH = 1222, + /** * The respective coin did not have sufficient residual value for the * /refresh/melt operation. The "history" in this response provdes @@ -544,7 +551,7 @@ enum TALER_ErrorCode * "original_value". This response is provided with HTTP status code * MHD_HTTP_CONFLICT. */ - TALER_EC_TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND = 1301, + TALER_EC_REFRESH_MELT_DENOMINATION_KEY_NOT_FOUND = 1301, /** * The exchange had an internal error reconstructing the transaction @@ -607,6 +614,13 @@ enum TALER_ErrorCode */ TALER_EC_MELT_INVALID_SIGNATURE_BY_EXCHANGE = 1310, + /** + * The currency specified for the melt amount is different from the + * currency of the coin. This response is provided with HTTP status + * code MHD_HTTP_PRECONDITION_FAILED. + */ + TALER_EC_MELT_CURRENCY_MISSMATCH = 1311, + /** * The exchange is unaware of the denomination key that was used to * sign the melted zombie coin. This response is provided with HTTP -- cgit v1.2.3