merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

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                                              &currency))
    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 */