LCOV - code coverage report
Current view: top level - backend - taler-merchant-exchangekeyupdate.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 58.6 % 382 224
Test Date: 2026-09-11 17:45:24 Functions: 92.3 % 13 12

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

Generated by: LCOV version 2.0-1