LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd_reserve_withdraw.c (source / functions) Hit Total Coverage
Test: rcoverage.info Lines: 97 167 58.1 %
Date: 2017-11-25 11:31:41 Functions: 4 4 100.0 %

          Line data    Source code
       1             : /*
       2             :   This file is part of TALER
       3             :   Copyright (C) 2014-2017 GNUnet e.V.
       4             : 
       5             :   TALER is free software; you can redistribute it and/or modify it under the
       6             :   terms of the GNU Affero General Public License as published by the Free Software
       7             :   Foundation; either version 3, or (at your option) any later version.
       8             : 
       9             :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10             :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11             :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12             : 
      13             :   You should have received a copy of the GNU Affero General Public License along with
      14             :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             : */
      16             : /**
      17             :  * @file taler-exchange-httpd_reserve_withdraw.c
      18             :  * @brief Handle /reserve/withdraw requests
      19             :  * @author Florian Dold
      20             :  * @author Benedikt Mueller
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "platform.h"
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <jansson.h>
      26             : #include "taler-exchange-httpd_reserve_withdraw.h"
      27             : #include "taler-exchange-httpd_parsing.h"
      28             : #include "taler-exchange-httpd_responses.h"
      29             : #include "taler-exchange-httpd_keystate.h"
      30             : 
      31             : 
      32             : /**
      33             :  * Send reserve status information to client with the
      34             :  * message that we have insufficient funds for the
      35             :  * requested /reserve/withdraw operation.
      36             :  *
      37             :  * @param connection connection to the client
      38             :  * @param rh reserve history to return
      39             :  * @return MHD result code
      40             :  */
      41             : static int
      42           1 : reply_reserve_withdraw_insufficient_funds (struct MHD_Connection *connection,
      43             :                                            const struct TALER_EXCHANGEDB_ReserveHistory *rh)
      44             : {
      45             :   json_t *json_balance;
      46             :   json_t *json_history;
      47             :   struct TALER_Amount balance;
      48             : 
      49           1 :   json_history = TEH_RESPONSE_compile_reserve_history (rh,
      50             :                                                        &balance);
      51           1 :   if (NULL == json_history)
      52           0 :     return TEH_RESPONSE_reply_internal_error (connection,
      53             :                                               TALER_EC_WITHDRAW_HISTORY_DB_ERROR_INSUFFICIENT_FUNDS,
      54             :                                               "balance calculation failure");
      55           1 :   json_balance = TALER_JSON_from_amount (&balance);
      56           1 :   return TEH_RESPONSE_reply_json_pack (connection,
      57             :                                        MHD_HTTP_FORBIDDEN,
      58             :                                        "{s:s, s:I, s:o, s:o}",
      59             :                                        "error", "Insufficient funds",
      60             :                                        "code", (json_int_t) TALER_EC_WITHDRAW_INSUFFICIENT_FUNDS,
      61             :                                        "balance", json_balance,
      62             :                                        "history", json_history);
      63             : }
      64             : 
      65             : 
      66             : /**
      67             :  * Send blinded coin information to client.
      68             :  *
      69             :  * @param connection connection to the client
      70             :  * @param collectable blinded coin to return
      71             :  * @return MHD result code
      72             :  */
      73             : static int
      74           6 : reply_reserve_withdraw_success (struct MHD_Connection *connection,
      75             :                                 const struct TALER_EXCHANGEDB_CollectableBlindcoin *collectable)
      76             : {
      77             :   json_t *sig_json;
      78             : 
      79           6 :   sig_json = GNUNET_JSON_from_rsa_signature (collectable->sig.rsa_signature);
      80           6 :   return TEH_RESPONSE_reply_json_pack (connection,
      81             :                                        MHD_HTTP_OK,
      82             :                                        "{s:o}",
      83             :                                        "ev_sig", sig_json);
      84             : }
      85             : 
      86             : 
      87             : /**
      88             :  * Context for #withdraw_transaction.
      89             :  */
      90             : struct WithdrawContext
      91             : {
      92             :   /**
      93             :    * Details about the withdrawal request.
      94             :    */
      95             :   struct TALER_WithdrawRequestPS wsrd;
      96             : 
      97             :   /**
      98             :    * Value of the coin plus withdraw fee.
      99             :    */
     100             :   struct TALER_Amount amount_required;
     101             : 
     102             :   /**
     103             :    * Denomination public key.
     104             :    */
     105             :   struct TALER_DenominationPublicKey denomination_pub;
     106             : 
     107             :   /**
     108             :    * Signature over the request.
     109             :    */
     110             :   struct TALER_ReserveSignatureP signature;
     111             : 
     112             :   /**
     113             :    * Blinded planchet.
     114             :    */
     115             :   char *blinded_msg;
     116             : 
     117             :   /**
     118             :    * Key state to use to inspect previous withdrawal values.
     119             :    */
     120             :   struct TEH_KS_StateHandle *key_state;
     121             : 
     122             :   /**
     123             :    * Number of bytes in @e blinded_msg.
     124             :    */
     125             :   size_t blinded_msg_len;
     126             : 
     127             :   /**
     128             :    * Details about denomination we are about to withdraw.
     129             :    */
     130             :   struct TALER_EXCHANGEDB_DenominationKeyIssueInformation *dki;
     131             : 
     132             :   /**
     133             :    * Set to the resulting signed coin data to be returned to the client.
     134             :    */
     135             :   struct TALER_EXCHANGEDB_CollectableBlindcoin collectable;
     136             : 
     137             : };
     138             : 
     139             : 
     140             : /**
     141             :  * Function implementing /reserve/withdraw transaction.  Runs the
     142             :  * transaction logic; IF it returns a non-error code, the transaction
     143             :  * logic MUST NOT queue a MHD response.  IF it returns an hard error,
     144             :  * the transaction logic MUST queue a MHD response and set @a mhd_ret.
     145             :  * IF it returns the soft error code, the function MAY be called again
     146             :  * to retry and MUST not queue a MHD response.
     147             :  *
     148             :  * @param cls a `struct WithdrawContext *`
     149             :  * @param connection MHD request which triggered the transaction
     150             :  * @param session database session to use
     151             :  * @param[out] mhd_ret set to MHD response status for @a connection,
     152             :  *             if transaction failed (!)
     153             :  * @return transaction status
     154             :  */
     155             : static enum GNUNET_DB_QueryStatus
     156           7 : withdraw_transaction (void *cls,
     157             :                       struct MHD_Connection *connection,
     158             :                       struct TALER_EXCHANGEDB_Session *session,
     159             :                       int *mhd_ret)
     160             : {
     161           7 :   struct WithdrawContext *wc = cls;
     162             :   struct TALER_EXCHANGEDB_ReserveHistory *rh;
     163             :   struct TALER_Amount deposit_total;
     164             :   struct TALER_Amount withdraw_total;
     165             :   struct TALER_Amount balance;
     166             :   struct TALER_Amount fee_withdraw;
     167             :   int res;
     168             :   enum GNUNET_DB_QueryStatus qs;
     169             :   struct TALER_DenominationSignature denom_sig;
     170             :   struct GNUNET_HashCode h_blind;
     171             : 
     172           7 :   GNUNET_CRYPTO_hash (wc->blinded_msg,
     173             :                       wc->blinded_msg_len,
     174             :                       &h_blind);
     175           7 :   qs = TEH_plugin->get_withdraw_info (TEH_plugin->cls,
     176             :                                       session,
     177             :                                       &h_blind,
     178             :                                       &wc->collectable);
     179           7 :   if (0 > qs)
     180             :   {
     181           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     182           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     183           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     184             :                                                        TALER_EC_WITHDRAW_DB_FETCH_ERROR);
     185           0 :     return qs;
     186             :   }
     187             : 
     188             :   /* Don't sign again if we have already signed the coin */
     189           7 :   if (1 == qs)
     190           0 :     return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
     191           7 :   GNUNET_assert (0 == qs);
     192             : 
     193             :   /* Check if balance is sufficient */
     194          14 :   qs = TEH_plugin->get_reserve_history (TEH_plugin->cls,
     195             :                                         session,
     196           7 :                                         &wc->wsrd.reserve_pub,
     197             :                                         &rh);
     198           7 :   if (0 > qs)
     199             :   {
     200           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     201           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     202             :                                                        TALER_EC_WITHDRAW_DB_FETCH_ERROR);
     203           0 :     return qs;
     204             :   }
     205           7 :   if (NULL == rh)
     206             :   {
     207           0 :     *mhd_ret = TEH_RESPONSE_reply_arg_unknown (connection,
     208             :                                                TALER_EC_WITHDRAW_RESERVE_UNKNOWN,
     209             :                                                "reserve_pub");
     210           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     211             :   }
     212             : 
     213             :   /* calculate balance of the reserve */
     214           7 :   res = 0;
     215          23 :   for (const struct TALER_EXCHANGEDB_ReserveHistory *pos = rh;
     216             :        NULL != pos;
     217           9 :        pos = pos->next)
     218             :   {
     219           9 :     switch (pos->type)
     220             :     {
     221             :     case TALER_EXCHANGEDB_RO_BANK_TO_EXCHANGE:
     222           7 :       if (0 == (res & 1))
     223           7 :         deposit_total = pos->details.bank->amount;
     224             :       else
     225           0 :         if (GNUNET_OK !=
     226           0 :             TALER_amount_add (&deposit_total,
     227             :                               &deposit_total,
     228           0 :                               &pos->details.bank->amount))
     229             :         {
     230           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     231             :                                                            TALER_EC_WITHDRAW_AMOUNT_DEPOSITS_OVERFLOW);
     232           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     233             :         }
     234           7 :       res |= 1;
     235           7 :       break;
     236             :     case TALER_EXCHANGEDB_RO_WITHDRAW_COIN:
     237             :       {
     238           2 :         if (0 == (res & 2))
     239           2 :           withdraw_total = pos->details.withdraw->amount_with_fee;
     240             :         else
     241           0 :           if (GNUNET_OK !=
     242           0 :               TALER_amount_add (&withdraw_total,
     243             :                                 &withdraw_total,
     244           0 :                                 &pos->details.withdraw->amount_with_fee))
     245             :           {
     246           0 :             *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     247             :                                                              TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW);
     248           0 :             return GNUNET_DB_STATUS_HARD_ERROR;
     249             :           }
     250           2 :         res |= 2;
     251           2 :         break;
     252             :       }
     253             :     case TALER_EXCHANGEDB_RO_PAYBACK_COIN:
     254           0 :       if (0 == (res & 1))
     255           0 :         deposit_total = pos->details.payback->value;
     256             :       else
     257           0 :         if (GNUNET_OK !=
     258           0 :             TALER_amount_add (&deposit_total,
     259             :                               &deposit_total,
     260           0 :                               &pos->details.payback->value))
     261             :         {
     262           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     263             :                                                            TALER_EC_WITHDRAW_AMOUNT_DEPOSITS_OVERFLOW);
     264           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     265             :         }
     266           0 :       res |= 1;
     267           0 :       break;
     268             : 
     269             :     case TALER_EXCHANGEDB_RO_EXCHANGE_TO_BANK:
     270           0 :       if (0 == (res & 2))
     271           0 :         withdraw_total = pos->details.closing->amount;
     272             :       else
     273           0 :         if (GNUNET_OK !=
     274           0 :             TALER_amount_add (&withdraw_total,
     275             :                               &withdraw_total,
     276           0 :                               &pos->details.closing->amount))
     277             :         {
     278           0 :           *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     279             :                                                            TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW);
     280           0 :           return GNUNET_DB_STATUS_HARD_ERROR;
     281             :         }
     282           0 :       if (GNUNET_OK !=
     283           0 :           TALER_amount_add (&withdraw_total,
     284             :                             &withdraw_total,
     285           0 :                             &pos->details.closing->closing_fee))
     286             :       {
     287           0 :         *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     288             :                                                          TALER_EC_WITHDRAW_AMOUNT_WITHDRAWALS_OVERFLOW);
     289           0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     290             :       }
     291             : 
     292           0 :       res |= 2;
     293           0 :       break;
     294             :     }
     295             :   }
     296           7 :   if (0 == (res & 1))
     297             :   {
     298             :     /* did not encounter any wire transfer operations, how can we have a reserve? */
     299           0 :     GNUNET_break (0);
     300           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     301             :                                                      TALER_EC_WITHDRAW_RESERVE_WITHOUT_WIRE_TRANSFER);
     302           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     303             :   }
     304           7 :   if (0 == (res & 2))
     305             :   {
     306             :     /* did not encounter any withdraw operations, set to zero */
     307           5 :     GNUNET_assert (GNUNET_OK ==
     308             :                    TALER_amount_get_zero (deposit_total.currency,
     309             :                                           &withdraw_total));
     310             :   }
     311             :   /* All reserve balances should be non-negative */
     312           7 :   if (GNUNET_SYSERR ==
     313           7 :       TALER_amount_subtract (&balance,
     314             :                              &deposit_total,
     315             :                              &withdraw_total))
     316             :   {
     317           0 :     GNUNET_break (0); /* database inconsistent */
     318           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     319             :                                                      TALER_EC_WITHDRAW_RESERVE_HISTORY_IMPOSSIBLE);
     320           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     321             :   }
     322             : 
     323           7 :   if (0 < TALER_amount_cmp (&wc->amount_required,
     324             :                             &balance))
     325             :   {
     326           1 :     *mhd_ret = reply_reserve_withdraw_insufficient_funds (connection,
     327             :                                                           rh);
     328           1 :     TEH_plugin->free_reserve_history (TEH_plugin->cls,
     329             :                                       rh);
     330           1 :     return GNUNET_DB_STATUS_HARD_ERROR;
     331             :   }
     332           6 :   TEH_plugin->free_reserve_history (TEH_plugin->cls,
     333             :                                     rh);
     334             : 
     335             :   /* Balance is good, sign the coin! */
     336             :   denom_sig.rsa_signature
     337          12 :     = GNUNET_CRYPTO_rsa_sign_blinded (wc->dki->denom_priv.rsa_private_key,
     338           6 :                                       wc->blinded_msg,
     339             :                                       wc->blinded_msg_len);
     340           6 :   if (NULL == denom_sig.rsa_signature)
     341             :   {
     342           0 :     GNUNET_break (0);
     343           0 :     *mhd_ret = TEH_RESPONSE_reply_internal_error (connection,
     344             :                                                   TALER_EC_WITHDRAW_SIGNATURE_FAILED,
     345             :                                                   "Internal error");
     346           0 :     return GNUNET_DB_STATUS_HARD_ERROR;
     347             :   }
     348           6 :   TALER_amount_ntoh (&fee_withdraw,
     349           6 :                      &wc->dki->issue.properties.fee_withdraw);
     350           6 :   wc->collectable.sig = denom_sig;
     351           6 :   wc->collectable.denom_pub = wc->denomination_pub;
     352           6 :   wc->collectable.amount_with_fee = wc->amount_required;
     353           6 :   wc->collectable.withdraw_fee = fee_withdraw;
     354           6 :   wc->collectable.reserve_pub = wc->wsrd.reserve_pub;
     355           6 :   wc->collectable.h_coin_envelope = h_blind;
     356           6 :   wc->collectable.reserve_sig = wc->signature;
     357          12 :   qs = TEH_plugin->insert_withdraw_info (TEH_plugin->cls,
     358             :                                          session,
     359           6 :                                          &wc->collectable);
     360           6 :   if (0 > qs)
     361             :   {
     362           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     363           0 :     GNUNET_CRYPTO_rsa_signature_free (denom_sig.rsa_signature);
     364           0 :     if (GNUNET_DB_STATUS_HARD_ERROR == qs)
     365           0 :       *mhd_ret = TEH_RESPONSE_reply_internal_db_error (connection,
     366             :                                                        TALER_EC_WITHDRAW_DB_STORE_ERROR);
     367           0 :     return qs;
     368             :   }
     369           6 :   return qs;
     370             : }
     371             : 
     372             : 
     373             : /**
     374             :  * Handle a "/reserve/withdraw" request.  Parses the "reserve_pub"
     375             :  * EdDSA key of the reserve and the requested "denom_pub" which
     376             :  * specifies the key/value of the coin to be withdrawn, and checks
     377             :  * that the signature "reserve_sig" makes this a valid withdrawal
     378             :  * request from the specified reserve.  If so, the envelope
     379             :  * with the blinded coin "coin_ev" is passed down to execute the
     380             :  * withdrawl operation.
     381             :  *
     382             :  * @param rh context of the handler
     383             :  * @param connection the MHD connection to handle
     384             :  * @param[in,out] connection_cls the connection's closure (can be updated)
     385             :  * @param upload_data upload data
     386             :  * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
     387             :  * @return MHD result code
     388             :  */
     389             : int
     390          24 : TEH_RESERVE_handler_reserve_withdraw (struct TEH_RequestHandler *rh,
     391             :                                       struct MHD_Connection *connection,
     392             :                                       void **connection_cls,
     393             :                                       const char *upload_data,
     394             :                                       size_t *upload_data_size)
     395             : {
     396             :   struct WithdrawContext wc;
     397             :   json_t *root;
     398             :   int res;
     399             :   int mhd_ret;
     400             :   struct TALER_Amount amount;
     401             :   struct TALER_Amount fee_withdraw;
     402          24 :   struct GNUNET_JSON_Specification spec[] = {
     403             :     GNUNET_JSON_spec_varsize ("coin_ev",
     404             :                               (void **) &wc.blinded_msg,
     405             :                               &wc.blinded_msg_len),
     406             :     GNUNET_JSON_spec_fixed_auto ("reserve_pub",
     407             :                                  &wc.wsrd.reserve_pub),
     408             :     GNUNET_JSON_spec_fixed_auto ("reserve_sig",
     409             :                                  &wc.signature),
     410             :     TALER_JSON_spec_denomination_public_key ("denom_pub",
     411             :                                              &wc.denomination_pub),
     412             :     GNUNET_JSON_spec_end ()
     413             :   };
     414             : 
     415          24 :   res = TEH_PARSE_post_json (connection,
     416             :                              connection_cls,
     417             :                              upload_data,
     418             :                              upload_data_size,
     419             :                              &root);
     420          24 :   if (GNUNET_SYSERR == res)
     421           0 :     return MHD_NO;
     422          24 :   if ( (GNUNET_NO == res) || (NULL == root) )
     423          16 :     return MHD_YES;
     424           8 :   res = TEH_PARSE_json_data (connection,
     425             :                              root,
     426             :                              spec);
     427           8 :   json_decref (root);
     428           8 :   if (GNUNET_OK != res)
     429           0 :     return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES;
     430           8 :   wc.key_state = TEH_KS_acquire ();
     431           8 :   if (NULL == wc.key_state)
     432             :   {
     433           0 :     TALER_LOG_ERROR ("Lacking keys to operate\n");
     434           0 :     GNUNET_JSON_parse_free (spec);
     435           0 :     return TEH_RESPONSE_reply_internal_error (connection,
     436             :                                               TALER_EC_EXCHANGE_BAD_CONFIGURATION,
     437             :                                               "no keys");
     438             :   }
     439           8 :   wc.dki = TEH_KS_denomination_key_lookup (wc.key_state,
     440             :                                            &wc.denomination_pub,
     441             :                                            TEH_KS_DKU_WITHDRAW);
     442           8 :   if (NULL == wc.dki)
     443             :   {
     444           1 :     GNUNET_JSON_parse_free (spec);
     445           1 :     TEH_KS_release (wc.key_state);
     446           1 :     return TEH_RESPONSE_reply_arg_unknown (connection,
     447             :                                            TALER_EC_WITHDRAW_DENOMINATION_KEY_NOT_FOUND,
     448             :                                            "denom_pub");
     449             :   }
     450           7 :   TALER_amount_ntoh (&amount,
     451           7 :                      &wc.dki->issue.properties.value);
     452           7 :   TALER_amount_ntoh (&fee_withdraw,
     453           7 :                      &wc.dki->issue.properties.fee_withdraw);
     454           7 :   if (GNUNET_OK !=
     455           7 :       TALER_amount_add (&wc.amount_required,
     456             :                         &amount,
     457             :                         &fee_withdraw))
     458             :   {
     459           0 :     GNUNET_JSON_parse_free (spec);
     460           0 :     TEH_KS_release (wc.key_state);
     461           0 :     return TEH_RESPONSE_reply_internal_error (connection,
     462             :                                               TALER_EC_WITHDRAW_AMOUNT_FEE_OVERFLOW,
     463             :                                               "amount overflow for value plus withdraw fee");
     464             :   }
     465           7 :   TALER_amount_hton (&wc.wsrd.amount_with_fee,
     466             :                      &wc.amount_required);
     467           7 :   TALER_amount_hton (&wc.wsrd.withdraw_fee,
     468             :                      &fee_withdraw);
     469             :   /* verify signature! */
     470             :   wc.wsrd.purpose.size
     471           7 :     = htonl (sizeof (struct TALER_WithdrawRequestPS));
     472             :   wc.wsrd.purpose.purpose
     473           7 :     = htonl (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW);
     474           7 :   GNUNET_CRYPTO_rsa_public_key_hash (wc.denomination_pub.rsa_public_key,
     475             :                                      &wc.wsrd.h_denomination_pub);
     476           7 :   GNUNET_CRYPTO_hash (wc.blinded_msg,
     477             :                       wc.blinded_msg_len,
     478             :                       &wc.wsrd.h_coin_envelope);
     479           7 :   if (GNUNET_OK !=
     480           7 :       GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_WALLET_RESERVE_WITHDRAW,
     481             :                                   &wc.wsrd.purpose,
     482             :                                   &wc.signature.eddsa_signature,
     483             :                                   &wc.wsrd.reserve_pub.eddsa_pub))
     484             :   {
     485           0 :     TALER_LOG_WARNING ("Client supplied invalid signature for /reserve/withdraw request\n");
     486           0 :     GNUNET_JSON_parse_free (spec);
     487           0 :     TEH_KS_release (wc.key_state);
     488           0 :     return TEH_RESPONSE_reply_signature_invalid (connection,
     489             :                                                  TALER_EC_WITHDRAW_RESERVE_SIGNATURE_INVALID,
     490             :                                                  "reserve_sig");
     491             :   }
     492             : 
     493           7 :   if (GNUNET_OK !=
     494           7 :       TEH_DB_run_transaction (connection,
     495             :                               &mhd_ret,
     496             :                               &withdraw_transaction,
     497             :                               &wc))
     498             :   {
     499           1 :     TEH_KS_release (wc.key_state);
     500           1 :     GNUNET_JSON_parse_free (spec);
     501           1 :     return mhd_ret;
     502             :   }
     503           6 :   TEH_KS_release (wc.key_state);
     504           6 :   GNUNET_JSON_parse_free (spec);
     505             : 
     506           6 :   mhd_ret = reply_reserve_withdraw_success (connection,
     507             :                                             &wc.collectable);
     508           6 :   GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature);
     509           6 :   return mhd_ret;
     510             : }
     511             : 
     512             : 
     513             : /* end of taler-exchange-httpd_reserve_withdraw.c */

Generated by: LCOV version 1.13