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