LCOV - code coverage report
Current view: top level - backend - taler-merchant-exchangekeyupdate.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 57.3 % 370 212
Test Date: 2025-11-06 19:31:41 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           47 : end_inquiry (void)
     195              : {
     196           47 :   GNUNET_assert (active_inquiries > 0);
     197           47 :   active_inquiries--;
     198           47 :   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           47 :   if ( (! at_limit) &&
     217           47 :        (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              :   {
     425           11 :     struct GNUNET_DB_EventHeaderP es = {
     426           11 :       .size = ntohs (sizeof (es)),
     427           11 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
     428              :     };
     429              : 
     430           11 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     431              :                 "Informing other processes about keys change for %s\n",
     432              :                 keys->exchange_url);
     433           11 :     db_plugin->event_notify (db_plugin->cls,
     434              :                              &es,
     435           11 :                              keys->exchange_url,
     436           11 :                              strlen (keys->exchange_url) + 1);
     437              :   }
     438           11 :   return qs;
     439              : }
     440              : 
     441              : 
     442              : /**
     443              :  * Run database transaction to store the @a keys in
     444              :  * the merchant database (and notify other processes
     445              :  * that may care about them).
     446              :  *
     447              :  * @param keys the keys to store
     448              :  * @param first_retry earliest we may retry fetching the keys
     449              :  * @return true on success
     450              :  */
     451              : static bool
     452           11 : store_keys (struct TALER_EXCHANGE_Keys *keys,
     453              :             struct GNUNET_TIME_Absolute first_retry)
     454              : {
     455              :   enum GNUNET_DB_QueryStatus qs;
     456              : 
     457           11 :   db_plugin->preflight (db_plugin->cls);
     458           11 :   for (unsigned int r = 0; r<MAX_RETRIES; r++)
     459              :   {
     460           11 :     if (GNUNET_OK !=
     461           11 :         db_plugin->start (db_plugin->cls,
     462              :                           "update exchange key data"))
     463              :     {
     464            0 :       db_plugin->rollback (db_plugin->cls);
     465            0 :       GNUNET_break (0);
     466            0 :       return false;
     467              :     }
     468              : 
     469           11 :     qs = insert_keys_data (keys,
     470              :                            first_retry);
     471           11 :     if (0 > qs)
     472              :     {
     473            0 :       db_plugin->rollback (db_plugin->cls);
     474            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     475            0 :         continue;
     476            0 :       GNUNET_break (0);
     477            0 :       return false;
     478              :     }
     479              : 
     480           11 :     qs = db_plugin->commit (db_plugin->cls);
     481           11 :     if (0 > qs)
     482              :     {
     483            0 :       db_plugin->rollback (db_plugin->cls);
     484            0 :       if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
     485            0 :         continue;
     486            0 :       GNUNET_break (0);
     487            0 :       return false;
     488              :     }
     489           11 :     break;
     490              :   } /* end of retry loop */
     491           11 :   if (qs < 0)
     492              :   {
     493            0 :     GNUNET_break (0);
     494            0 :     return false;
     495              :   }
     496           11 :   return true;
     497              : }
     498              : 
     499              : 
     500              : /**
     501              :  * Function called with information about who is auditing
     502              :  * a particular exchange and what keys the exchange is using.
     503              :  *
     504              :  * @param cls closure with a `struct Exchange *`
     505              :  * @param kr response data
     506              :  * @param[in] keys the keys of the exchange
     507              :  */
     508              : static void
     509           47 : cert_cb (
     510              :   void *cls,
     511              :   const struct TALER_EXCHANGE_KeysResponse *kr,
     512              :   struct TALER_EXCHANGE_Keys *keys)
     513              : {
     514           47 :   struct Exchange *e = cls;
     515              :   struct GNUNET_TIME_Absolute n;
     516              :   struct GNUNET_TIME_Absolute first_retry;
     517              : 
     518           47 :   e->conn = NULL;
     519           47 :   switch (kr->hr.http_status)
     520              :   {
     521           11 :   case MHD_HTTP_OK:
     522           11 :     TALER_EXCHANGE_keys_decref (e->keys);
     523           11 :     e->keys = NULL;
     524           11 :     if (0 != strcmp (e->currency,
     525           11 :                      keys->currency))
     526              :     {
     527            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     528              :                   "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n",
     529              :                   e->exchange_url,
     530              :                   keys->currency,
     531              :                   e->currency);
     532            0 :       TALER_EXCHANGE_keys_decref (keys);
     533            0 :       break;
     534              :     }
     535           11 :     if (0 != GNUNET_memcmp (&keys->master_pub,
     536              :                             &e->master_pub))
     537              :     {
     538            0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     539              :                   "Master public key in %skeys response does not match. Ignoring response.\n",
     540              :                   e->exchange_url);
     541            0 :       TALER_EXCHANGE_keys_decref (keys);
     542            0 :       break;
     543              :     }
     544           11 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     545              :                 "Got new keys for %s, updating database\n",
     546              :                 e->exchange_url);
     547           11 :     first_retry = GNUNET_TIME_relative_to_absolute (
     548              :       EXCHANGE_MAXFREQ);
     549           11 :     if (! store_keys (keys,
     550              :                       first_retry))
     551              :     {
     552            0 :       GNUNET_break (0);
     553            0 :       TALER_EXCHANGE_keys_decref (keys);
     554            0 :       break;
     555              :     }
     556           11 :     e->keys = keys;
     557              :     /* Reset back-off */
     558           11 :     e->retry_delay = EXCHANGE_MAXFREQ;
     559              :     /* limit retry */
     560           11 :     e->first_retry = first_retry;
     561              :     /* Limit by expiration */
     562           11 :     n = GNUNET_TIME_absolute_max (e->first_retry,
     563              :                                   keys->key_data_expiration.abs_time);
     564           11 :     if (NULL != e->retry_task)
     565            0 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     566           11 :     e->retry_task = GNUNET_SCHEDULER_add_at (n,
     567              :                                              &download_keys,
     568              :                                              e);
     569           11 :     end_inquiry ();
     570           11 :     return;
     571           36 :   default:
     572           36 :     GNUNET_break (NULL == keys);
     573           36 :     break;
     574              :   }
     575              :   /* Try again (soon-ish) */
     576              :   e->retry_delay
     577           36 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     578           36 :   n = GNUNET_TIME_absolute_max (
     579              :     e->first_retry,
     580              :     GNUNET_TIME_relative_to_absolute (e->retry_delay));
     581           36 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     582              :               "Will download %skeys in %s\n",
     583              :               e->exchange_url,
     584              :               GNUNET_TIME_relative2s (
     585              :                 GNUNET_TIME_absolute_get_remaining (n),
     586              :                 true));
     587           36 :   if (NULL != e->retry_task)
     588            0 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     589              :   e->retry_task
     590           36 :     = GNUNET_SCHEDULER_add_at (n,
     591              :                                &download_keys,
     592              :                                e);
     593           36 :   end_inquiry ();
     594              : }
     595              : 
     596              : 
     597              : static void
     598           47 : download_keys (void *cls)
     599              : {
     600           47 :   struct Exchange *e = cls;
     601              : 
     602           47 :   e->retry_task = NULL;
     603           47 :   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
     604           47 :   if (OPEN_INQUIRY_LIMIT <= active_inquiries)
     605              :   {
     606            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     607              :                 "Cannot run job: at limit\n");
     608            0 :     e->limited = true;
     609            0 :     at_limit = true;
     610            0 :     return;
     611              :   }
     612              :   e->retry_delay
     613           47 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     614           47 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     615              :               "Downloading keys from %s (%s)\n",
     616              :               e->exchange_url,
     617              :               e->force_retry ? "forced" : "regular");
     618           47 :   e->conn = TALER_EXCHANGE_get_keys (ctx,
     619           47 :                                      e->exchange_url,
     620           47 :                                      e->force_retry
     621              :                                      ? NULL
     622              :                                      : e->keys,
     623              :                                      &cert_cb,
     624              :                                      e);
     625           47 :   e->force_retry = false;
     626           47 :   if (NULL != e->conn)
     627              :   {
     628           47 :     active_inquiries++;
     629              :   }
     630              :   else
     631              :   {
     632              :     struct GNUNET_TIME_Relative n;
     633              : 
     634            0 :     n = GNUNET_TIME_relative_max (e->retry_delay,
     635              :                                   EXCHANGE_MAXFREQ);
     636              :     e->retry_task
     637            0 :       = GNUNET_SCHEDULER_add_delayed (n,
     638              :                                       &download_keys,
     639              :                                       e);
     640              :   }
     641              : }
     642              : 
     643              : 
     644              : /**
     645              :  * Lookup exchange by @a exchange_url. Create one
     646              :  * if it does not exist.
     647              :  *
     648              :  * @param exchange_url base URL to match against
     649              :  * @return NULL if not found
     650              :  */
     651              : static struct Exchange *
     652           11 : lookup_exchange (const char *exchange_url)
     653              : {
     654           11 :   for (struct Exchange *e = e_head;
     655           11 :        NULL != e;
     656            0 :        e = e->next)
     657           11 :     if (0 == strcmp (e->exchange_url,
     658              :                      exchange_url))
     659           11 :       return e;
     660            0 :   GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     661              :               "Got notification about unknown exchange `%s'\n",
     662              :               exchange_url);
     663            0 :   return NULL;
     664              : }
     665              : 
     666              : 
     667              : /**
     668              :  * Force immediate (re)loading of /keys for an exchange.
     669              :  *
     670              :  * @param cls NULL
     671              :  * @param extra base URL of the exchange that changed
     672              :  * @param extra_len number of bytes in @a extra
     673              :  */
     674              : static void
     675           11 : force_exchange_keys (void *cls,
     676              :                      const void *extra,
     677              :                      size_t extra_len)
     678              : {
     679           11 :   const char *url = extra;
     680              :   struct Exchange *e;
     681              : 
     682           11 :   if ( (NULL == extra) ||
     683              :        (0 == extra_len) )
     684              :   {
     685            0 :     GNUNET_break (0);
     686            0 :     return;
     687              :   }
     688           11 :   if ('\0' != url[extra_len - 1])
     689              :   {
     690            0 :     GNUNET_break (0);
     691            0 :     return;
     692              :   }
     693           11 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     694              :               "Received keys change notification: reload `%s'\n",
     695              :               url);
     696           11 :   e = lookup_exchange (url);
     697           11 :   if (NULL == e)
     698              :   {
     699            0 :     GNUNET_break (0);
     700            0 :     return;
     701              :   }
     702           11 :   if (NULL != e->conn)
     703              :   {
     704            6 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     705              :                 "Already downloading %skeys\n",
     706              :                 url);
     707            6 :     return;
     708              :   }
     709            5 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     710              :               "Will download %skeys in %s\n",
     711              :               url,
     712              :               GNUNET_TIME_relative2s (
     713              :                 GNUNET_TIME_absolute_get_remaining (
     714              :                   e->first_retry),
     715              :                 true));
     716            5 :   if (NULL != e->retry_task)
     717            5 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     718            5 :   e->force_retry = true;
     719              :   e->retry_task
     720            5 :     = GNUNET_SCHEDULER_add_at (e->first_retry,
     721              :                                &download_keys,
     722              :                                e);
     723              : }
     724              : 
     725              : 
     726              : /**
     727              :  * Function called on each configuration section. Finds sections
     728              :  * about exchanges, parses the entries.
     729              :  *
     730              :  * @param cls NULL
     731              :  * @param section name of the section
     732              :  */
     733              : static void
     734          603 : accept_exchanges (void *cls,
     735              :                   const char *section)
     736              : {
     737              :   char *url;
     738              :   char *mks;
     739              :   char *currency;
     740              : 
     741              :   (void) cls;
     742          603 :   if (0 !=
     743          603 :       strncasecmp (section,
     744              :                    "merchant-exchange-",
     745              :                    strlen ("merchant-exchange-")))
     746          588 :     return;
     747           45 :   if (GNUNET_YES ==
     748           45 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     749              :                                             section,
     750              :                                             "DISABLED"))
     751           30 :     return;
     752           15 :   if (GNUNET_OK !=
     753           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     754              :                                              section,
     755              :                                              "EXCHANGE_BASE_URL",
     756              :                                              &url))
     757              :   {
     758            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     759              :                                section,
     760              :                                "EXCHANGE_BASE_URL");
     761            0 :     global_ret = EXIT_NOTCONFIGURED;
     762            0 :     GNUNET_SCHEDULER_shutdown ();
     763            0 :     return;
     764              :   }
     765           15 :   for (struct Exchange *e = e_head;
     766           15 :        NULL != e;
     767            0 :        e = e->next)
     768              :   {
     769            0 :     if (0 == strcmp (url,
     770            0 :                      e->exchange_url))
     771              :     {
     772            0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     773              :                   "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n",
     774              :                   url,
     775              :                   section);
     776            0 :       GNUNET_free (url);
     777            0 :       global_ret = EXIT_NOTCONFIGURED;
     778            0 :       GNUNET_SCHEDULER_shutdown ();
     779            0 :       return;
     780              :     }
     781              :   }
     782           15 :   if (GNUNET_OK !=
     783           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     784              :                                              section,
     785              :                                              "CURRENCY",
     786              :                                              &currency))
     787              :   {
     788            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     789              :                                section,
     790              :                                "CURRENCY");
     791            0 :     GNUNET_free (url);
     792            0 :     global_ret = EXIT_NOTCONFIGURED;
     793            0 :     GNUNET_SCHEDULER_shutdown ();
     794            0 :     return;
     795              :   }
     796           15 :   if (GNUNET_OK !=
     797           15 :       GNUNET_CONFIGURATION_get_value_string (cfg,
     798              :                                              section,
     799              :                                              "MASTER_KEY",
     800              :                                              &mks))
     801              :   {
     802            0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
     803              :                                section,
     804              :                                "MASTER_KEY");
     805            0 :     global_ret = EXIT_NOTCONFIGURED;
     806            0 :     GNUNET_SCHEDULER_shutdown ();
     807            0 :     GNUNET_free (currency);
     808            0 :     GNUNET_free (url);
     809            0 :     return;
     810              :   }
     811              : 
     812              :   {
     813              :     struct Exchange *e;
     814              : 
     815           15 :     e = GNUNET_new (struct Exchange);
     816           15 :     e->exchange_url = url;
     817           15 :     e->currency = currency;
     818           15 :     GNUNET_CONTAINER_DLL_insert (e_head,
     819              :                                  e_tail,
     820              :                                  e);
     821           15 :     if (GNUNET_OK !=
     822           15 :         GNUNET_CRYPTO_eddsa_public_key_from_string (
     823              :           mks,
     824              :           strlen (mks),
     825              :           &e->master_pub.eddsa_pub))
     826              :     {
     827            0 :       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     828              :                                  section,
     829              :                                  "MASTER_KEY",
     830              :                                  "malformed EdDSA key");
     831            0 :       global_ret = EXIT_NOTCONFIGURED;
     832            0 :       GNUNET_SCHEDULER_shutdown ();
     833            0 :       GNUNET_free (mks);
     834            0 :       return;
     835              :     }
     836           15 :     GNUNET_free (mks);
     837              : 
     838              :     {
     839              :       enum GNUNET_DB_QueryStatus qs;
     840           15 :       struct TALER_EXCHANGE_Keys *keys = NULL;
     841              : 
     842           15 :       qs = db_plugin->select_exchange_keys (db_plugin->cls,
     843              :                                             url,
     844              :                                             &e->first_retry,
     845              :                                             &keys);
     846           15 :       if (qs < 0)
     847              :       {
     848            0 :         GNUNET_break (0);
     849            0 :         global_ret = EXIT_FAILURE;
     850            0 :         GNUNET_SCHEDULER_shutdown ();
     851            0 :         return;
     852              :       }
     853           15 :       if ( (NULL != keys) &&
     854            0 :            (0 != strcmp (keys->currency,
     855            0 :                          e->currency)) )
     856              :       {
     857            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     858              :                     "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n",
     859              :                     keys->currency,
     860              :                     e->currency);
     861            0 :         TALER_EXCHANGE_keys_decref (keys);
     862            0 :         keys = NULL;
     863              :       }
     864           15 :       if ( (NULL != keys) &&
     865            0 :            (0 != GNUNET_memcmp (&e->master_pub,
     866              :                                 &keys->master_pub)) )
     867              :       {
     868              :         /* master pub differs => fetch keys again */
     869            0 :         GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
     870              :                     "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n",
     871              :                     e->exchange_url);
     872            0 :         TALER_EXCHANGE_keys_decref (keys);
     873            0 :         keys = NULL;
     874              :       }
     875           15 :       e->keys = keys;
     876           15 :       if (NULL == keys)
     877              :       {
     878              :         /* done synchronously so that the active_inquiries
     879              :            is updated immediately */
     880              : 
     881           15 :         download_keys (e);
     882              :       }
     883              :       else
     884              :       {
     885              :         e->retry_task
     886            0 :           = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
     887              :                                      &download_keys,
     888              :                                      e);
     889              :       }
     890              :     }
     891           15 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     892              :                 "Exchange `%s' setup\n",
     893              :                 e->exchange_url);
     894              :   }
     895              : }
     896              : 
     897              : 
     898              : /**
     899              :  * We're being aborted with CTRL-C (or SIGTERM). Shut down.
     900              :  *
     901              :  * @param cls closure (NULL)
     902              :  */
     903              : static void
     904           15 : shutdown_task (void *cls)
     905              : {
     906              :   (void) cls;
     907           15 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     908              :               "Running shutdown\n");
     909           30 :   while (NULL != e_head)
     910              :   {
     911           15 :     struct Exchange *e = e_head;
     912              : 
     913           15 :     GNUNET_free (e->exchange_url);
     914           15 :     GNUNET_free (e->currency);
     915           15 :     if (NULL != e->conn)
     916              :     {
     917            0 :       TALER_EXCHANGE_get_keys_cancel (e->conn);
     918            0 :       e->conn = NULL;
     919              :     }
     920           15 :     if (NULL != e->keys)
     921              :     {
     922           11 :       TALER_EXCHANGE_keys_decref (e->keys);
     923           11 :       e->keys = NULL;
     924              :     }
     925           15 :     if (NULL != e->retry_task)
     926              :     {
     927           15 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     928           15 :       e->retry_task = NULL;
     929              :     }
     930           15 :     GNUNET_CONTAINER_DLL_remove (e_head,
     931              :                                  e_tail,
     932              :                                  e);
     933           15 :     GNUNET_free (e);
     934              :   }
     935           15 :   if (NULL != eh)
     936              :   {
     937           15 :     db_plugin->event_listen_cancel (eh);
     938           15 :     eh = NULL;
     939              :   }
     940           15 :   TALER_MERCHANTDB_plugin_unload (db_plugin);
     941           15 :   db_plugin = NULL;
     942           15 :   cfg = NULL;
     943           15 :   if (NULL != ctx)
     944              :   {
     945           15 :     GNUNET_CURL_fini (ctx);
     946           15 :     ctx = NULL;
     947              :   }
     948           15 :   if (NULL != rc)
     949              :   {
     950           15 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     951           15 :     rc = NULL;
     952              :   }
     953           15 : }
     954              : 
     955              : 
     956              : /**
     957              :  * First task.
     958              :  *
     959              :  * @param cls closure, NULL
     960              :  * @param args remaining command-line arguments
     961              :  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
     962              :  * @param c configuration
     963              :  */
     964              : static void
     965           15 : run (void *cls,
     966              :      char *const *args,
     967              :      const char *cfgfile,
     968              :      const struct GNUNET_CONFIGURATION_Handle *c)
     969              : {
     970              :   (void) args;
     971              :   (void) cfgfile;
     972              : 
     973           15 :   cfg = c;
     974           15 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
     975              :                                  NULL);
     976           15 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
     977              :                           &rc);
     978           15 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
     979           15 :   if (NULL == ctx)
     980              :   {
     981            0 :     GNUNET_break (0);
     982            0 :     GNUNET_SCHEDULER_shutdown ();
     983            0 :     global_ret = EXIT_FAILURE;
     984            0 :     return;
     985              :   }
     986           15 :   if (NULL ==
     987           15 :       (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
     988              :   {
     989            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     990              :                 "Failed to initialize DB subsystem\n");
     991            0 :     GNUNET_SCHEDULER_shutdown ();
     992            0 :     global_ret = EXIT_NOTCONFIGURED;
     993            0 :     return;
     994              :   }
     995           15 :   if (GNUNET_OK !=
     996           15 :       db_plugin->connect (db_plugin->cls))
     997              :   {
     998            0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
     999              :                 "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
    1000            0 :     GNUNET_SCHEDULER_shutdown ();
    1001            0 :     global_ret = EXIT_FAILURE;
    1002            0 :     return;
    1003              :   }
    1004              :   {
    1005           15 :     struct GNUNET_DB_EventHeaderP es = {
    1006           15 :       .size = ntohs (sizeof (es)),
    1007           15 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
    1008              :     };
    1009              : 
    1010           30 :     eh = db_plugin->event_listen (db_plugin->cls,
    1011              :                                   &es,
    1012           15 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1013              :                                   &force_exchange_keys,
    1014              :                                   NULL);
    1015              :   }
    1016           15 :   GNUNET_CONFIGURATION_iterate_sections (cfg,
    1017              :                                          &accept_exchanges,
    1018              :                                          NULL);
    1019           15 :   if ( (0 == active_inquiries) &&
    1020              :        (test_mode) )
    1021              :   {
    1022            0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1023              :                 "No more open inquiries and in test mode. Existing.\n");
    1024            0 :     GNUNET_SCHEDULER_shutdown ();
    1025            0 :     return;
    1026              :   }
    1027              : }
    1028              : 
    1029              : 
    1030              : /**
    1031              :  * The main function of taler-merchant-exchangekeyupdate
    1032              :  *
    1033              :  * @param argc number of arguments from the command line
    1034              :  * @param argv command line arguments
    1035              :  * @return 0 ok, 1 on error
    1036              :  */
    1037              : int
    1038           15 : main (int argc,
    1039              :       char *const *argv)
    1040              : {
    1041           15 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1042           15 :     GNUNET_GETOPT_option_timetravel ('T',
    1043              :                                      "timetravel"),
    1044           15 :     GNUNET_GETOPT_option_flag ('t',
    1045              :                                "test",
    1046              :                                "run in test mode and exit when idle",
    1047              :                                &test_mode),
    1048           15 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1049              :     GNUNET_GETOPT_OPTION_END
    1050              :   };
    1051              :   enum GNUNET_GenericReturnValue ret;
    1052              : 
    1053           15 :   ret = GNUNET_PROGRAM_run (
    1054              :     TALER_MERCHANT_project_data (),
    1055              :     argc, argv,
    1056              :     "taler-merchant-exchangekeyupdate",
    1057              :     gettext_noop (
    1058              :       "background process that ensures our key and configuration data on exchanges is up-to-date"),
    1059              :     options,
    1060              :     &run, NULL);
    1061           15 :   if (GNUNET_SYSERR == ret)
    1062            0 :     return EXIT_INVALIDARGUMENT;
    1063           15 :   if (GNUNET_NO == ret)
    1064            0 :     return EXIT_SUCCESS;
    1065           15 :   return global_ret;
    1066              : }
    1067              : 
    1068              : 
    1069              : /* end of taler-merchant-exchangekeyupdate.c */
        

Generated by: LCOV version 2.0-1