config.c (18705B)
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 section, 109 "FEE_WITHDRAW", 110 "amount required"); 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 if (GNUNET_OK != 169 GNUNET_CONFIGURATION_get_value_string (cfg, 170 section, 171 "CURRENCY", 172 currency)) 173 { 174 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 175 section, 176 "CURRENCY"); 177 return GNUNET_SYSERR; 178 } 179 if (GNUNET_OK != 180 TALER_check_currency (*currency)) 181 { 182 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 183 "Currency `%s' must only use characters from the A-Z range.", 184 *currency); 185 GNUNET_free (*currency); 186 *currency = NULL; 187 return GNUNET_SYSERR; 188 } 189 return GNUNET_OK; 190 } 191 192 193 /** 194 * Closure for #parse_currencies_cb(). 195 */ 196 struct CurrencyParserContext 197 { 198 /** 199 * Current offset in @e cspecs. 200 */ 201 unsigned int num_currencies; 202 203 /** 204 * Length of the @e cspecs array. 205 */ 206 unsigned int len_cspecs; 207 208 /** 209 * Array of currency specifications (see DD 51). 210 */ 211 struct TALER_CurrencySpecification *cspecs; 212 213 /** 214 * Configuration we are parsing. 215 */ 216 const struct GNUNET_CONFIGURATION_Handle *cfg; 217 218 /** 219 * Set to true if the configuration was malformed. 220 */ 221 bool failure; 222 }; 223 224 225 /** 226 * Function to iterate over section. 227 * 228 * @param cls closure with a `struct CurrencyParserContext *` 229 * @param section name of the section 230 */ 231 static void 232 parse_currencies_cb (void *cls, 233 const char *section) 234 { 235 struct CurrencyParserContext *cpc = cls; 236 struct TALER_CurrencySpecification *cspec; 237 unsigned long long num; 238 char *str; 239 240 if (cpc->failure) 241 return; 242 if (0 != strncasecmp (section, 243 "currency-", 244 strlen ("currency-"))) 245 return; /* not interesting */ 246 if (GNUNET_YES != 247 GNUNET_CONFIGURATION_get_value_yesno (cpc->cfg, 248 section, 249 "ENABLED")) 250 return; /* disabled */ 251 if (cpc->len_cspecs == cpc->num_currencies) 252 { 253 GNUNET_array_grow (cpc->cspecs, 254 cpc->len_cspecs, 255 cpc->len_cspecs * 2 + 4); 256 } 257 cspec = &cpc->cspecs[cpc->num_currencies++]; 258 if (GNUNET_OK != 259 GNUNET_CONFIGURATION_get_value_string (cpc->cfg, 260 section, 261 "CODE", 262 &str)) 263 { 264 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 265 section, 266 "CODE"); 267 cpc->failure = true; 268 return; 269 } 270 if (GNUNET_OK != 271 TALER_check_currency (str)) 272 { 273 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 274 section, 275 "CODE", 276 "Currency code name given is invalid"); 277 cpc->failure = true; 278 GNUNET_free (str); 279 return; 280 } 281 memset (cspec->currency, 282 0, 283 sizeof (cspec->currency)); 284 /* Already checked in TALER_check_currency(), repeated here 285 just to make static analysis happy */ 286 GNUNET_assert (strlen (str) < TALER_CURRENCY_LEN); 287 strcpy (cspec->currency, 288 str); 289 GNUNET_free (str); 290 291 if (GNUNET_OK != 292 GNUNET_CONFIGURATION_get_value_string (cpc->cfg, 293 section, 294 "NAME", 295 &str)) 296 { 297 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 298 section, 299 "NAME"); 300 cpc->failure = true; 301 return; 302 } 303 cspec->name = str; 304 305 if (GNUNET_OK != 306 GNUNET_CONFIGURATION_get_value_string (cpc->cfg, 307 section, 308 "COMMON_AMOUNTS", 309 &str)) 310 { 311 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING, 312 section, 313 "COMMON_AMOUNTS"); 314 } 315 else 316 { 317 for (const char *tok = strtok (str, 318 " "); 319 NULL != tok; 320 tok = strtok (NULL, 321 " ")) 322 { 323 struct TALER_Amount val; 324 325 if (GNUNET_OK != 326 TALER_string_to_amount (tok, 327 &val)) 328 { 329 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 330 section, 331 "COMMON_AMOUNTS", 332 tok); 333 GNUNET_free (str); 334 cpc->failure = true; 335 return; 336 } 337 if (0 != strcasecmp (val.currency, 338 cspec->currency)) 339 { 340 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 341 section, 342 "COMMON_AMOUNTS", 343 "currency mismatch"); 344 GNUNET_free (str); 345 cpc->failure = true; 346 return; 347 } 348 GNUNET_array_append (cspec->common_amounts, 349 cspec->num_common_amounts, 350 val); 351 } 352 GNUNET_free (str); 353 } 354 if (GNUNET_OK != 355 GNUNET_CONFIGURATION_get_value_number (cpc->cfg, 356 section, 357 "FRACTIONAL_INPUT_DIGITS", 358 &num)) 359 { 360 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 361 section, 362 "FRACTIONAL_INPUT_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_INPUT_DIGITS", 371 "Number given is too big"); 372 cpc->failure = true; 373 return; 374 } 375 cspec->num_fractional_input_digits = num; 376 if (GNUNET_OK != 377 GNUNET_CONFIGURATION_get_value_number (cpc->cfg, 378 section, 379 "FRACTIONAL_NORMAL_DIGITS", 380 &num)) 381 { 382 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 383 section, 384 "FRACTIONAL_NORMAL_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_NORMAL_DIGITS", 393 "Number given is too big"); 394 cpc->failure = true; 395 return; 396 } 397 cspec->num_fractional_normal_digits = num; 398 if (GNUNET_OK != 399 GNUNET_CONFIGURATION_get_value_number (cpc->cfg, 400 section, 401 "FRACTIONAL_TRAILING_ZERO_DIGITS", 402 &num)) 403 { 404 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 405 section, 406 "FRACTIONAL_TRAILING_ZERO_DIGITS"); 407 cpc->failure = true; 408 return; 409 } 410 if (num > TALER_AMOUNT_FRAC_LEN) 411 { 412 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 413 section, 414 "FRACTIONAL_TRAILING_ZERO_DIGITS", 415 "Number given is too big"); 416 cpc->failure = true; 417 return; 418 } 419 cspec->num_fractional_trailing_zero_digits = num; 420 421 if (GNUNET_OK != 422 GNUNET_CONFIGURATION_get_value_string (cpc->cfg, 423 section, 424 "ALT_UNIT_NAMES", 425 &str)) 426 { 427 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 428 section, 429 "ALT_UNIT_NAMES"); 430 cpc->failure = true; 431 return; 432 } 433 { 434 json_error_t err; 435 436 cspec->map_alt_unit_names = json_loads (str, 437 JSON_REJECT_DUPLICATES, 438 &err); 439 GNUNET_free (str); 440 if (NULL == cspec->map_alt_unit_names) 441 { 442 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 443 section, 444 "ALT_UNIT_NAMES", 445 err.text); 446 cpc->failure = true; 447 return; 448 } 449 } 450 if (GNUNET_OK != 451 TALER_check_currency_scale_map (cspec->map_alt_unit_names)) 452 { 453 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 454 section, 455 "ALT_UNIT_NAMES", 456 "invalid map entry detected"); 457 cpc->failure = true; 458 json_decref (cspec->map_alt_unit_names); 459 cspec->map_alt_unit_names = NULL; 460 return; 461 } 462 } 463 464 465 enum GNUNET_GenericReturnValue 466 TALER_check_currency_scale_map (const json_t *map) 467 { 468 /* validate map only maps from decimal numbers to strings! */ 469 const char *str; 470 const json_t *val; 471 bool zf = false; 472 473 if (! json_is_object (map)) 474 { 475 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 476 "Object required for currency scale map\n"); 477 return GNUNET_SYSERR; 478 } 479 json_object_foreach ((json_t *) map, str, val) 480 { 481 int idx; 482 char dummy; 483 484 if ( (1 != sscanf (str, 485 "%d%c", 486 &idx, 487 &dummy)) || 488 (idx < -12) || 489 (idx > 24) || 490 (! json_is_string (val) ) ) 491 { 492 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 493 "Invalid entry `%s' in currency scale map\n", 494 str); 495 return GNUNET_SYSERR; 496 } 497 if (0 == idx) 498 zf = true; 499 } 500 if (! zf) 501 { 502 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 503 "Entry for 0 missing in currency scale map\n"); 504 return GNUNET_SYSERR; 505 } 506 return GNUNET_OK; 507 } 508 509 510 enum GNUNET_GenericReturnValue 511 TALER_CONFIG_parse_currencies (const struct GNUNET_CONFIGURATION_Handle *cfg, 512 const char *main_currency, 513 unsigned int *num_currencies, 514 struct TALER_CurrencySpecification **cspecs) 515 { 516 struct CurrencyParserContext cpc = { 517 .cfg = cfg 518 }; 519 static struct TALER_CurrencySpecification defspec = { 520 .num_fractional_input_digits = 2, 521 .num_fractional_normal_digits = 2, 522 .num_fractional_trailing_zero_digits = 2 523 }; 524 525 GNUNET_CONFIGURATION_iterate_sections (cfg, 526 &parse_currencies_cb, 527 &cpc); 528 if (cpc.failure) 529 { 530 TALER_CONFIG_free_currencies (cpc.num_currencies, 531 cpc.cspecs); 532 return GNUNET_SYSERR; 533 } 534 /* Make sure that there is some sane fallback for the main currency */ 535 if (NULL != main_currency) 536 { 537 struct TALER_CurrencySpecification *mspec = NULL; 538 for (unsigned int i = 0; i<cpc.num_currencies; i++) 539 { 540 struct TALER_CurrencySpecification *cspec; 541 542 cspec = &cpc.cspecs[i]; 543 if (0 == strcasecmp (main_currency, 544 cspec->currency)) 545 { 546 mspec = cspec; 547 break; 548 } 549 } 550 if (NULL == mspec) 551 { 552 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 553 "Lacking enabled currency specification for main currency %s, using fallback currency specification.\n", 554 main_currency); 555 if (cpc.len_cspecs == cpc.num_currencies) 556 { 557 GNUNET_array_grow (cpc.cspecs, 558 cpc.len_cspecs, 559 cpc.len_cspecs + 1); 560 } 561 mspec = &cpc.cspecs[cpc.num_currencies++]; 562 *mspec = defspec; 563 GNUNET_assert (strlen (main_currency) < TALER_CURRENCY_LEN); 564 strcpy (mspec->currency, 565 main_currency); 566 mspec->map_alt_unit_names 567 = GNUNET_JSON_PACK ( 568 GNUNET_JSON_pack_string ("0", 569 main_currency) 570 ); 571 mspec->name = GNUNET_strdup (main_currency); 572 } 573 } 574 /* cspecs might've been overgrown, grow back to minimum size */ 575 GNUNET_array_grow (cpc.cspecs, 576 cpc.len_cspecs, 577 cpc.num_currencies); 578 *num_currencies = cpc.num_currencies; 579 *cspecs = cpc.cspecs; 580 if (0 == *num_currencies) 581 { 582 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 583 "No currency formatting specification found! Please check your installation!\n"); 584 return GNUNET_NO; 585 } 586 return GNUNET_OK; 587 } 588 589 590 void 591 TALER_CONFIG_free_currencies ( 592 unsigned int num_currencies, 593 struct TALER_CurrencySpecification cspecs[static num_currencies]) 594 { 595 for (unsigned int i = 0; i<num_currencies; i++) 596 { 597 struct TALER_CurrencySpecification *cspec = &cspecs[i]; 598 599 GNUNET_free (cspec->name); 600 json_decref (cspec->map_alt_unit_names); 601 GNUNET_array_grow (cspec->common_amounts, 602 cspec->num_common_amounts, 603 0); 604 } 605 GNUNET_array_grow (cspecs, 606 num_currencies, 607 0); 608 }