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