Line data Source code
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 4919 : 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 4919 : if (GNUNET_OK !=
36 4919 : GNUNET_CONFIGURATION_get_value_string (cfg,
37 : section,
38 : option,
39 : &str))
40 : {
41 : /* may be OK! */
42 21 : return GNUNET_NO;
43 : }
44 4898 : if (GNUNET_OK !=
45 4898 : TALER_string_to_amount (str,
46 : denom))
47 : {
48 0 : GNUNET_free (str);
49 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
50 : section,
51 : option,
52 : "invalid amount");
53 0 : return GNUNET_SYSERR;
54 : }
55 4898 : GNUNET_free (str);
56 4898 : return GNUNET_OK;
57 : }
58 :
59 :
60 : enum GNUNET_GenericReturnValue
61 0 : 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 0 : al->tal = NULL;
69 0 : al->tal_len = 0;
70 0 : if (GNUNET_OK !=
71 0 : GNUNET_CONFIGURATION_get_value_string (cfg,
72 : section,
73 : option,
74 : &str))
75 : {
76 : /* may be OK! */
77 0 : return GNUNET_NO;
78 : }
79 0 : if (GNUNET_OK !=
80 0 : TALER_string_to_amount_list (str,
81 : al))
82 : {
83 0 : GNUNET_free (str);
84 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
85 : section,
86 : option,
87 : "invalid list of amounts");
88 0 : return GNUNET_SYSERR;
89 : }
90 0 : GNUNET_free (str);
91 0 : return GNUNET_OK;
92 : }
93 :
94 :
95 : enum GNUNET_GenericReturnValue
96 949 : 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 949 : if (GNUNET_OK !=
102 949 : TALER_config_get_amount (cfg,
103 : section,
104 : "FEE_WITHDRAW",
105 : &fees->withdraw))
106 : {
107 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
108 : section,
109 : "FEE_WITHDRAW",
110 : "amount required");
111 0 : return GNUNET_SYSERR;
112 : }
113 949 : if (GNUNET_OK !=
114 949 : TALER_config_get_amount (cfg,
115 : section,
116 : "FEE_DEPOSIT",
117 : &fees->deposit))
118 : {
119 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
120 : "Need amount for option `%s' in section `%s'\n",
121 : "FEE_DEPOSIT",
122 : section);
123 0 : return GNUNET_SYSERR;
124 : }
125 949 : if (GNUNET_OK !=
126 949 : TALER_config_get_amount (cfg,
127 : section,
128 : "FEE_REFRESH",
129 : &fees->refresh))
130 : {
131 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
132 : "Need amount for option `%s' in section `%s'\n",
133 : "FEE_REFRESH",
134 : section);
135 0 : return GNUNET_SYSERR;
136 : }
137 949 : if (GNUNET_OK !=
138 949 : TALER_config_get_amount (cfg,
139 : section,
140 : "FEE_REFUND",
141 : &fees->refund))
142 : {
143 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
144 : "Need amount for option `%s' in section `%s'\n",
145 : "FEE_REFUND",
146 : section);
147 0 : return GNUNET_SYSERR;
148 : }
149 949 : if (GNUNET_OK !=
150 949 : TALER_denom_fee_check_currency (currency,
151 : fees))
152 : {
153 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
154 : "Need fee amounts in section `%s' to use currency `%s'\n",
155 : section,
156 : currency);
157 0 : return GNUNET_SYSERR;
158 : }
159 949 : return GNUNET_OK;
160 : }
161 :
162 :
163 : enum GNUNET_GenericReturnValue
164 485 : TALER_config_get_currency (const struct GNUNET_CONFIGURATION_Handle *cfg,
165 : const char *section,
166 : char **currency)
167 : {
168 485 : if (GNUNET_OK !=
169 485 : GNUNET_CONFIGURATION_get_value_string (cfg,
170 : section,
171 : "CURRENCY",
172 : currency))
173 : {
174 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
175 : section,
176 : "CURRENCY");
177 0 : return GNUNET_SYSERR;
178 : }
179 485 : if (GNUNET_OK !=
180 485 : TALER_check_currency (*currency))
181 : {
182 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
183 : "Currency `%s' must only use characters from the A-Z range.",
184 : *currency);
185 0 : GNUNET_free (*currency);
186 0 : *currency = NULL;
187 0 : return GNUNET_SYSERR;
188 : }
189 485 : 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 892 : parse_currencies_cb (void *cls,
233 : const char *section)
234 : {
235 892 : struct CurrencyParserContext *cpc = cls;
236 : struct TALER_CurrencySpecification *cspec;
237 : unsigned long long num;
238 : char *str;
239 :
240 892 : if (cpc->failure)
241 802 : return;
242 892 : if (0 != strncasecmp (section,
243 : "currency-",
244 : strlen ("currency-")))
245 712 : return; /* not interesting */
246 180 : if (GNUNET_YES !=
247 180 : GNUNET_CONFIGURATION_get_value_yesno (cpc->cfg,
248 : section,
249 : "ENABLED"))
250 90 : return; /* disabled */
251 90 : if (cpc->len_cspecs == cpc->num_currencies)
252 : {
253 36 : GNUNET_array_grow (cpc->cspecs,
254 : cpc->len_cspecs,
255 : cpc->len_cspecs * 2 + 4);
256 : }
257 90 : cspec = &cpc->cspecs[cpc->num_currencies++];
258 90 : if (GNUNET_OK !=
259 90 : GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
260 : section,
261 : "CODE",
262 : &str))
263 : {
264 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
265 : section,
266 : "CODE");
267 0 : cpc->failure = true;
268 0 : return;
269 : }
270 90 : if (GNUNET_OK !=
271 90 : TALER_check_currency (str))
272 : {
273 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
274 : section,
275 : "CODE",
276 : "Currency code name given is invalid");
277 0 : cpc->failure = true;
278 0 : GNUNET_free (str);
279 0 : return;
280 : }
281 90 : 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 90 : GNUNET_assert (strlen (str) < TALER_CURRENCY_LEN);
287 90 : strcpy (cspec->currency,
288 : str);
289 90 : GNUNET_free (str);
290 :
291 90 : if (GNUNET_OK !=
292 90 : GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
293 : section,
294 : "NAME",
295 : &str))
296 : {
297 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
298 : section,
299 : "NAME");
300 0 : cpc->failure = true;
301 0 : return;
302 : }
303 90 : cspec->name = str;
304 :
305 90 : if (GNUNET_OK !=
306 90 : GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
307 : section,
308 : "COMMON_AMOUNTS",
309 : &str))
310 : {
311 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
312 : section,
313 : "COMMON_AMOUNTS");
314 : }
315 : else
316 : {
317 90 : for (const char *tok = strtok (str,
318 : " ");
319 450 : NULL != tok;
320 360 : tok = strtok (NULL,
321 : " "))
322 : {
323 : struct TALER_Amount val;
324 :
325 360 : if (GNUNET_OK !=
326 360 : TALER_string_to_amount (tok,
327 : &val))
328 : {
329 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
330 : section,
331 : "COMMON_AMOUNTS",
332 : tok);
333 0 : GNUNET_free (str);
334 0 : cpc->failure = true;
335 0 : return;
336 : }
337 360 : if (0 != strcasecmp (val.currency,
338 360 : cspec->currency))
339 : {
340 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
341 : section,
342 : "COMMON_AMOUNTS",
343 : "currency mismatch");
344 0 : GNUNET_free (str);
345 0 : cpc->failure = true;
346 0 : return;
347 : }
348 360 : GNUNET_array_append (cspec->common_amounts,
349 : cspec->num_common_amounts,
350 : val);
351 : }
352 90 : GNUNET_free (str);
353 : }
354 90 : if (GNUNET_OK !=
355 90 : GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
356 : section,
357 : "FRACTIONAL_INPUT_DIGITS",
358 : &num))
359 : {
360 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
361 : section,
362 : "FRACTIONAL_INPUT_DIGITS");
363 0 : cpc->failure = true;
364 0 : return;
365 : }
366 90 : if (num > TALER_AMOUNT_FRAC_LEN)
367 : {
368 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
369 : section,
370 : "FRACTIONAL_INPUT_DIGITS",
371 : "Number given is too big");
372 0 : cpc->failure = true;
373 0 : return;
374 : }
375 90 : cspec->num_fractional_input_digits = num;
376 90 : if (GNUNET_OK !=
377 90 : GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
378 : section,
379 : "FRACTIONAL_NORMAL_DIGITS",
380 : &num))
381 : {
382 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
383 : section,
384 : "FRACTIONAL_NORMAL_DIGITS");
385 0 : cpc->failure = true;
386 0 : return;
387 : }
388 90 : if (num > TALER_AMOUNT_FRAC_LEN)
389 : {
390 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
391 : section,
392 : "FRACTIONAL_NORMAL_DIGITS",
393 : "Number given is too big");
394 0 : cpc->failure = true;
395 0 : return;
396 : }
397 90 : cspec->num_fractional_normal_digits = num;
398 90 : if (GNUNET_OK !=
399 90 : GNUNET_CONFIGURATION_get_value_number (cpc->cfg,
400 : section,
401 : "FRACTIONAL_TRAILING_ZERO_DIGITS",
402 : &num))
403 : {
404 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
405 : section,
406 : "FRACTIONAL_TRAILING_ZERO_DIGITS");
407 0 : cpc->failure = true;
408 0 : return;
409 : }
410 90 : if (num > TALER_AMOUNT_FRAC_LEN)
411 : {
412 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
413 : section,
414 : "FRACTIONAL_TRAILING_ZERO_DIGITS",
415 : "Number given is too big");
416 0 : cpc->failure = true;
417 0 : return;
418 : }
419 90 : cspec->num_fractional_trailing_zero_digits = num;
420 :
421 90 : if (GNUNET_OK !=
422 90 : GNUNET_CONFIGURATION_get_value_string (cpc->cfg,
423 : section,
424 : "ALT_UNIT_NAMES",
425 : &str))
426 : {
427 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
428 : section,
429 : "ALT_UNIT_NAMES");
430 0 : cpc->failure = true;
431 0 : return;
432 : }
433 : {
434 : json_error_t err;
435 :
436 90 : cspec->map_alt_unit_names = json_loads (str,
437 : JSON_REJECT_DUPLICATES,
438 : &err);
439 90 : GNUNET_free (str);
440 90 : if (NULL == cspec->map_alt_unit_names)
441 : {
442 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
443 : section,
444 : "ALT_UNIT_NAMES",
445 : err.text);
446 0 : cpc->failure = true;
447 0 : return;
448 : }
449 : }
450 90 : if (GNUNET_OK !=
451 90 : TALER_check_currency_scale_map (cspec->map_alt_unit_names))
452 : {
453 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
454 : section,
455 : "ALT_UNIT_NAMES",
456 : "invalid map entry detected");
457 0 : cpc->failure = true;
458 0 : json_decref (cspec->map_alt_unit_names);
459 0 : cspec->map_alt_unit_names = NULL;
460 0 : return;
461 : }
462 : }
463 :
464 :
465 : enum GNUNET_GenericReturnValue
466 132 : 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 132 : bool zf = false;
472 :
473 132 : if (! json_is_object (map))
474 : {
475 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
476 : "Object required for currency scale map\n");
477 0 : return GNUNET_SYSERR;
478 : }
479 318 : json_object_foreach ((json_t *) map, str, val)
480 : {
481 : int idx;
482 : char dummy;
483 :
484 186 : if ( (1 != sscanf (str,
485 : "%d%c",
486 : &idx,
487 186 : &dummy)) ||
488 186 : (idx < -12) ||
489 186 : (idx > 24) ||
490 186 : (! json_is_string (val) ) )
491 : {
492 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
493 : "Invalid entry `%s' in currency scale map\n",
494 : str);
495 0 : return GNUNET_SYSERR;
496 : }
497 186 : if (0 == idx)
498 132 : zf = true;
499 : }
500 132 : if (! zf)
501 : {
502 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
503 : "Entry for 0 missing in currency scale map\n");
504 0 : return GNUNET_SYSERR;
505 : }
506 132 : return GNUNET_OK;
507 : }
508 :
509 :
510 : enum GNUNET_GenericReturnValue
511 18 : 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 18 : 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 18 : GNUNET_CONFIGURATION_iterate_sections (cfg,
526 : &parse_currencies_cb,
527 : &cpc);
528 18 : if (cpc.failure)
529 : {
530 0 : TALER_CONFIG_free_currencies (cpc.num_currencies,
531 : cpc.cspecs);
532 0 : return GNUNET_SYSERR;
533 : }
534 : /* Make sure that there is some sane fallback for the main currency */
535 18 : if (NULL != main_currency)
536 : {
537 18 : struct TALER_CurrencySpecification *mspec = NULL;
538 88 : for (unsigned int i = 0; i<cpc.num_currencies; i++)
539 : {
540 : struct TALER_CurrencySpecification *cspec;
541 :
542 88 : cspec = &cpc.cspecs[i];
543 88 : if (0 == strcasecmp (main_currency,
544 88 : cspec->currency))
545 : {
546 18 : mspec = cspec;
547 18 : break;
548 : }
549 : }
550 18 : if (NULL == mspec)
551 : {
552 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
553 : "Lacking enabled currency specification for main currency %s, using fallback currency specification.\n",
554 : main_currency);
555 0 : if (cpc.len_cspecs == cpc.num_currencies)
556 : {
557 0 : GNUNET_array_grow (cpc.cspecs,
558 : cpc.len_cspecs,
559 : cpc.len_cspecs + 1);
560 : }
561 0 : mspec = &cpc.cspecs[cpc.num_currencies++];
562 0 : *mspec = defspec;
563 0 : GNUNET_assert (strlen (main_currency) < TALER_CURRENCY_LEN);
564 0 : strcpy (mspec->currency,
565 : main_currency);
566 : mspec->map_alt_unit_names
567 0 : = GNUNET_JSON_PACK (
568 : GNUNET_JSON_pack_string ("0",
569 : main_currency)
570 : );
571 0 : mspec->name = GNUNET_strdup (main_currency);
572 : }
573 : }
574 : /* cspecs might've been overgrown, grow back to minimum size */
575 18 : GNUNET_array_grow (cpc.cspecs,
576 : cpc.len_cspecs,
577 : cpc.num_currencies);
578 18 : *num_currencies = cpc.num_currencies;
579 18 : *cspecs = cpc.cspecs;
580 18 : if (0 == *num_currencies)
581 : {
582 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
583 : "No currency formatting specification found! Please check your installation!\n");
584 0 : return GNUNET_NO;
585 : }
586 18 : return GNUNET_OK;
587 : }
588 :
589 :
590 : void
591 18 : TALER_CONFIG_free_currencies (
592 : unsigned int num_currencies,
593 : struct TALER_CurrencySpecification cspecs[static num_currencies])
594 18 : {
595 108 : for (unsigned int i = 0; i<num_currencies; i++)
596 : {
597 90 : struct TALER_CurrencySpecification *cspec = &cspecs[i];
598 :
599 90 : GNUNET_free (cspec->name);
600 90 : json_decref (cspec->map_alt_unit_names);
601 90 : GNUNET_array_grow (cspec->common_amounts,
602 : cspec->num_common_amounts,
603 : 0);
604 : }
605 18 : GNUNET_array_grow (cspecs,
606 : num_currencies,
607 : 0);
608 18 : }
|