LCOV - code coverage report
Current view: top level - backend - taler-merchant-exchangekeyupdate.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 57.4 % 371 213
Test Date: 2025-11-28 21:09:21 Functions: 91.7 % 12 11

            Line data    Source code
       1              : /*
       2              :   This file is part of TALER
       3              :   Copyright (C) 2023, 2024 Taler Systems SA
       4              : 
       5              :   TALER is free software; you can redistribute it and/or modify it under the
       6              :   terms of the GNU Affero General Public License as published by the Free Software
       7              :   Foundation; either version 3, or (at your option) any later version.
       8              : 
       9              :   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
      10              :   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
      11              :   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
      12              : 
      13              :   You should have received a copy of the GNU Affero General Public License along with
      14              :   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15              : */
      16              : /**
      17              :  * @file taler-merchant-exchangekeyupdate.c
      18              :  * @brief Process that ensures our /keys data for all exchanges is current
      19              :  * @author Christian Grothoff
      20              :  */
      21              : #include "platform.h"
      22              : #include "microhttpd.h"
      23              : #include <gnunet/gnunet_util_lib.h>
      24              : #include <jansson.h>
      25              : #include <pthread.h>
      26              : #include <taler/taler_dbevents.h>
      27              : #include "taler_merchant_util.h"
      28              : #include "taler_merchant_bank_lib.h"
      29              : #include "taler_merchantdb_lib.h"
      30              : #include "taler_merchantdb_plugin.h"
      31              : 
      32              : /**
      33              :  * Maximum frequency for the exchange interaction.
      34              :  */
      35              : #define EXCHANGE_MAXFREQ GNUNET_TIME_relative_multiply ( \
      36              :           GNUNET_TIME_UNIT_MINUTES, \
      37              :           5)
      38              : 
      39              : /**
      40              :  * How many inquiries do we process concurrently at most.
      41              :  */
      42              : #define OPEN_INQUIRY_LIMIT 1024
      43              : 
      44              : /**
      45              :  * How often do we retry after DB serialization errors (at most)?
      46              :  */
      47              : #define MAX_RETRIES 3
      48              : 
      49              : /**
      50              :  * Information about an exchange.
      51              :  */
      52              : struct Exchange
      53              : {
      54              :   /**
      55              :    * Kept in a DLL.
      56              :    */
      57              :   struct Exchange *next;
      58              : 
      59              :   /**
      60              :    * Kept in a DLL.
      61              :    */
      62              :   struct Exchange *prev;
      63              : 
      64              :   /**
      65              :    * Base URL of the exchange are we tracking here.
      66              :    */
      67              :   char *exchange_url;
      68              : 
      69              :   /**
      70              :    * Expected currency of the exchange.
      71              :    */
      72              :   char *currency;
      73              : 
      74              :   /**
      75              :    * A /keys request to this exchange, NULL if not active.
      76              :    */
      77              :   struct TALER_EXCHANGE_GetKeysHandle *conn;
      78              : 
      79              :   /**
      80              :    * The keys of this exchange, NULL if not known.
      81              :    */
      82              :   struct TALER_EXCHANGE_Keys *keys;
      83              : 
      84              :   /**
      85              :    * Task where we retry fetching /keys from the exchange.
      86              :    */
      87              :   struct GNUNET_SCHEDULER_Task *retry_task;
      88              : 
      89              :   /**
      90              :    * Master public key expected for this exchange.
      91              :    */
      92              :   struct TALER_MasterPublicKeyP master_pub;
      93              : 
      94              :   /**
      95              :    * How soon can may we, at the earliest, re-download /keys?
      96              :    */
      97              :   struct GNUNET_TIME_Absolute first_retry;
      98              : 
      99              :   /**
     100              :    * How long should we wait between the next retry?
     101              :    * Used for exponential back-offs.
     102              :    */
     103              :   struct GNUNET_TIME_Relative retry_delay;
     104              : 
     105              :   /**
     106              :    * Are we waiting for /keys downloads due to our
     107              :    * hard limit?
     108              :    */
     109              :   bool limited;
     110              : 
     111              :   /**
     112              :    * Are we force-retrying a /keys download because some keys
     113              :    * were missing (and we thus should not cherry-pick, as
     114              :    * a major reason for a force-reload would be an
     115              :    * exchange that has lost keys and backfilled them, which
     116              :    * breaks keys downloads with cherry-picking).
     117              :    */
     118              :   bool force_retry;
     119              : };
     120              : 
     121              : 
     122              : /**
     123              :  * Head of known exchanges.
     124              :  */
     125              : static struct Exchange *e_head;
     126              : 
     127              : /**
     128              :  * Tail of known exchanges.
     129              :  */
     130              : static struct Exchange *e_tail;
     131              : 
     132              : /**
     133              :  * The merchant's configuration.
     134              :  */
     135              : static const struct GNUNET_CONFIGURATION_Handle *cfg;
     136              : 
     137              : /**
     138              :  * Our database plugin.
     139              :  */
     140              : static struct TALER_MERCHANTDB_Plugin *db_plugin;
     141              : 
     142              : /**
     143              :  * Our event handler listening for /keys forced downloads.
     144              :  */
     145              : static struct GNUNET_DB_EventHandler *eh;
     146              : 
     147              : /**
     148              :  * Handle to the context for interacting with the bank.
     149              :  */
     150              : static struct GNUNET_CURL_Context *ctx;
     151              : 
     152              : /**
     153              :  * Scheduler context for running the @e ctx.
     154              :  */
     155              : static struct GNUNET_CURL_RescheduleContext *rc;
     156              : 
     157              : /**
     158              :  * How many active inquiries do we have right now.
     159              :  */
     160              : static unsigned int active_inquiries;
     161              : 
     162              : /**
     163              :  * Value to return from main(). 0 on success, non-zero on errors.
     164              :  */
     165              : static int global_ret;
     166              : 
     167              : /**
     168              :  * #GNUNET_YES if we are in test mode and should exit when idle.
     169              :  */
     170              : static int test_mode;
     171              : 
     172              : /**
     173              :  * True if the last DB query was limited by the
     174              :  * #OPEN_INQUIRY_LIMIT and we thus should check again
     175              :  * as soon as we are substantially below that limit,
     176              :  * and not only when we get a DB notification.
     177              :  */
     178              : static bool at_limit;
     179              : 
     180              : 
     181              : /**
     182              :  * Function that initiates a /keys download.
     183              :  *
     184              :  * @param cls a `struct Exchange *`
     185              :  */
     186              : static void
     187              : download_keys (void *cls);
     188              : 
     189              : 
     190              : /**
     191              :  * An inquiry finished, check if we need to start more.
     192              :  */
     193              : static void
     194           41 : end_inquiry (void)
     195              : {
     196           41 :   GNUNET_assert (active_inquiries > 0);
     197           41 :   active_inquiries--;
     198           41 :   if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
     199              :        (at_limit) )
     200              :   {
     201            0 :     at_limit = false;
     202            0 :     for (struct Exchange *e = e_head;
     203            0 :          NULL != e;
     204            0 :          e = e->next)
     205              :     {
     206            0 :       if (! e->limited)
     207            0 :         continue;
     208            0 :       e->limited = false;
     209              :       /* done synchronously so that the active_inquiries
     210              :          is updated immediately */
     211            0 :       download_keys (e);
     212            0 :       if (at_limit)
     213            0 :         break;
     214              :     }
     215              :   }
     216           41 :   if ( (! at_limit) &&
     217           41 :        (0 == active_inquiries) &&
     218              :        (test_mode) )
     219              :   {
     220            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     221              :                 "No more open inquiries and in test mode. Existing.\n");
     222            0 :     GNUNET_SCHEDULER_shutdown ();
     223            0 :     return;
     224              :   }
     225              : }
     226              : 
     227              : 
     228              : /**
     229              :  * Add account restriction @a a to array of @a restrictions.
     230              :  *
     231              :  * @param[in,out] restrictions JSON array to build
     232              :  * @param r restriction to add to @a restrictions
     233              :  * @return #GNUNET_SYSERR if @a r is malformed
     234              :  */
     235              : static enum GNUNET_GenericReturnValue
     236            0 : add_restriction (json_t *restrictions,
     237              :                  const struct TALER_EXCHANGE_AccountRestriction *r)
     238              : {
     239              :   json_t *jr;
     240              : 
     241            0 :   jr = NULL;
     242            0 :   switch (r->type)
     243              :   {
     244            0 :   case TALER_EXCHANGE_AR_INVALID:
     245            0 :     GNUNET_break_op (0);
     246            0 :     return GNUNET_SYSERR;
     247            0 :   case TALER_EXCHANGE_AR_DENY:
     248            0 :     jr = GNUNET_JSON_PACK (
     249              :       GNUNET_JSON_pack_string ("type",
     250              :                                "deny")
     251              :       );
     252            0 :     break;
     253            0 :   case TALER_EXCHANGE_AR_REGEX:
     254            0 :     jr = GNUNET_JSON_PACK (
     255              :       GNUNET_JSON_pack_string (
     256              :         "type",
     257              :         "regex"),
     258              :       GNUNET_JSON_pack_string (
     259              :         "regex",
     260              :         r->details.regex.posix_egrep),
     261              :       GNUNET_JSON_pack_string (
     262              :         "human_hint",
     263              :         r->details.regex.human_hint),
     264              :       GNUNET_JSON_pack_object_incref (
     265              :         "human_hint_i18n",
     266              :         (json_t *) r->details.regex.human_hint_i18n)
     267              :       );
     268            0 :     break;
     269              :   }
     270            0 :   if (NULL == jr)
     271              :   {
     272            0 :     GNUNET_break_op (0);
     273            0 :     return GNUNET_SYSERR;
     274              :   }
     275            0 :   GNUNET_assert (0 ==
     276              :                  json_array_append_new (restrictions,
     277              :                                         jr));
     278            0 :   return GNUNET_OK;
     279              : 
     280              : }
     281              : 
     282              : 
     283              : /**
     284              :  * Update our information in the database about the
     285              :  * /keys of an exchange. Run inside of a database
     286              :  * transaction scope that will re-try and/or commit
     287              :  * depending on the return value.
     288              :  *
     289              :  * @param keys information to persist
     290              :  * @param first_retry earliest we may retry fetching the keys
     291              :  * @return transaction status
     292              :  */
     293              : static enum GNUNET_DB_QueryStatus
     294           11 : insert_keys_data (const struct TALER_EXCHANGE_Keys *keys,
     295              :                   struct GNUNET_TIME_Absolute first_retry)
     296              : {
     297              :   enum GNUNET_DB_QueryStatus qs;
     298              : 
     299              :   /* store exchange online signing keys in our DB */
     300           66 :   for (unsigned int i = 0; i<keys->num_sign_keys; i++)
     301              :   {
     302           55 :     const struct TALER_EXCHANGE_SigningPublicKey *sign_key
     303           55 :       = &keys->sign_keys[i];
     304              : 
     305           55 :     qs = db_plugin->insert_exchange_signkey (
     306           55 :       db_plugin->cls,
     307              :       &keys->master_pub,
     308              :       &sign_key->key,
     309              :       sign_key->valid_from,
     310              :       sign_key->valid_until,
     311              :       sign_key->valid_legal,
     312              :       &sign_key->master_sig);
     313              :     /* 0 is OK, we may already have the key in the DB! */
     314           55 :     if (0 > qs)
     315              :     {
     316            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     317            0 :       return qs;
     318              :     }
     319              :   }
     320              : 
     321           11 :   qs = db_plugin->insert_exchange_keys (db_plugin->cls,
     322              :                                         keys,
     323              :                                         first_retry);
     324           11 :   if (0 > qs)
     325              :   {
     326            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     327            0 :     return qs;
     328              :   }
     329              : 
     330           11 :   qs = db_plugin->delete_exchange_accounts (db_plugin->cls,
     331              :                                             &keys->master_pub);
     332           11 :   if (0 > qs)
     333              :   {
     334            0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     335            0 :     return qs;
     336              :   }
     337              : 
     338           22 :   for (unsigned int i = 0; i<keys->accounts_len; i++)
     339              :   {
     340           11 :     const struct TALER_EXCHANGE_WireAccount *account
     341           11 :       = &keys->accounts[i];
     342              :     json_t *debit_restrictions;
     343              :     json_t *credit_restrictions;
     344              : 
     345           11 :     debit_restrictions = json_array ();
     346           11 :     GNUNET_assert (NULL != debit_restrictions);
     347           11 :     credit_restrictions = json_array ();
     348           11 :     GNUNET_assert (NULL != credit_restrictions);
     349           11 :     for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
     350              :     {
     351            0 :       if (GNUNET_OK !=
     352            0 :           add_restriction (debit_restrictions,
     353            0 :                            &account->debit_restrictions[j]))
     354              :       {
     355            0 :         db_plugin->rollback (db_plugin->cls);
     356            0 :         GNUNET_break (0);
     357            0 :         json_decref (debit_restrictions);
     358            0 :         json_decref (credit_restrictions);
     359            0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     360              :       }
     361              :     }
     362           11 :     for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
     363              :     {
     364            0 :       if (GNUNET_OK !=
     365            0 :           add_restriction (credit_restrictions,
     366            0 :                            &account->credit_restrictions[j]))
     367              :       {
     368            0 :         db_plugin->rollback (db_plugin->cls);
     369            0 :         GNUNET_break (0);
     370            0 :         json_decref (debit_restrictions);
     371            0 :         json_decref (credit_restrictions);
     372            0 :         return GNUNET_DB_STATUS_HARD_ERROR;
     373              :       }
     374              :     }
     375           11 :     qs = db_plugin->insert_exchange_account (
     376           11 :       db_plugin->cls,
     377              :       &keys->master_pub,
     378              :       account->fpayto_uri,
     379           11 :       account->conversion_url,
     380              :       debit_restrictions,
     381              :       credit_restrictions,
     382              :       &account->master_sig);
     383           11 :     json_decref (debit_restrictions);
     384           11 :     json_decref (credit_restrictions);
     385           11 :     if (qs < 0)
     386              :     {
     387            0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     388            0 :       return qs;
     389              :     }
     390              :   } /* end 'for all accounts' */
     391              : 
     392           22 :   for (unsigned int i = 0; i<keys->fees_len; i++)
     393              :   {
     394           11 :     const struct TALER_EXCHANGE_WireFeesByMethod *fbm
     395           11 :       = &keys->fees[i];
     396           11 :     const char *wire_method = fbm->method;
     397           11 :     const struct TALER_EXCHANGE_WireAggregateFees *fees
     398              :       = fbm->fees_head;
     399              : 
     400           22 :     while (NULL != fees)
     401              :     {
     402              :       struct GNUNET_HashCode h_wire_method;
     403              : 
     404           11 :       GNUNET_CRYPTO_hash (wire_method,
     405           11 :                           strlen (wire_method) + 1,
     406              :                           &h_wire_method);
     407           11 :       qs = db_plugin->store_wire_fee_by_exchange (
     408           11 :         db_plugin->cls,
     409              :         &keys->master_pub,
     410              :         &h_wire_method,
     411              :         &fees->fees,
     412              :         fees->start_date,
     413              :         fees->end_date,
     414              :         &fees->master_sig);
     415           11 :       if (0 > qs)
     416              :       {
     417            0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     418            0 :         return qs;
     419              :       }
     420           11 :       fees = fees->next;
     421              :     } /* all fees for this method */
     422              :   } /* for all methods (i) */
     423              : 
     424           11 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     425              :               "Updated keys for %s, inserted %d signing keys, %d denom keys, %d fees-by-wire\n",
     426              :               keys->exchange_url,
     427              :               keys->num_sign_keys,
     428              :               keys->num_denom_keys,
     429              :               keys->fees_len);
     430              : 
     431              :   {
     432           11 :     struct GNUNET_DB_EventHeaderP es = {
     433           11 :       .size = ntohs (sizeof (es)),
     434           11 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
     435              :     };
     436              : 
     437           11 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     438              :                 "Informing other processes about keys change for %s\n",
     439              :                 keys->exchange_url);
     440           11 :     db_plugin->event_notify (db_plugin->cls,
     441              :                              &es,
     442           11 :                              keys->exchange_url,
     443           11 :                              strlen (keys->exchange_url) + 1);
     444              :   }
     445           11 :   return qs;
     446              : }
     447              : 
     448              : 
     449              : /**
     450              :  * Run database transaction to store the @a keys in
     451              :  * the merchant database (and notify other processes
     452              :  * that may care about them).
     453              :  *
     454              :  * @param keys the keys to store
     455              :  * @param first_retry earliest we may retry fetching the keys
     456              :  * @return true on success
     457              :  */
     458              : static bool
     459           11 : store_keys (struct TALER_EXCHANGE_Keys *keys,
     460              :             struct GNUNET_TIME_Absolute first_retry)
     461              : {
     462              :   enum GNUNET_DB_QueryStatus qs;
     463              : 
     464           11 :   db_plugin->preflight (db_plugin->cls);
     465           11 :   for (unsigned int r = 0; r<MAX_RETRIES; r++)
     466              :   {
     467           11 :     if (GNUNET_OK !=
     468           11 :         db_plugin->start (db_plugin->cls,
     469              :                           "update exchange key data"))
     470              :     {
     471            0 :       db_plugin->rollback (db_plugin->cls);
     472            0 :       GNUNET_break (0);
     473            0 :       return false;
     474              :     }
     475              : 
     476           11 :     qs = insert_keys_data (keys,
     477              :                            first_retry);
     478           11 :     if (0 > qs)
     479              :     {
     480            0 :       db_plugin->rollback (db_plugin->cls);
     481            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     482            0 :         continue;
     483            0 :       GNUNET_break (0);
     484            0 :       return false;
     485              :     }
     486              : 
     487           11 :     qs = db_plugin->commit (db_plugin->cls);
     488           11 :     if (0 > qs)
     489              :     {
     490            0 :       db_plugin->rollback (db_plugin->cls);
     491            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     492            0 :         continue;
     493            0 :       GNUNET_break (0);
     494            0 :       return false;
     495              :     }
     496           11 :     break;
     497              :   } /* end of retry loop */
     498           11 :   if (qs < 0)
     499              :   {
     500            0 :     GNUNET_break (0);
     501            0 :     return false;
     502              :   }
     503           11 :   return true;
     504              : }
     505              : 
     506              : 
     507              : /**
     508              :  * Function called with information about who is auditing
     509              :  * a particular exchange and what keys the exchange is using.
     510              :  *
     511              :  * @param cls closure with a `struct Exchange *`
     512              :  * @param kr response data
     513              :  * @param[in] keys the keys of the exchange
     514              :  */
     515              : static void
     516           41 : cert_cb (
     517              :   void *cls,
     518              :   const struct TALER_EXCHANGE_KeysResponse *kr,
     519              :   struct TALER_EXCHANGE_Keys *keys)
     520              : {
     521           41 :   struct Exchange *e = cls;
     522              :   struct GNUNET_TIME_Absolute n;
     523              :   struct GNUNET_TIME_Absolute first_retry;
     524              : 
     525           41 :   e->conn = NULL;
     526           41 :   switch (kr->hr.http_status)
     527              :   {
     528           11 :   case MHD_HTTP_OK:
     529           11 :     TALER_EXCHANGE_keys_decref (e->keys);
     530           11 :     e->keys = NULL;
     531           11 :     if (0 != strcmp (e->currency,
     532           11 :                      keys->currency))
     533              :     {
     534            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     535              :                   "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n",
     536              :                   e->exchange_url,
     537              :                   keys->currency,
     538              :                   e->currency);
     539            0 :       TALER_EXCHANGE_keys_decref (keys);
     540            0 :       break;
     541              :     }
     542           11 :     if (0 != GNUNET_memcmp (&keys->master_pub,
     543              :                             &e->master_pub))
     544              :     {
     545            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     546              :                   "Master public key in %skeys response does not match. Ignoring response.\n",
     547              :                   e->exchange_url);
     548            0 :       TALER_EXCHANGE_keys_decref (keys);
     549            0 :       break;
     550              :     }
     551           11 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     552              :                 "Got new keys for %s, updating database\n",
     553              :                 e->exchange_url);
     554           11 :     first_retry = GNUNET_TIME_relative_to_absolute (
     555              :       EXCHANGE_MAXFREQ);
     556           11 :     if (! store_keys (keys,
     557              :                       first_retry))
     558              :     {
     559            0 :       GNUNET_break (0);
     560            0 :       TALER_EXCHANGE_keys_decref (keys);
     561            0 :       break;
     562              :     }
     563           11 :     e->keys = keys;
     564              :     /* Reset back-off */
     565           11 :     e->retry_delay = EXCHANGE_MAXFREQ;
     566              :     /* limit retry */
     567           11 :     e->first_retry = first_retry;
     568              :     /* Limit by expiration */
     569           11 :     n = GNUNET_TIME_absolute_max (e->first_retry,
     570              :                                   keys->key_data_expiration.abs_time);
     571           11 :     if (NULL != e->retry_task)
     572            0 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     573           11 :     e->retry_task = GNUNET_SCHEDULER_add_at (n,
     574              :                                              &download_keys,
     575              :                                              e);
     576           11 :     end_inquiry ();
     577           11 :     return;
     578           30 :   default:
     579           30 :     GNUNET_break (NULL == keys);
     580           30 :     break;
     581              :   }
     582              :   /* Try again (soon-ish) */
     583              :   e->retry_delay
     584           30 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     585           30 :   n = GNUNET_TIME_absolute_max (
     586              :     e->first_retry,
     587              :     GNUNET_TIME_relative_to_absolute (e->retry_delay));
     588           30 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     589              :               "Will download %skeys in %s\n",
     590              :               e->exchange_url,
     591              :               GNUNET_TIME_relative2s (
     592              :                 GNUNET_TIME_absolute_get_remaining (n),
     593              :                 true));
     594           30 :   if (NULL != e->retry_task)
     595            0 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     596              :   e->retry_task
     597           30 :     = GNUNET_SCHEDULER_add_at (n,
     598              :                                &download_keys,
     599              :                                e);
     600           30 :   end_inquiry ();
     601              : }
     602              : 
     603              : 
     604              : static void
     605           41 : download_keys (void *cls)
     606              : {
     607           41 :   struct Exchange *e = cls;
     608              : 
     609           41 :   e->retry_task = NULL;
     610           41 :   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
     611           41 :   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
     612              :   {
     613            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     614              :                 "Cannot run job: at limit\n");
     615            0 :     e->limited = true;
     616            0 :     at_limit = true;
     617            0 :     return;
     618              :   }
     619              :   e->retry_delay
     620           41 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     621           41 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     622              :               "Downloading keys from %s (%s)\n",
     623              :               e->exchange_url,
     624              :               e->force_retry ? "forced" : "regular");
     625           41 :   e->conn = TALER_EXCHANGE_get_keys (ctx,
     626           41 :                                      e->exchange_url,
     627           41 :                                      e->force_retry
     628              :                                      ? NULL
     629              :                                      : e->keys,
     630              :                                      &cert_cb,
     631              :                                      e);
     632           41 :   e->force_retry = false;
     633           41 :   if (NULL != e->conn)
     634              :   {
     635           41 :     active_inquiries++;
     636              :   }
     637              :   else
     638              :   {
     639              :     struct GNUNET_TIME_Relative n;
     640              : 
     641            0 :     n = GNUNET_TIME_relative_max (e->retry_delay,
     642              :                                   EXCHANGE_MAXFREQ);
     643              :     e->retry_task
     644            0 :       = GNUNET_SCHEDULER_add_delayed (n,
     645              :                                       &download_keys,
     646              :                                       e);
     647              :   }
     648              : }
     649              : 
     650              : 
     651              : /**
     652              :  * Lookup exchange by @a exchange_url. Create one
     653              :  * if it does not exist.
     654              :  *
     655              :  * @param exchange_url base URL to match against
     656              :  * @return NULL if not found
     657              :  */
     658              : static struct Exchange *
     659           15 : lookup_exchange (const char *exchange_url)
     660              : {
     661           15 :   for (struct Exchange *e = e_head;
     662           15 :        NULL != e;
     663            0 :        e = e->next)
     664           15 :     if (0 == strcmp (e->exchange_url,
     665              :                      exchange_url))
     666           15 :       return e;
     667            0 :   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     668              :               "Got notification about unknown exchange `%s'\n",
     669              :               exchange_url);
     670            0 :   return NULL;
     671              : }
     672              : 
     673              : 
     674              : /**
     675              :  * Force immediate (re)loading of /keys for an exchange.
     676              :  *
     677              :  * @param cls NULL
     678              :  * @param extra base URL of the exchange that changed
     679              :  * @param extra_len number of bytes in @a extra
     680              :  */
     681              : static void
     682           15 : force_exchange_keys (void *cls,
     683              :                      const void *extra,
     684              :                      size_t extra_len)
     685              : {
     686           15 :   const char *url = extra;
     687              :   struct Exchange *e;
     688              : 
     689           15 :   if ( (NULL == extra) ||
     690              :        (0 == extra_len) )
     691              :   {
     692            0 :     GNUNET_break (0);
     693            0 :     return;
     694              :   }
     695           15 :   if ('\0' != url[extra_len - 1])
     696              :   {
     697            0 :     GNUNET_break (0);
     698            0 :     return;
     699              :   }
     700           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     701              :               "Received keys change notification: reload `%s'\n",
     702              :               url);
     703           15 :   e = lookup_exchange (url);
     704           15 :   if (NULL == e)
     705              :   {
     706            0 :     GNUNET_break (0);
     707            0 :     return;
     708              :   }
     709           15 :   if (NULL != e->conn)
     710              :   {
     711            8 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     712              :                 "Already downloading %skeys\n",
     713              :                 url);
     714            8 :     return;
     715              :   }
     716            7 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     717              :               "Will download %skeys in %s\n",
     718              :               url,
     719              :               GNUNET_TIME_relative2s (
     720              :                 GNUNET_TIME_absolute_get_remaining (
     721              :                   e->first_retry),
     722              :                 true));
     723            7 :   if (NULL != e->retry_task)
     724            7 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     725            7 :   e->force_retry = true;
     726              :   e->retry_task
     727            7 :     = GNUNET_SCHEDULER_add_at (e->first_retry,
     728              :                                &download_keys,
     729              :                                e);
     730              : }
     731              : 
     732              : 
     733              : /**
     734              :  * Function called on each configuration section. Finds sections
     735              :  * about exchanges, parses the entries.
     736              :  *
     737              :  * @param cls NULL
     738              :  * @param section name of the section
     739              :  */
     740              : static void
     741          603 : accept_exchanges (void *cls,
     742              :                   const char *section)
     743              : {
     744              :   char *url;
     745              :   char *mks;
     746              :   char *currency;
     747              : 
     748              :   (void) cls;
     749          603 :   if (0 !=
     750          603 :       strncasecmp (section,
     751              :                    "merchant-exchange-",
     752              :                    strlen ("merchant-exchange-")))
     753          588 :     return;
     754           45 :   if (GNUNET_YES ==
     755           45 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     756              :                                             section,
     757              :                                             "DISABLED"))
     758           30 :     return;
     759           15 :   if (GNUNET_OK !=
     760           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     761              :                                              section,
     762              :                                              "EXCHANGE_BASE_URL",
     763              :                                              &url))
     764              :   {
     765            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     766              :                                section,
     767              :                                "EXCHANGE_BASE_URL");
     768            0 :     global_ret = EXIT_NOTCONFIGURED;
     769            0 :     GNUNET_SCHEDULER_shutdown ();
     770            0 :     return;
     771              :   }
     772           15 :   for (struct Exchange *e = e_head;
     773           15 :        NULL != e;
     774            0 :        e = e->next)
     775              :   {
     776            0 :     if (0 == strcmp (url,
     777            0 :                      e->exchange_url))
     778              :     {
     779            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     780              :                   "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n",
     781              :                   url,
     782              :                   section);
     783            0 :       GNUNET_free (url);
     784            0 :       global_ret = EXIT_NOTCONFIGURED;
     785            0 :       GNUNET_SCHEDULER_shutdown ();
     786            0 :       return;
     787              :     }
     788              :   }
     789           15 :   if (GNUNET_OK !=
     790           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     791              :                                              section,
     792              :                                              "CURRENCY",
     793              :                                              &currency))
     794              :   {
     795            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     796              :                                section,
     797              :                                "CURRENCY");
     798            0 :     GNUNET_free (url);
     799            0 :     global_ret = EXIT_NOTCONFIGURED;
     800            0 :     GNUNET_SCHEDULER_shutdown ();
     801            0 :     return;
     802              :   }
     803           15 :   if (GNUNET_OK !=
     804           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     805              :                                              section,
     806              :                                              "MASTER_KEY",
     807              :                                              &mks))
     808              :   {
     809            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     810              :                                section,
     811              :                                "MASTER_KEY");
     812            0 :     global_ret = EXIT_NOTCONFIGURED;
     813            0 :     GNUNET_SCHEDULER_shutdown ();
     814            0 :     GNUNET_free (currency);
     815            0 :     GNUNET_free (url);
     816            0 :     return;
     817              :   }
     818              : 
     819              :   {
     820              :     struct Exchange *e;
     821              : 
     822           15 :     e = GNUNET_new (struct Exchange);
     823           15 :     e->exchange_url = url;
     824           15 :     e->currency = currency;
     825           15 :     GNUNET_CONTAINER_DLL_insert (e_head,
     826              :                                  e_tail,
     827              :                                  e);
     828           15 :     if (GNUNET_OK !=
     829           15 :         GNUNET_CRYPTO_eddsa_public_key_from_string (
     830              :           mks,
     831              :           strlen (mks),
     832              :           &e->master_pub.eddsa_pub))
     833              :     {
     834            0 :       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     835              :                                  section,
     836              :                                  "MASTER_KEY",
     837              :                                  "malformed EdDSA key");
     838            0 :       global_ret = EXIT_NOTCONFIGURED;
     839            0 :       GNUNET_SCHEDULER_shutdown ();
     840            0 :       GNUNET_free (mks);
     841            0 :       return;
     842              :     }
     843           15 :     GNUNET_free (mks);
     844              : 
     845              :     {
     846              :       enum GNUNET_DB_QueryStatus qs;
     847           15 :       struct TALER_EXCHANGE_Keys *keys = NULL;
     848              : 
     849           15 :       qs = db_plugin->select_exchange_keys (db_plugin->cls,
     850              :                                             url,
     851              :                                             &e->first_retry,
     852              :                                             &keys);
     853           15 :       if (qs < 0)
     854              :       {
     855            0 :         GNUNET_break (0);
     856            0 :         global_ret = EXIT_FAILURE;
     857            0 :         GNUNET_SCHEDULER_shutdown ();
     858            0 :         return;
     859              :       }
     860           15 :       if ( (NULL != keys) &&
     861            0 :            (0 != strcmp (keys->currency,
     862            0 :                          e->currency)) )
     863              :       {
     864            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     865              :                     "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n",
     866              :                     keys->currency,
     867              :                     e->currency);
     868            0 :         TALER_EXCHANGE_keys_decref (keys);
     869            0 :         keys = NULL;
     870              :       }
     871           15 :       if ( (NULL != keys) &&
     872            0 :            (0 != GNUNET_memcmp (&e->master_pub,
     873              :                                 &keys->master_pub)) )
     874              :       {
     875              :         /* master pub differs => fetch keys again */
     876            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     877              :                     "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n",
     878              :                     e->exchange_url);
     879            0 :         TALER_EXCHANGE_keys_decref (keys);
     880            0 :         keys = NULL;
     881              :       }
     882           15 :       e->keys = keys;
     883           15 :       if (NULL == keys)
     884              :       {
     885              :         /* done synchronously so that the active_inquiries
     886              :            is updated immediately */
     887              : 
     888           15 :         download_keys (e);
     889              :       }
     890              :       else
     891              :       {
     892              :         e->retry_task
     893            0 :           = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
     894              :                                      &download_keys,
     895              :                                      e);
     896              :       }
     897              :     }
     898           15 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     899              :                 "Exchange `%s' setup\n",
     900              :                 e->exchange_url);
     901              :   }
     902              : }
     903              : 
     904              : 
     905              : /**
     906              :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     907              :  *
     908              :  * @param cls closure (NULL)
     909              :  */
     910              : static void
     911           15 : shutdown_task (void *cls)
     912              : {
     913              :   (void) cls;
     914           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     915              :               "Running shutdown\n");
     916           30 :   while (NULL != e_head)
     917              :   {
     918           15 :     struct Exchange *e = e_head;
     919              : 
     920           15 :     GNUNET_free (e->exchange_url);
     921           15 :     GNUNET_free (e->currency);
     922           15 :     if (NULL != e->conn)
     923              :     {
     924            0 :       TALER_EXCHANGE_get_keys_cancel (e->conn);
     925            0 :       e->conn = NULL;
     926              :     }
     927           15 :     if (NULL != e->keys)
     928              :     {
     929           11 :       TALER_EXCHANGE_keys_decref (e->keys);
     930           11 :       e->keys = NULL;
     931              :     }
     932           15 :     if (NULL != e->retry_task)
     933              :     {
     934           15 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     935           15 :       e->retry_task = NULL;
     936              :     }
     937           15 :     GNUNET_CONTAINER_DLL_remove (e_head,
     938              :                                  e_tail,
     939              :                                  e);
     940           15 :     GNUNET_free (e);
     941              :   }
     942           15 :   if (NULL != eh)
     943              :   {
     944           15 :     db_plugin->event_listen_cancel (eh);
     945           15 :     eh = NULL;
     946              :   }
     947           15 :   TALER_MERCHANTDB_plugin_unload (db_plugin);
     948           15 :   db_plugin = NULL;
     949           15 :   cfg = NULL;
     950           15 :   if (NULL != ctx)
     951              :   {
     952           15 :     GNUNET_CURL_fini (ctx);
     953           15 :     ctx = NULL;
     954              :   }
     955           15 :   if (NULL != rc)
     956              :   {
     957           15 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     958           15 :     rc = NULL;
     959              :   }
     960           15 : }
     961              : 
     962              : 
     963              : /**
     964              :  * First task.
     965              :  *
     966              :  * @param cls closure, NULL
     967              :  * @param args remaining command-line arguments
     968              :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
     969              :  * @param c configuration
     970              :  */
     971              : static void
     972           15 : run (void *cls,
     973              :      char *const *args,
     974              :      const char *cfgfile,
     975              :      const struct GNUNET_CONFIGURATION_Handle *c)
     976              : {
     977              :   (void) args;
     978              :   (void) cfgfile;
     979              : 
     980           15 :   cfg = c;
     981           15 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
     982              :                                  NULL);
     983           15 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
     984              :                           &rc);
     985           15 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
     986           15 :   if (NULL == ctx)
     987              :   {
     988            0 :     GNUNET_break (0);
     989            0 :     GNUNET_SCHEDULER_shutdown ();
     990            0 :     global_ret = EXIT_FAILURE;
     991            0 :     return;
     992              :   }
     993           15 :   if (NULL ==
     994           15 :       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
     995              :   {
     996            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     997              :                 "Failed to initialize DB subsystem\n");
     998            0 :     GNUNET_SCHEDULER_shutdown ();
     999            0 :     global_ret = EXIT_NOTCONFIGURED;
    1000            0 :     return;
    1001              :   }
    1002           15 :   if (GNUNET_OK !=
    1003           15 :       db_plugin->connect (db_plugin->cls))
    1004              :   {
    1005            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1006              :                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    1007            0 :     GNUNET_SCHEDULER_shutdown ();
    1008            0 :     global_ret = EXIT_FAILURE;
    1009            0 :     return;
    1010              :   }
    1011              :   {
    1012           15 :     struct GNUNET_DB_EventHeaderP es = {
    1013           15 :       .size = ntohs (sizeof (es)),
    1014           15 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
    1015              :     };
    1016              : 
    1017           30 :     eh = db_plugin->event_listen (db_plugin->cls,
    1018              :                                   &es,
    1019           15 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1020              :                                   &force_exchange_keys,
    1021              :                                   NULL);
    1022              :   }
    1023           15 :   GNUNET_CONFIGURATION_iterate_sections (cfg,
    1024              :                                          &accept_exchanges,
    1025              :                                          NULL);
    1026           15 :   if ( (0 == active_inquiries) &&
    1027              :        (test_mode) )
    1028              :   {
    1029            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1030              :                 "No more open inquiries and in test mode. Existing.\n");
    1031            0 :     GNUNET_SCHEDULER_shutdown ();
    1032            0 :     return;
    1033              :   }
    1034              : }
    1035              : 
    1036              : 
    1037              : /**
    1038              :  * The main function of taler-merchant-exchangekeyupdate
    1039              :  *
    1040              :  * @param argc number of arguments from the command line
    1041              :  * @param argv command line arguments
    1042              :  * @return 0 ok, 1 on error
    1043              :  */
    1044              : int
    1045           15 : main (int argc,
    1046              :       char *const *argv)
    1047              : {
    1048           15 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1049           15 :     GNUNET_GETOPT_option_timetravel ('T',
    1050              :                                      "timetravel"),
    1051           15 :     GNUNET_GETOPT_option_flag ('t',
    1052              :                                "test",
    1053              :                                "run in test mode and exit when idle",
    1054              :                                &test_mode),
    1055           15 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1056              :     GNUNET_GETOPT_OPTION_END
    1057              :   };
    1058              :   enum GNUNET_GenericReturnValue ret;
    1059              : 
    1060           15 :   ret = GNUNET_PROGRAM_run (
    1061              :     TALER_MERCHANT_project_data (),
    1062              :     argc, argv,
    1063              :     "taler-merchant-exchangekeyupdate",
    1064              :     gettext_noop (
    1065              :       "background process that ensures our key and configuration data on exchanges is up-to-date"),
    1066              :     options,
    1067              :     &run, NULL);
    1068           15 :   if (GNUNET_SYSERR == ret)
    1069            0 :     return EXIT_INVALIDARGUMENT;
    1070           15 :   if (GNUNET_NO == ret)
    1071            0 :     return EXIT_SUCCESS;
    1072           15 :   return global_ret;
    1073              : }
    1074              : 
    1075              : 
    1076              : /* end of taler-merchant-exchangekeyupdate.c */
        

Generated by: LCOV version 2.0-1