merchant

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

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