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