anastasis-httpd_truth-challenge.c (41785B)
1 /* 2 This file is part of Anastasis 3 Copyright (C) 2019-2022 Anastasis SARL 4 5 Anastasis 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 Anastasis 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 Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file anastasis-httpd_truth-challenge.c 18 * @brief functions to handle incoming requests on /truth/$TID/challenge 19 * @author Dennis Neufeld 20 * @author Dominik Meister 21 * @author Christian Grothoff 22 */ 23 #include "platform.h" 24 struct ChallengeContext; 25 struct RefundEntry; 26 #define TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE struct \ 27 ChallengeContext 28 #define TALER_MERCHANT_POST_PRIVATE_ORDERS_REFUND_RESULT_CLOSURE struct \ 29 RefundEntry 30 #define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct ChallengeContext 31 #include "anastasis-httpd.h" 32 #include "anastasis_service.h" 33 #include "anastasis-httpd_truth.h" 34 #include <gnunet/gnunet_util_lib.h> 35 #include <gnunet/gnunet_rest_lib.h> 36 #include "anastasis_authorization_lib.h" 37 #include <taler/taler_merchant_service.h> 38 #include <taler/taler_json_lib.h> 39 #include <taler/taler_mhd_lib.h> 40 #include <taler/merchant/post-private-orders.h> 41 #include <taler/merchant/get-private-orders-ORDER_ID.h> 42 #include <taler/merchant/post-private-orders-ORDER_ID-refund.h> 43 44 /** 45 * What is the maximum frequency at which we allow 46 * clients to attempt to answer security questions? 47 */ 48 #define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \ 49 GNUNET_TIME_UNIT_SECONDS, 30) 50 51 /** 52 * How long should the wallet check for auto-refunds before giving up? 53 */ 54 #define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ 55 GNUNET_TIME_UNIT_MINUTES, 2) 56 57 /** 58 * How long should the wallet check for payment before giving up? 59 */ 60 #define PAYMENT_TIMEOUT GNUNET_TIME_relative_multiply ( \ 61 GNUNET_TIME_UNIT_SECONDS, 15) 62 63 64 /** 65 * How many retries do we allow per code? 66 */ 67 #define INITIAL_RETRY_COUNTER 3 68 69 70 struct ChallengeContext 71 { 72 73 /** 74 * Payment Identifier 75 */ 76 struct ANASTASIS_PaymentSecretP payment_identifier; 77 78 /** 79 * Public key of the challenge which is solved. 80 */ 81 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; 82 83 /** 84 * Key to decrypt the truth. 85 */ 86 struct ANASTASIS_CRYPTO_TruthKeyP truth_key; 87 88 /** 89 * Cost for paying the challenge, one entry per currency the wallet may 90 * settle in. Borrowed from the authorization plugin or from 91 * #AH_question_costs, both of which outlive the request. 92 */ 93 const struct TALER_AmountList *challenge_costs; 94 95 /** 96 * What the wallet actually paid, in the currency it chose. Only 97 * meaningful if @e have_payment is true. 98 */ 99 struct TALER_Amount challenge_paid; 100 101 /** 102 * Our handler context. 103 */ 104 struct TM_HandlerContext *hc; 105 106 /** 107 * Opaque parsing context. 108 */ 109 void *opaque_post_parsing_context; 110 111 /** 112 * Uploaded JSON data, NULL if upload is not yet complete. 113 */ 114 json_t *root; 115 116 /** 117 * Kept in DLL for shutdown handling while suspended. 118 */ 119 struct ChallengeContext *next; 120 121 /** 122 * Kept in DLL for shutdown handling while suspended. 123 */ 124 struct ChallengeContext *prev; 125 126 /** 127 * Connection handle for closing or resuming 128 */ 129 struct MHD_Connection *connection; 130 131 /** 132 * Reference to the authorization plugin which was loaded 133 */ 134 struct ANASTASIS_AuthorizationPlugin *authorization; 135 136 /** 137 * Status of the authorization 138 */ 139 struct ANASTASIS_AUTHORIZATION_State *as; 140 141 /** 142 * Used while we are awaiting proposal creation. 143 */ 144 struct TALER_MERCHANT_PostPrivateOrdersHandle *po; 145 146 /** 147 * Used while we are waiting payment. 148 */ 149 struct TALER_MERCHANT_GetPrivateOrderHandle *cpo; 150 151 /** 152 * HTTP response code to use on resume, if non-NULL. 153 */ 154 struct MHD_Response *resp; 155 156 /** 157 * Our entry in the #to_heap, or NULL. 158 */ 159 struct GNUNET_CONTAINER_HeapNode *hn; 160 161 /** 162 * When should this request time out? 163 */ 164 struct GNUNET_TIME_Absolute timeout; 165 166 /** 167 * Random authorization code we are using. 168 */ 169 uint64_t code; 170 171 /** 172 * HTTP response code to use on resume, if resp is set. 173 */ 174 unsigned int response_code; 175 176 /** 177 * true if client did not provide a payment secret / order ID. 178 */ 179 bool no_payment_identifier_provided; 180 181 /** 182 * True if @e challenge_paid holds what the wallet paid for this 183 * challenge, as read back from the database. 184 */ 185 bool have_payment; 186 187 /** 188 * True if this entry is in the #gc_head DLL. 189 */ 190 bool in_list; 191 192 /** 193 * True if this entry is currently suspended. 194 */ 195 bool suspended; 196 197 }; 198 199 200 /** 201 * Information we track for refunds. 202 */ 203 struct RefundEntry 204 { 205 /** 206 * Kept in a DLL. 207 */ 208 struct RefundEntry *next; 209 210 /** 211 * Kept in a DLL. 212 */ 213 struct RefundEntry *prev; 214 215 /** 216 * Operation handle. 217 */ 218 struct TALER_MERCHANT_PostPrivateOrdersRefundHandle *ro; 219 220 /** 221 * Which order is being refunded. 222 */ 223 char *order_id; 224 225 /** 226 * Payment Identifier 227 */ 228 struct ANASTASIS_PaymentSecretP payment_identifier; 229 230 /** 231 * Public key of the challenge which is solved. 232 */ 233 struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; 234 }; 235 236 237 /** 238 * Head of linked list of active refund operations. 239 */ 240 static struct RefundEntry *re_head; 241 242 /** 243 * Tail of linked list of active refund operations. 244 */ 245 static struct RefundEntry *re_tail; 246 247 /** 248 * Head of linked list over all authorization processes 249 */ 250 static struct ChallengeContext *gc_head; 251 252 /** 253 * Tail of linked list over all authorization processes 254 */ 255 static struct ChallengeContext *gc_tail; 256 257 /** 258 * Task running #do_timeout(). 259 */ 260 static struct GNUNET_SCHEDULER_Task *to_task; 261 262 263 /** 264 * Generate a response telling the client that answering this 265 * challenge failed because the rate limit has been exceeded. 266 * 267 * @param gc request to answer for 268 * @return MHD status code 269 */ 270 static enum MHD_Result 271 reply_rate_limited (const struct ChallengeContext *gc) 272 { 273 return TALER_MHD_REPLY_JSON_PACK ( 274 gc->connection, 275 MHD_HTTP_TOO_MANY_REQUESTS, 276 TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), 277 GNUNET_JSON_pack_uint64 ("request_limit", 278 gc->authorization->retry_counter), 279 GNUNET_JSON_pack_time_rel ("request_frequency", 280 gc->authorization->code_rotation_period)); 281 } 282 283 284 /** 285 * Timeout requests that are past their due date. 286 * 287 * @param cls NULL 288 */ 289 static void 290 do_timeout (void *cls) 291 { 292 struct ChallengeContext *gc; 293 294 (void) cls; 295 to_task = NULL; 296 while (NULL != 297 (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap))) 298 { 299 if (GNUNET_TIME_absolute_is_future (gc->timeout)) 300 break; 301 if (gc->suspended) 302 { 303 /* Test needed as we may have a "concurrent" 304 wakeup from another task that did not clear 305 this entry from the heap before the 306 response process concluded. */ 307 gc->suspended = false; 308 MHD_resume_connection (gc->connection); 309 } 310 GNUNET_assert (NULL != gc->hn); 311 gc->hn = NULL; 312 GNUNET_assert (gc == 313 GNUNET_CONTAINER_heap_remove_root (AH_to_heap)); 314 } 315 if (NULL == gc) 316 return; 317 to_task = GNUNET_SCHEDULER_add_at (gc->timeout, 318 &do_timeout, 319 NULL); 320 } 321 322 323 void 324 AH_truth_challenge_shutdown (void) 325 { 326 struct ChallengeContext *gc; 327 struct RefundEntry *re; 328 329 while (NULL != (re = re_head)) 330 { 331 GNUNET_CONTAINER_DLL_remove (re_head, 332 re_tail, 333 re); 334 if (NULL != re->ro) 335 { 336 TALER_MERCHANT_post_private_orders_refund_cancel (re->ro); 337 re->ro = NULL; 338 } 339 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 340 "Refund `%s' failed due to shutdown\n", 341 re->order_id); 342 GNUNET_free (re->order_id); 343 GNUNET_free (re); 344 } 345 346 while (NULL != (gc = gc_head)) 347 { 348 GNUNET_CONTAINER_DLL_remove (gc_head, 349 gc_tail, 350 gc); 351 gc->in_list = false; 352 if (NULL != gc->cpo) 353 { 354 TALER_MERCHANT_get_private_order_cancel (gc->cpo); 355 gc->cpo = NULL; 356 } 357 if (NULL != gc->po) 358 { 359 TALER_MERCHANT_post_private_orders_cancel (gc->po); 360 gc->po = NULL; 361 } 362 if (gc->suspended) 363 { 364 gc->suspended = false; 365 MHD_resume_connection (gc->connection); 366 } 367 if (NULL != gc->as) 368 { 369 gc->authorization->cleanup (gc->as); 370 gc->as = NULL; 371 gc->authorization = NULL; 372 } 373 } 374 ANASTASIS_authorization_plugin_shutdown (); 375 if (NULL != to_task) 376 { 377 GNUNET_SCHEDULER_cancel (to_task); 378 to_task = NULL; 379 } 380 } 381 382 383 /** 384 * Callback to process a POST /orders/ID/refund request 385 * 386 * @param cls closure with a `struct RefundEntry *` 387 * @param rr response details 388 */ 389 static void 390 refund_cb ( 391 struct RefundEntry *re, 392 const struct TALER_MERCHANT_PostPrivateOrdersRefundResponse *rr) 393 { 394 395 re->ro = NULL; 396 switch (rr->hr.http_status) 397 { 398 case MHD_HTTP_OK: 399 { 400 enum GNUNET_DB_QueryStatus qs; 401 402 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 403 "Refund `%s' succeeded\n", 404 re->order_id); 405 qs = ANASTASIS_DB_update_to_challenge_payment_refunded ( 406 &re->truth_uuid, 407 &re->payment_identifier); 408 switch (qs) 409 { 410 case GNUNET_DB_STATUS_HARD_ERROR: 411 GNUNET_break (0); 412 break; 413 case GNUNET_DB_STATUS_SOFT_ERROR: 414 GNUNET_break (0); 415 break; 416 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 417 GNUNET_break (0); 418 break; 419 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 420 break; 421 } 422 } 423 break; 424 default: 425 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 426 "Refund `%s' failed with HTTP status %u: %s (#%u)\n", 427 re->order_id, 428 rr->hr.http_status, 429 rr->hr.hint, 430 (unsigned int) rr->hr.ec); 431 break; 432 } 433 GNUNET_CONTAINER_DLL_remove (re_head, 434 re_tail, 435 re); 436 GNUNET_free (re->order_id); 437 GNUNET_free (re); 438 } 439 440 441 /** 442 * Start to give a refund for the challenge created by @a gc. 443 * 444 * @param gc request where we failed and should now grant a refund for 445 */ 446 static void 447 begin_refund (const struct ChallengeContext *gc) 448 { 449 struct RefundEntry *re; 450 451 re = GNUNET_new (struct RefundEntry); 452 re->order_id = GNUNET_STRINGS_data_to_string_alloc ( 453 &gc->payment_identifier, 454 sizeof (gc->payment_identifier)); 455 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 456 "Challenge execution failed, triggering refund for order `%s'\n", 457 re->order_id); 458 re->payment_identifier = gc->payment_identifier; 459 re->truth_uuid = gc->truth_uuid; 460 /* Refund what was actually paid, in the currency it was paid in --- 461 with several currencies on offer the configured price no longer 462 identifies it. The fallback is for a payment_plugin_managed method, 463 where the order was never ours to begin with. */ 464 re->ro = TALER_MERCHANT_post_private_orders_refund_create ( 465 AH_ctx, 466 AH_backend_url, 467 re->order_id, 468 gc->have_payment 469 ? &gc->challenge_paid 470 : AH_primary_price (gc->challenge_costs), 471 "failed to issue challenge"); 472 { 473 enum TALER_ErrorCode ec; 474 475 ec = TALER_MERCHANT_post_private_orders_refund_start (re->ro, 476 &refund_cb, 477 re); 478 if (TALER_EC_NONE != ec) 479 { 480 TALER_MERCHANT_post_private_orders_refund_cancel (re->ro); 481 re->ro = NULL; 482 GNUNET_break (0); 483 GNUNET_free (re->order_id); 484 GNUNET_free (re); 485 return; 486 } 487 } 488 GNUNET_CONTAINER_DLL_insert (re_head, 489 re_tail, 490 re); 491 } 492 493 494 /** 495 * Callback used to notify the application about completed requests. 496 * Cleans up the requests data structures. 497 * 498 * @param hc 499 */ 500 static void 501 request_done (struct TM_HandlerContext *hc) 502 { 503 struct ChallengeContext *gc = hc->ctx; 504 505 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 506 "Request completed\n"); 507 if (NULL == gc) 508 return; 509 hc->cc = NULL; 510 GNUNET_assert (! gc->suspended); 511 if (gc->in_list) 512 { 513 GNUNET_CONTAINER_DLL_remove (gc_head, 514 gc_tail, 515 gc); 516 gc->in_list = false; 517 } 518 if (NULL != gc->hn) 519 { 520 GNUNET_assert (gc == 521 GNUNET_CONTAINER_heap_remove_node (gc->hn)); 522 gc->hn = NULL; 523 } 524 if (NULL != gc->as) 525 { 526 gc->authorization->cleanup (gc->as); 527 gc->authorization = NULL; 528 gc->as = NULL; 529 } 530 if (NULL != gc->cpo) 531 { 532 TALER_MERCHANT_get_private_order_cancel (gc->cpo); 533 gc->cpo = NULL; 534 } 535 if (NULL != gc->po) 536 { 537 TALER_MERCHANT_post_private_orders_cancel (gc->po); 538 gc->po = NULL; 539 } 540 if (NULL != gc->root) 541 { 542 json_decref (gc->root); 543 gc->root = NULL; 544 } 545 TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context); 546 GNUNET_free (gc); 547 hc->ctx = NULL; 548 } 549 550 551 /** 552 * Transmit a payment request for @a order_id on @a connection 553 * 554 * @param gc context to make payment request for 555 */ 556 static void 557 make_payment_request (struct ChallengeContext *gc) 558 { 559 struct MHD_Response *resp; 560 561 resp = MHD_create_response_from_buffer (0, 562 NULL, 563 MHD_RESPMEM_PERSISTENT); 564 GNUNET_assert (NULL != resp); 565 TALER_MHD_add_global_headers (resp, 566 false); 567 { 568 char *hdr; 569 char *order_id; 570 const char *pfx; 571 const char *hn; 572 573 if (0 == strncasecmp ("https://", 574 AH_backend_url, 575 strlen ("https://"))) 576 { 577 pfx = "taler://"; 578 hn = &AH_backend_url[strlen ("https://")]; 579 } 580 else if (0 == strncasecmp ("http://", 581 AH_backend_url, 582 strlen ("http://"))) 583 { 584 pfx = "taler+http://"; 585 hn = &AH_backend_url[strlen ("http://")]; 586 } 587 else 588 { 589 /* This invariant holds as per check in anastasis-httpd.c */ 590 GNUNET_assert (0); 591 } 592 /* This invariant holds as per check in anastasis-httpd.c */ 593 GNUNET_assert (0 != strlen (hn)); 594 595 order_id = GNUNET_STRINGS_data_to_string_alloc ( 596 &gc->payment_identifier, 597 sizeof (gc->payment_identifier)); 598 GNUNET_asprintf (&hdr, 599 "%spay/%s%s/", 600 pfx, 601 hn, 602 order_id); 603 GNUNET_free (order_id); 604 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 605 "Sending payment request `%s'\n", 606 hdr); 607 GNUNET_break (MHD_YES == 608 MHD_add_response_header (resp, 609 ANASTASIS_HTTP_HEADER_TALER, 610 hdr)); 611 GNUNET_free (hdr); 612 } 613 gc->resp = resp; 614 gc->response_code = MHD_HTTP_PAYMENT_REQUIRED; 615 } 616 617 618 /** 619 * Callbacks of this type are used to serve the result of submitting a 620 * /contract request to a merchant. 621 * 622 * @param cls our `struct ChallengeContext` 623 * @param por response details 624 */ 625 static void 626 proposal_cb (struct ChallengeContext *gc, 627 const struct TALER_MERCHANT_PostPrivateOrdersResponse *por) 628 { 629 enum GNUNET_DB_QueryStatus qs; 630 631 gc->po = NULL; 632 GNUNET_assert (gc->in_list); 633 GNUNET_CONTAINER_DLL_remove (gc_head, 634 gc_tail, 635 gc); 636 gc->in_list = false; 637 GNUNET_assert (gc->suspended); 638 gc->suspended = false; 639 MHD_resume_connection (gc->connection); 640 AH_trigger_daemon (NULL); 641 if (MHD_HTTP_OK != por->hr.http_status) 642 { 643 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 644 "Backend returned status %u/%d\n", 645 por->hr.http_status, 646 (int) por->hr.ec); 647 GNUNET_break (0); 648 gc->resp = TALER_MHD_MAKE_JSON_PACK ( 649 GNUNET_JSON_pack_uint64 ("code", 650 TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR), 651 GNUNET_JSON_pack_string ("hint", 652 "Failed to setup order with merchant backend"), 653 GNUNET_JSON_pack_uint64 ("backend-ec", 654 por->hr.ec), 655 GNUNET_JSON_pack_uint64 ("backend-http-status", 656 por->hr.http_status), 657 GNUNET_JSON_pack_allow_null ( 658 GNUNET_JSON_pack_object_steal ("backend-reply", 659 (json_t *) por->hr.reply))); 660 gc->response_code = MHD_HTTP_BAD_GATEWAY; 661 return; 662 } 663 /* Records the asking price in the primary currency; which currency the 664 wallet settles in is only known once it has paid, and is written by 665 ANASTASIS_DB_update_to_challenge_payment_paid() then. */ 666 qs = ANASTASIS_DB_insert_challenge_payment ( 667 &gc->truth_uuid, 668 &gc->payment_identifier, 669 AH_primary_price (gc->challenge_costs)); 670 if (0 >= qs) 671 { 672 GNUNET_break (0); 673 gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, 674 "insert challenge payment"); 675 gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 676 return; 677 } 678 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 679 "Setup fresh order, creating payment request\n"); 680 make_payment_request (gc); 681 } 682 683 684 /** 685 * Callback to process a GET /check-payment request 686 * 687 * @param cls our `struct ChallengeContext` 688 * @param osr order status 689 */ 690 static void 691 check_payment_cb (struct ChallengeContext *gc, 692 const struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 693 694 { 695 const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; 696 697 gc->cpo = NULL; 698 GNUNET_assert (gc->in_list); 699 GNUNET_CONTAINER_DLL_remove (gc_head, 700 gc_tail, 701 gc); 702 gc->in_list = false; 703 GNUNET_assert (gc->suspended); 704 gc->suspended = false; 705 MHD_resume_connection (gc->connection); 706 AH_trigger_daemon (NULL); 707 708 switch (hr->http_status) 709 { 710 case MHD_HTTP_OK: 711 GNUNET_assert (NULL != osr); 712 break; 713 case MHD_HTTP_NOT_FOUND: 714 /* We created this order before, how can it be not found now? */ 715 GNUNET_break (0); 716 gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED, 717 NULL); 718 gc->response_code = MHD_HTTP_BAD_GATEWAY; 719 return; 720 case MHD_HTTP_BAD_GATEWAY: 721 gc->resp = TALER_MHD_make_error ( 722 TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD, 723 NULL); 724 gc->response_code = MHD_HTTP_BAD_GATEWAY; 725 return; 726 case MHD_HTTP_GATEWAY_TIMEOUT: 727 gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT, 728 "Timeout check payment status"); 729 GNUNET_assert (NULL != gc->resp); 730 gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; 731 return; 732 default: 733 { 734 char status[14]; 735 736 GNUNET_snprintf (status, 737 sizeof (status), 738 "%u", 739 hr->http_status); 740 gc->resp = TALER_MHD_make_error ( 741 TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS, 742 status); 743 GNUNET_assert (NULL != gc->resp); 744 gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 745 return; 746 } 747 } 748 749 GNUNET_assert (MHD_HTTP_OK == hr->http_status); 750 switch (osr->details.ok.status) 751 { 752 case TALER_MERCHANT_OSC_PAID: 753 { 754 enum GNUNET_DB_QueryStatus qs; 755 756 if (GNUNET_OK != 757 AH_paid_amount (osr, 758 &gc->challenge_paid)) 759 { 760 GNUNET_break (0); 761 gc->resp = TALER_MHD_make_error ( 762 TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID, 763 "no amount given"); 764 gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 765 return; 766 } 767 gc->have_payment = true; 768 qs = ANASTASIS_DB_update_to_challenge_payment_paid ( 769 &gc->truth_uuid, 770 &gc->payment_identifier, 771 &gc->challenge_paid); 772 if (0 <= qs) 773 { 774 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 775 "Order has been paid, continuing with request processing\n") 776 ; 777 return; /* continue as planned */ 778 } 779 GNUNET_break (0); 780 gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, 781 "update to challenge payment paid"); 782 gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; 783 return; /* continue as planned */ 784 } 785 case TALER_MERCHANT_OSC_CLAIMED: 786 case TALER_MERCHANT_OSC_UNPAID: 787 /* repeat payment request */ 788 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 789 "Order remains unpaid, sending payment request again\n"); 790 make_payment_request (gc); 791 return; 792 } 793 /* should never get here */ 794 GNUNET_break (0); 795 } 796 797 798 /** 799 * Helper function used to ask our backend to begin processing a 800 * payment for the user's account. May perform asynchronous 801 * operations by suspending the connection if required. 802 * 803 * @param gc context to begin payment for. 804 * @return MHD status code 805 */ 806 static enum MHD_Result 807 begin_payment (struct ChallengeContext *gc) 808 { 809 enum GNUNET_DB_QueryStatus qs; 810 char *order_id; 811 812 qs = ANASTASIS_DB_get_pending_challenge_payment ( 813 &gc->truth_uuid, 814 &gc->payment_identifier); 815 if (qs < 0) 816 { 817 GNUNET_break (0); 818 return TALER_MHD_reply_with_error (gc->connection, 819 MHD_HTTP_INTERNAL_SERVER_ERROR, 820 TALER_EC_GENERIC_DB_FETCH_FAILED, 821 "get pending challenge payment"); 822 } 823 GNUNET_assert (! gc->in_list); 824 gc->in_list = true; 825 GNUNET_CONTAINER_DLL_insert (gc_head, 826 gc_tail, 827 gc); 828 GNUNET_assert (! gc->suspended); 829 gc->suspended = true; 830 MHD_suspend_connection (gc->connection); 831 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) 832 { 833 /* We already created the order, check if it was paid */ 834 struct GNUNET_TIME_Relative timeout; 835 836 order_id = GNUNET_STRINGS_data_to_string_alloc ( 837 &gc->payment_identifier, 838 sizeof (gc->payment_identifier)); 839 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 840 "Order exists, checking payment status for order `%s'\n", 841 order_id); 842 timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout); 843 gc->cpo = TALER_MERCHANT_get_private_order_create (AH_ctx, 844 AH_backend_url, 845 order_id); 846 GNUNET_assert (NULL != gc->cpo); 847 GNUNET_assert ( 848 GNUNET_OK == 849 TALER_MERCHANT_get_private_order_set_options ( 850 gc->cpo, 851 TALER_MERCHANT_get_private_order_option_timeout (timeout))); 852 GNUNET_assert (TALER_EC_NONE == 853 TALER_MERCHANT_get_private_order_start (gc->cpo, 854 &check_payment_cb, 855 gc)); 856 } 857 else 858 { 859 /* Create a fresh order */ 860 struct GNUNET_TIME_Timestamp pay_deadline; 861 862 GNUNET_CRYPTO_random_block (&gc->payment_identifier, 863 sizeof (struct ANASTASIS_PaymentSecretP)); 864 order_id = GNUNET_STRINGS_data_to_string_alloc ( 865 &gc->payment_identifier, 866 sizeof (gc->payment_identifier)); 867 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 868 "Creating fresh order `%s'\n", 869 order_id); 870 pay_deadline = GNUNET_TIME_relative_to_timestamp ( 871 ANASTASIS_CHALLENGE_OFFER_LIFETIME); 872 { 873 json_t *order; 874 875 order = AH_make_order (order_id, 876 "challenge fee for anastasis service", 877 gc->challenge_costs); 878 GNUNET_assert (0 == 879 json_object_update_new ( 880 order, 881 GNUNET_JSON_PACK ( 882 GNUNET_JSON_pack_time_rel ("auto_refund", 883 AUTO_REFUND_TIMEOUT), 884 GNUNET_JSON_pack_timestamp ("pay_deadline", 885 pay_deadline)))); 886 gc->po = TALER_MERCHANT_post_private_orders_create (AH_ctx, 887 AH_backend_url, 888 order); 889 json_decref (order); 890 } 891 GNUNET_assert (NULL != gc->po); 892 GNUNET_assert ( 893 GNUNET_OK == 894 TALER_MERCHANT_post_private_orders_set_options ( 895 gc->po, 896 TALER_MERCHANT_post_private_orders_option_create_token (false), 897 TALER_MERCHANT_post_private_orders_option_refund_delay ( 898 AUTO_REFUND_TIMEOUT))); 899 GNUNET_assert (TALER_EC_NONE == 900 TALER_MERCHANT_post_private_orders_start ( 901 gc->po, 902 &proposal_cb, 903 gc)); 904 } 905 GNUNET_free (order_id); 906 AH_trigger_curl (); 907 return MHD_YES; 908 } 909 910 911 /** 912 * Mark @a gc as suspended and update the respective 913 * data structures and jobs. 914 * 915 * @param[in,out] gc context of the suspended operation 916 */ 917 static void 918 gc_suspended (struct ChallengeContext *gc) 919 { 920 gc->suspended = true; 921 if (NULL == AH_to_heap) 922 AH_to_heap = GNUNET_CONTAINER_heap_create ( 923 GNUNET_CONTAINER_HEAP_ORDER_MIN); 924 gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, 925 gc, 926 gc->timeout.abs_value_us); 927 if (NULL != to_task) 928 { 929 GNUNET_SCHEDULER_cancel (to_task); 930 to_task = NULL; 931 } 932 { 933 struct ChallengeContext *rn; 934 935 rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); 936 to_task = GNUNET_SCHEDULER_add_at (rn->timeout, 937 &do_timeout, 938 NULL); 939 } 940 } 941 942 943 /** 944 * Run the authorization method-specific 'process' function and continue 945 * based on its result with generating an HTTP response. 946 * 947 * @param connection the connection we are handling 948 * @param gc our overall handler context 949 */ 950 static enum MHD_Result 951 run_authorization_process (struct MHD_Connection *connection, 952 struct ChallengeContext *gc) 953 { 954 enum ANASTASIS_AUTHORIZATION_ChallengeResult ret; 955 enum GNUNET_DB_QueryStatus qs; 956 957 GNUNET_assert (! gc->suspended); 958 if (NULL == gc->authorization->challenge) 959 { 960 GNUNET_break (0); 961 return TALER_MHD_reply_with_error (gc->connection, 962 MHD_HTTP_INTERNAL_SERVER_ERROR, 963 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, 964 "challenge method not implemented for authorization method"); 965 } 966 ret = gc->authorization->challenge (gc->as, 967 connection); 968 switch (ret) 969 { 970 case ANASTASIS_AUTHORIZATION_CRES_SUCCESS: 971 /* Challenge sent successfully */ 972 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 973 "Authorization request %llu for %s sent successfully\n", 974 (unsigned long long) gc->code, 975 TALER_B2S (&gc->truth_uuid)); 976 qs = ANASTASIS_DB_update_to_challenge_code_sent ( 977 &gc->payment_identifier, 978 &gc->truth_uuid, 979 gc->code); 980 GNUNET_break (0 < qs); 981 gc->authorization->cleanup (gc->as); 982 gc->as = NULL; 983 return MHD_YES; 984 case ANASTASIS_AUTHORIZATION_CRES_FAILED: 985 if (! gc->no_payment_identifier_provided) 986 { 987 begin_refund (gc); 988 } 989 gc->authorization->cleanup (gc->as); 990 gc->as = NULL; 991 return MHD_YES; 992 case ANASTASIS_AUTHORIZATION_CRES_SUSPENDED: 993 /* connection was suspended */ 994 gc_suspended (gc); 995 return MHD_YES; 996 case ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED: 997 /* Challenge sent successfully */ 998 qs = ANASTASIS_DB_update_to_challenge_code_sent ( 999 &gc->payment_identifier, 1000 &gc->truth_uuid, 1001 gc->code); 1002 GNUNET_break (0 < qs); 1003 gc->authorization->cleanup (gc->as); 1004 gc->as = NULL; 1005 return MHD_NO; 1006 case ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED: 1007 gc->authorization->cleanup (gc->as); 1008 gc->as = NULL; 1009 return MHD_NO; 1010 } 1011 GNUNET_break (0); 1012 return MHD_NO; 1013 } 1014 1015 1016 enum MHD_Result 1017 AH_handler_truth_challenge ( 1018 struct MHD_Connection *connection, 1019 struct TM_HandlerContext *hc, 1020 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 1021 const char *upload_data, 1022 size_t *upload_data_size) 1023 { 1024 struct ChallengeContext *gc = hc->ctx; 1025 void *encrypted_truth; 1026 size_t encrypted_truth_size; 1027 void *decrypted_truth; 1028 size_t decrypted_truth_size; 1029 char *truth_mime = NULL; 1030 1031 if (NULL == gc) 1032 { 1033 /* Fresh request, do initial setup */ 1034 gc = GNUNET_new (struct ChallengeContext); 1035 gc->hc = hc; 1036 hc->ctx = gc; 1037 gc->connection = connection; 1038 gc->truth_uuid = *truth_uuid; 1039 gc->hc->cc = &request_done; 1040 gc->timeout = GNUNET_TIME_relative_to_absolute ( 1041 PAYMENT_TIMEOUT); 1042 } /* end of first-time initialization (if NULL == gc) */ 1043 else 1044 { 1045 /* might have been woken up by authorization plugin, 1046 so clear the flag. MDH called us, so we are 1047 clearly no longer suspended */ 1048 gc->suspended = false; 1049 if (NULL != gc->resp) 1050 { 1051 enum MHD_Result ret; 1052 1053 /* We generated a response asynchronously, queue that */ 1054 ret = MHD_queue_response (connection, 1055 gc->response_code, 1056 gc->resp); 1057 GNUNET_break (MHD_YES == ret); 1058 MHD_destroy_response (gc->resp); 1059 gc->resp = NULL; 1060 return ret; 1061 } 1062 if (NULL != gc->as) 1063 { 1064 /* Authorization process is "running", check what is going on */ 1065 GNUNET_assert (NULL != gc->authorization); 1066 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1067 "Continuing with running the authorization process\n"); 1068 GNUNET_assert (! gc->suspended); 1069 return run_authorization_process (connection, 1070 gc); 1071 1072 } 1073 /* We get here if the async check for payment said this request 1074 was indeed paid! */ 1075 } 1076 1077 /* parse byte stream upload into JSON */ 1078 if (NULL == gc->root) 1079 { 1080 enum GNUNET_GenericReturnValue res; 1081 1082 res = TALER_MHD_parse_post_json (connection, 1083 &gc->opaque_post_parsing_context, 1084 upload_data, 1085 upload_data_size, 1086 &gc->root); 1087 if (GNUNET_SYSERR == res) 1088 { 1089 GNUNET_assert (NULL == gc->root); 1090 return MHD_NO; /* bad upload, could not even generate error */ 1091 } 1092 if ( (GNUNET_NO == res) || 1093 (NULL == gc->root) ) 1094 { 1095 GNUNET_assert (NULL == gc->root); 1096 return MHD_YES; /* so far incomplete upload or parser error */ 1097 } 1098 1099 /* 'root' is now initialized */ 1100 { 1101 struct GNUNET_JSON_Specification spec[] = { 1102 GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", 1103 &gc->truth_key), 1104 GNUNET_JSON_spec_mark_optional ( 1105 GNUNET_JSON_spec_fixed_auto ("payment_secret", 1106 &gc->payment_identifier), 1107 &gc->no_payment_identifier_provided), 1108 GNUNET_JSON_spec_end () 1109 }; 1110 enum GNUNET_GenericReturnValue pres; 1111 1112 pres = TALER_MHD_parse_json_data (connection, 1113 gc->root, 1114 spec); 1115 if (GNUNET_SYSERR == pres) 1116 { 1117 GNUNET_break (0); 1118 return MHD_NO; /* hard failure */ 1119 } 1120 if (GNUNET_NO == pres) 1121 { 1122 GNUNET_break_op (0); 1123 return MHD_YES; /* failure */ 1124 } 1125 if (! gc->no_payment_identifier_provided) 1126 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1127 "Client provided payment identifier `%s'\n", 1128 TALER_B2S (&gc->payment_identifier)); 1129 } 1130 } 1131 1132 { 1133 /* load encrypted truth from DB */ 1134 enum GNUNET_DB_QueryStatus qs; 1135 char *method; 1136 1137 qs = ANASTASIS_DB_get_truth ( 1138 &gc->truth_uuid, 1139 &encrypted_truth, 1140 &encrypted_truth_size, 1141 &truth_mime, 1142 &method); 1143 switch (qs) 1144 { 1145 case GNUNET_DB_STATUS_HARD_ERROR: 1146 case GNUNET_DB_STATUS_SOFT_ERROR: 1147 GNUNET_break (0); 1148 return TALER_MHD_reply_with_error (gc->connection, 1149 MHD_HTTP_INTERNAL_SERVER_ERROR, 1150 TALER_EC_GENERIC_DB_FETCH_FAILED, 1151 "get escrow challenge"); 1152 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1153 return TALER_MHD_reply_with_error (connection, 1154 MHD_HTTP_NOT_FOUND, 1155 TALER_EC_ANASTASIS_TRUTH_UNKNOWN, 1156 NULL); 1157 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1158 break; 1159 } 1160 if (0 == strcmp ("question", 1161 method)) 1162 { 1163 GNUNET_break_op (0); 1164 GNUNET_free (encrypted_truth); 1165 GNUNET_free (truth_mime); 1166 GNUNET_free (method); 1167 return TALER_MHD_reply_with_error (connection, 1168 MHD_HTTP_BAD_REQUEST, 1169 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, 1170 "question"); 1171 } 1172 1173 gc->authorization 1174 = ANASTASIS_authorization_plugin_load (method, 1175 AH_cfg); 1176 if (NULL == gc->authorization) 1177 { 1178 enum MHD_Result ret; 1179 1180 ret = TALER_MHD_reply_with_error ( 1181 connection, 1182 MHD_HTTP_INTERNAL_SERVER_ERROR, 1183 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, 1184 method); 1185 GNUNET_free (encrypted_truth); 1186 GNUNET_free (truth_mime); 1187 GNUNET_free (method); 1188 return ret; 1189 } 1190 1191 if (gc->authorization->user_provided_code) 1192 { 1193 enum MHD_Result ret; 1194 1195 GNUNET_break_op (0); 1196 ret = TALER_MHD_reply_with_error (connection, 1197 MHD_HTTP_BAD_REQUEST, 1198 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, 1199 method); 1200 GNUNET_free (encrypted_truth); 1201 GNUNET_free (truth_mime); 1202 GNUNET_free (method); 1203 return ret; 1204 } 1205 1206 gc->challenge_costs = &gc->authorization->costs; 1207 GNUNET_free (method); 1208 } 1209 1210 if (! gc->authorization->payment_plugin_managed) 1211 { 1212 /* GNUNET_SYSERR is unreachable: anastasis-httpd refuses to start on 1213 a price list that mixes free and non-free currencies. */ 1214 GNUNET_assert (GNUNET_SYSERR != 1215 TALER_amount_list_check_uniform (gc->challenge_costs)); 1216 if (GNUNET_NO != 1217 TALER_amount_list_check_uniform (gc->challenge_costs)) 1218 { 1219 /* Check database to see if the transaction is paid for */ 1220 enum GNUNET_DB_QueryStatus qs; 1221 bool paid; 1222 1223 if (gc->no_payment_identifier_provided) 1224 { 1225 GNUNET_free (truth_mime); 1226 GNUNET_free (encrypted_truth); 1227 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1228 "Beginning payment, client did not provide payment identifier\n"); 1229 return begin_payment (gc); 1230 } 1231 qs = ANASTASIS_DB_get_challenge_payment ( 1232 &gc->payment_identifier, 1233 &gc->truth_uuid, 1234 &paid, 1235 &gc->challenge_paid); 1236 switch (qs) 1237 { 1238 case GNUNET_DB_STATUS_HARD_ERROR: 1239 case GNUNET_DB_STATUS_SOFT_ERROR: 1240 GNUNET_break (0); 1241 GNUNET_free (truth_mime); 1242 GNUNET_free (encrypted_truth); 1243 return TALER_MHD_reply_with_error (gc->connection, 1244 MHD_HTTP_INTERNAL_SERVER_ERROR, 1245 TALER_EC_GENERIC_DB_FETCH_FAILED, 1246 "get challenge payment"); 1247 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1248 /* Create fresh payment identifier (cannot trust client) */ 1249 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1250 "Client-provided payment identifier is unknown.\n"); 1251 GNUNET_free (truth_mime); 1252 GNUNET_free (encrypted_truth); 1253 return begin_payment (gc); 1254 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1255 if (! paid) 1256 { 1257 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1258 "Payment identifier known. Checking payment with client's payment identifier\n"); 1259 GNUNET_free (truth_mime); 1260 GNUNET_free (encrypted_truth); 1261 return begin_payment (gc); 1262 } 1263 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1264 "Payment confirmed\n"); 1265 gc->have_payment = true; 1266 break; 1267 } 1268 } 1269 else 1270 { 1271 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1272 "Request is free of charge\n"); 1273 } 1274 } 1275 1276 /* We've been paid, now validate response */ 1277 { 1278 enum GNUNET_GenericReturnValue res; 1279 1280 /* decrypt encrypted_truth */ 1281 res = ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, 1282 encrypted_truth, 1283 encrypted_truth_size, 1284 &decrypted_truth, 1285 &decrypted_truth_size); 1286 GNUNET_free (encrypted_truth); 1287 if (GNUNET_OK != res) 1288 { 1289 GNUNET_free (truth_mime); 1290 return TALER_MHD_reply_with_error (connection, 1291 MHD_HTTP_CONFLICT, 1292 TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, 1293 NULL); 1294 } 1295 } 1296 1297 /* Not security question and no answer: use plugin to check if 1298 decrypted truth is a valid challenge! */ 1299 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1300 "No challenge provided, creating fresh challenge\n"); 1301 { 1302 enum GNUNET_GenericReturnValue ret; 1303 1304 ret = gc->authorization->validate (gc->authorization->cls, 1305 connection, 1306 truth_mime, 1307 decrypted_truth, 1308 decrypted_truth_size); 1309 GNUNET_free (truth_mime); 1310 switch (ret) 1311 { 1312 case GNUNET_OK: 1313 /* data valid, continued below */ 1314 break; 1315 case GNUNET_NO: 1316 /* data invalid, reply was queued */ 1317 GNUNET_free (decrypted_truth); 1318 return MHD_YES; 1319 case GNUNET_SYSERR: 1320 /* data invalid, reply was NOT queued */ 1321 GNUNET_free (decrypted_truth); 1322 return MHD_NO; 1323 } 1324 } 1325 1326 /* Setup challenge and begin authorization process */ 1327 { 1328 struct GNUNET_TIME_Timestamp transmission_date; 1329 enum GNUNET_DB_QueryStatus qs; 1330 1331 qs = ANASTASIS_DB_insert_challenge_code ( 1332 &gc->truth_uuid, 1333 gc->authorization->code_rotation_period, 1334 gc->authorization->code_validity_period, 1335 gc->authorization->retry_counter, 1336 &transmission_date, 1337 &gc->code); 1338 switch (qs) 1339 { 1340 case GNUNET_DB_STATUS_HARD_ERROR: 1341 case GNUNET_DB_STATUS_SOFT_ERROR: 1342 GNUNET_break (0); 1343 GNUNET_free (decrypted_truth); 1344 return TALER_MHD_reply_with_error (gc->connection, 1345 MHD_HTTP_INTERNAL_SERVER_ERROR, 1346 TALER_EC_GENERIC_DB_FETCH_FAILED, 1347 "insert_challenge_code"); 1348 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1349 /* 0 == retry_counter of existing challenge => rate limit exceeded */ 1350 GNUNET_free (decrypted_truth); 1351 return reply_rate_limited (gc); 1352 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1353 /* challenge code was stored successfully*/ 1354 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1355 "Created fresh challenge\n"); 1356 break; 1357 } 1358 1359 if (GNUNET_TIME_relative_cmp ( 1360 GNUNET_TIME_absolute_get_duration ( 1361 transmission_date.abs_time), 1362 <, 1363 gc->authorization->code_retransmission_frequency) ) 1364 { 1365 /* Too early for a retransmission! */ 1366 GNUNET_free (decrypted_truth); 1367 return TALER_MHD_REPLY_JSON_PACK ( 1368 gc->connection, 1369 MHD_HTTP_OK, 1370 GNUNET_JSON_pack_string ("challenge_type", 1371 "TAN_ALREADY_SENT")); 1372 } 1373 } 1374 1375 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1376 "Beginning authorization process\n"); 1377 gc->as = gc->authorization->start (gc->authorization->cls, 1378 &AH_trigger_daemon, 1379 NULL, 1380 &gc->truth_uuid, 1381 gc->code, 1382 decrypted_truth, 1383 decrypted_truth_size); 1384 GNUNET_free (decrypted_truth); 1385 if (NULL == gc->as) 1386 { 1387 GNUNET_break (0); 1388 return TALER_MHD_reply_with_error (gc->connection, 1389 MHD_HTTP_INTERNAL_SERVER_ERROR, 1390 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, 1391 NULL); 1392 } 1393 if (! gc->in_list) 1394 { 1395 gc->in_list = true; 1396 GNUNET_CONTAINER_DLL_insert (gc_head, 1397 gc_tail, 1398 gc); 1399 } 1400 GNUNET_assert (! gc->suspended); 1401 return run_authorization_process (connection, 1402 gc); 1403 }