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