sync-httpd2_backup-post.c (30612B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2019--2025 Taler Systems SA 4 5 TALER 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 TALER 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 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file sync-httpd_backup_post.c 18 * @brief functions to handle incoming requests for backups 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 struct BackupContext; 23 #define TALER_MERCHANT_POST_PRIVATE_ORDERS_RESULT_CLOSURE struct BackupContext 24 #define TALER_MERCHANT_GET_PRIVATE_ORDER_RESULT_CLOSURE struct BackupContext 25 #include "sync-httpd2.h" 26 #include <gnunet/gnunet_util_lib.h> 27 #include "sync-httpd2_backup.h" 28 #include <microhttpd2.h> 29 #include <taler/taler_json_lib.h> 30 #include <taler/taler_mhd2_lib.h> 31 #include <taler/taler_merchant_service.h> 32 #include <taler/taler_signatures.h> 33 34 35 /** 36 * How long do we hold an HTTP client connection if 37 * we are awaiting payment before giving up? 38 */ 39 #define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ 40 GNUNET_TIME_UNIT_MINUTES, 30) 41 42 43 /** 44 * Context for an upload operation. 45 */ 46 struct BackupContext 47 { 48 49 /** 50 * Context for cleanup logic. 51 */ 52 struct TM_HandlerContext hc; 53 54 /** 55 * Signature of the account holder. 56 */ 57 struct SYNC_AccountSignatureP account_sig; 58 59 /** 60 * Public key of the account holder. 61 */ 62 struct SYNC_AccountPublicKeyP account; 63 64 /** 65 * Hash of the previous upload, or zeros if first upload. 66 */ 67 struct GNUNET_HashCode old_backup_hash; 68 69 /** 70 * Hash of the upload we are receiving right now (as promised 71 * by the client, to be verified!). 72 */ 73 struct GNUNET_HashCode new_backup_hash; 74 75 /** 76 * Claim token, all zeros if not known. Only set if @e existing_order_id is non-NULL. 77 */ 78 struct TALER_ClaimTokenP token; 79 80 /** 81 * Kept in DLL for shutdown handling while suspended. 82 */ 83 struct BackupContext *next; 84 85 /** 86 * Kept in DLL for shutdown handling while suspended. 87 */ 88 struct BackupContext *prev; 89 90 /** 91 * Used while suspended for resumption. 92 */ 93 struct MHD_Request *request; 94 95 /** 96 * To be returned upon resuming. 97 */ 98 struct MHD_Response *resp; 99 100 /** 101 * Used while we are awaiting proposal creation. 102 */ 103 struct TALER_MERCHANT_PostPrivateOrdersHandle *po; 104 105 /** 106 * Used while we are waiting payment. 107 */ 108 struct TALER_MERCHANT_GetPrivateOrderHandle *omgh; 109 110 /** 111 * Order under which the client promised payment, or NULL. 112 */ 113 const char *order_id; 114 115 /** 116 * Order ID for the client that we found in our database. 117 */ 118 char *existing_order_id; 119 120 /** 121 * Timestamp of the order in @e existing_order_id. Used to 122 * select the most recent unpaid offer. 123 */ 124 struct GNUNET_TIME_Timestamp existing_order_timestamp; 125 126 /** 127 * Do not look for an existing order, force a fresh order to be created. 128 */ 129 bool force_fresh_order; 130 }; 131 132 133 /** 134 * Kept in DLL for shutdown handling while suspended. 135 */ 136 static struct BackupContext *bc_head; 137 138 /** 139 * Kept in DLL for shutdown handling while suspended. 140 */ 141 static struct BackupContext *bc_tail; 142 143 144 /** 145 * Service is shutting down, resume all MHD requests NOW. 146 */ 147 void 148 SH_resume_all_bc () 149 { 150 struct BackupContext *bc; 151 152 while (NULL != (bc = bc_head)) 153 { 154 GNUNET_CONTAINER_DLL_remove (bc_head, 155 bc_tail, 156 bc); 157 MHD_request_resume (bc->request); 158 if (NULL != bc->po) 159 { 160 TALER_MERCHANT_post_private_orders_cancel (bc->po); 161 bc->po = NULL; 162 } 163 if (NULL != bc->omgh) 164 { 165 TALER_MERCHANT_get_private_order_cancel (bc->omgh); 166 bc->omgh = NULL; 167 } 168 } 169 } 170 171 172 /** 173 * Function called to clean up a backup context. 174 * 175 * @param cls the `struct BackupContext` 176 * @param data the details about the event 177 * @param request_app_context the application request context 178 */ 179 static void 180 cleanup_ctx (void *cls, 181 const struct MHD_RequestEndedData *data, 182 void *request_app_context) 183 { 184 struct BackupContext *bc = cls; 185 186 GNUNET_assert (cls == request_app_context); 187 if (NULL != bc->po) 188 { 189 TALER_MERCHANT_post_private_orders_cancel (bc->po); 190 bc->po = NULL; 191 } 192 if (NULL != bc->omgh) 193 { 194 TALER_MERCHANT_get_private_order_cancel (bc->omgh); 195 bc->omgh = NULL; 196 } 197 GNUNET_free (bc->existing_order_id); 198 GNUNET_free (bc); 199 } 200 201 202 /** 203 * Transmit a payment request for @a order_id on @a request 204 * 205 * @param order_id our backend's order ID 206 * @param token the claim token generated by the merchant (NULL if 207 * it wasn't generated). 208 * @return MHD response to use 209 */ 210 static struct MHD_Response * 211 make_payment_request (const char *order_id, 212 const struct TALER_ClaimTokenP *token) 213 { 214 struct MHD_Response *resp; 215 216 /* request payment via Taler */ 217 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 218 "Creating payment request for order `%s'\n", 219 order_id); 220 resp = MHD_response_from_empty (MHD_HTTP_STATUS_PAYMENT_REQUIRED); 221 TALER_MHD2_add_global_headers (resp, 222 false); 223 { 224 char *hdr; 225 const char *pfx; 226 char *hn; 227 struct GNUNET_Buffer hdr_buf = { 0 }; 228 229 if (0 == strncasecmp ("https://", 230 SH_backend_url, 231 strlen ("https://"))) 232 { 233 pfx = "taler://"; 234 hn = &SH_backend_url[strlen ("https://")]; 235 } 236 else if (0 == strncasecmp ("http://", 237 SH_backend_url, 238 strlen ("http://"))) 239 { 240 pfx = "taler+http://"; 241 hn = &SH_backend_url[strlen ("http://")]; 242 } 243 else 244 { 245 GNUNET_break (0); 246 MHD_response_destroy (resp); 247 return NULL; 248 } 249 if (0 == strlen (hn)) 250 { 251 GNUNET_break (0); 252 MHD_response_destroy (resp); 253 return NULL; 254 } 255 256 GNUNET_buffer_write_str (&hdr_buf, pfx); 257 GNUNET_buffer_write_str (&hdr_buf, "pay/"); 258 GNUNET_buffer_write_str (&hdr_buf, hn); 259 GNUNET_buffer_write_path (&hdr_buf, order_id); 260 /* No session ID */ 261 GNUNET_buffer_write_path (&hdr_buf, ""); 262 if (NULL != token) 263 { 264 GNUNET_buffer_write_str (&hdr_buf, "?c="); 265 GNUNET_buffer_write_data_encoded (&hdr_buf, 266 token, 267 sizeof (*token)); 268 } 269 hdr = GNUNET_buffer_reap_str (&hdr_buf); 270 GNUNET_break (MHD_SC_OK == 271 MHD_response_add_header (resp, 272 "Taler", 273 hdr)); 274 GNUNET_free (hdr); 275 } 276 return resp; 277 } 278 279 280 /** 281 * Callbacks of this type are used to serve the result of submitting a 282 * /contract request to a merchant. 283 * 284 * @param cls our `struct BackupContext` 285 * @param por response details 286 */ 287 static void 288 proposal_cb ( 289 struct BackupContext *bc, 290 const struct TALER_MERCHANT_PostPrivateOrdersResponse *por) 291 { 292 enum SYNC_DB_QueryStatus qs; 293 294 bc->po = NULL; 295 GNUNET_CONTAINER_DLL_remove (bc_head, 296 bc_tail, 297 bc); 298 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 299 "Resuming request with order `%s'\n", 300 bc->order_id); 301 MHD_request_resume (bc->request); 302 SH_trigger_daemon (); 303 if (MHD_HTTP_STATUS_OK != por->hr.http_status) 304 { 305 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 306 "Backend returned status %u/%u\n", 307 por->hr.http_status, 308 (unsigned int) por->hr.ec); 309 GNUNET_break_op (0); 310 bc->resp = TALER_MHD2_MAKE_JSON_PACK ( 311 MHD_HTTP_STATUS_BAD_GATEWAY, 312 TALER_JSON_pack_ec (TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR), 313 GNUNET_JSON_pack_uint64 ("backend-ec", 314 por->hr.ec), 315 GNUNET_JSON_pack_uint64 ("backend-http-status", 316 por->hr.http_status), 317 GNUNET_JSON_pack_allow_null ( 318 GNUNET_JSON_pack_object_incref ("backend-reply", 319 (json_t *) por->hr.reply))); 320 return; 321 } 322 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 323 "Storing payment request for order `%s'\n", 324 por->details.ok.order_id); 325 qs = SYNCDB_store_payment_TR ( 326 &bc->account, 327 por->details.ok.order_id, 328 por->details.ok.token, 329 &SH_annual_fee); 330 if (0 >= qs) 331 { 332 GNUNET_break (0); 333 bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, 334 "Failed to persist payment request in sync database"); 335 GNUNET_assert (NULL != bc->resp); 336 return; 337 } 338 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 339 "Obtained fresh order `%s'\n", 340 por->details.ok.order_id); 341 bc->resp = make_payment_request (por->details.ok.order_id, 342 por->details.ok.token); 343 GNUNET_assert (NULL != bc->resp); 344 } 345 346 347 /** 348 * Function called on all pending payments for the right 349 * account. 350 * 351 * @param cls closure, our `struct BackupContext` 352 * @param timestamp for how long have we been waiting 353 * @param order_id order id in the backend 354 * @param token claim token to use (or NULL for none) 355 * @param amount how much is the order for 356 */ 357 static void 358 ongoing_payment_cb (void *cls, 359 struct GNUNET_TIME_Timestamp timestamp, 360 const char *order_id, 361 const struct TALER_ClaimTokenP *token, 362 const struct TALER_Amount *amount) 363 { 364 struct BackupContext *bc = cls; 365 366 (void) amount; 367 if (0 != TALER_amount_cmp (amount, 368 &SH_annual_fee)) 369 return; /* can't reuse, fees changed */ 370 if ( (NULL == bc->existing_order_id) || 371 (GNUNET_TIME_timestamp_cmp (bc->existing_order_timestamp, 372 <, 373 timestamp)) ) 374 { 375 GNUNET_free (bc->existing_order_id); 376 bc->existing_order_id = GNUNET_strdup (order_id); 377 bc->existing_order_timestamp = timestamp; 378 if (NULL != token) 379 bc->token = *token; 380 } 381 } 382 383 384 /** 385 * Callback to process a GET /check-payment request 386 * 387 * @param cls our `struct BackupContext` 388 * @param osr order status 389 */ 390 static void 391 check_payment_cb ( 392 struct BackupContext *bc, 393 const struct TALER_MERCHANT_GetPrivateOrderResponse *osr) 394 { 395 const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; 396 397 /* refunds are not supported, verify */ 398 bc->omgh = NULL; 399 GNUNET_CONTAINER_DLL_remove (bc_head, 400 bc_tail, 401 bc); 402 MHD_request_resume (bc->request); 403 SH_trigger_daemon (); 404 switch (hr->http_status) 405 { 406 case 0: 407 /* Likely timeout, complain! */ 408 bc->resp = TALER_MHD2_make_error ( 409 TALER_EC_SYNC_GENERIC_BACKEND_TIMEOUT, 410 NULL); 411 return; 412 case MHD_HTTP_STATUS_OK: 413 break; /* handled below */ 414 default: 415 /* Unexpected backend response */ 416 bc->resp = TALER_MHD2_MAKE_JSON_PACK ( 417 MHD_HTTP_STATUS_BAD_GATEWAY, 418 GNUNET_JSON_pack_uint64 ("code", 419 TALER_EC_SYNC_GENERIC_BACKEND_ERROR), 420 GNUNET_JSON_pack_string ("hint", 421 TALER_ErrorCode_get_hint ( 422 TALER_EC_SYNC_GENERIC_BACKEND_ERROR)), 423 GNUNET_JSON_pack_uint64 ("backend-ec", 424 (json_int_t) hr->ec), 425 GNUNET_JSON_pack_uint64 ("backend-http-status", 426 (json_int_t) hr->http_status), 427 GNUNET_JSON_pack_allow_null ( 428 GNUNET_JSON_pack_object_incref ("backend-reply", 429 (json_t *) hr->reply))); 430 return; 431 } 432 433 GNUNET_assert (MHD_HTTP_STATUS_OK == hr->http_status); 434 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 435 "Payment status checked: %d\n", 436 osr->details.ok.status); 437 switch (osr->details.ok.status) 438 { 439 case TALER_MERCHANT_OSC_PAID: 440 { 441 enum SYNC_DB_QueryStatus qs; 442 443 qs = SYNCDB_increment_lifetime_TR ( 444 &bc->account, 445 bc->order_id, 446 GNUNET_TIME_UNIT_YEARS); /* always annual */ 447 if (0 <= qs) 448 return; /* continue as planned */ 449 GNUNET_break (0); 450 bc->resp = TALER_MHD2_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, 451 "increment lifetime"); 452 GNUNET_assert (NULL != bc->resp); 453 return; /* continue as planned */ 454 } 455 case TALER_MERCHANT_OSC_UNPAID: 456 case TALER_MERCHANT_OSC_CLAIMED: 457 break; 458 } 459 if (NULL != bc->existing_order_id) 460 { 461 /* repeat payment request */ 462 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 463 "Repeating payment request\n"); 464 bc->resp = make_payment_request (bc->existing_order_id, 465 (GNUNET_YES == GNUNET_is_zero (&bc->token)) 466 ? NULL 467 : &bc->token); 468 GNUNET_assert (NULL != bc->resp); 469 return; 470 } 471 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 472 "Timeout waiting for payment\n"); 473 bc->resp = TALER_MHD2_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT, 474 "Timeout awaiting promised payment"); 475 GNUNET_assert (NULL != bc->resp); 476 } 477 478 479 /** 480 * Helper function used to ask our backend to await 481 * a payment for the user's account. 482 * 483 * @param bc context to begin payment for. 484 * @param timeout when to give up trying 485 * @param order_id which order to check for the payment 486 */ 487 static void 488 await_payment (struct BackupContext *bc, 489 struct GNUNET_TIME_Relative timeout, 490 const char *order_id) 491 { 492 GNUNET_CONTAINER_DLL_insert (bc_head, 493 bc_tail, 494 bc); 495 bc->order_id = order_id; 496 bc->omgh = TALER_MERCHANT_get_private_order_create ( 497 SH_ctx, 498 SH_backend_url, 499 order_id); 500 GNUNET_assert (GNUNET_OK == 501 TALER_MERCHANT_get_private_order_set_options ( 502 bc->omgh, 503 TALER_MERCHANT_get_private_order_option_timeout (timeout))); 504 GNUNET_assert (TALER_EC_NONE == 505 TALER_MERCHANT_get_private_order_start ( 506 bc->omgh, 507 &check_payment_cb, 508 bc)); 509 SH_trigger_curl (); 510 } 511 512 513 /** 514 * Helper function used to ask our backend to begin 515 * processing a payment for the user's account. 516 * May perform asynchronous operations by suspending the request 517 * if required. 518 * 519 * @param bc context to begin payment for. 520 * @param pay_req #GNUNET_YES if payment was explicitly requested, 521 * #GNUNET_NO if payment is needed 522 * @param[out] suspend set to true if the request should be suspended 523 * @return MHD response 524 */ 525 static struct MHD_Response * 526 begin_payment (struct BackupContext *bc, 527 int pay_req, 528 bool *suspend) 529 { 530 *suspend = false; 531 if (! bc->force_fresh_order) 532 { 533 enum GNUNET_DB_QueryStatus qs; 534 535 qs = SYNCDB_lookup_pending_payments_by_account_TR ( 536 &bc->account, 537 &ongoing_payment_cb, 538 bc); 539 if (qs < 0) 540 { 541 GNUNET_break (0); 542 return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED, 543 "pending payments"); 544 } 545 if (NULL != bc->existing_order_id) 546 { 547 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 548 "Have existing order, waiting for `%s' to complete\n", 549 bc->existing_order_id); 550 await_payment (bc, 551 GNUNET_TIME_UNIT_ZERO /* no long polling */, 552 bc->existing_order_id); 553 *suspend = true; 554 return NULL; 555 } 556 } 557 GNUNET_CONTAINER_DLL_insert (bc_head, 558 bc_tail, 559 bc); 560 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 561 "Suspending request while creating order at `%s'\n", 562 SH_backend_url); 563 *suspend = true; 564 { 565 json_t *order; 566 567 order = GNUNET_JSON_PACK ( 568 TALER_JSON_pack_amount ("amount", 569 &SH_annual_fee), 570 GNUNET_JSON_pack_string ("summary", 571 "annual fee for sync service"), 572 GNUNET_JSON_pack_string ("fulfillment_url", 573 SH_fulfillment_url)); 574 bc->po = TALER_MERCHANT_post_private_orders_create (SH_ctx, 575 SH_backend_url, 576 order); 577 json_decref (order); 578 } 579 GNUNET_assert (TALER_EC_NONE == 580 TALER_MERCHANT_post_private_orders_start ( 581 bc->po, 582 &proposal_cb, 583 bc)); 584 SH_trigger_curl (); 585 return NULL; 586 } 587 588 589 /** 590 * We got some query status from the DB. Handle the error cases. 591 * May perform asynchronous operations by suspending the request 592 * if required. 593 * 594 * @param bc request to handle status for 595 * @param qs query status to handle 596 * @param[out] suspend set to true if the request should be suspended 597 * @return MHD response to return 598 */ 599 static struct MHD_Response * 600 handle_database_error (struct BackupContext *bc, 601 enum SYNC_DB_QueryStatus qs, 602 bool *suspend) 603 { 604 *suspend = false; 605 switch (qs) 606 { 607 case SYNC_DB_OLD_BACKUP_MISSING: 608 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 609 "Update failed: no existing backup\n"); 610 return TALER_MHD2_make_error (TALER_EC_SYNC_PREVIOUS_BACKUP_UNKNOWN, 611 NULL); 612 case SYNC_DB_OLD_BACKUP_MISMATCH: 613 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 614 "Conflict detected, returning existing backup\n"); 615 return SH_make_backup (&bc->account, 616 MHD_HTTP_STATUS_CONFLICT); 617 case SYNC_DB_PAYMENT_REQUIRED: 618 { 619 struct MHD_StringNullable order_id; 620 621 if ( (MHD_YES != 622 MHD_request_get_value (bc->request, 623 MHD_VK_URI_QUERY_PARAM, 624 "paying", 625 &order_id)) || 626 (NULL == order_id.cstr) ) 627 { 628 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 629 "Payment required, starting payment process\n"); 630 return begin_payment (bc, 631 GNUNET_NO, 632 suspend); 633 } 634 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 635 "Payment required, awaiting completion of `%s'\n", 636 order_id.cstr); 637 await_payment (bc, 638 CHECK_PAYMENT_GENERIC_TIMEOUT, 639 order_id.cstr); 640 *suspend = true; 641 return NULL; 642 } 643 case SYNC_DB_HARD_ERROR: 644 GNUNET_break (0); 645 return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_COMMIT_FAILED, 646 NULL); 647 case SYNC_DB_SOFT_ERROR: 648 GNUNET_break (0); 649 return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE, 650 NULL); 651 case SYNC_DB_NO_RESULTS: 652 GNUNET_assert (0); 653 return NULL; 654 /* intentional fall-through! */ 655 case SYNC_DB_ONE_RESULT: 656 GNUNET_assert (0); 657 return NULL; 658 } 659 GNUNET_break (0); 660 return NULL; 661 } 662 663 664 /** 665 * Function to process data uploaded by a client. 666 * 667 * @param upload_cls our `struct BackupContext *` 668 * @param request the request is being processed 669 * @param content_data_size the size of the @a content_data, 670 * zero when all data have been processed 671 * @param[in] content_data the uploaded content data, 672 * may be modified in the callback, 673 * valid only until return from the callback, 674 * NULL when all data have been processed 675 * @return action specifying how to proceed: 676 * #MHD_upload_action_continue() to continue upload (for incremental 677 * upload processing only), 678 * #MHD_upload_action_suspend() to stop reading the upload until 679 * the request is resumed, 680 * #MHD_upload_action_abort_request() to close the socket, 681 * or a response to discard the rest of the upload and transmit 682 * the response 683 */ 684 static const struct MHD_UploadAction * 685 handle_upload (void *upload_cls, 686 struct MHD_Request *request, 687 size_t content_data_size, 688 void *content_data) 689 { 690 struct BackupContext *bc = upload_cls; 691 692 if (NULL != bc->resp) 693 { 694 /* This can happen on resume */ 695 return MHD_upload_action_from_response (request, 696 bc->resp); 697 } 698 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 699 "Processing %llu bytes of upload data\n", 700 (unsigned long long) content_data_size); 701 /* Check hash matches */ 702 { 703 struct GNUNET_HashCode our_hash; 704 705 GNUNET_CRYPTO_hash (content_data, 706 content_data_size, 707 &our_hash); 708 if (0 != GNUNET_memcmp (&our_hash, 709 &bc->new_backup_hash)) 710 { 711 GNUNET_break_op (0); 712 return MHD_upload_action_from_response ( 713 request, 714 TALER_MHD2_make_error ( 715 TALER_EC_SYNC_INVALID_UPLOAD, 716 NULL)); 717 } 718 } 719 720 /* store backup to database */ 721 { 722 enum SYNC_DB_QueryStatus qs; 723 724 if (GNUNET_is_zero (&bc->old_backup_hash)) 725 { 726 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 727 "Uploading first backup to account\n"); 728 qs = SYNCDB_store_backup_TR ( 729 &bc->account, 730 &bc->account_sig, 731 &bc->new_backup_hash, 732 content_data_size, 733 content_data); 734 } 735 else 736 { 737 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 738 "Uploading existing backup of account\n"); 739 qs = SYNCDB_update_backup_TR ( 740 &bc->account, 741 &bc->old_backup_hash, 742 &bc->account_sig, 743 &bc->new_backup_hash, 744 content_data_size, 745 content_data); 746 } 747 if (qs < 0) 748 { 749 bool suspend; 750 struct MHD_Response *resp; 751 752 resp = handle_database_error (bc, 753 qs, 754 &suspend); 755 if (suspend) 756 return MHD_upload_action_suspend (request); 757 return MHD_upload_action_from_response (request, 758 resp); 759 } 760 if (0 == qs) 761 { 762 /* database says nothing actually changed, 304 (could 763 theoretically happen if another equivalent upload succeeded 764 since we last checked!) */ 765 struct MHD_Response *resp; 766 767 resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED); 768 TALER_MHD2_add_global_headers (resp, 769 false); 770 return MHD_upload_action_from_response (request, 771 resp); 772 } 773 } 774 775 /* generate main (204) standard success reply */ 776 { 777 struct MHD_Response *resp; 778 779 resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT); 780 TALER_MHD2_add_global_headers (resp, 781 false); 782 return MHD_upload_action_from_response (request, 783 resp); 784 } 785 } 786 787 788 const struct MHD_Action * 789 SH_backup_post (struct MHD_Request *request, 790 const struct SYNC_AccountPublicKeyP *account, 791 uint_fast64_t upload_size) 792 { 793 struct BackupContext *bc; 794 union MHD_RequestInfoFixedData fi; 795 796 GNUNET_assert (MHD_SC_OK == 797 MHD_request_get_info_fixed ( 798 request, 799 MHD_REQUEST_INFO_FIXED_APP_CONTEXT, 800 &fi)); 801 bc = *fi.v_app_context_ppvoid; 802 if (NULL == bc) 803 { 804 /* first call, setup internals */ 805 bc = GNUNET_new (struct BackupContext); 806 bc->request = request; 807 bc->account = *account; 808 #if BUG 809 GNUNET_assert (MHD_SC_OK == 810 MHD_REQUEST_SET_OPTIONS ( 811 MHD_R_OPTION_TERMINATION_CALLBACK (&cleanup_ctx, 812 bc))); 813 #endif 814 *fi.v_app_context_ppvoid = bc; 815 } 816 if (NULL != bc->resp) 817 return MHD_action_from_response (request, 818 bc->resp); 819 if ( (upload_size / 1024 / 1024 >= SH_upload_limit_mb) || 820 (upload_size > SIZE_MAX) ) 821 { 822 GNUNET_break_op (0); 823 return TALER_MHD2_reply_with_error ( 824 request, 825 MHD_HTTP_STATUS_CONTENT_TOO_LARGE, 826 TALER_EC_SYNC_EXCESSIVE_CONTENT_LENGTH, 827 NULL); 828 } 829 { 830 struct MHD_StringNullable fresh; 831 832 if (MHD_YES == 833 MHD_request_get_value (request, 834 MHD_VK_URI_QUERY_PARAM, 835 "fresh", 836 &fresh)) 837 bc->force_fresh_order = true; 838 } 839 { 840 struct MHD_StringNullable im; 841 842 if ( (MHD_YES == 843 MHD_request_get_value (request, 844 MHD_VK_HEADER, 845 MHD_HTTP_HEADER_IF_MATCH, 846 &im)) && 847 ( (2 >= im.len) || 848 ('"' != im.cstr[0]) || 849 ('"' != im.cstr[im.len - 1]) || 850 (GNUNET_OK != 851 GNUNET_STRINGS_string_to_data (im.cstr + 1, 852 im.len - 2, 853 &bc->old_backup_hash, 854 sizeof (bc->old_backup_hash))) ) ) 855 { 856 GNUNET_break_op (0); 857 return TALER_MHD2_reply_with_error ( 858 request, 859 MHD_HTTP_STATUS_BAD_REQUEST, 860 TALER_EC_SYNC_BAD_IF_MATCH, 861 NULL); 862 } 863 } 864 { 865 struct MHD_StringNullable sig_s; 866 867 if ( (MHD_YES != 868 MHD_request_get_value (request, 869 MHD_VK_HEADER, 870 "Sync-Signature", 871 &sig_s)) || 872 (GNUNET_OK != 873 GNUNET_STRINGS_string_to_data (sig_s.cstr, 874 sig_s.len, 875 &bc->account_sig, 876 sizeof (bc->account_sig))) ) 877 { 878 GNUNET_break_op (0); 879 return TALER_MHD2_reply_with_error ( 880 request, 881 MHD_HTTP_STATUS_BAD_REQUEST, 882 TALER_EC_SYNC_BAD_SYNC_SIGNATURE, 883 NULL); 884 } 885 } 886 { 887 struct MHD_StringNullable etag; 888 889 if ( (MHD_YES != 890 MHD_request_get_value (request, 891 MHD_VK_HEADER, 892 MHD_HTTP_HEADER_IF_NONE_MATCH, 893 &etag)) || 894 (2 >= etag.len) || 895 ('"' != etag.cstr[0]) || 896 ('"' != etag.cstr[etag.len - 1]) || 897 (GNUNET_OK != 898 GNUNET_STRINGS_string_to_data (etag.cstr + 1, 899 etag.len - 2, 900 &bc->new_backup_hash, 901 sizeof (bc->new_backup_hash))) ) 902 { 903 GNUNET_break_op (0); 904 return TALER_MHD2_reply_with_error ( 905 request, 906 MHD_HTTP_STATUS_BAD_REQUEST, 907 TALER_EC_SYNC_BAD_IF_NONE_MATCH, 908 NULL); 909 } 910 } 911 /* validate signature */ 912 { 913 struct SYNC_UploadSignaturePS usp = { 914 .purpose.size = htonl (sizeof (usp)), 915 .purpose.purpose = htonl (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD), 916 .old_backup_hash = bc->old_backup_hash, 917 .new_backup_hash = bc->new_backup_hash 918 }; 919 920 if (GNUNET_OK != 921 GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_SYNC_BACKUP_UPLOAD, 922 &usp, 923 &bc->account_sig.eddsa_sig, 924 &account->eddsa_pub)) 925 { 926 GNUNET_break_op (0); 927 return TALER_MHD2_reply_with_error ( 928 request, 929 MHD_HTTP_STATUS_FORBIDDEN, 930 TALER_EC_SYNC_INVALID_SIGNATURE, 931 NULL); 932 } 933 } 934 935 /* Check database to see if the transaction is permissible */ 936 { 937 struct GNUNET_HashCode hc; 938 enum SYNC_DB_QueryStatus qs; 939 940 qs = SYNCDB_lookup_account_TR ( 941 account, 942 &hc); 943 if (qs < 0) 944 { 945 bool suspend; 946 struct MHD_Response *resp; 947 948 resp = handle_database_error (bc, 949 qs, 950 &suspend); 951 if (suspend) 952 return MHD_action_suspend (request); 953 return MHD_action_from_response (request, 954 resp); 955 } 956 if (SYNC_DB_NO_RESULTS == qs) 957 memset (&hc, 958 0, 959 sizeof (hc)); 960 if (0 == GNUNET_memcmp (&hc, 961 &bc->new_backup_hash)) 962 { 963 /* Refuse upload: we already have that backup! */ 964 struct MHD_Response *resp; 965 966 resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED); 967 TALER_MHD2_add_global_headers (resp, 968 false); 969 return MHD_action_from_response (request, 970 resp); 971 } 972 if (0 != GNUNET_memcmp (&hc, 973 &bc->old_backup_hash)) 974 { 975 /* Refuse upload: if-none-match failed! */ 976 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 977 "Conflict detected, returning existing backup\n"); 978 return MHD_action_from_response ( 979 request, 980 SH_make_backup (account, 981 MHD_HTTP_STATUS_CONFLICT)); 982 } 983 } 984 /* check if the client insists on paying */ 985 { 986 struct MHD_StringNullable order_req; 987 988 if (MHD_YES == 989 MHD_request_get_value (request, 990 MHD_VK_URI_QUERY_PARAM, 991 "pay", 992 &order_req)) 993 { 994 struct MHD_Response *resp; 995 bool suspend; 996 997 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 998 "Payment requested, starting payment process\n"); 999 resp = begin_payment (bc, 1000 GNUNET_YES, 1001 &suspend); 1002 if (suspend) 1003 return MHD_action_suspend (request); 1004 return MHD_action_from_response (request, 1005 resp); 1006 } 1007 } 1008 /* ready to begin upload! */ 1009 return MHD_action_process_upload_full (request, 1010 upload_size, 1011 &handle_upload, 1012 bc); 1013 }