diff --git a/src/auditor/taler-helper-auditor-reserves.c b/src/auditor/taler-helper-auditor-reserves.c index aa9c241bb..c27574d12 100644 --- a/src/auditor/taler-helper-auditor-reserves.c +++ b/src/auditor/taler-helper-auditor-reserves.c @@ -1083,10 +1083,13 @@ verify_reserve_balance (void *cls, internal audit, as otherwise the balance of the 'reserves' table is not replicated at the auditor. */ struct TALER_EXCHANGEDB_Reserve reserve; + struct TALER_EXCHANGEDB_KycStatus kyc; reserve.pub = rs->reserve_pub; qs = TALER_ARL_edb->reserves_get (TALER_ARL_edb->cls, - &reserve); + &reserve, + &kyc); + // FIXME: figure out what to do with KYC status! if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs) { /* If the exchange doesn't have this reserve in the summary, it diff --git a/src/exchange/taler-exchange-httpd_withdraw.c b/src/exchange/taler-exchange-httpd_withdraw.c index ca5618af6..4839ec97a 100644 --- a/src/exchange/taler-exchange-httpd_withdraw.c +++ b/src/exchange/taler-exchange-httpd_withdraw.c @@ -129,9 +129,44 @@ struct WithdrawContext */ struct TALER_EXCHANGEDB_CollectableBlindcoin collectable; + /** + * KYC status for the operation. + */ + struct TALER_EXCHANGEDB_KycStatus kyc; + + /** + * Set to true if the operation was denied due to + * failing @e kyc checks. + */ + bool kyc_denied; + }; +/** + * Function called with another amount that was + * already withdrawn. Accumulates all amounts in + * @a cls. + * + * @param[in,out] cls a `struct TALER_Amount` + * @param val value to add to @a cls + */ +static void +accumulate_withdraws (void *cls, + const struct TALER_Amount *val) +{ + struct TALER_Amount *acc = cls; + + if (GNUNET_OK != + TALER_amount_is_valid (acc)) + return; /* ignore */ + GNUNET_break (0 <= + TALER_amount_add (acc, + acc, + val)); +} + + /** * Function implementing withdraw transaction. Runs the * transaction logic; IF it returns a non-error code, the transaction @@ -165,7 +200,6 @@ withdraw_transaction (void *cls, struct TALER_EXCHANGEDB_Reserve r; enum GNUNET_DB_QueryStatus qs; struct TALER_DenominationSignature denom_sig; - struct TALER_EXCHANGEDB_KycStatus kyc; #if OPTIMISTIC_SIGN /* store away optimistic signature to protect @@ -211,7 +245,7 @@ withdraw_transaction (void *cls, TALER_B2S (&r.pub)); qs = TEH_plugin->reserves_get (TEH_plugin->cls, &r, - &kyc); + &wc->kyc); if (0 > qs) { if (GNUNET_DB_STATUS_HARD_ERROR == qs) @@ -270,11 +304,60 @@ withdraw_transaction (void *cls, return GNUNET_DB_STATUS_HARD_ERROR; } - if ( (! kyc.ok) && - (TEH_KYC_NONE != TEH_kyc_config.mode) ) + if ( (! wc->kyc.ok) && + (TEH_KYC_NONE != TEH_kyc_config.mode) && + (TALER_EXCHANGEDB_KYC_W2W == wc->kyc.type) ) { - // FIXME: check if we are above the limit - // for KYC, and if so, deny the transaction! + /* Wallet-to-wallet payments _always_ require KYC */ + wc->kyc_denied = true; + return qs; + } + if ( (! wc->kyc.ok) && + (TEH_KYC_NONE != TEH_kyc_config.mode) && + (TALER_EXCHANGEDB_KYC_WITHDRAW == wc->kyc.type) && + (! GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period)) ) + { + /* Withdraws require KYC if above threshold */ + struct TALER_Amount acc; + enum GNUNET_DB_QueryStatus qs2; + + TALER_amount_set_zero (TEH_currency, + &acc); + accumulate_withdraws (&acc, + &wc->amount_required); + qs2 = TEH_plugin->select_withdraw_amounts_by_account ( + TEH_plugin->cls, + &wc->wsrd.reserve_pub, + TEH_kyc_config.withdraw_period, + &accumulate_withdraws, + &acc); + if (0 > qs2) + { + GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs2); + if (GNUNET_DB_STATUS_HARD_ERROR == qs2) + *mhd_ret = TALER_MHD_reply_with_error (connection, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TALER_EC_GENERIC_DB_FETCH_FAILED, + "withdraw details"); + return qs2; + } + + if (GNUNET_OK != + TALER_amount_is_valid (&acc)) + { + GNUNET_break (0); + *mhd_ret = TALER_MHD_reply_with_ec (connection, + TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, + NULL); + return GNUNET_DB_STATUS_HARD_ERROR; + } + if (1 == /* 1: acc > withdraw_limit */ + TALER_amount_cmp (&acc, + &TEH_kyc_config.withdraw_limit)) + { + wc->kyc_denied = true; + return qs; + } } /* Balance is good, sign the coin! */ @@ -338,6 +421,9 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, enum TALER_ErrorCode ec; struct TEH_DenominationKey *dk; + memset (&wc, + 0, + sizeof (wc)); if (GNUNET_OK != GNUNET_STRINGS_string_to_data (args[0], strlen (args[0]), @@ -480,6 +566,7 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, #endif /* run transaction and sign (if not optimistically signed before) */ + wc.kyc_denied = false; { MHD_RESULT mhd_ret; @@ -499,9 +586,21 @@ TEH_handler_withdraw (struct TEH_RequestContext *rc, } } - /* Clean up and send back final (positive) response */ + /* Clean up and send back final response */ GNUNET_JSON_parse_free (spec); + if (wc.kyc_denied) + { + if (NULL != wc.collectable.sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (wc.collectable.sig.rsa_signature); + + return TALER_MHD_REPLY_JSON_PACK ( + rc->connection, + MHD_HTTP_ACCEPTED, + GNUNET_JSON_pack_uint64 ("payment_target_uuid", + wc.kyc.payment_target_uuid)); + } + { MHD_RESULT ret; diff --git a/src/exchangedb/plugin_exchangedb_postgres.c b/src/exchangedb/plugin_exchangedb_postgres.c index 34b785e7f..eda6468ba 100644 --- a/src/exchangedb/plugin_exchangedb_postgres.c +++ b/src/exchangedb/plugin_exchangedb_postgres.c @@ -4388,6 +4388,126 @@ postgres_get_reserve_history (void *cls, } +/** + * Closure for withdraw_amount_by_account_cb() + */ +struct WithdrawAmountByAccountContext +{ + /** + * Function to call on each amount. + */ + TALER_EXCHANGEDB_WithdrawHistoryCallback cb; + + /** + * Closure for @e cb + */ + void *cb_cls; + + /** + * Our plugin's context. + */ + struct PostgresClosure *pg; + + /** + * Set to true on failures. + */ + bool failed; +}; + + +/** + * Helper function for #postgres_select_withdraw_amounts_by_account(). + * To be called with the results of a SELECT statement + * that has returned @a num_results results. + * + * @param cls closure of type `struct WithdrawAmountByAccountContext *` + * @param result the postgres result + * @param num_results the number of results in @a result + */ +static void +withdraw_amount_by_account_cb (void *cls, + PGresult *result, + unsigned int num_results) +{ + struct WithdrawAmountByAccountContext *wac = cls; + struct PostgresClosure *pg = wac->pg; + + for (unsigned int i = 0; num_results; i++) + { + struct TALER_Amount val; + struct GNUNET_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_AMOUNT ("val", + &val), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_OK != + GNUNET_PQ_extract_result (result, + rs, + i)) + { + GNUNET_break (0); + wac->failed = true; + return; + } + wac->cb (wac->cb_cls, + &val); + } +} + + +/** + * Find out all of the amounts that have been withdrawn + * so far from the same bank account that created the + * given reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param reserve_pub reserve to select withdrawals by + * @param duration how far back should we select withdrawals + * @param cb function to call on each amount withdrawn + * @param cb_cls closure for @a cb + * @return transaction status + */ +static enum GNUNET_DB_QueryStatus +postgres_select_withdraw_amounts_by_account ( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct GNUNET_TIME_Relative duration, + TALER_EXCHANGEDB_WithdrawHistoryCallback cb, + void *cb_cls) +{ + struct PostgresClosure *pg = cls; + struct WithdrawAmountByAccountContext wac = { + .pg = pg, + .cb = cb, + .cb_cls = cb_cls + }; + struct GNUNET_TIME_Absolute start + = GNUNET_TIME_absolute_subtract (GNUNET_TIME_absolute_get (), + duration); + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (reserve_pub), + GNUNET_PQ_query_param_absolute_time (&start), + GNUNET_PQ_query_param_end + }; + enum GNUNET_DB_QueryStatus qs; + + qs = GNUNET_PQ_eval_prepared_multi_select ( + pg->conn, + "select_XXX", + params, + &withdraw_amount_by_account_cb, + &wac); + + if (wac.failed) + { + GNUNET_break (0); + qs = GNUNET_DB_STATUS_HARD_ERROR; + } + return qs; +} + + /** * Check if we have the specified deposit already in the database. * @@ -10957,6 +11077,8 @@ libtaler_plugin_exchangedb_postgres_init (void *cls) plugin->get_withdraw_info = &postgres_get_withdraw_info; plugin->insert_withdraw_info = &postgres_insert_withdraw_info; plugin->get_reserve_history = &postgres_get_reserve_history; + plugin->select_withdraw_amounts_by_account + = &postgres_select_withdraw_amounts_by_account; plugin->free_reserve_history = &common_free_reserve_history; plugin->count_known_coins = &postgres_count_known_coins; plugin->ensure_coin_known = &postgres_ensure_coin_known; diff --git a/src/include/taler_exchangedb_plugin.h b/src/include/taler_exchangedb_plugin.h index d94a985d8..34196aadd 100644 --- a/src/include/taler_exchangedb_plugin.h +++ b/src/include/taler_exchangedb_plugin.h @@ -2011,6 +2011,18 @@ typedef enum GNUNET_GenericReturnValue const struct TALER_WireTransferIdentifierRawP *wtid); +/** + * Function called with the amounts historically + * withdrawn from the same origin account. + * + * @param cls closure + * @param val one of the withdrawn amounts + */ +typedef void +(*TALER_EXCHANGEDB_WithdrawHistoryCallback)( + void *cls, + const struct TALER_Amount *val); + /** * Function called with details about expired reserves. * @@ -2450,6 +2462,27 @@ struct TALER_EXCHANGEDB_Plugin struct TALER_EXCHANGEDB_ReserveHistory **rhp); + /** + * Find out all of the amounts that have been withdrawn + * so far from the same bank account that created the + * given reserve. + * + * @param cls closure + * @param reserve_pub reserve to select withdrawals by + * @param duration how far back should we select withdrawals + * @param cb function to call on each amount withdrawn + * @param cb_cls closure for @a cb + * @return transaction status + */ + enum GNUNET_DB_QueryStatus + (*select_withdraw_amounts_by_account)( + void *cls, + const struct TALER_ReservePublicKeyP *reserve_pub, + struct GNUNET_TIME_Relative duration, + TALER_EXCHANGEDB_WithdrawHistoryCallback cb, + void *cb_cls); + + /** * Free memory associated with the given reserve history. *