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 }