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