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