anastasis-httpd_truth-challenge.c (41083B)
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 = db->record_challenge_refund (db->cls, 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 = db->record_challenge_payment (db->cls, 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 = db->update_challenge_payment (db->cls, 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 = db->lookup_challenge_payment (db->cls, 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 (GNUNET_CRYPTO_QUALITY_WEAK, 827 &gc->payment_identifier, 828 sizeof (struct ANASTASIS_PaymentSecretP)); 829 order_id = GNUNET_STRINGS_data_to_string_alloc ( 830 &gc->payment_identifier, 831 sizeof (gc->payment_identifier)); 832 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 833 "Creating fresh order `%s'\n", 834 order_id); 835 pay_deadline = GNUNET_TIME_relative_to_timestamp ( 836 ANASTASIS_CHALLENGE_OFFER_LIFETIME); 837 { 838 json_t *order; 839 840 order = GNUNET_JSON_PACK ( 841 TALER_JSON_pack_amount ("amount", 842 &gc->challenge_cost), 843 GNUNET_JSON_pack_string ("summary", 844 "challenge fee for anastasis service"), 845 GNUNET_JSON_pack_string ("order_id", 846 order_id), 847 GNUNET_JSON_pack_time_rel ("auto_refund", 848 AUTO_REFUND_TIMEOUT), 849 GNUNET_JSON_pack_timestamp ("pay_deadline", 850 pay_deadline)); 851 gc->po = TALER_MERCHANT_post_private_orders_create (AH_ctx, 852 AH_backend_url, 853 order); 854 json_decref (order); 855 } 856 GNUNET_assert (NULL != gc->po); 857 GNUNET_assert ( 858 GNUNET_OK == 859 TALER_MERCHANT_post_private_orders_set_options ( 860 gc->po, 861 TALER_MERCHANT_post_private_orders_option_create_token (false), 862 TALER_MERCHANT_post_private_orders_option_refund_delay ( 863 AUTO_REFUND_TIMEOUT))); 864 GNUNET_assert (TALER_EC_NONE == 865 TALER_MERCHANT_post_private_orders_start ( 866 gc->po, 867 &proposal_cb, 868 gc)); 869 } 870 GNUNET_free (order_id); 871 AH_trigger_curl (); 872 return MHD_YES; 873 } 874 875 876 /** 877 * Mark @a gc as suspended and update the respective 878 * data structures and jobs. 879 * 880 * @param[in,out] gc context of the suspended operation 881 */ 882 static void 883 gc_suspended (struct ChallengeContext *gc) 884 { 885 gc->suspended = true; 886 if (NULL == AH_to_heap) 887 AH_to_heap = GNUNET_CONTAINER_heap_create ( 888 GNUNET_CONTAINER_HEAP_ORDER_MIN); 889 gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, 890 gc, 891 gc->timeout.abs_value_us); 892 if (NULL != to_task) 893 { 894 GNUNET_SCHEDULER_cancel (to_task); 895 to_task = NULL; 896 } 897 { 898 struct ChallengeContext *rn; 899 900 rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); 901 to_task = GNUNET_SCHEDULER_add_at (rn->timeout, 902 &do_timeout, 903 NULL); 904 } 905 } 906 907 908 /** 909 * Run the authorization method-specific 'process' function and continue 910 * based on its result with generating an HTTP response. 911 * 912 * @param connection the connection we are handling 913 * @param gc our overall handler context 914 */ 915 static enum MHD_Result 916 run_authorization_process (struct MHD_Connection *connection, 917 struct ChallengeContext *gc) 918 { 919 enum ANASTASIS_AUTHORIZATION_ChallengeResult ret; 920 enum GNUNET_DB_QueryStatus qs; 921 922 GNUNET_assert (! gc->suspended); 923 if (NULL == gc->authorization->challenge) 924 { 925 GNUNET_break (0); 926 return TALER_MHD_reply_with_error (gc->connection, 927 MHD_HTTP_INTERNAL_SERVER_ERROR, 928 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, 929 "challenge method not implemented for authorization method"); 930 } 931 ret = gc->authorization->challenge (gc->as, 932 connection); 933 switch (ret) 934 { 935 case ANASTASIS_AUTHORIZATION_CRES_SUCCESS: 936 /* Challenge sent successfully */ 937 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 938 "Authorization request %llu for %s sent successfully\n", 939 (unsigned long long) gc->code, 940 TALER_B2S (&gc->truth_uuid)); 941 qs = db->mark_challenge_sent (db->cls, 942 &gc->payment_identifier, 943 &gc->truth_uuid, 944 gc->code); 945 GNUNET_break (0 < qs); 946 gc->authorization->cleanup (gc->as); 947 gc->as = NULL; 948 return MHD_YES; 949 case ANASTASIS_AUTHORIZATION_CRES_FAILED: 950 if (! gc->no_payment_identifier_provided) 951 { 952 begin_refund (gc); 953 } 954 gc->authorization->cleanup (gc->as); 955 gc->as = NULL; 956 return MHD_YES; 957 case ANASTASIS_AUTHORIZATION_CRES_SUSPENDED: 958 /* connection was suspended */ 959 gc_suspended (gc); 960 return MHD_YES; 961 case ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED: 962 /* Challenge sent successfully */ 963 qs = db->mark_challenge_sent (db->cls, 964 &gc->payment_identifier, 965 &gc->truth_uuid, 966 gc->code); 967 GNUNET_break (0 < qs); 968 gc->authorization->cleanup (gc->as); 969 gc->as = NULL; 970 return MHD_NO; 971 case ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED: 972 gc->authorization->cleanup (gc->as); 973 gc->as = NULL; 974 return MHD_NO; 975 } 976 GNUNET_break (0); 977 return MHD_NO; 978 } 979 980 981 enum MHD_Result 982 AH_handler_truth_challenge ( 983 struct MHD_Connection *connection, 984 struct TM_HandlerContext *hc, 985 const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, 986 const char *upload_data, 987 size_t *upload_data_size) 988 { 989 struct ChallengeContext *gc = hc->ctx; 990 void *encrypted_truth; 991 size_t encrypted_truth_size; 992 void *decrypted_truth; 993 size_t decrypted_truth_size; 994 char *truth_mime = NULL; 995 996 if (NULL == gc) 997 { 998 /* Fresh request, do initial setup */ 999 gc = GNUNET_new (struct ChallengeContext); 1000 gc->hc = hc; 1001 hc->ctx = gc; 1002 gc->connection = connection; 1003 gc->truth_uuid = *truth_uuid; 1004 gc->hc->cc = &request_done; 1005 gc->timeout = GNUNET_TIME_relative_to_absolute ( 1006 PAYMENT_TIMEOUT); 1007 } /* end of first-time initialization (if NULL == gc) */ 1008 else 1009 { 1010 /* might have been woken up by authorization plugin, 1011 so clear the flag. MDH called us, so we are 1012 clearly no longer suspended */ 1013 gc->suspended = false; 1014 if (NULL != gc->resp) 1015 { 1016 enum MHD_Result ret; 1017 1018 /* We generated a response asynchronously, queue that */ 1019 ret = MHD_queue_response (connection, 1020 gc->response_code, 1021 gc->resp); 1022 GNUNET_break (MHD_YES == ret); 1023 MHD_destroy_response (gc->resp); 1024 gc->resp = NULL; 1025 return ret; 1026 } 1027 if (NULL != gc->as) 1028 { 1029 /* Authorization process is "running", check what is going on */ 1030 GNUNET_assert (NULL != gc->authorization); 1031 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1032 "Continuing with running the authorization process\n"); 1033 GNUNET_assert (! gc->suspended); 1034 return run_authorization_process (connection, 1035 gc); 1036 1037 } 1038 /* We get here if the async check for payment said this request 1039 was indeed paid! */ 1040 } 1041 1042 /* parse byte stream upload into JSON */ 1043 if (NULL == gc->root) 1044 { 1045 enum GNUNET_GenericReturnValue res; 1046 1047 res = TALER_MHD_parse_post_json (connection, 1048 &gc->opaque_post_parsing_context, 1049 upload_data, 1050 upload_data_size, 1051 &gc->root); 1052 if (GNUNET_SYSERR == res) 1053 { 1054 GNUNET_assert (NULL == gc->root); 1055 return MHD_NO; /* bad upload, could not even generate error */ 1056 } 1057 if ( (GNUNET_NO == res) || 1058 (NULL == gc->root) ) 1059 { 1060 GNUNET_assert (NULL == gc->root); 1061 return MHD_YES; /* so far incomplete upload or parser error */ 1062 } 1063 1064 /* 'root' is now initialized */ 1065 { 1066 struct GNUNET_JSON_Specification spec[] = { 1067 GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", 1068 &gc->truth_key), 1069 GNUNET_JSON_spec_mark_optional ( 1070 GNUNET_JSON_spec_fixed_auto ("payment_secret", 1071 &gc->payment_identifier), 1072 &gc->no_payment_identifier_provided), 1073 GNUNET_JSON_spec_end () 1074 }; 1075 enum GNUNET_GenericReturnValue pres; 1076 1077 pres = TALER_MHD_parse_json_data (connection, 1078 gc->root, 1079 spec); 1080 if (GNUNET_SYSERR == pres) 1081 { 1082 GNUNET_break (0); 1083 return MHD_NO; /* hard failure */ 1084 } 1085 if (GNUNET_NO == pres) 1086 { 1087 GNUNET_break_op (0); 1088 return MHD_YES; /* failure */ 1089 } 1090 if (! gc->no_payment_identifier_provided) 1091 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1092 "Client provided payment identifier `%s'\n", 1093 TALER_B2S (&gc->payment_identifier)); 1094 } 1095 } 1096 1097 { 1098 /* load encrypted truth from DB */ 1099 enum GNUNET_DB_QueryStatus qs; 1100 char *method; 1101 1102 qs = db->get_escrow_challenge (db->cls, 1103 &gc->truth_uuid, 1104 &encrypted_truth, 1105 &encrypted_truth_size, 1106 &truth_mime, 1107 &method); 1108 switch (qs) 1109 { 1110 case GNUNET_DB_STATUS_HARD_ERROR: 1111 case GNUNET_DB_STATUS_SOFT_ERROR: 1112 GNUNET_break (0); 1113 return TALER_MHD_reply_with_error (gc->connection, 1114 MHD_HTTP_INTERNAL_SERVER_ERROR, 1115 TALER_EC_GENERIC_DB_FETCH_FAILED, 1116 "get escrow challenge"); 1117 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1118 return TALER_MHD_reply_with_error (connection, 1119 MHD_HTTP_NOT_FOUND, 1120 TALER_EC_ANASTASIS_TRUTH_UNKNOWN, 1121 NULL); 1122 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1123 break; 1124 } 1125 if (0 == strcmp ("question", 1126 method)) 1127 { 1128 GNUNET_break_op (0); 1129 GNUNET_free (encrypted_truth); 1130 GNUNET_free (truth_mime); 1131 GNUNET_free (method); 1132 return TALER_MHD_reply_with_error (connection, 1133 MHD_HTTP_BAD_REQUEST, 1134 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, 1135 "question"); 1136 } 1137 1138 gc->authorization 1139 = ANASTASIS_authorization_plugin_load (method, 1140 db, 1141 AH_cfg); 1142 if (NULL == gc->authorization) 1143 { 1144 enum MHD_Result ret; 1145 1146 ret = TALER_MHD_reply_with_error ( 1147 connection, 1148 MHD_HTTP_INTERNAL_SERVER_ERROR, 1149 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, 1150 method); 1151 GNUNET_free (encrypted_truth); 1152 GNUNET_free (truth_mime); 1153 GNUNET_free (method); 1154 return ret; 1155 } 1156 1157 if (gc->authorization->user_provided_code) 1158 { 1159 enum MHD_Result ret; 1160 1161 GNUNET_break_op (0); 1162 ret = TALER_MHD_reply_with_error (connection, 1163 MHD_HTTP_BAD_REQUEST, 1164 TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, 1165 method); 1166 GNUNET_free (encrypted_truth); 1167 GNUNET_free (truth_mime); 1168 GNUNET_free (method); 1169 return ret; 1170 } 1171 1172 gc->challenge_cost = gc->authorization->cost; 1173 GNUNET_free (method); 1174 } 1175 1176 if (! gc->authorization->payment_plugin_managed) 1177 { 1178 if (! TALER_amount_is_zero (&gc->challenge_cost)) 1179 { 1180 /* Check database to see if the transaction is paid for */ 1181 enum GNUNET_DB_QueryStatus qs; 1182 bool paid; 1183 1184 if (gc->no_payment_identifier_provided) 1185 { 1186 GNUNET_free (truth_mime); 1187 GNUNET_free (encrypted_truth); 1188 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1189 "Beginning payment, client did not provide payment identifier\n"); 1190 return begin_payment (gc); 1191 } 1192 qs = db->check_challenge_payment (db->cls, 1193 &gc->payment_identifier, 1194 &gc->truth_uuid, 1195 &paid); 1196 switch (qs) 1197 { 1198 case GNUNET_DB_STATUS_HARD_ERROR: 1199 case GNUNET_DB_STATUS_SOFT_ERROR: 1200 GNUNET_break (0); 1201 GNUNET_free (truth_mime); 1202 GNUNET_free (encrypted_truth); 1203 return TALER_MHD_reply_with_error (gc->connection, 1204 MHD_HTTP_INTERNAL_SERVER_ERROR, 1205 TALER_EC_GENERIC_DB_FETCH_FAILED, 1206 "check challenge payment"); 1207 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1208 /* Create fresh payment identifier (cannot trust client) */ 1209 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1210 "Client-provided payment identifier is unknown.\n"); 1211 GNUNET_free (truth_mime); 1212 GNUNET_free (encrypted_truth); 1213 return begin_payment (gc); 1214 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1215 if (! paid) 1216 { 1217 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1218 "Payment identifier known. Checking payment with client's payment identifier\n"); 1219 GNUNET_free (truth_mime); 1220 GNUNET_free (encrypted_truth); 1221 return begin_payment (gc); 1222 } 1223 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1224 "Payment confirmed\n"); 1225 break; 1226 } 1227 } 1228 else 1229 { 1230 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1231 "Request is free of charge\n"); 1232 } 1233 } 1234 1235 /* We've been paid, now validate response */ 1236 { 1237 /* decrypt encrypted_truth */ 1238 ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, 1239 encrypted_truth, 1240 encrypted_truth_size, 1241 &decrypted_truth, 1242 &decrypted_truth_size); 1243 GNUNET_free (encrypted_truth); 1244 } 1245 if (NULL == decrypted_truth) 1246 { 1247 GNUNET_free (truth_mime); 1248 return TALER_MHD_reply_with_error (connection, 1249 MHD_HTTP_CONFLICT, 1250 TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, 1251 NULL); 1252 } 1253 1254 /* Not security question and no answer: use plugin to check if 1255 decrypted truth is a valid challenge! */ 1256 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1257 "No challenge provided, creating fresh challenge\n"); 1258 { 1259 enum GNUNET_GenericReturnValue ret; 1260 1261 ret = gc->authorization->validate (gc->authorization->cls, 1262 connection, 1263 truth_mime, 1264 decrypted_truth, 1265 decrypted_truth_size); 1266 GNUNET_free (truth_mime); 1267 switch (ret) 1268 { 1269 case GNUNET_OK: 1270 /* data valid, continued below */ 1271 break; 1272 case GNUNET_NO: 1273 /* data invalid, reply was queued */ 1274 GNUNET_free (decrypted_truth); 1275 return MHD_YES; 1276 case GNUNET_SYSERR: 1277 /* data invalid, reply was NOT queued */ 1278 GNUNET_free (decrypted_truth); 1279 return MHD_NO; 1280 } 1281 } 1282 1283 /* Setup challenge and begin authorization process */ 1284 { 1285 struct GNUNET_TIME_Timestamp transmission_date; 1286 enum GNUNET_DB_QueryStatus qs; 1287 1288 qs = db->create_challenge_code (db->cls, 1289 &gc->truth_uuid, 1290 gc->authorization->code_rotation_period, 1291 gc->authorization->code_validity_period, 1292 gc->authorization->retry_counter, 1293 &transmission_date, 1294 &gc->code); 1295 switch (qs) 1296 { 1297 case GNUNET_DB_STATUS_HARD_ERROR: 1298 case GNUNET_DB_STATUS_SOFT_ERROR: 1299 GNUNET_break (0); 1300 GNUNET_free (decrypted_truth); 1301 return TALER_MHD_reply_with_error (gc->connection, 1302 MHD_HTTP_INTERNAL_SERVER_ERROR, 1303 TALER_EC_GENERIC_DB_FETCH_FAILED, 1304 "create_challenge_code"); 1305 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1306 /* 0 == retry_counter of existing challenge => rate limit exceeded */ 1307 GNUNET_free (decrypted_truth); 1308 return reply_rate_limited (gc); 1309 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1310 /* challenge code was stored successfully*/ 1311 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1312 "Created fresh challenge\n"); 1313 break; 1314 } 1315 1316 if (GNUNET_TIME_relative_cmp ( 1317 GNUNET_TIME_absolute_get_duration ( 1318 transmission_date.abs_time), 1319 <, 1320 gc->authorization->code_retransmission_frequency) ) 1321 { 1322 /* Too early for a retransmission! */ 1323 GNUNET_free (decrypted_truth); 1324 return TALER_MHD_REPLY_JSON_PACK ( 1325 gc->connection, 1326 MHD_HTTP_OK, 1327 GNUNET_JSON_pack_string ("challenge_type", 1328 "TAN_ALREADY_SENT")); 1329 } 1330 } 1331 1332 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1333 "Beginning authorization process\n"); 1334 gc->as = gc->authorization->start (gc->authorization->cls, 1335 &AH_trigger_daemon, 1336 NULL, 1337 &gc->truth_uuid, 1338 gc->code, 1339 decrypted_truth, 1340 decrypted_truth_size); 1341 GNUNET_free (decrypted_truth); 1342 if (NULL == gc->as) 1343 { 1344 GNUNET_break (0); 1345 return TALER_MHD_reply_with_error (gc->connection, 1346 MHD_HTTP_INTERNAL_SERVER_ERROR, 1347 TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, 1348 NULL); 1349 } 1350 if (! gc->in_list) 1351 { 1352 gc->in_list = true; 1353 GNUNET_CONTAINER_DLL_insert (gc_head, 1354 gc_tail, 1355 gc); 1356 } 1357 GNUNET_assert (! gc->suspended); 1358 return run_authorization_process (connection, 1359 gc); 1360 }