taler-exchange-httpd_reserves_close.c (15168B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2022 Taler Systems SA 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_reserves_close.c 18 * @brief Handle /reserves/$RESERVE_PUB/close requests 19 * @author Florian Dold 20 * @author Benedikt Mueller 21 * @author Christian Grothoff 22 */ 23 #include "taler/platform.h" 24 #include <gnunet/gnunet_util_lib.h> 25 #include <jansson.h> 26 #include "taler/taler_kyclogic_lib.h" 27 #include "taler/taler_mhd_lib.h" 28 #include "taler/taler_json_lib.h" 29 #include "taler/taler_dbevents.h" 30 #include "taler-exchange-httpd_common_kyc.h" 31 #include "taler-exchange-httpd_keys.h" 32 #include "taler-exchange-httpd_reserves_close.h" 33 #include "taler-exchange-httpd_responses.h" 34 35 36 /** 37 * How far do we allow a client's time to be off when 38 * checking the request timestamp? 39 */ 40 #define TIMESTAMP_TOLERANCE \ 41 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15) 42 43 44 /** 45 * Closure for #reserve_close_transaction. 46 */ 47 struct ReserveCloseContext 48 { 49 50 /** 51 * Kept in a DLL. 52 */ 53 struct ReserveCloseContext *next; 54 55 /** 56 * Kept in a DLL. 57 */ 58 struct ReserveCloseContext *prev; 59 60 /** 61 * Our request context. 62 */ 63 struct TEH_RequestContext *rc; 64 65 /** 66 * Handle for legitimization check. 67 */ 68 struct TEH_LegitimizationCheckHandle *lch; 69 70 /** 71 * Where to wire the funds, may be NULL. 72 */ 73 struct TALER_FullPayto payto_uri; 74 75 /** 76 * Response to return. Note that the response must 77 * be queued or destroyed by the callee. NULL 78 * if the legitimization check was successful and the handler should return 79 * a handler-specific result. 80 */ 81 struct MHD_Response *response; 82 83 /** 84 * Public key of the reserve the inquiry is about. 85 */ 86 struct TALER_ReservePublicKeyP reserve_pub; 87 88 /** 89 * Timestamp of the request. 90 */ 91 struct GNUNET_TIME_Timestamp timestamp; 92 93 /** 94 * Client signature approving the request. 95 */ 96 struct TALER_ReserveSignatureP reserve_sig; 97 98 /** 99 * Amount that will be wired (after closing fees). 100 */ 101 struct TALER_Amount wire_amount; 102 103 /** 104 * Current balance of the reserve. 105 */ 106 struct TALER_Amount balance; 107 108 /** 109 * Hash of the @e payto_uri, if given (otherwise zero). 110 */ 111 struct TALER_FullPaytoHashP h_payto; 112 113 /** 114 * KYC status for the request. 115 */ 116 struct TALER_EXCHANGEDB_KycStatus kyc; 117 118 /** 119 * Hash of the payto-URI that was used for the KYC decision. 120 */ 121 struct TALER_NormalizedPaytoHashP kyc_payto; 122 123 /** 124 * Query status from the amount_it() helper function. 125 */ 126 enum GNUNET_DB_QueryStatus qs; 127 128 /** 129 * HTTP status code for @a response, or 0 130 */ 131 unsigned int http_status; 132 133 /** 134 * Set to true if the request was suspended. 135 */ 136 bool suspended; 137 138 /** 139 * Set to true if the request was suspended. 140 */ 141 bool resumed; 142 }; 143 144 145 /** 146 * Kept in a DLL. 147 */ 148 static struct ReserveCloseContext *rcc_head; 149 150 /** 151 * Kept in a DLL. 152 */ 153 static struct ReserveCloseContext *rcc_tail; 154 155 156 void 157 TEH_reserves_close_cleanup () 158 { 159 struct ReserveCloseContext *rcc; 160 161 while (NULL != (rcc = rcc_head)) 162 { 163 GNUNET_CONTAINER_DLL_remove (rcc_head, 164 rcc_tail, 165 rcc); 166 MHD_resume_connection (rcc->rc->connection); 167 } 168 } 169 170 171 /** 172 * Send reserve close to client. 173 * 174 * @param rhc reserve close to return 175 * @return MHD result code 176 */ 177 static MHD_RESULT 178 reply_reserve_close_success ( 179 const struct ReserveCloseContext *rhc) 180 { 181 struct MHD_Connection *connection = rhc->rc->connection; 182 return TALER_MHD_REPLY_JSON_PACK ( 183 connection, 184 MHD_HTTP_OK, 185 TALER_JSON_pack_amount ("wire_amount", 186 &rhc->wire_amount)); 187 } 188 189 190 /** 191 * Function called with the result of a legitimization 192 * check. 193 * 194 * @param cls closure 195 * @param lcr legitimization check result 196 */ 197 static void 198 reserve_close_legi_cb ( 199 void *cls, 200 const struct TEH_LegitimizationCheckResult *lcr) 201 { 202 struct ReserveCloseContext *rcc = cls; 203 204 rcc->lch = NULL; 205 rcc->http_status = lcr->http_status; 206 rcc->response = lcr->response; 207 rcc->kyc = lcr->kyc; 208 GNUNET_CONTAINER_DLL_remove (rcc_head, 209 rcc_tail, 210 rcc); 211 MHD_resume_connection (rcc->rc->connection); 212 rcc->resumed = true; 213 rcc->suspended = false; 214 TALER_MHD_daemon_trigger (); 215 } 216 217 218 /** 219 * Function called to iterate over KYC-relevant 220 * transaction amounts for a particular time range. 221 * Called within a database transaction, so must 222 * not start a new one. 223 * 224 * @param cls closure, identifies the event type and 225 * account to iterate over events for 226 * @param limit maximum time-range for which events 227 * should be fetched (timestamp in the past) 228 * @param cb function to call on each event found, 229 * events must be returned in reverse chronological 230 * order 231 * @param cb_cls closure for @a cb 232 * @return transaction status 233 */ 234 static enum GNUNET_DB_QueryStatus 235 amount_it (void *cls, 236 struct GNUNET_TIME_Absolute limit, 237 TALER_EXCHANGEDB_KycAmountCallback cb, 238 void *cb_cls) 239 { 240 struct ReserveCloseContext *rcc = cls; 241 enum GNUNET_GenericReturnValue ret; 242 243 ret = cb (cb_cls, 244 &rcc->balance, 245 GNUNET_TIME_absolute_get ()); 246 GNUNET_break (GNUNET_SYSERR != ret); 247 if (GNUNET_OK != ret) 248 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 249 rcc->qs 250 = TEH_plugin->iterate_reserve_close_info ( 251 TEH_plugin->cls, 252 &rcc->kyc_payto, 253 limit, 254 cb, 255 cb_cls); 256 return rcc->qs; 257 } 258 259 260 /** 261 * Function implementing /reserves/$RID/close transaction. Given the public 262 * key of a reserve, return the associated transaction close. Runs the 263 * transaction logic; IF it returns a non-error code, the transaction logic 264 * MUST NOT queue a MHD response. IF it returns an hard error, the 265 * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it 266 * returns the soft error code, the function MAY be called again to retry and 267 * MUST not queue a MHD response. 268 * 269 * @param cls a `struct ReserveCloseContext *` 270 * @param connection MHD request which triggered the transaction 271 * @param[out] mhd_ret set to MHD response status for @a connection, 272 * if transaction failed (!); unused 273 * @return transaction status 274 */ 275 static enum GNUNET_DB_QueryStatus 276 reserve_close_transaction ( 277 void *cls, 278 struct MHD_Connection *connection, 279 MHD_RESULT *mhd_ret) 280 { 281 struct ReserveCloseContext *rcc = cls; 282 enum GNUNET_DB_QueryStatus qs; 283 struct TALER_FullPayto payto_uri = { 284 .full_payto = NULL 285 }; 286 const struct TALER_WireFeeSet *wf; 287 288 qs = TEH_plugin->select_reserve_close_info ( 289 TEH_plugin->cls, 290 &rcc->reserve_pub, 291 &rcc->balance, 292 &payto_uri); 293 switch (qs) 294 { 295 case GNUNET_DB_STATUS_HARD_ERROR: 296 GNUNET_break (0); 297 *mhd_ret 298 = TALER_MHD_reply_with_error ( 299 connection, 300 MHD_HTTP_INTERNAL_SERVER_ERROR, 301 TALER_EC_GENERIC_DB_FETCH_FAILED, 302 "select_reserve_close_info"); 303 return qs; 304 case GNUNET_DB_STATUS_SOFT_ERROR: 305 return qs; 306 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 307 *mhd_ret 308 = TALER_MHD_reply_with_error ( 309 connection, 310 MHD_HTTP_NOT_FOUND, 311 TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN, 312 NULL); 313 return GNUNET_DB_STATUS_HARD_ERROR; 314 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 315 break; 316 } 317 318 if ( (NULL == rcc->payto_uri.full_payto) && 319 (NULL == payto_uri.full_payto) ) 320 { 321 *mhd_ret 322 = TALER_MHD_reply_with_error ( 323 connection, 324 MHD_HTTP_CONFLICT, 325 TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT, 326 NULL); 327 return GNUNET_DB_STATUS_HARD_ERROR; 328 } 329 330 if ( (! rcc->resumed) && 331 (NULL != rcc->payto_uri.full_payto) && 332 ( (NULL == payto_uri.full_payto) || 333 (0 != TALER_full_payto_cmp (payto_uri, 334 rcc->payto_uri)) ) ) 335 { 336 /* KYC check may be needed: we're not returning 337 the money to the account that funded the reserve 338 in the first place. */ 339 340 TALER_full_payto_normalize_and_hash (rcc->payto_uri, 341 &rcc->kyc_payto); 342 rcc->lch = TEH_legitimization_check ( 343 &rcc->rc->async_scope_id, 344 TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE, 345 rcc->payto_uri, 346 &rcc->kyc_payto, 347 NULL, /* no account_pub: this is about the origin/destination account */ 348 &amount_it, 349 rcc, 350 &reserve_close_legi_cb, 351 rcc); 352 GNUNET_assert (NULL != rcc->lch); 353 GNUNET_CONTAINER_DLL_insert (rcc_head, 354 rcc_tail, 355 rcc); 356 MHD_suspend_connection (rcc->rc->connection); 357 rcc->suspended = true; 358 GNUNET_free (payto_uri.full_payto); 359 return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS; 360 } 361 rcc->kyc.ok = true; 362 if (NULL == rcc->payto_uri.full_payto) 363 rcc->payto_uri = payto_uri; 364 365 { 366 char *method; 367 368 method = TALER_payto_get_method (rcc->payto_uri.full_payto); 369 wf = TEH_wire_fees_by_time (rcc->timestamp, 370 method); 371 if (NULL == wf) 372 { 373 GNUNET_break (0); 374 *mhd_ret = TALER_MHD_reply_with_error ( 375 connection, 376 MHD_HTTP_INTERNAL_SERVER_ERROR, 377 TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED, 378 method); 379 GNUNET_free (method); 380 GNUNET_free (payto_uri.full_payto); 381 return GNUNET_DB_STATUS_HARD_ERROR; 382 } 383 GNUNET_free (method); 384 } 385 386 if (0 > 387 TALER_amount_subtract (&rcc->wire_amount, 388 &rcc->balance, 389 &wf->closing)) 390 { 391 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 392 "Client attempted to close reserve with insufficient balance.\n"); 393 GNUNET_assert (GNUNET_OK == 394 TALER_amount_set_zero (TEH_currency, 395 &rcc->wire_amount)); 396 *mhd_ret = reply_reserve_close_success (rcc); 397 GNUNET_free (payto_uri.full_payto); 398 return GNUNET_DB_STATUS_HARD_ERROR; 399 } 400 401 qs = TEH_plugin->insert_close_request ( 402 TEH_plugin->cls, 403 &rcc->reserve_pub, 404 payto_uri, 405 &rcc->reserve_sig, 406 rcc->timestamp, 407 &rcc->balance, 408 &wf->closing); 409 GNUNET_free (payto_uri.full_payto); 410 rcc->payto_uri.full_payto = NULL; 411 if (GNUNET_DB_STATUS_HARD_ERROR == qs) 412 { 413 GNUNET_break (0); 414 *mhd_ret 415 = TALER_MHD_reply_with_error (connection, 416 MHD_HTTP_INTERNAL_SERVER_ERROR, 417 TALER_EC_GENERIC_DB_FETCH_FAILED, 418 "insert_close_request"); 419 return qs; 420 } 421 if (qs <= 0) 422 { 423 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 424 return qs; 425 } 426 return qs; 427 } 428 429 430 /** 431 * Cleanup routine. Function called 432 * upon completion of the request that should 433 * clean up @a rh_ctx. Can be NULL. 434 * 435 * @param rc request to clean up context for 436 */ 437 static void 438 reserve_close_cleanup (struct TEH_RequestContext *rc) 439 { 440 struct ReserveCloseContext *rcc = rc->rh_ctx; 441 442 if (NULL != rcc->lch) 443 { 444 TEH_legitimization_check_cancel (rcc->lch); 445 rcc->lch = NULL; 446 } 447 GNUNET_free (rcc); 448 } 449 450 451 MHD_RESULT 452 TEH_handler_reserves_close ( 453 struct TEH_RequestContext *rc, 454 const struct TALER_ReservePublicKeyP *reserve_pub, 455 const json_t *root) 456 { 457 struct ReserveCloseContext *rcc = rc->rh_ctx; 458 MHD_RESULT mhd_ret; 459 460 if (NULL == rcc) 461 { 462 rcc = GNUNET_new (struct ReserveCloseContext); 463 rc->rh_ctx = rcc; 464 rc->rh_cleaner = &reserve_close_cleanup; 465 rcc->reserve_pub = *reserve_pub; 466 rcc->rc = rc; 467 468 { 469 struct GNUNET_JSON_Specification spec[] = { 470 GNUNET_JSON_spec_timestamp ("request_timestamp", 471 &rcc->timestamp), 472 GNUNET_JSON_spec_mark_optional ( 473 TALER_JSON_spec_full_payto_uri ("payto_uri", 474 &rcc->payto_uri), 475 NULL), 476 GNUNET_JSON_spec_fixed_auto ("reserve_sig", 477 &rcc->reserve_sig), 478 GNUNET_JSON_spec_end () 479 }; 480 481 { 482 enum GNUNET_GenericReturnValue res; 483 484 res = TALER_MHD_parse_json_data (rc->connection, 485 root, 486 spec); 487 if (GNUNET_SYSERR == res) 488 { 489 GNUNET_break (0); 490 return MHD_NO; /* hard failure */ 491 } 492 if (GNUNET_NO == res) 493 { 494 GNUNET_break_op (0); 495 return MHD_YES; /* failure */ 496 } 497 } 498 } 499 500 { 501 struct GNUNET_TIME_Timestamp now; 502 503 now = GNUNET_TIME_timestamp_get (); 504 if (! GNUNET_TIME_absolute_approx_eq ( 505 now.abs_time, 506 rcc->timestamp.abs_time, 507 TIMESTAMP_TOLERANCE)) 508 { 509 GNUNET_break_op (0); 510 return TALER_MHD_reply_with_error ( 511 rc->connection, 512 MHD_HTTP_BAD_REQUEST, 513 TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW, 514 NULL); 515 } 516 } 517 518 if (NULL != rcc->payto_uri.full_payto) 519 TALER_full_payto_hash (rcc->payto_uri, 520 &rcc->h_payto); 521 if (GNUNET_OK != 522 TALER_wallet_reserve_close_verify ( 523 rcc->timestamp, 524 &rcc->h_payto, 525 &rcc->reserve_pub, 526 &rcc->reserve_sig)) 527 { 528 GNUNET_break_op (0); 529 return TALER_MHD_reply_with_error ( 530 rc->connection, 531 MHD_HTTP_FORBIDDEN, 532 TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE, 533 NULL); 534 } 535 } 536 if (NULL != rcc->response) 537 return MHD_queue_response (rc->connection, 538 rcc->http_status, 539 rcc->response); 540 if (rcc->resumed && 541 (! rcc->kyc.ok) ) 542 { 543 if (0 == rcc->kyc.requirement_row) 544 { 545 GNUNET_break (0); 546 return TALER_MHD_reply_with_error ( 547 rc->connection, 548 MHD_HTTP_INTERNAL_SERVER_ERROR, 549 TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE, 550 "requirement row not set"); 551 } 552 return TEH_RESPONSE_reply_kyc_required ( 553 rc->connection, 554 &rcc->kyc_payto, 555 &rcc->kyc, 556 false); 557 } 558 559 if (GNUNET_OK != 560 TEH_DB_run_transaction (rc->connection, 561 "reserve close", 562 TEH_MT_REQUEST_OTHER, 563 &mhd_ret, 564 &reserve_close_transaction, 565 rcc)) 566 { 567 return mhd_ret; 568 } 569 if (rcc->suspended) 570 return MHD_YES; 571 return reply_reserve_close_success (rcc); 572 } 573 574 575 /* end of taler-exchange-httpd_reserves_close.c */