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