exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

config.c (19143B)


      1 /*
      2   This file is part of TALER
      3   Copyright (C) 2014-2023 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU 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 General Public License for more details.
     12 
     13   You should have received a copy of the GNU General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 */
     16 /**
     17  * @file config.c
     18  * @brief configuration parsing functions for Taler-specific data types
     19  * @author Florian Dold
     20  * @author Benedikt Mueller
     21  */
     22 #include "platform.h"  /* UNNECESSARY? */
     23 #include "taler/taler_util.h"
     24 #include <gnunet/gnunet_json_lib.h>
     25 
     26 
     27 enum GNUNET_GenericReturnValue
     28 TALER_config_get_amount (const struct GNUNET_CONFIGURATION_Handle *cfg,
     29                          const char *section,
     30                          const char *option,
     31                          struct TALER_Amount *denom)
     32 {
     33   char *str;
     34 
     35   if (GNUNET_OK !=
     36       GNUNET_CONFIGURATION_get_value_string (cfg,
     37                                              section,
     38                                              option,
     39                                              &str))
     40   {
     41     /* may be OK! */
     42     return GNUNET_NO;
     43   }
     44   if (GNUNET_OK !=
     45       TALER_string_to_amount (str,
     46                               denom))
     47   {
     48     GNUNET_free (str);
     49     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     50                                section,
     51                                option,
     52                                "invalid amount");
     53     return GNUNET_SYSERR;
     54   }
     55   GNUNET_free (str);
     56   return GNUNET_OK;
     57 }
     58 
     59 
     60 enum GNUNET_GenericReturnValue
     61 TALER_config_get_amount_list (const struct GNUNET_CONFIGURATION_Handle *cfg,
     62                               const char *section,
     63                               const char *option,
     64                               struct TALER_AmountList *al)
     65 {
     66   char *str;
     67 
     68   al->tal = NULL;
     69   al->tal_len = 0;
     70   if (GNUNET_OK !=
     71       GNUNET_CONFIGURATION_get_value_string (cfg,
     72                                              section,
     73                                              option,
     74                                              &str))
     75   {
     76     /* may be OK! */
     77     return GNUNET_NO;
     78   }
     79   if (GNUNET_OK !=
     80       TALER_string_to_amount_list (str,
     81                                    al))
     82   {
     83     GNUNET_free (str);
     84     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
     85                                section,
     86                                option,
     87                                "invalid list of amounts");
     88     return GNUNET_SYSERR;
     89   }
     90   GNUNET_free (str);
     91   return GNUNET_OK;
     92 }
     93 
     94 
     95 enum GNUNET_GenericReturnValue
     96 TALER_config_get_denom_fees (const struct GNUNET_CONFIGURATION_Handle *cfg,
     97                              const char *currency,
     98                              const char *section,
     99                              struct TALER_DenomFeeSet *fees)
    100 {
    101   if (GNUNET_OK !=
    102       TALER_config_get_amount (cfg,
    103                                section,
    104                                "FEE_WITHDRAW",
    105                                &fees->withdraw))
    106   {
    107     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    108                                "Need amount for option `%s' in section `%s'\n",
    109                                "FEE_WITHDRAW",
    110                                section);
    111     return GNUNET_SYSERR;
    112   }
    113   if (GNUNET_OK !=
    114       TALER_config_get_amount (cfg,
    115                                section,
    116                                "FEE_DEPOSIT",
    117                                &fees->deposit))
    118   {
    119     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    120                                "Need amount for option `%s' in section `%s'\n",
    121                                "FEE_DEPOSIT",
    122                                section);
    123     return GNUNET_SYSERR;
    124   }
    125   if (GNUNET_OK !=
    126       TALER_config_get_amount (cfg,
    127                                section,
    128                                "FEE_REFRESH",
    129                                &fees->refresh))
    130   {
    131     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    132                                "Need amount for option `%s' in section `%s'\n",
    133                                "FEE_REFRESH",
    134                                section);
    135     return GNUNET_SYSERR;
    136   }
    137   if (GNUNET_OK !=
    138       TALER_config_get_amount (cfg,
    139                                section,
    140                                "FEE_REFUND",
    141                                &fees->refund))
    142   {
    143     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    144                                "Need amount for option `%s' in section `%s'\n",
    145                                "FEE_REFUND",
    146                                section);
    147     return GNUNET_SYSERR;
    148   }
    149   if (GNUNET_OK !=
    150       TALER_denom_fee_check_currency (currency,
    151                                       fees))
    152   {
    153     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    154                 "Need fee amounts in section `%s' to use currency `%s'\n",
    155                 section,
    156                 currency);
    157     return GNUNET_SYSERR;
    158   }
    159   return GNUNET_OK;
    160 }
    161 
    162 
    163 enum GNUNET_GenericReturnValue
    164 TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
    165                            const char *section,
    166                            char **currency)
    167 {
    168   size_t slen;
    169 
    170   if (GNUNET_OK !=
    171       GNUNET_CONFIGURATION_get_value_string (cfg,
    172                                              section,
    173                                              "CURRENCY",
    174                                              currency))
    175   {
    176     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    177                                section,
    178                                "CURRENCY");
    179     return GNUNET_SYSERR;
    180   }
    181   slen = strlen (*currency);
    182   if (slen >= TALER_CURRENCY_LEN)
    183   {
    184     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    185                 "Currency `%s' longer than the allowed limit of %u characters.",
    186                 *currency,
    187                 (unsigned int) TALER_CURRENCY_LEN);
    188     GNUNET_free (*currency);
    189     *currency = NULL;
    190     return GNUNET_SYSERR;
    191   }
    192   for (size_t i = 0; i<slen; i++)
    193     if (! isalpha ((unsigned char) (*currency)[i]))
    194     {
    195       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    196                   "Currency `%s' must only use characters from the A-Z range.",
    197                   *currency);
    198       GNUNET_free (*currency);
    199       *currency = NULL;
    200       return GNUNET_SYSERR;
    201     }
    202   return GNUNET_OK;
    203 }
    204 
    205 
    206 /**
    207  * Closure for #parse_currencies_cb().
    208  */
    209 struct CurrencyParserContext
    210 {
    211   /**
    212    * Current offset in @e cspecs.
    213    */
    214   unsigned int num_currencies;
    215 
    216   /**
    217    * Length of the @e cspecs array.
    218    */
    219   unsigned int len_cspecs;
    220 
    221   /**
    222    * Array of currency specifications (see DD 51).
    223    */
    224   struct TALER_CurrencySpecification *cspecs;
    225 
    226   /**
    227    * Configuration we are parsing.
    228    */
    229   const struct GNUNET_CONFIGURATION_Handle *cfg;
    230 
    231   /**
    232    * Set to true if the configuration was malformed.
    233    */
    234   bool failure;
    235 };
    236 
    237 
    238 /**
    239  * Function to iterate over section.
    240  *
    241  * @param cls closure with a `struct CurrencyParserContext *`
    242  * @param section name of the section
    243  */
    244 static void
    245 parse_currencies_cb (void *cls,
    246                      const char *section)
    247 {
    248   struct CurrencyParserContext *cpc = cls;
    249   struct TALER_CurrencySpecification *cspec;
    250   unsigned long long num;
    251   char *str;
    252 
    253   if (cpc->failure)
    254     return;
    255   if (0 != strncasecmp (section,
    256                         "currency-",
    257                         strlen ("currency-")))
    258     return; /* not interesting */
    259   if (GNUNET_YES !=
    260       GNUNET_CONFIGURATION_get_value_yesno (cpc->cfg,
    261                                             section,
    262                                             "ENABLED"))
    263     return; /* disabled */
    264   if (cpc->len_cspecs == cpc->num_currencies)
    265   {
    266     GNUNET_array_grow (cpc->cspecs,
    267                        cpc->len_cspecs,
    268                        cpc->len_cspecs * 2 + 4);
    269   }
    270   cspec = &cpc->cspecs[cpc->num_currencies++];
    271   if (GNUNET_OK !=
    272       GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
    273                                              section,
    274                                              "CODE",
    275                                              &str))
    276   {
    277     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    278                                section,
    279                                "CODE");
    280     cpc->failure = true;
    281     return;
    282   }
    283   if (GNUNET_OK !=
    284       TALER_check_currency (str))
    285   {
    286     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    287                                section,
    288                                "CODE",
    289                                "Currency code name given is invalid");
    290     cpc->failure = true;
    291     GNUNET_free (str);
    292     return;
    293   }
    294   memset (cspec->currency,
    295           0,
    296           sizeof (cspec->currency));
    297   /* Already checked in TALER_check_currency(), repeated here
    298      just to make static analysis happy */
    299   GNUNET_assert (strlen (str) < TALER_CURRENCY_LEN);
    300   strcpy (cspec->currency,
    301           str);
    302   GNUNET_free (str);
    303 
    304   if (GNUNET_OK !=
    305       GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
    306                                              section,
    307                                              "NAME",
    308                                              &str))
    309   {
    310     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    311                                section,
    312                                "NAME");
    313     cpc->failure = true;
    314     return;
    315   }
    316   cspec->name = str;
    317 
    318   if (GNUNET_OK !=
    319       GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
    320                                              section,
    321                                              "COMMON_AMOUNTS",
    322                                              &str))
    323   {
    324     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
    325                                section,
    326                                "COMMON_AMOUNTS");
    327   }
    328   else
    329   {
    330     for (const char *tok = strtok (str,
    331                                    " ");
    332          NULL != tok;
    333          tok = strtok (NULL,
    334                        " "))
    335     {
    336       struct TALER_Amount val;
    337 
    338       if (GNUNET_OK !=
    339           TALER_string_to_amount (tok,
    340                                   &val))
    341       {
    342         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    343                                    section,
    344                                    "COMMON_AMOUNTS",
    345                                    tok);
    346         GNUNET_free (str);
    347         cpc->failure = true;
    348         return;
    349       }
    350       if (0 != strcasecmp (val.currency,
    351                            cspec->currency))
    352       {
    353         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    354                                    section,
    355                                    "COMMON_AMOUNTS",
    356                                    "currency mismatch");
    357         GNUNET_free (str);
    358         cpc->failure = true;
    359         return;
    360       }
    361       GNUNET_array_append (cspec->common_amounts,
    362                            cspec->num_common_amounts,
    363                            val);
    364     }
    365     GNUNET_free (str);
    366   }
    367   if (GNUNET_OK !=
    368       GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
    369                                              section,
    370                                              "FRACTIONAL_INPUT_DIGITS",
    371                                              &num))
    372   {
    373     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    374                                section,
    375                                "FRACTIONAL_INPUT_DIGITS");
    376     cpc->failure = true;
    377     return;
    378   }
    379   if (num > TALER_AMOUNT_FRAC_LEN)
    380   {
    381     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    382                                section,
    383                                "FRACTIONAL_INPUT_DIGITS",
    384                                "Number given is too big");
    385     cpc->failure = true;
    386     return;
    387   }
    388   cspec->num_fractional_input_digits = num;
    389   if (GNUNET_OK !=
    390       GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
    391                                              section,
    392                                              "FRACTIONAL_NORMAL_DIGITS",
    393                                              &num))
    394   {
    395     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    396                                section,
    397                                "FRACTIONAL_NORMAL_DIGITS");
    398     cpc->failure = true;
    399     return;
    400   }
    401   if (num > TALER_AMOUNT_FRAC_LEN)
    402   {
    403     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    404                                section,
    405                                "FRACTIONAL_NORMAL_DIGITS",
    406                                "Number given is too big");
    407     cpc->failure = true;
    408     return;
    409   }
    410   cspec->num_fractional_normal_digits = num;
    411   if (GNUNET_OK !=
    412       GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
    413                                              section,
    414                                              "FRACTIONAL_TRAILING_ZERO_DIGITS",
    415                                              &num))
    416   {
    417     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    418                                section,
    419                                "FRACTIONAL_TRAILING_ZERO_DIGITS");
    420     cpc->failure = true;
    421     return;
    422   }
    423   if (num > TALER_AMOUNT_FRAC_LEN)
    424   {
    425     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    426                                section,
    427                                "FRACTIONAL_TRAILING_ZERO_DIGITS",
    428                                "Number given is too big");
    429     cpc->failure = true;
    430     return;
    431   }
    432   cspec->num_fractional_trailing_zero_digits = num;
    433 
    434   if (GNUNET_OK !=
    435       GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
    436                                              section,
    437                                              "ALT_UNIT_NAMES",
    438                                              &str))
    439   {
    440     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    441                                section,
    442                                "ALT_UNIT_NAMES");
    443     cpc->failure = true;
    444     return;
    445   }
    446   {
    447     json_error_t err;
    448 
    449     cspec->map_alt_unit_names = json_loads (str,
    450                                             JSON_REJECT_DUPLICATES,
    451                                             &err);
    452     GNUNET_free (str);
    453     if (NULL == cspec->map_alt_unit_names)
    454     {
    455       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    456                                  section,
    457                                  "ALT_UNIT_NAMES",
    458                                  err.text);
    459       cpc->failure = true;
    460       return;
    461     }
    462   }
    463   if (GNUNET_OK !=
    464       TALER_check_currency_scale_map (cspec->map_alt_unit_names))
    465   {
    466     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    467                                section,
    468                                "ALT_UNIT_NAMES",
    469                                "invalid map entry detected");
    470     cpc->failure = true;
    471     json_decref (cspec->map_alt_unit_names);
    472     cspec->map_alt_unit_names = NULL;
    473     return;
    474   }
    475 }
    476 
    477 
    478 enum GNUNET_GenericReturnValue
    479 TALER_check_currency_scale_map (const json_t *map)
    480 {
    481   /* validate map only maps from decimal numbers to strings! */
    482   const char *str;
    483   const json_t *val;
    484   bool zf = false;
    485 
    486   if (! json_is_object (map))
    487   {
    488     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    489                 "Object required for currency scale map\n");
    490     return GNUNET_SYSERR;
    491   }
    492   json_object_foreach ((json_t *) map, str, val)
    493   {
    494     int idx;
    495     char dummy;
    496 
    497     if ( (1 != sscanf (str,
    498                        "%d%c",
    499                        &idx,
    500                        &dummy)) ||
    501          (idx < -12) ||
    502          (idx > 24) ||
    503          (! json_is_string (val) ) )
    504     {
    505       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    506                   "Invalid entry `%s' in currency scale map\n",
    507                   str);
    508       return GNUNET_SYSERR;
    509     }
    510     if (0 == idx)
    511       zf = true;
    512   }
    513   if (! zf)
    514   {
    515     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    516                 "Entry for 0 missing in currency scale map\n");
    517     return GNUNET_SYSERR;
    518   }
    519   return GNUNET_OK;
    520 }
    521 
    522 
    523 enum GNUNET_GenericReturnValue
    524 TALER_CONFIG_parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg,
    525                                const char *main_currency,
    526                                unsigned int *num_currencies,
    527                                struct TALER_CurrencySpecification **cspecs)
    528 {
    529   struct CurrencyParserContext cpc = {
    530     .cfg = cfg
    531   };
    532   static struct TALER_CurrencySpecification defspec = {
    533     .num_fractional_input_digits = 2,
    534     .num_fractional_normal_digits = 2,
    535     .num_fractional_trailing_zero_digits = 2
    536   };
    537 
    538   GNUNET_CONFIGURATION_iterate_sections (cfg,
    539                                          &parse_currencies_cb,
    540                                          &cpc);
    541   if (cpc.failure)
    542   {
    543     TALER_CONFIG_free_currencies (cpc.num_currencies,
    544                                   cpc.cspecs);
    545     return GNUNET_SYSERR;
    546   }
    547   /* Make sure that there is some sane fallback for the main currency */
    548   if (NULL != main_currency)
    549   {
    550     struct TALER_CurrencySpecification *mspec = NULL;
    551     for (unsigned int i = 0; i<cpc.num_currencies; i++)
    552     {
    553       struct TALER_CurrencySpecification *cspec;
    554 
    555       cspec = &cpc.cspecs[i];
    556       if (0 == strcasecmp (main_currency,
    557                            cspec->currency))
    558       {
    559         mspec = cspec;
    560         break;
    561       }
    562     }
    563     if (NULL == mspec)
    564     {
    565       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    566                   "Lacking enabled currency specification for main currency %s, using fallback currency specification.\n",
    567                   main_currency);
    568       if (cpc.len_cspecs == cpc.num_currencies)
    569       {
    570         GNUNET_array_grow (cpc.cspecs,
    571                            cpc.len_cspecs,
    572                            cpc.len_cspecs + 1);
    573       }
    574       mspec = &cpc.cspecs[cpc.num_currencies++];
    575       *mspec = defspec;
    576       GNUNET_assert (strlen (main_currency) < TALER_CURRENCY_LEN);
    577       strcpy (mspec->currency,
    578               main_currency);
    579       mspec->map_alt_unit_names
    580         = GNUNET_JSON_PACK (
    581             GNUNET_JSON_pack_string ("0",
    582                                      main_currency)
    583             );
    584       mspec->name = GNUNET_strdup (main_currency);
    585     }
    586   }
    587   /* cspecs might've been overgrown, grow back to minimum size */
    588   GNUNET_array_grow (cpc.cspecs,
    589                      cpc.len_cspecs,
    590                      cpc.num_currencies);
    591   *num_currencies = cpc.num_currencies;
    592   *cspecs = cpc.cspecs;
    593   if (0 == *num_currencies)
    594   {
    595     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    596                 "No currency formatting specification found! Please check your installation!\n");
    597     return GNUNET_NO;
    598   }
    599   return GNUNET_OK;
    600 }
    601 
    602 
    603 void
    604 TALER_CONFIG_free_currencies (
    605   unsigned int num_currencies,
    606   struct TALER_CurrencySpecification cspecs[static num_currencies])
    607 {
    608   for (unsigned int i = 0; i<num_currencies; i++)
    609   {
    610     struct TALER_CurrencySpecification *cspec = &cspecs[i];
    611 
    612     GNUNET_free (cspec->name);
    613     json_decref (cspec->map_alt_unit_names);
    614     GNUNET_array_grow (cspec->common_amounts,
    615                        cspec->num_common_amounts,
    616                        0);
    617   }
    618   GNUNET_array_grow (cspecs,
    619                      num_currencies,
    620                      0);
    621 }