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