taler-merchant-exchangekeyupdate.c (31007B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2023, 2024, 2026 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 src/backend/taler-merchant-exchangekeyupdate.c 18 * @brief Process that ensures our /keys data for all exchanges is current 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include "microhttpd.h" 23 #include <gnunet/gnunet_util_lib.h> 24 #include <jansson.h> 25 #include <pthread.h> 26 #include <taler/taler_dbevents.h> 27 struct Exchange; 28 #define TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE struct Exchange 29 #include <taler/exchange/get-keys.h> 30 31 #include "taler/taler_merchant_util.h" 32 #include "taler/taler_merchant_bank_lib.h" 33 #include "merchantdb_lib.h" 34 #include "merchant-database/delete_exchange_accounts.h" 35 #include "merchant-database/insert_exchange_keys.h" 36 #include "merchant-database/insert_exchange_signing_key.h" 37 #include "merchant-database/start.h" 38 #include "merchant-database/preflight.h" 39 #include "merchant-database/event_notify.h" 40 #include "merchant-database/event_listen.h" 41 #include "merchant-database/insert_exchange_account.h" 42 #include "merchant-database/get_exchange_keys.h" 43 #include "merchant-database/insert_exchange_wire_fee.h" 44 45 /** 46 * Maximum frequency for the exchange interaction. 47 */ 48 #define EXCHANGE_MAXFREQ GNUNET_TIME_relative_multiply ( \ 49 GNUNET_TIME_UNIT_MINUTES, \ 50 5) 51 52 /** 53 * How many inquiries do we process concurrently at most. 54 */ 55 #define OPEN_INQUIRY_LIMIT 1024 56 57 /** 58 * How often do we retry after DB serialization errors (at most)? 59 */ 60 #define MAX_RETRIES 3 61 62 /** 63 * Information about an exchange. 64 */ 65 struct Exchange 66 { 67 /** 68 * Kept in a DLL. 69 */ 70 struct Exchange *next; 71 72 /** 73 * Kept in a DLL. 74 */ 75 struct Exchange *prev; 76 77 /** 78 * Base URL of the exchange are we tracking here. 79 */ 80 char *exchange_url; 81 82 /** 83 * Expected currency of the exchange. 84 */ 85 char *currency; 86 87 /** 88 * A /keys request to this exchange, NULL if not active. 89 */ 90 struct TALER_EXCHANGE_GetKeysHandle *conn; 91 92 /** 93 * The keys of this exchange, NULL if not known. 94 */ 95 struct TALER_EXCHANGE_Keys *keys; 96 97 /** 98 * Task where we retry fetching /keys from the exchange. 99 */ 100 struct GNUNET_SCHEDULER_Task *retry_task; 101 102 /** 103 * Master public key expected for this exchange. 104 */ 105 struct TALER_MasterPublicKeyP master_pub; 106 107 /** 108 * How soon can may we, at the earliest, re-download /keys? 109 */ 110 struct GNUNET_TIME_Absolute first_retry; 111 112 /** 113 * How long should we wait between the next retry? 114 * Used for exponential back-offs. 115 */ 116 struct GNUNET_TIME_Relative retry_delay; 117 118 /** 119 * Are we waiting for /keys downloads due to our 120 * hard limit? 121 */ 122 bool limited; 123 124 /** 125 * Are we force-retrying a /keys download because some keys 126 * were missing (and we thus should not cherry-pick, as 127 * a major reason for a force-reload would be an 128 * exchange that has lost keys and backfilled them, which 129 * breaks keys downloads with cherry-picking). 130 */ 131 bool force_retry; 132 }; 133 134 135 /** 136 * Head of known exchanges. 137 */ 138 static struct Exchange *e_head; 139 140 /** 141 * Tail of known exchanges. 142 */ 143 static struct Exchange *e_tail; 144 145 /** 146 * The merchant's configuration. 147 */ 148 static const struct GNUNET_CONFIGURATION_Handle *cfg; 149 150 /** 151 * Our database plugin. 152 */ 153 static struct TALER_MERCHANTDB_PostgresContext *pg; 154 155 /** 156 * Our event handler listening for /keys forced downloads. 157 */ 158 static struct GNUNET_DB_EventHandler *eh; 159 160 /** 161 * Handle to the context for interacting with the bank. 162 */ 163 static struct GNUNET_CURL_Context *ctx; 164 165 /** 166 * Scheduler context for running the @e ctx. 167 */ 168 static struct GNUNET_CURL_RescheduleContext *rc; 169 170 /** 171 * How many active inquiries do we have right now. 172 */ 173 static unsigned int active_inquiries; 174 175 /** 176 * Value to return from main(). 0 on success, non-zero on errors. 177 */ 178 static int global_ret; 179 180 /** 181 * Should we enable HTTP/2 and HTTP/3 when talking to the exchange? 182 * Those are not expected to be terribly beneficial for a client with 183 * stable connections to a few servers, but they could cause stability 184 * issues with libcurl. Hence we *default* to HTTP/1.1-only, as that 185 * is the conservative and most tested code path. 186 */ 187 static int enable_h3; 188 189 /** 190 * #GNUNET_YES if we are in test mode and should exit when idle. 191 */ 192 static int test_mode; 193 194 /** 195 * True if the last DB query was limited by the 196 * #OPEN_INQUIRY_LIMIT and we thus should check again 197 * as soon as we are substantially below that limit, 198 * and not only when we get a DB notification. 199 */ 200 static bool at_limit; 201 202 203 /** 204 * Function that initiates a /keys download. 205 * 206 * @param cls a `struct Exchange *` 207 */ 208 static void 209 download_keys (void *cls); 210 211 212 /** 213 * An inquiry finished, check if we need to start more. 214 */ 215 static void 216 end_inquiry (void) 217 { 218 GNUNET_assert (active_inquiries > 0); 219 active_inquiries--; 220 if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) && 221 (at_limit) ) 222 { 223 at_limit = false; 224 for (struct Exchange *e = e_head; 225 NULL != e; 226 e = e->next) 227 { 228 if (! e->limited) 229 continue; 230 e->limited = false; 231 /* done synchronously so that the active_inquiries 232 is updated immediately */ 233 download_keys (e); 234 if (at_limit) 235 break; 236 } 237 } 238 if ( (! at_limit) && 239 (0 == active_inquiries) && 240 (test_mode) ) 241 { 242 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 243 "No more open inquiries and in test mode. Existing.\n"); 244 GNUNET_SCHEDULER_shutdown (); 245 return; 246 } 247 } 248 249 250 /** 251 * Add account restriction @a a to array of @a restrictions. 252 * 253 * @param[in,out] restrictions JSON array to build 254 * @param r restriction to add to @a restrictions 255 * @return #GNUNET_SYSERR if @a r is malformed 256 */ 257 static enum GNUNET_GenericReturnValue 258 add_restriction (json_t *restrictions, 259 const struct TALER_EXCHANGE_AccountRestriction *r) 260 { 261 json_t *jr; 262 263 jr = NULL; 264 switch (r->type) 265 { 266 case TALER_EXCHANGE_AR_INVALID: 267 GNUNET_break_op (0); 268 return GNUNET_SYSERR; 269 case TALER_EXCHANGE_AR_DENY: 270 jr = GNUNET_JSON_PACK ( 271 GNUNET_JSON_pack_string ("type", 272 "deny") 273 ); 274 break; 275 case TALER_EXCHANGE_AR_REGEX: 276 jr = GNUNET_JSON_PACK ( 277 GNUNET_JSON_pack_string ( 278 "type", 279 "regex"), 280 GNUNET_JSON_pack_string ( 281 "regex", 282 r->details.regex.posix_egrep), 283 GNUNET_JSON_pack_string ( 284 "human_hint", 285 r->details.regex.human_hint), 286 GNUNET_JSON_pack_object_incref ( 287 "human_hint_i18n", 288 (json_t *) r->details.regex.human_hint_i18n) 289 ); 290 break; 291 } 292 if (NULL == jr) 293 { 294 GNUNET_break_op (0); 295 return GNUNET_SYSERR; 296 } 297 GNUNET_assert (0 == 298 json_array_append_new (restrictions, 299 jr)); 300 return GNUNET_OK; 301 302 } 303 304 305 /** 306 * The /keys download from @e failed with @a http_status and @a ec. 307 * Record the failure in the database. 308 * 309 * @param e exchange that failed 310 * @param http_status HTTP status returned 311 * @param ec Taler error code 312 */ 313 static void 314 fail_keys (const struct Exchange *e, 315 unsigned int http_status, 316 enum TALER_ErrorCode ec) 317 { 318 enum GNUNET_DB_QueryStatus qs; 319 320 qs = TALER_MERCHANTDB_insert_exchange_keys ( 321 pg, 322 e->exchange_url, 323 NULL, 324 GNUNET_TIME_relative_to_absolute (e->retry_delay), 325 http_status, 326 ec); 327 if (0 > qs) 328 { 329 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 330 return; 331 } 332 } 333 334 335 /** 336 * Update our information in the database about the 337 * /keys of an exchange. Run inside of a database 338 * transaction scope that will re-try and/or commit 339 * depending on the return value. 340 * 341 * @param keys information to persist 342 * @param first_retry earliest we may retry fetching the keys 343 * @return transaction status 344 */ 345 static enum GNUNET_DB_QueryStatus 346 insert_keys_data (const struct TALER_EXCHANGE_Keys *keys, 347 struct GNUNET_TIME_Absolute first_retry) 348 { 349 enum GNUNET_DB_QueryStatus qs; 350 351 /* store exchange online signing keys in our DB */ 352 for (unsigned int i = 0; i<keys->num_sign_keys; i++) 353 { 354 const struct TALER_EXCHANGE_SigningPublicKey *sign_key 355 = &keys->sign_keys[i]; 356 357 qs = TALER_MERCHANTDB_insert_exchange_signing_key ( 358 pg, 359 &keys->master_pub, 360 &sign_key->key, 361 sign_key->valid_from, 362 sign_key->valid_until, 363 sign_key->valid_legal, 364 &sign_key->master_sig); 365 /* 0 is OK, we may already have the key in the DB! */ 366 if (0 > qs) 367 { 368 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 369 return qs; 370 } 371 } 372 373 qs = TALER_MERCHANTDB_insert_exchange_keys (pg, 374 keys->exchange_url, 375 keys, 376 first_retry, 377 MHD_HTTP_OK, 378 TALER_EC_NONE); 379 if (0 > qs) 380 { 381 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 382 return qs; 383 } 384 385 qs = TALER_MERCHANTDB_delete_exchange_accounts (pg, 386 &keys->master_pub); 387 if (0 > qs) 388 { 389 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 390 return qs; 391 } 392 393 for (unsigned int i = 0; i<keys->accounts_len; i++) 394 { 395 const struct TALER_EXCHANGE_WireAccount *account 396 = &keys->accounts[i]; 397 json_t *debit_restrictions; 398 json_t *credit_restrictions; 399 400 debit_restrictions = json_array (); 401 GNUNET_assert (NULL != debit_restrictions); 402 credit_restrictions = json_array (); 403 GNUNET_assert (NULL != credit_restrictions); 404 for (unsigned int j = 0; j<account->debit_restrictions_length; j++) 405 { 406 if (GNUNET_OK != 407 add_restriction (debit_restrictions, 408 &account->debit_restrictions[j])) 409 { 410 TALER_MERCHANTDB_rollback (pg); 411 GNUNET_break (0); 412 json_decref (debit_restrictions); 413 json_decref (credit_restrictions); 414 return GNUNET_DB_STATUS_HARD_ERROR; 415 } 416 } 417 for (unsigned int j = 0; j<account->credit_restrictions_length; j++) 418 { 419 if (GNUNET_OK != 420 add_restriction (credit_restrictions, 421 &account->credit_restrictions[j])) 422 { 423 TALER_MERCHANTDB_rollback (pg); 424 GNUNET_break (0); 425 json_decref (debit_restrictions); 426 json_decref (credit_restrictions); 427 return GNUNET_DB_STATUS_HARD_ERROR; 428 } 429 } 430 qs = TALER_MERCHANTDB_insert_exchange_account ( 431 pg, 432 &keys->master_pub, 433 account->fpayto_uri, 434 account->conversion_url, 435 debit_restrictions, 436 credit_restrictions, 437 &account->master_sig); 438 json_decref (debit_restrictions); 439 json_decref (credit_restrictions); 440 if (qs < 0) 441 { 442 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 443 return qs; 444 } 445 } /* end 'for all accounts' */ 446 447 for (unsigned int i = 0; i<keys->fees_len; i++) 448 { 449 const struct TALER_EXCHANGE_WireFeesByMethod *fbm 450 = &keys->fees[i]; 451 const char *wire_method = fbm->method; 452 const struct TALER_EXCHANGE_WireAggregateFees *fees 453 = fbm->fees_head; 454 455 while (NULL != fees) 456 { 457 struct GNUNET_HashCode h_wire_method; 458 459 GNUNET_CRYPTO_hash (wire_method, 460 strlen (wire_method) + 1, 461 &h_wire_method); 462 qs = TALER_MERCHANTDB_insert_exchange_wire_fee ( 463 pg, 464 &keys->master_pub, 465 &h_wire_method, 466 &fees->fees, 467 fees->start_date, 468 fees->end_date, 469 &fees->master_sig); 470 if (0 > qs) 471 { 472 GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs); 473 return qs; 474 } 475 fees = fees->next; 476 } /* all fees for this method */ 477 } /* for all methods (i) */ 478 479 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 480 "Updated keys for %s, inserted %d signing keys, %d denom keys, %d fees-by-wire\n", 481 keys->exchange_url, 482 keys->num_sign_keys, 483 keys->num_denom_keys, 484 keys->fees_len); 485 486 { 487 struct GNUNET_DB_EventHeaderP es = { 488 .size = htons (sizeof (es)), 489 .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS) 490 }; 491 492 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 493 "Informing other processes about keys change for %s\n", 494 keys->exchange_url); 495 TALER_MERCHANTDB_event_notify (pg, 496 &es, 497 keys->exchange_url, 498 strlen (keys->exchange_url) + 1); 499 } 500 return qs; 501 } 502 503 504 /** 505 * Run database transaction to store the @a keys in 506 * the merchant database (and notify other processes 507 * that may care about them). 508 * 509 * @param keys the keys to store 510 * @param first_retry earliest we may retry fetching the keys 511 * @return true on success 512 */ 513 static bool 514 store_keys (struct TALER_EXCHANGE_Keys *keys, 515 struct GNUNET_TIME_Absolute first_retry) 516 { 517 enum GNUNET_DB_QueryStatus qs; 518 519 TALER_MERCHANTDB_preflight (pg); 520 for (unsigned int r = 0; r<MAX_RETRIES; r++) 521 { 522 if (GNUNET_OK != 523 TALER_MERCHANTDB_start (pg, 524 "update exchange key data")) 525 { 526 TALER_MERCHANTDB_rollback (pg); 527 GNUNET_break (0); 528 return false; 529 } 530 531 qs = insert_keys_data (keys, 532 first_retry); 533 if (0 > qs) 534 { 535 TALER_MERCHANTDB_rollback (pg); 536 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 537 continue; 538 GNUNET_break (0); 539 return false; 540 } 541 542 qs = TALER_MERCHANTDB_commit (pg); 543 if (0 > qs) 544 { 545 TALER_MERCHANTDB_rollback (pg); 546 if (GNUNET_DB_STATUS_SOFT_ERROR == qs) 547 continue; 548 GNUNET_break (0); 549 return false; 550 } 551 break; 552 } /* end of retry loop */ 553 if (qs < 0) 554 { 555 GNUNET_break (0); 556 return false; 557 } 558 return true; 559 } 560 561 562 /** 563 * Function called with information about who is auditing 564 * a particular exchange and what keys the exchange is using. 565 * 566 * @param e the exchange to update 567 * @param kr response data 568 * @param[in] keys the keys of the exchange 569 */ 570 static void 571 cert_cb ( 572 struct Exchange *e, 573 const struct TALER_EXCHANGE_KeysResponse *kr, 574 struct TALER_EXCHANGE_Keys *keys) 575 { 576 struct GNUNET_TIME_Absolute n; 577 struct GNUNET_TIME_Absolute first_retry; 578 579 e->conn = NULL; 580 e->retry_delay 581 = GNUNET_TIME_STD_BACKOFF (e->retry_delay); 582 switch (kr->hr.http_status) 583 { 584 case MHD_HTTP_OK: 585 TALER_EXCHANGE_keys_decref (e->keys); 586 e->keys = NULL; 587 if (0 != strcasecmp (e->currency, 588 keys->currency)) 589 { 590 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 591 "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n", 592 e->exchange_url, 593 keys->currency, 594 e->currency); 595 fail_keys (e, 596 MHD_HTTP_OK, 597 TALER_EC_GENERIC_CURRENCY_MISMATCH); 598 TALER_EXCHANGE_keys_decref (keys); 599 break; 600 } 601 if (0 != GNUNET_memcmp (&keys->master_pub, 602 &e->master_pub)) 603 { 604 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 605 "Master public key in %skeys response does not match. Ignoring response.\n", 606 e->exchange_url); 607 fail_keys (e, 608 MHD_HTTP_OK, 609 TALER_EC_MERCHANT_GENERIC_EXCHANGE_MASTER_KEY_MISMATCH); 610 TALER_EXCHANGE_keys_decref (keys); 611 break; 612 } 613 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 614 "Got new keys for %s, updating database\n", 615 e->exchange_url); 616 first_retry = GNUNET_TIME_relative_to_absolute ( 617 EXCHANGE_MAXFREQ); 618 if (! store_keys (keys, 619 first_retry)) 620 { 621 GNUNET_break (0); 622 TALER_EXCHANGE_keys_decref (keys); 623 break; 624 } 625 e->keys = keys; 626 /* Reset back-off */ 627 e->retry_delay = EXCHANGE_MAXFREQ; 628 /* limit retry */ 629 e->first_retry = first_retry; 630 /* Limit by expiration */ 631 n = GNUNET_TIME_absolute_max (e->first_retry, 632 keys->key_data_expiration.abs_time); 633 if (NULL != e->retry_task) 634 GNUNET_SCHEDULER_cancel (e->retry_task); 635 e->retry_task = GNUNET_SCHEDULER_add_at (n, 636 &download_keys, 637 e); 638 end_inquiry (); 639 return; 640 default: 641 GNUNET_break (NULL == keys); 642 fail_keys (e, 643 kr->hr.http_status, 644 TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE); 645 break; 646 } 647 /* Try again (soon-ish) */ 648 n = GNUNET_TIME_absolute_max ( 649 e->first_retry, 650 GNUNET_TIME_relative_to_absolute (e->retry_delay)); 651 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 652 "Will download %skeys in %s\n", 653 e->exchange_url, 654 GNUNET_TIME_relative2s ( 655 GNUNET_TIME_absolute_get_remaining (n), 656 true)); 657 if (NULL != e->retry_task) 658 GNUNET_SCHEDULER_cancel (e->retry_task); 659 e->retry_task 660 = GNUNET_SCHEDULER_add_at (n, 661 &download_keys, 662 e); 663 end_inquiry (); 664 } 665 666 667 static void 668 download_keys (void *cls) 669 { 670 struct Exchange *e = cls; 671 672 e->retry_task = NULL; 673 GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries); 674 if (OPEN_INQUIRY_LIMIT <= active_inquiries) 675 { 676 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 677 "Cannot run job: at limit\n"); 678 e->limited = true; 679 at_limit = true; 680 return; 681 } 682 e->retry_delay 683 = GNUNET_TIME_STD_BACKOFF (e->retry_delay); 684 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 685 "Downloading keys from %s (%s)\n", 686 e->exchange_url, 687 e->force_retry ? "forced" : "regular"); 688 e->conn = TALER_EXCHANGE_get_keys_create (ctx, 689 e->exchange_url); 690 if ( (NULL != e->conn) && 691 (! e->force_retry) ) 692 TALER_EXCHANGE_get_keys_set_options ( 693 e->conn, 694 TALER_EXCHANGE_get_keys_option_last_keys (e->keys)); 695 e->force_retry = false; 696 if ( (NULL != e->conn) && 697 (TALER_EC_NONE == 698 TALER_EXCHANGE_get_keys_start (e->conn, 699 &cert_cb, 700 e)) ) 701 { 702 active_inquiries++; 703 } 704 else 705 { 706 struct GNUNET_TIME_Relative n; 707 708 if (NULL != e->conn) 709 { 710 TALER_EXCHANGE_get_keys_cancel (e->conn); 711 e->conn = NULL; 712 } 713 n = GNUNET_TIME_relative_max (e->retry_delay, 714 EXCHANGE_MAXFREQ); 715 e->retry_task 716 = GNUNET_SCHEDULER_add_delayed (n, 717 &download_keys, 718 e); 719 } 720 } 721 722 723 /** 724 * Lookup exchange by @a exchange_url. Create one 725 * if it does not exist. 726 * 727 * @param exchange_url base URL to match against 728 * @return NULL if not found 729 */ 730 static struct Exchange * 731 lookup_exchange (const char *exchange_url) 732 { 733 for (struct Exchange *e = e_head; 734 NULL != e; 735 e = e->next) 736 if (0 == strcmp (e->exchange_url, 737 exchange_url)) 738 return e; 739 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 740 "Got notification about unknown exchange `%s'\n", 741 exchange_url); 742 return NULL; 743 } 744 745 746 /** 747 * Force immediate (re)loading of /keys for an exchange. 748 * 749 * @param cls NULL 750 * @param extra base URL of the exchange that changed 751 * @param extra_len number of bytes in @a extra 752 */ 753 static void 754 force_exchange_keys (void *cls, 755 const void *extra, 756 size_t extra_len) 757 { 758 const char *url = extra; 759 struct Exchange *e; 760 761 if ( (NULL == extra) || 762 (0 == extra_len) ) 763 { 764 GNUNET_break (0); 765 return; 766 } 767 if ('\0' != url[extra_len - 1]) 768 { 769 GNUNET_break (0); 770 return; 771 } 772 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 773 "Received keys change notification: reload `%s'\n", 774 url); 775 e = lookup_exchange (url); 776 if (NULL == e) 777 { 778 GNUNET_break (0); 779 return; 780 } 781 if (NULL != e->conn) 782 { 783 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 784 "Already downloading %skeys\n", 785 url); 786 return; 787 } 788 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 789 "Will download %skeys in %s\n", 790 url, 791 GNUNET_TIME_relative2s ( 792 GNUNET_TIME_absolute_get_remaining ( 793 e->first_retry), 794 true)); 795 if (NULL != e->retry_task) 796 GNUNET_SCHEDULER_cancel (e->retry_task); 797 e->force_retry = true; 798 e->retry_task 799 = GNUNET_SCHEDULER_add_at (e->first_retry, 800 &download_keys, 801 e); 802 } 803 804 805 /** 806 * Function called on each configuration section. Finds sections 807 * about exchanges, parses the entries. 808 * 809 * @param cls NULL 810 * @param section name of the section 811 */ 812 static void 813 accept_exchanges (void *cls, 814 const char *section) 815 { 816 char *url; 817 char *mks; 818 char *currency; 819 820 (void) cls; 821 if (0 != 822 strncasecmp (section, 823 "merchant-exchange-", 824 strlen ("merchant-exchange-"))) 825 return; 826 if (GNUNET_YES == 827 GNUNET_CONFIGURATION_get_value_yesno (cfg, 828 section, 829 "DISABLED")) 830 return; 831 if (GNUNET_OK != 832 GNUNET_CONFIGURATION_get_value_string (cfg, 833 section, 834 "EXCHANGE_BASE_URL", 835 &url)) 836 { 837 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 838 section, 839 "EXCHANGE_BASE_URL"); 840 global_ret = EXIT_NOTCONFIGURED; 841 GNUNET_SCHEDULER_shutdown (); 842 return; 843 } 844 for (struct Exchange *e = e_head; 845 NULL != e; 846 e = e->next) 847 { 848 if (0 == strcmp (url, 849 e->exchange_url)) 850 { 851 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 852 "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n", 853 url, 854 section); 855 GNUNET_free (url); 856 global_ret = EXIT_NOTCONFIGURED; 857 GNUNET_SCHEDULER_shutdown (); 858 return; 859 } 860 } 861 if (GNUNET_OK != 862 GNUNET_CONFIGURATION_get_value_string (cfg, 863 section, 864 "CURRENCY", 865 ¤cy)) 866 { 867 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 868 section, 869 "CURRENCY"); 870 GNUNET_free (url); 871 global_ret = EXIT_NOTCONFIGURED; 872 GNUNET_SCHEDULER_shutdown (); 873 return; 874 } 875 if (GNUNET_OK != 876 GNUNET_CONFIGURATION_get_value_string (cfg, 877 section, 878 "MASTER_KEY", 879 &mks)) 880 { 881 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 882 section, 883 "MASTER_KEY"); 884 global_ret = EXIT_NOTCONFIGURED; 885 GNUNET_SCHEDULER_shutdown (); 886 GNUNET_free (currency); 887 GNUNET_free (url); 888 return; 889 } 890 891 { 892 struct Exchange *e; 893 894 e = GNUNET_new (struct Exchange); 895 e->exchange_url = url; 896 e->currency = currency; 897 GNUNET_CONTAINER_DLL_insert (e_head, 898 e_tail, 899 e); 900 if (GNUNET_OK != 901 GNUNET_CRYPTO_eddsa_public_key_from_string ( 902 mks, 903 strlen (mks), 904 &e->master_pub.eddsa_pub)) 905 { 906 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 907 section, 908 "MASTER_KEY", 909 "malformed EdDSA key"); 910 global_ret = EXIT_NOTCONFIGURED; 911 GNUNET_SCHEDULER_shutdown (); 912 GNUNET_free (mks); 913 return; 914 } 915 GNUNET_free (mks); 916 917 { 918 enum GNUNET_DB_QueryStatus qs; 919 struct TALER_EXCHANGE_Keys *keys = NULL; 920 921 qs = TALER_MERCHANTDB_get_exchange_keys (pg, 922 url, 923 &e->first_retry, 924 &keys); 925 if (qs < 0) 926 { 927 GNUNET_break (0); 928 global_ret = EXIT_FAILURE; 929 GNUNET_SCHEDULER_shutdown (); 930 return; 931 } 932 if ( (NULL != keys) && 933 (0 != strcasecmp (keys->currency, 934 e->currency)) ) 935 { 936 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 937 "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n", 938 keys->currency, 939 e->currency); 940 TALER_EXCHANGE_keys_decref (keys); 941 keys = NULL; 942 } 943 if ( (NULL != keys) && 944 (0 != GNUNET_memcmp (&e->master_pub, 945 &keys->master_pub)) ) 946 { 947 /* master pub differs => fetch keys again */ 948 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 949 "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n", 950 e->exchange_url); 951 TALER_EXCHANGE_keys_decref (keys); 952 keys = NULL; 953 } 954 e->keys = keys; 955 if (NULL == keys) 956 { 957 /* done synchronously so that the active_inquiries 958 is updated immediately */ 959 960 download_keys (e); 961 } 962 else 963 { 964 e->retry_task 965 = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time, 966 &download_keys, 967 e); 968 } 969 } 970 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 971 "Exchange `%s' setup\n", 972 e->exchange_url); 973 } 974 } 975 976 977 /** 978 * We're being aborted with CTRL-C (or SIGTERM). Shut down. 979 * 980 * @param cls closure (NULL) 981 */ 982 static void 983 shutdown_task (void *cls) 984 { 985 (void) cls; 986 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 987 "Running shutdown\n"); 988 while (NULL != e_head) 989 { 990 struct Exchange *e = e_head; 991 992 GNUNET_free (e->exchange_url); 993 GNUNET_free (e->currency); 994 if (NULL != e->conn) 995 { 996 TALER_EXCHANGE_get_keys_cancel (e->conn); 997 e->conn = NULL; 998 } 999 if (NULL != e->keys) 1000 { 1001 TALER_EXCHANGE_keys_decref (e->keys); 1002 e->keys = NULL; 1003 } 1004 if (NULL != e->retry_task) 1005 { 1006 GNUNET_SCHEDULER_cancel (e->retry_task); 1007 e->retry_task = NULL; 1008 } 1009 GNUNET_CONTAINER_DLL_remove (e_head, 1010 e_tail, 1011 e); 1012 GNUNET_free (e); 1013 } 1014 if (NULL != eh) 1015 { 1016 TALER_MERCHANTDB_event_listen_cancel (eh); 1017 eh = NULL; 1018 } 1019 if (NULL != pg) 1020 { 1021 TALER_MERCHANTDB_disconnect (pg); 1022 pg = NULL; 1023 } 1024 cfg = NULL; 1025 if (NULL != ctx) 1026 { 1027 GNUNET_CURL_fini (ctx); 1028 ctx = NULL; 1029 } 1030 if (NULL != rc) 1031 { 1032 GNUNET_CURL_gnunet_rc_destroy (rc); 1033 rc = NULL; 1034 } 1035 } 1036 1037 1038 /** 1039 * First task. 1040 * 1041 * @param cls closure, NULL 1042 * @param args remaining command-line arguments 1043 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 1044 * @param c configuration 1045 */ 1046 static void 1047 run (void *cls, 1048 char *const *args, 1049 const char *cfgfile, 1050 const struct GNUNET_CONFIGURATION_Handle *c) 1051 { 1052 (void) args; 1053 (void) cfgfile; 1054 1055 cfg = c; 1056 TALER_EXCHANGE_setup (enable_h3 1057 ? TALER_EXCHANGE_GO_ENABLE_HTTP3 1058 : TALER_EXCHANGE_GO_FORCE_HTTP1_1); 1059 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, 1060 NULL); 1061 ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1062 &rc); 1063 rc = GNUNET_CURL_gnunet_rc_create (ctx); 1064 if (NULL == ctx) 1065 { 1066 GNUNET_break (0); 1067 GNUNET_SCHEDULER_shutdown (); 1068 global_ret = EXIT_FAILURE; 1069 return; 1070 } 1071 if (NULL == 1072 (pg = TALER_MERCHANTDB_connect (cfg))) 1073 { 1074 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1075 "Failed to initialize DB subsystem. Consider running taler-merchant-dbconfig!\n"); 1076 GNUNET_SCHEDULER_shutdown (); 1077 global_ret = EXIT_NOTCONFIGURED; 1078 return; 1079 } 1080 { 1081 struct GNUNET_DB_EventHeaderP es = { 1082 .size = htons (sizeof (es)), 1083 .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS) 1084 }; 1085 1086 eh = TALER_MERCHANTDB_event_listen (pg, 1087 &es, 1088 GNUNET_TIME_UNIT_FOREVER_REL, 1089 &force_exchange_keys, 1090 NULL); 1091 } 1092 GNUNET_CONFIGURATION_iterate_sections (cfg, 1093 &accept_exchanges, 1094 NULL); 1095 if ( (0 == active_inquiries) && 1096 (test_mode) ) 1097 { 1098 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1099 "No more open inquiries and in test mode. Existing.\n"); 1100 GNUNET_SCHEDULER_shutdown (); 1101 return; 1102 } 1103 } 1104 1105 1106 /** 1107 * The main function of taler-merchant-exchangekeyupdate 1108 * 1109 * @param argc number of arguments from the command line 1110 * @param argv command line arguments 1111 * @return 0 ok, 1 on error 1112 */ 1113 int 1114 main (int argc, 1115 char *const *argv) 1116 { 1117 struct GNUNET_GETOPT_CommandLineOption options[] = { 1118 GNUNET_GETOPT_option_flag ('3', 1119 "http3", 1120 "enable support for HTTP/2 and HTTP/3", 1121 &enable_h3), 1122 GNUNET_GETOPT_option_timetravel ('T', 1123 "timetravel"), 1124 GNUNET_GETOPT_option_flag ('t', 1125 "test", 1126 "run in test mode and exit when idle", 1127 &test_mode), 1128 GNUNET_GETOPT_option_version (VERSION), 1129 GNUNET_GETOPT_OPTION_END 1130 }; 1131 enum GNUNET_GenericReturnValue ret; 1132 1133 ret = GNUNET_PROGRAM_run ( 1134 TALER_MERCHANT_project_data (), 1135 argc, argv, 1136 "taler-merchant-exchangekeyupdate", 1137 gettext_noop ( 1138 "background process that ensures our key and configuration data on exchanges is up-to-date"), 1139 options, 1140 &run, NULL); 1141 if (GNUNET_SYSERR == ret) 1142 return EXIT_INVALIDARGUMENT; 1143 if (GNUNET_NO == ret) 1144 return EXIT_SUCCESS; 1145 return global_ret; 1146 } 1147 1148 1149 /* end of taler-merchant-exchangekeyupdate.c */