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