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