LCOV - code coverage report
Current view: top level - backend - taler-merchant-exchangekeyupdate.c (source / functions) Hit Total Coverage
Test: coverage.info Lines: 236 370 63.8 %
Date: 2025-06-23 16:22:09 Functions: 12 12 100.0 %

          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          44 : end_inquiry (void)
     195             : {
     196          44 :   GNUNET_assert (active_inquiries > 0);
     197          44 :   active_inquiries--;
     198          44 :   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          44 :   if ( (! at_limit) &&
     217          44 :        (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          26 : add_restriction (json_t *restrictions,
     237             :                  const struct TALER_EXCHANGE_AccountRestriction *r)
     238             : {
     239             :   json_t *jr;
     240             : 
     241          26 :   jr = NULL;
     242          26 :   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          26 :     break;
     253          26 :   case TALER_EXCHANGE_AR_REGEX:
     254          26 :     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          26 :     break;
     269             :   }
     270          26 :   if (NULL == jr)
     271             :   {
     272           0 :     GNUNET_break_op (0);
     273           0 :     return GNUNET_SYSERR;
     274             :   }
     275          26 :   GNUNET_assert (0 ==
     276             :                  json_array_append_new (restrictions,
     277             :                                         jr));
     278          26 :   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          23 : 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         138 :   for (unsigned int i = 0; i<keys->num_sign_keys; i++)
     301             :   {
     302         115 :     const struct TALER_EXCHANGE_SigningPublicKey *sign_key
     303         115 :       = &keys->sign_keys[i];
     304             : 
     305         115 :     qs = db_plugin->insert_exchange_signkey (
     306         115 :       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         115 :     if (0 > qs)
     315             :     {
     316           0 :       GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     317           0 :       return qs;
     318             :     }
     319             :   }
     320             : 
     321          23 :   qs = db_plugin->insert_exchange_keys (db_plugin->cls,
     322             :                                         keys,
     323             :                                         first_retry);
     324          23 :   if (0 > qs)
     325             :   {
     326           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     327           0 :     return qs;
     328             :   }
     329             : 
     330          23 :   qs = db_plugin->delete_exchange_accounts (db_plugin->cls,
     331             :                                             &keys->master_pub);
     332          23 :   if (0 > qs)
     333             :   {
     334           0 :     GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     335           0 :     return qs;
     336             :   }
     337             : 
     338          46 :   for (unsigned int i = 0; i<keys->accounts_len; i++)
     339             :   {
     340          23 :     const struct TALER_EXCHANGE_WireAccount *account
     341          23 :       = &keys->accounts[i];
     342             :     json_t *debit_restrictions;
     343             :     json_t *credit_restrictions;
     344             : 
     345          23 :     debit_restrictions = json_array ();
     346          23 :     GNUNET_assert (NULL != debit_restrictions);
     347          23 :     credit_restrictions = json_array ();
     348          23 :     GNUNET_assert (NULL != credit_restrictions);
     349          36 :     for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
     350             :     {
     351          13 :       if (GNUNET_OK !=
     352          13 :           add_restriction (debit_restrictions,
     353          13 :                            &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          36 :     for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
     363             :     {
     364          13 :       if (GNUNET_OK !=
     365          13 :           add_restriction (credit_restrictions,
     366          13 :                            &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          23 :     qs = db_plugin->insert_exchange_account (
     376          23 :       db_plugin->cls,
     377             :       &keys->master_pub,
     378             :       account->fpayto_uri,
     379          23 :       account->conversion_url,
     380             :       debit_restrictions,
     381             :       credit_restrictions,
     382             :       &account->master_sig);
     383          23 :     json_decref (debit_restrictions);
     384          23 :     json_decref (credit_restrictions);
     385          23 :     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          46 :   for (unsigned int i = 0; i<keys->fees_len; i++)
     393             :   {
     394          23 :     const struct TALER_EXCHANGE_WireFeesByMethod *fbm
     395          23 :       = &keys->fees[i];
     396          23 :     const char *wire_method = fbm->method;
     397          23 :     const struct TALER_EXCHANGE_WireAggregateFees *fees
     398             :       = fbm->fees_head;
     399             : 
     400          59 :     while (NULL != fees)
     401             :     {
     402             :       struct GNUNET_HashCode h_wire_method;
     403             : 
     404          36 :       GNUNET_CRYPTO_hash (wire_method,
     405          36 :                           strlen (wire_method) + 1,
     406             :                           &h_wire_method);
     407          36 :       qs = db_plugin->store_wire_fee_by_exchange (
     408          36 :         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          36 :       if (0 > qs)
     416             :       {
     417           0 :         GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
     418           0 :         return qs;
     419             :       }
     420          36 :       fees = fees->next;
     421             :     } /* all fees for this method */
     422             :   } /* for all methods (i) */
     423             : 
     424             :   {
     425          23 :     struct GNUNET_DB_EventHeaderP es = {
     426          23 :       .size = ntohs (sizeof (es)),
     427          23 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
     428             :     };
     429             : 
     430          23 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     431             :                 "Informing other processes about keys change for %s\n",
     432             :                 keys->exchange_url);
     433          23 :     db_plugin->event_notify (db_plugin->cls,
     434             :                              &es,
     435          23 :                              keys->exchange_url,
     436          23 :                              strlen (keys->exchange_url) + 1);
     437             :   }
     438          23 :   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          23 : store_keys (struct TALER_EXCHANGE_Keys *keys,
     453             :             struct GNUNET_TIME_Absolute first_retry)
     454             : {
     455             :   enum GNUNET_DB_QueryStatus qs;
     456             : 
     457          23 :   db_plugin->preflight (db_plugin->cls);
     458          23 :   for (unsigned int r = 0; r<MAX_RETRIES; r++)
     459             :   {
     460          23 :     if (GNUNET_OK !=
     461          23 :         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          23 :     qs = insert_keys_data (keys,
     470             :                            first_retry);
     471          23 :     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          23 :     qs = db_plugin->commit (db_plugin->cls);
     481          23 :     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          23 :     break;
     490             :   } /* end of retry loop */
     491          23 :   if (qs < 0)
     492             :   {
     493           0 :     GNUNET_break (0);
     494           0 :     return false;
     495             :   }
     496          23 :   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          44 : cert_cb (
     510             :   void *cls,
     511             :   const struct TALER_EXCHANGE_KeysResponse *kr,
     512             :   struct TALER_EXCHANGE_Keys *keys)
     513             : {
     514          44 :   struct Exchange *e = cls;
     515             :   struct GNUNET_TIME_Absolute n;
     516             :   struct GNUNET_TIME_Absolute first_retry;
     517             : 
     518          44 :   e->conn = NULL;
     519          44 :   switch (kr->hr.http_status)
     520             :   {
     521          23 :   case MHD_HTTP_OK:
     522          23 :     TALER_EXCHANGE_keys_decref (e->keys);
     523          23 :     e->keys = NULL;
     524          23 :     if (0 != strcmp (e->currency,
     525          23 :                      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          23 :     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          23 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     545             :                 "Got new keys for %s, updating database\n",
     546             :                 e->exchange_url);
     547          23 :     first_retry = GNUNET_TIME_relative_to_absolute (
     548             :       EXCHANGE_MAXFREQ);
     549          23 :     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          23 :     e->keys = keys;
     557             :     /* Reset back-off */
     558          23 :     e->retry_delay = EXCHANGE_MAXFREQ;
     559             :     /* limit retry */
     560          23 :     e->first_retry = first_retry;
     561             :     /* Limit by expiration */
     562          23 :     n = GNUNET_TIME_absolute_max (e->first_retry,
     563             :                                   keys->key_data_expiration.abs_time);
     564          23 :     if (NULL != e->retry_task)
     565           0 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     566          23 :     e->retry_task = GNUNET_SCHEDULER_add_at (n,
     567             :                                              &download_keys,
     568             :                                              e);
     569          23 :     end_inquiry ();
     570          23 :     return;
     571          21 :   default:
     572          21 :     GNUNET_break (NULL == keys);
     573          21 :     break;
     574             :   }
     575             :   /* Try again (soon-ish) */
     576             :   e->retry_delay
     577          21 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     578          21 :   n = GNUNET_TIME_absolute_max (
     579             :     e->first_retry,
     580             :     GNUNET_TIME_relative_to_absolute (e->retry_delay));
     581          21 :   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          21 :   if (NULL != e->retry_task)
     588           0 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     589             :   e->retry_task
     590          21 :     = GNUNET_SCHEDULER_add_at (n,
     591             :                                &download_keys,
     592             :                                e);
     593          21 :   end_inquiry ();
     594             : }
     595             : 
     596             : 
     597             : static void
     598          44 : download_keys (void *cls)
     599             : {
     600          44 :   struct Exchange *e = cls;
     601             : 
     602          44 :   e->retry_task = NULL;
     603          44 :   GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
     604          44 :   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          44 :     = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
     614          44 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     615             :               "Downloading keys from %s (%s)\n",
     616             :               e->exchange_url,
     617             :               e->force_retry ? "forced" : "regular");
     618          44 :   e->conn = TALER_EXCHANGE_get_keys (ctx,
     619          44 :                                      e->exchange_url,
     620          44 :                                      e->force_retry
     621             :                                      ? NULL
     622             :                                      : e->keys,
     623             :                                      &cert_cb,
     624             :                                      e);
     625          44 :   e->force_retry = false;
     626          44 :   if (NULL != e->conn)
     627             :   {
     628          44 :     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          31 : lookup_exchange (const char *exchange_url)
     653             : {
     654          31 :   for (struct Exchange *e = e_head;
     655          47 :        NULL != e;
     656          16 :        e = e->next)
     657          47 :     if (0 == strcmp (e->exchange_url,
     658             :                      exchange_url))
     659          31 :       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          31 : force_exchange_keys (void *cls,
     676             :                      const void *extra,
     677             :                      size_t extra_len)
     678             : {
     679          31 :   const char *url = extra;
     680             :   struct Exchange *e;
     681             : 
     682          31 :   if ( (NULL == extra) ||
     683             :        (0 == extra_len) )
     684             :   {
     685           0 :     GNUNET_break (0);
     686           0 :     return;
     687             :   }
     688          31 :   if ('\0' != url[extra_len - 1])
     689             :   {
     690           0 :     GNUNET_break (0);
     691           0 :     return;
     692             :   }
     693          31 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     694             :               "Received keys change notification: reload `%s'\n",
     695             :               url);
     696          31 :   e = lookup_exchange (url);
     697          31 :   if (NULL == e)
     698             :   {
     699           0 :     GNUNET_break (0);
     700           0 :     return;
     701             :   }
     702          31 :   if (NULL != e->conn)
     703             :   {
     704          11 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     705             :                 "Already downloading %skeys\n",
     706             :                 url);
     707          11 :     return;
     708             :   }
     709          20 :   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          20 :   if (NULL != e->retry_task)
     717          20 :     GNUNET_SCHEDULER_cancel (e->retry_task);
     718          20 :   e->force_retry = true;
     719             :   e->retry_task
     720          20 :     = 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         545 : accept_exchanges (void *cls,
     735             :                   const char *section)
     736             : {
     737             :   char *url;
     738             :   char *mks;
     739             :   char *currency;
     740             : 
     741             :   (void) cls;
     742         545 :   if (0 !=
     743         545 :       strncasecmp (section,
     744             :                    "merchant-exchange-",
     745             :                    strlen ("merchant-exchange-")))
     746         517 :     return;
     747          42 :   if (GNUNET_YES ==
     748          42 :       GNUNET_CONFIGURATION_get_value_yesno (cfg,
     749             :                                             section,
     750             :                                             "DISABLED"))
     751          14 :     return;
     752          28 :   if (GNUNET_OK !=
     753          28 :       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          28 :   for (struct Exchange *e = e_head;
     766          42 :        NULL != e;
     767          14 :        e = e->next)
     768             :   {
     769          14 :     if (0 == strcmp (url,
     770          14 :                      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          28 :   if (GNUNET_OK !=
     783          28 :       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          28 :   if (GNUNET_OK !=
     797          28 :       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          28 :     e = GNUNET_new (struct Exchange);
     816          28 :     e->exchange_url = url;
     817          28 :     e->currency = currency;
     818          28 :     GNUNET_CONTAINER_DLL_insert (e_head,
     819             :                                  e_tail,
     820             :                                  e);
     821          28 :     if (GNUNET_OK !=
     822          28 :         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          28 :     GNUNET_free (mks);
     837             : 
     838             :     {
     839             :       enum GNUNET_DB_QueryStatus qs;
     840          28 :       struct TALER_EXCHANGE_Keys *keys = NULL;
     841             : 
     842          28 :       qs = db_plugin->select_exchange_keys (db_plugin->cls,
     843             :                                             url,
     844             :                                             &e->first_retry,
     845             :                                             &keys);
     846          28 :       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          28 :       if ( (NULL != keys) &&
     854           1 :            (0 != strcmp (keys->currency,
     855           1 :                          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          28 :       if ( (NULL != keys) &&
     865           1 :            (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          28 :       e->keys = keys;
     876          28 :       if (NULL == keys)
     877             :       {
     878             :         /* done synchronously so that the active_inquiries
     879             :            is updated immediately */
     880             : 
     881          27 :         download_keys (e);
     882             :       }
     883             :       else
     884             :       {
     885             :         e->retry_task
     886           1 :           = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
     887             :                                      &download_keys,
     888             :                                      e);
     889             :       }
     890             :     }
     891          28 :     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          14 : shutdown_task (void *cls)
     905             : {
     906             :   (void) cls;
     907          14 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     908             :               "Running shutdown\n");
     909          42 :   while (NULL != e_head)
     910             :   {
     911          28 :     struct Exchange *e = e_head;
     912             : 
     913          28 :     GNUNET_free (e->exchange_url);
     914          28 :     GNUNET_free (e->currency);
     915          28 :     if (NULL != e->conn)
     916             :     {
     917           0 :       TALER_EXCHANGE_get_keys_cancel (e->conn);
     918           0 :       e->conn = NULL;
     919             :     }
     920          28 :     if (NULL != e->keys)
     921             :     {
     922          24 :       TALER_EXCHANGE_keys_decref (e->keys);
     923          24 :       e->keys = NULL;
     924             :     }
     925          28 :     if (NULL != e->retry_task)
     926             :     {
     927          28 :       GNUNET_SCHEDULER_cancel (e->retry_task);
     928          28 :       e->retry_task = NULL;
     929             :     }
     930          28 :     GNUNET_CONTAINER_DLL_remove (e_head,
     931             :                                  e_tail,
     932             :                                  e);
     933          28 :     GNUNET_free (e);
     934             :   }
     935          14 :   if (NULL != eh)
     936             :   {
     937          14 :     db_plugin->event_listen_cancel (eh);
     938          14 :     eh = NULL;
     939             :   }
     940          14 :   TALER_MERCHANTDB_plugin_unload (db_plugin);
     941          14 :   db_plugin = NULL;
     942          14 :   cfg = NULL;
     943          14 :   if (NULL != ctx)
     944             :   {
     945          14 :     GNUNET_CURL_fini (ctx);
     946          14 :     ctx = NULL;
     947             :   }
     948          14 :   if (NULL != rc)
     949             :   {
     950          14 :     GNUNET_CURL_gnunet_rc_destroy (rc);
     951          14 :     rc = NULL;
     952             :   }
     953          14 : }
     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          14 : 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          14 :   cfg = c;
     974          14 :   GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
     975             :                                  NULL);
     976          14 :   ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
     977             :                           &rc);
     978          14 :   rc = GNUNET_CURL_gnunet_rc_create (ctx);
     979          14 :   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          14 :   if (NULL ==
     987          14 :       (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          14 :   if (GNUNET_OK !=
     996          14 :       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          14 :     struct GNUNET_DB_EventHeaderP es = {
    1006          14 :       .size = ntohs (sizeof (es)),
    1007          14 :       .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
    1008             :     };
    1009             : 
    1010          28 :     eh = db_plugin->event_listen (db_plugin->cls,
    1011             :                                   &es,
    1012          14 :                                   GNUNET_TIME_UNIT_FOREVER_REL,
    1013             :                                   &force_exchange_keys,
    1014             :                                   NULL);
    1015             :   }
    1016          14 :   GNUNET_CONFIGURATION_iterate_sections (cfg,
    1017             :                                          &accept_exchanges,
    1018             :                                          NULL);
    1019          14 :   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          14 : main (int argc,
    1039             :       char *const *argv)
    1040             : {
    1041          14 :   struct GNUNET_GETOPT_CommandLineOption options[] = {
    1042          14 :     GNUNET_GETOPT_option_timetravel ('T',
    1043             :                                      "timetravel"),
    1044          14 :     GNUNET_GETOPT_option_flag ('t',
    1045             :                                "test",
    1046             :                                "run in test mode and exit when idle",
    1047             :                                &test_mode),
    1048          14 :     GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
    1049             :     GNUNET_GETOPT_OPTION_END
    1050             :   };
    1051             :   enum GNUNET_GenericReturnValue ret;
    1052             : 
    1053          14 :   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          14 :   if (GNUNET_SYSERR == ret)
    1062           0 :     return EXIT_INVALIDARGUMENT;
    1063          14 :   if (GNUNET_NO == ret)
    1064           0 :     return EXIT_SUCCESS;
    1065          14 :   return global_ret;
    1066             : }
    1067             : 
    1068             : 
    1069             : /* end of taler-merchant-exchangekeyupdate.c */

Generated by: LCOV version 1.16