merchant

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

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