Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2023, 2024, 2026 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero 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 Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file src/backend/taler-merchant-exchangekeyupdate.c
18 : * @brief Process that ensures our /keys data for all exchanges is current
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include "microhttpd.h"
23 : #include <gnunet/gnunet_util_lib.h>
24 : #include <jansson.h>
25 : #include <pthread.h>
26 : #include <taler/taler_dbevents.h>
27 : struct Exchange;
28 : #define TALER_EXCHANGE_GET_KEYS_RESULT_CLOSURE struct Exchange
29 : #include <taler/exchange/get-keys.h>
30 :
31 : #include "taler/taler_merchant_util.h"
32 : #include "taler/taler_merchant_bank_lib.h"
33 : #include "merchantdb_lib.h"
34 : #include "merchant-database/delete_exchange_accounts.h"
35 : #include "merchant-database/insert_exchange_keys.h"
36 : #include "merchant-database/insert_exchange_signing_key.h"
37 : #include "merchant-database/start.h"
38 : #include "merchant-database/preflight.h"
39 : #include "merchant-database/event_notify.h"
40 : #include "merchant-database/event_listen.h"
41 : #include "merchant-database/insert_exchange_account.h"
42 : #include "merchant-database/get_exchange_keys.h"
43 : #include "merchant-database/insert_exchange_wire_fee.h"
44 :
45 : /**
46 : * Maximum frequency for the exchange interaction.
47 : */
48 : #define EXCHANGE_MAXFREQ GNUNET_TIME_relative_multiply ( \
49 : GNUNET_TIME_UNIT_MINUTES, \
50 : 5)
51 :
52 : /**
53 : * How many inquiries do we process concurrently at most.
54 : */
55 : #define OPEN_INQUIRY_LIMIT 1024
56 :
57 : /**
58 : * How often do we retry after DB serialization errors (at most)?
59 : */
60 : #define MAX_RETRIES 3
61 :
62 : /**
63 : * Information about an exchange.
64 : */
65 : struct Exchange
66 : {
67 : /**
68 : * Kept in a DLL.
69 : */
70 : struct Exchange *next;
71 :
72 : /**
73 : * Kept in a DLL.
74 : */
75 : struct Exchange *prev;
76 :
77 : /**
78 : * Base URL of the exchange are we tracking here.
79 : */
80 : char *exchange_url;
81 :
82 : /**
83 : * Expected currency of the exchange.
84 : */
85 : char *currency;
86 :
87 : /**
88 : * A /keys request to this exchange, NULL if not active.
89 : */
90 : struct TALER_EXCHANGE_GetKeysHandle *conn;
91 :
92 : /**
93 : * The keys of this exchange, NULL if not known.
94 : */
95 : struct TALER_EXCHANGE_Keys *keys;
96 :
97 : /**
98 : * Task where we retry fetching /keys from the exchange.
99 : */
100 : struct GNUNET_SCHEDULER_Task *retry_task;
101 :
102 : /**
103 : * Master public key expected for this exchange.
104 : */
105 : struct TALER_MasterPublicKeyP master_pub;
106 :
107 : /**
108 : * How soon can may we, at the earliest, re-download /keys?
109 : */
110 : struct GNUNET_TIME_Absolute first_retry;
111 :
112 : /**
113 : * How long should we wait between the next retry?
114 : * Used for exponential back-offs.
115 : */
116 : struct GNUNET_TIME_Relative retry_delay;
117 :
118 : /**
119 : * Are we waiting for /keys downloads due to our
120 : * hard limit?
121 : */
122 : bool limited;
123 :
124 : /**
125 : * Are we force-retrying a /keys download because some keys
126 : * were missing (and we thus should not cherry-pick, as
127 : * a major reason for a force-reload would be an
128 : * exchange that has lost keys and backfilled them, which
129 : * breaks keys downloads with cherry-picking).
130 : */
131 : bool force_retry;
132 : };
133 :
134 :
135 : /**
136 : * Head of known exchanges.
137 : */
138 : static struct Exchange *e_head;
139 :
140 : /**
141 : * Tail of known exchanges.
142 : */
143 : static struct Exchange *e_tail;
144 :
145 : /**
146 : * The merchant's configuration.
147 : */
148 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
149 :
150 : /**
151 : * Our database plugin.
152 : */
153 : static struct TALER_MERCHANTDB_PostgresContext *pg;
154 :
155 : /**
156 : * Our event handler listening for /keys forced downloads.
157 : */
158 : static struct GNUNET_DB_EventHandler *eh;
159 :
160 : /**
161 : * Handle to the context for interacting with the bank.
162 : */
163 : static struct GNUNET_CURL_Context *ctx;
164 :
165 : /**
166 : * Scheduler context for running the @e ctx.
167 : */
168 : static struct GNUNET_CURL_RescheduleContext *rc;
169 :
170 : /**
171 : * How many active inquiries do we have right now.
172 : */
173 : static unsigned int active_inquiries;
174 :
175 : /**
176 : * Value to return from main(). 0 on success, non-zero on errors.
177 : */
178 : static int global_ret;
179 :
180 : /**
181 : * Should we enable HTTP/2 and HTTP/3 when talking to the exchange?
182 : * Those are not expected to be terribly beneficial for a client with
183 : * stable connections to a few servers, but they could cause stability
184 : * issues with libcurl. Hence we *default* to HTTP/1.1-only, as that
185 : * is the conservative and most tested code path.
186 : */
187 : static int enable_h3;
188 :
189 : /**
190 : * #GNUNET_YES if we are in test mode and should exit when idle.
191 : */
192 : static int test_mode;
193 :
194 : /**
195 : * True if the last DB query was limited by the
196 : * #OPEN_INQUIRY_LIMIT and we thus should check again
197 : * as soon as we are substantially below that limit,
198 : * and not only when we get a DB notification.
199 : */
200 : static bool at_limit;
201 :
202 :
203 : /**
204 : * Function that initiates a /keys download.
205 : *
206 : * @param cls a `struct Exchange *`
207 : */
208 : static void
209 : download_keys (void *cls);
210 :
211 :
212 : /**
213 : * An inquiry finished, check if we need to start more.
214 : */
215 : static void
216 41 : end_inquiry (void)
217 : {
218 41 : GNUNET_assert (active_inquiries > 0);
219 41 : active_inquiries--;
220 41 : if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
221 : (at_limit) )
222 : {
223 0 : at_limit = false;
224 0 : for (struct Exchange *e = e_head;
225 0 : NULL != e;
226 0 : e = e->next)
227 : {
228 0 : if (! e->limited)
229 0 : continue;
230 0 : e->limited = false;
231 : /* done synchronously so that the active_inquiries
232 : is updated immediately */
233 0 : download_keys (e);
234 0 : if (at_limit)
235 0 : break;
236 : }
237 : }
238 41 : if ( (! at_limit) &&
239 41 : (0 == active_inquiries) &&
240 : (test_mode) )
241 : {
242 1 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
243 : "No more open inquiries and in test mode. Existing.\n");
244 1 : GNUNET_SCHEDULER_shutdown ();
245 1 : return;
246 : }
247 : }
248 :
249 :
250 : /**
251 : * Add account restriction @a a to array of @a restrictions.
252 : *
253 : * @param[in,out] restrictions JSON array to build
254 : * @param r restriction to add to @a restrictions
255 : * @return #GNUNET_SYSERR if @a r is malformed
256 : */
257 : static enum GNUNET_GenericReturnValue
258 0 : add_restriction (json_t *restrictions,
259 : const struct TALER_EXCHANGE_AccountRestriction *r)
260 : {
261 : json_t *jr;
262 :
263 0 : jr = NULL;
264 0 : switch (r->type)
265 : {
266 0 : case TALER_EXCHANGE_AR_INVALID:
267 0 : GNUNET_break_op (0);
268 0 : return GNUNET_SYSERR;
269 0 : case TALER_EXCHANGE_AR_DENY:
270 0 : jr = GNUNET_JSON_PACK (
271 : GNUNET_JSON_pack_string ("type",
272 : "deny")
273 : );
274 0 : break;
275 0 : case TALER_EXCHANGE_AR_REGEX:
276 0 : jr = GNUNET_JSON_PACK (
277 : GNUNET_JSON_pack_string (
278 : "type",
279 : "regex"),
280 : GNUNET_JSON_pack_string (
281 : "regex",
282 : r->details.regex.posix_egrep),
283 : GNUNET_JSON_pack_string (
284 : "human_hint",
285 : r->details.regex.human_hint),
286 : GNUNET_JSON_pack_object_incref (
287 : "human_hint_i18n",
288 : (json_t *) r->details.regex.human_hint_i18n)
289 : );
290 0 : break;
291 : }
292 0 : if (NULL == jr)
293 : {
294 0 : GNUNET_break_op (0);
295 0 : return GNUNET_SYSERR;
296 : }
297 0 : GNUNET_assert (0 ==
298 : json_array_append_new (restrictions,
299 : jr));
300 0 : return GNUNET_OK;
301 :
302 : }
303 :
304 :
305 : /**
306 : * The /keys download from @e failed with @a http_status and @a ec.
307 : * Record the failure in the database.
308 : *
309 : * @param e exchange that failed
310 : * @param http_status HTTP status returned
311 : * @param ec Taler error code
312 : */
313 : static void
314 27 : fail_keys (const struct Exchange *e,
315 : unsigned int http_status,
316 : enum TALER_ErrorCode ec)
317 : {
318 : enum GNUNET_DB_QueryStatus qs;
319 :
320 27 : qs = TALER_MERCHANTDB_insert_exchange_keys (
321 : pg,
322 27 : e->exchange_url,
323 : NULL,
324 : GNUNET_TIME_relative_to_absolute (e->retry_delay),
325 : http_status,
326 : ec);
327 27 : if (0 > qs)
328 : {
329 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
330 0 : return;
331 : }
332 : }
333 :
334 :
335 : /**
336 : * Update our information in the database about the
337 : * /keys of an exchange. Run inside of a database
338 : * transaction scope that will re-try and/or commit
339 : * depending on the return value.
340 : *
341 : * @param keys information to persist
342 : * @param first_retry earliest we may retry fetching the keys
343 : * @return transaction status
344 : */
345 : static enum GNUNET_DB_QueryStatus
346 14 : insert_keys_data (const struct TALER_EXCHANGE_Keys *keys,
347 : struct GNUNET_TIME_Absolute first_retry)
348 : {
349 : enum GNUNET_DB_QueryStatus qs;
350 :
351 : /* store exchange online signing keys in our DB */
352 84 : for (unsigned int i = 0; i<keys->num_sign_keys; i++)
353 : {
354 70 : const struct TALER_EXCHANGE_SigningPublicKey *sign_key
355 70 : = &keys->sign_keys[i];
356 :
357 70 : qs = TALER_MERCHANTDB_insert_exchange_signing_key (
358 : pg,
359 : &keys->master_pub,
360 : &sign_key->key,
361 : sign_key->valid_from,
362 : sign_key->valid_until,
363 : sign_key->valid_legal,
364 : &sign_key->master_sig);
365 : /* 0 is OK, we may already have the key in the DB! */
366 70 : if (0 > qs)
367 : {
368 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
369 0 : return qs;
370 : }
371 : }
372 :
373 14 : qs = TALER_MERCHANTDB_insert_exchange_keys (pg,
374 14 : keys->exchange_url,
375 : keys,
376 : first_retry,
377 : MHD_HTTP_OK,
378 : TALER_EC_NONE);
379 14 : if (0 > qs)
380 : {
381 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
382 0 : return qs;
383 : }
384 :
385 14 : qs = TALER_MERCHANTDB_delete_exchange_accounts (pg,
386 : &keys->master_pub);
387 14 : if (0 > qs)
388 : {
389 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
390 0 : return qs;
391 : }
392 :
393 28 : for (unsigned int i = 0; i<keys->accounts_len; i++)
394 : {
395 14 : const struct TALER_EXCHANGE_WireAccount *account
396 14 : = &keys->accounts[i];
397 : json_t *debit_restrictions;
398 : json_t *credit_restrictions;
399 :
400 14 : debit_restrictions = json_array ();
401 14 : GNUNET_assert (NULL != debit_restrictions);
402 14 : credit_restrictions = json_array ();
403 14 : GNUNET_assert (NULL != credit_restrictions);
404 14 : for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
405 : {
406 0 : if (GNUNET_OK !=
407 0 : add_restriction (debit_restrictions,
408 0 : &account->debit_restrictions[j]))
409 : {
410 0 : TALER_MERCHANTDB_rollback (pg);
411 0 : GNUNET_break (0);
412 0 : json_decref (debit_restrictions);
413 0 : json_decref (credit_restrictions);
414 0 : return GNUNET_DB_STATUS_HARD_ERROR;
415 : }
416 : }
417 14 : for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
418 : {
419 0 : if (GNUNET_OK !=
420 0 : add_restriction (credit_restrictions,
421 0 : &account->credit_restrictions[j]))
422 : {
423 0 : TALER_MERCHANTDB_rollback (pg);
424 0 : GNUNET_break (0);
425 0 : json_decref (debit_restrictions);
426 0 : json_decref (credit_restrictions);
427 0 : return GNUNET_DB_STATUS_HARD_ERROR;
428 : }
429 : }
430 14 : qs = TALER_MERCHANTDB_insert_exchange_account (
431 : pg,
432 : &keys->master_pub,
433 : account->fpayto_uri,
434 14 : account->conversion_url,
435 : debit_restrictions,
436 : credit_restrictions,
437 : &account->master_sig);
438 14 : json_decref (debit_restrictions);
439 14 : json_decref (credit_restrictions);
440 14 : if (qs < 0)
441 : {
442 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
443 0 : return qs;
444 : }
445 : } /* end 'for all accounts' */
446 :
447 28 : for (unsigned int i = 0; i<keys->fees_len; i++)
448 : {
449 14 : const struct TALER_EXCHANGE_WireFeesByMethod *fbm
450 14 : = &keys->fees[i];
451 14 : const char *wire_method = fbm->method;
452 14 : const struct TALER_EXCHANGE_WireAggregateFees *fees
453 : = fbm->fees_head;
454 :
455 42 : while (NULL != fees)
456 : {
457 : struct GNUNET_HashCode h_wire_method;
458 :
459 28 : GNUNET_CRYPTO_hash (wire_method,
460 28 : strlen (wire_method) + 1,
461 : &h_wire_method);
462 28 : qs = TALER_MERCHANTDB_insert_exchange_wire_fee (
463 : pg,
464 : &keys->master_pub,
465 : &h_wire_method,
466 : &fees->fees,
467 : fees->start_date,
468 : fees->end_date,
469 : &fees->master_sig);
470 28 : if (0 > qs)
471 : {
472 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
473 0 : return qs;
474 : }
475 28 : fees = fees->next;
476 : } /* all fees for this method */
477 : } /* for all methods (i) */
478 :
479 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
480 : "Updated keys for %s, inserted %d signing keys, %d denom keys, %d fees-by-wire\n",
481 : keys->exchange_url,
482 : keys->num_sign_keys,
483 : keys->num_denom_keys,
484 : keys->fees_len);
485 :
486 : {
487 14 : struct GNUNET_DB_EventHeaderP es = {
488 14 : .size = htons (sizeof (es)),
489 14 : .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
490 : };
491 :
492 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
493 : "Informing other processes about keys change for %s\n",
494 : keys->exchange_url);
495 14 : TALER_MERCHANTDB_event_notify (pg,
496 : &es,
497 14 : keys->exchange_url,
498 14 : strlen (keys->exchange_url) + 1);
499 : }
500 14 : return qs;
501 : }
502 :
503 :
504 : /**
505 : * Run database transaction to store the @a keys in
506 : * the merchant database (and notify other processes
507 : * that may care about them).
508 : *
509 : * @param keys the keys to store
510 : * @param first_retry earliest we may retry fetching the keys
511 : * @return true on success
512 : */
513 : static bool
514 14 : store_keys (struct TALER_EXCHANGE_Keys *keys,
515 : struct GNUNET_TIME_Absolute first_retry)
516 : {
517 : enum GNUNET_DB_QueryStatus qs;
518 :
519 14 : TALER_MERCHANTDB_preflight (pg);
520 14 : for (unsigned int r = 0; r<MAX_RETRIES; r++)
521 : {
522 14 : if (GNUNET_OK !=
523 14 : TALER_MERCHANTDB_start (pg,
524 : "update exchange key data"))
525 : {
526 0 : TALER_MERCHANTDB_rollback (pg);
527 0 : GNUNET_break (0);
528 0 : return false;
529 : }
530 :
531 14 : qs = insert_keys_data (keys,
532 : first_retry);
533 14 : if (0 > qs)
534 : {
535 0 : TALER_MERCHANTDB_rollback (pg);
536 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
537 0 : continue;
538 0 : GNUNET_break (0);
539 0 : return false;
540 : }
541 :
542 14 : qs = TALER_MERCHANTDB_commit (pg);
543 14 : if (0 > qs)
544 : {
545 0 : TALER_MERCHANTDB_rollback (pg);
546 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
547 0 : continue;
548 0 : GNUNET_break (0);
549 0 : return false;
550 : }
551 14 : break;
552 : } /* end of retry loop */
553 14 : if (qs < 0)
554 : {
555 0 : GNUNET_break (0);
556 0 : return false;
557 : }
558 14 : return true;
559 : }
560 :
561 :
562 : /**
563 : * Function called with information about who is auditing
564 : * a particular exchange and what keys the exchange is using.
565 : *
566 : * @param e the exchange to update
567 : * @param kr response data
568 : * @param[in] keys the keys of the exchange
569 : */
570 : static void
571 41 : cert_cb (
572 : struct Exchange *e,
573 : const struct TALER_EXCHANGE_KeysResponse *kr,
574 : struct TALER_EXCHANGE_Keys *keys)
575 : {
576 : struct GNUNET_TIME_Absolute n;
577 : struct GNUNET_TIME_Absolute first_retry;
578 :
579 41 : e->conn = NULL;
580 : e->retry_delay
581 41 : = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
582 41 : switch (kr->hr.http_status)
583 : {
584 14 : case MHD_HTTP_OK:
585 14 : TALER_EXCHANGE_keys_decref (e->keys);
586 14 : e->keys = NULL;
587 14 : if (0 != strcasecmp (e->currency,
588 14 : keys->currency))
589 : {
590 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
591 : "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n",
592 : e->exchange_url,
593 : keys->currency,
594 : e->currency);
595 0 : fail_keys (e,
596 : MHD_HTTP_OK,
597 : TALER_EC_GENERIC_CURRENCY_MISMATCH);
598 0 : TALER_EXCHANGE_keys_decref (keys);
599 0 : break;
600 : }
601 14 : if (0 != GNUNET_memcmp (&keys->master_pub,
602 : &e->master_pub))
603 : {
604 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
605 : "Master public key in %skeys response does not match. Ignoring response.\n",
606 : e->exchange_url);
607 0 : fail_keys (e,
608 : MHD_HTTP_OK,
609 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_MASTER_KEY_MISMATCH);
610 0 : TALER_EXCHANGE_keys_decref (keys);
611 0 : break;
612 : }
613 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
614 : "Got new keys for %s, updating database\n",
615 : e->exchange_url);
616 14 : first_retry = GNUNET_TIME_relative_to_absolute (
617 : EXCHANGE_MAXFREQ);
618 14 : if (! store_keys (keys,
619 : first_retry))
620 : {
621 0 : GNUNET_break (0);
622 0 : TALER_EXCHANGE_keys_decref (keys);
623 0 : break;
624 : }
625 14 : e->keys = keys;
626 : /* Reset back-off */
627 14 : e->retry_delay = EXCHANGE_MAXFREQ;
628 : /* limit retry */
629 14 : e->first_retry = first_retry;
630 : /* Limit by expiration */
631 14 : n = GNUNET_TIME_absolute_max (e->first_retry,
632 : keys->key_data_expiration.abs_time);
633 14 : if (NULL != e->retry_task)
634 0 : GNUNET_SCHEDULER_cancel (e->retry_task);
635 14 : e->retry_task = GNUNET_SCHEDULER_add_at (n,
636 : &download_keys,
637 : e);
638 14 : end_inquiry ();
639 14 : return;
640 27 : default:
641 27 : GNUNET_break (NULL == keys);
642 27 : fail_keys (e,
643 27 : kr->hr.http_status,
644 : TALER_EC_MERCHANT_GENERIC_EXCHANGE_KEYS_FAILURE);
645 27 : break;
646 : }
647 : /* Try again (soon-ish) */
648 27 : n = GNUNET_TIME_absolute_max (
649 : e->first_retry,
650 : GNUNET_TIME_relative_to_absolute (e->retry_delay));
651 27 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
652 : "Will download %skeys in %s\n",
653 : e->exchange_url,
654 : GNUNET_TIME_relative2s (
655 : GNUNET_TIME_absolute_get_remaining (n),
656 : true));
657 27 : if (NULL != e->retry_task)
658 0 : GNUNET_SCHEDULER_cancel (e->retry_task);
659 : e->retry_task
660 27 : = GNUNET_SCHEDULER_add_at (n,
661 : &download_keys,
662 : e);
663 27 : end_inquiry ();
664 : }
665 :
666 :
667 : static void
668 41 : download_keys (void *cls)
669 : {
670 41 : struct Exchange *e = cls;
671 :
672 41 : e->retry_task = NULL;
673 41 : GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
674 41 : if (OPEN_INQUIRY_LIMIT <= active_inquiries)
675 : {
676 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
677 : "Cannot run job: at limit\n");
678 0 : e->limited = true;
679 0 : at_limit = true;
680 0 : return;
681 : }
682 : e->retry_delay
683 41 : = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
684 41 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
685 : "Downloading keys from %s (%s)\n",
686 : e->exchange_url,
687 : e->force_retry ? "forced" : "regular");
688 82 : e->conn = TALER_EXCHANGE_get_keys_create (ctx,
689 41 : e->exchange_url);
690 41 : if ( (NULL != e->conn) &&
691 41 : (! e->force_retry) )
692 36 : TALER_EXCHANGE_get_keys_set_options (
693 : e->conn,
694 : TALER_EXCHANGE_get_keys_option_last_keys (e->keys));
695 41 : e->force_retry = false;
696 82 : if ( (NULL != e->conn) &&
697 : (TALER_EC_NONE ==
698 41 : TALER_EXCHANGE_get_keys_start (e->conn,
699 : &cert_cb,
700 : e)) )
701 : {
702 41 : active_inquiries++;
703 : }
704 : else
705 : {
706 : struct GNUNET_TIME_Relative n;
707 :
708 0 : if (NULL != e->conn)
709 : {
710 0 : TALER_EXCHANGE_get_keys_cancel (e->conn);
711 0 : e->conn = NULL;
712 : }
713 0 : n = GNUNET_TIME_relative_max (e->retry_delay,
714 : EXCHANGE_MAXFREQ);
715 : e->retry_task
716 0 : = GNUNET_SCHEDULER_add_delayed (n,
717 : &download_keys,
718 : e);
719 : }
720 : }
721 :
722 :
723 : /**
724 : * Lookup exchange by @a exchange_url. Create one
725 : * if it does not exist.
726 : *
727 : * @param exchange_url base URL to match against
728 : * @return NULL if not found
729 : */
730 : static struct Exchange *
731 17 : lookup_exchange (const char *exchange_url)
732 : {
733 17 : for (struct Exchange *e = e_head;
734 17 : NULL != e;
735 0 : e = e->next)
736 17 : if (0 == strcmp (e->exchange_url,
737 : exchange_url))
738 17 : return e;
739 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
740 : "Got notification about unknown exchange `%s'\n",
741 : exchange_url);
742 0 : return NULL;
743 : }
744 :
745 :
746 : /**
747 : * Force immediate (re)loading of /keys for an exchange.
748 : *
749 : * @param cls NULL
750 : * @param extra base URL of the exchange that changed
751 : * @param extra_len number of bytes in @a extra
752 : */
753 : static void
754 17 : force_exchange_keys (void *cls,
755 : const void *extra,
756 : size_t extra_len)
757 : {
758 17 : const char *url = extra;
759 : struct Exchange *e;
760 :
761 17 : if ( (NULL == extra) ||
762 : (0 == extra_len) )
763 : {
764 0 : GNUNET_break (0);
765 0 : return;
766 : }
767 17 : if ('\0' != url[extra_len - 1])
768 : {
769 0 : GNUNET_break (0);
770 0 : return;
771 : }
772 17 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
773 : "Received keys change notification: reload `%s'\n",
774 : url);
775 17 : e = lookup_exchange (url);
776 17 : if (NULL == e)
777 : {
778 0 : GNUNET_break (0);
779 0 : return;
780 : }
781 17 : if (NULL != e->conn)
782 : {
783 12 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
784 : "Already downloading %skeys\n",
785 : url);
786 12 : return;
787 : }
788 5 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
789 : "Will download %skeys in %s\n",
790 : url,
791 : GNUNET_TIME_relative2s (
792 : GNUNET_TIME_absolute_get_remaining (
793 : e->first_retry),
794 : true));
795 5 : if (NULL != e->retry_task)
796 5 : GNUNET_SCHEDULER_cancel (e->retry_task);
797 5 : e->force_retry = true;
798 : e->retry_task
799 5 : = GNUNET_SCHEDULER_add_at (e->first_retry,
800 : &download_keys,
801 : e);
802 : }
803 :
804 :
805 : /**
806 : * Function called on each configuration section. Finds sections
807 : * about exchanges, parses the entries.
808 : *
809 : * @param cls NULL
810 : * @param section name of the section
811 : */
812 : static void
813 779 : accept_exchanges (void *cls,
814 : const char *section)
815 : {
816 : char *url;
817 : char *mks;
818 : char *currency;
819 :
820 : (void) cls;
821 779 : if (0 !=
822 779 : strncasecmp (section,
823 : "merchant-exchange-",
824 : strlen ("merchant-exchange-")))
825 760 : return;
826 57 : if (GNUNET_YES ==
827 57 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
828 : section,
829 : "DISABLED"))
830 38 : return;
831 19 : if (GNUNET_OK !=
832 19 : GNUNET_CONFIGURATION_get_value_string (cfg,
833 : section,
834 : "EXCHANGE_BASE_URL",
835 : &url))
836 : {
837 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
838 : section,
839 : "EXCHANGE_BASE_URL");
840 0 : global_ret = EXIT_NOTCONFIGURED;
841 0 : GNUNET_SCHEDULER_shutdown ();
842 0 : return;
843 : }
844 19 : for (struct Exchange *e = e_head;
845 19 : NULL != e;
846 0 : e = e->next)
847 : {
848 0 : if (0 == strcmp (url,
849 0 : e->exchange_url))
850 : {
851 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
852 : "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n",
853 : url,
854 : section);
855 0 : GNUNET_free (url);
856 0 : global_ret = EXIT_NOTCONFIGURED;
857 0 : GNUNET_SCHEDULER_shutdown ();
858 0 : return;
859 : }
860 : }
861 19 : if (GNUNET_OK !=
862 19 : GNUNET_CONFIGURATION_get_value_string (cfg,
863 : section,
864 : "CURRENCY",
865 : ¤cy))
866 : {
867 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
868 : section,
869 : "CURRENCY");
870 0 : GNUNET_free (url);
871 0 : global_ret = EXIT_NOTCONFIGURED;
872 0 : GNUNET_SCHEDULER_shutdown ();
873 0 : return;
874 : }
875 19 : if (GNUNET_OK !=
876 19 : GNUNET_CONFIGURATION_get_value_string (cfg,
877 : section,
878 : "MASTER_KEY",
879 : &mks))
880 : {
881 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
882 : section,
883 : "MASTER_KEY");
884 0 : global_ret = EXIT_NOTCONFIGURED;
885 0 : GNUNET_SCHEDULER_shutdown ();
886 0 : GNUNET_free (currency);
887 0 : GNUNET_free (url);
888 0 : return;
889 : }
890 :
891 : {
892 : struct Exchange *e;
893 :
894 19 : e = GNUNET_new (struct Exchange);
895 19 : e->exchange_url = url;
896 19 : e->currency = currency;
897 19 : GNUNET_CONTAINER_DLL_insert (e_head,
898 : e_tail,
899 : e);
900 19 : if (GNUNET_OK !=
901 19 : GNUNET_CRYPTO_eddsa_public_key_from_string (
902 : mks,
903 : strlen (mks),
904 : &e->master_pub.eddsa_pub))
905 : {
906 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
907 : section,
908 : "MASTER_KEY",
909 : "malformed EdDSA key");
910 0 : global_ret = EXIT_NOTCONFIGURED;
911 0 : GNUNET_SCHEDULER_shutdown ();
912 0 : GNUNET_free (mks);
913 0 : return;
914 : }
915 19 : GNUNET_free (mks);
916 :
917 : {
918 : enum GNUNET_DB_QueryStatus qs;
919 19 : struct TALER_EXCHANGE_Keys *keys = NULL;
920 :
921 19 : qs = TALER_MERCHANTDB_get_exchange_keys (pg,
922 : url,
923 : &e->first_retry,
924 : &keys);
925 19 : if (qs < 0)
926 : {
927 0 : GNUNET_break (0);
928 0 : global_ret = EXIT_FAILURE;
929 0 : GNUNET_SCHEDULER_shutdown ();
930 0 : return;
931 : }
932 19 : if ( (NULL != keys) &&
933 0 : (0 != strcasecmp (keys->currency,
934 0 : e->currency)) )
935 : {
936 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
937 : "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n",
938 : keys->currency,
939 : e->currency);
940 0 : TALER_EXCHANGE_keys_decref (keys);
941 0 : keys = NULL;
942 : }
943 19 : if ( (NULL != keys) &&
944 0 : (0 != GNUNET_memcmp (&e->master_pub,
945 : &keys->master_pub)) )
946 : {
947 : /* master pub differs => fetch keys again */
948 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
949 : "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n",
950 : e->exchange_url);
951 0 : TALER_EXCHANGE_keys_decref (keys);
952 0 : keys = NULL;
953 : }
954 19 : e->keys = keys;
955 19 : if (NULL == keys)
956 : {
957 : /* done synchronously so that the active_inquiries
958 : is updated immediately */
959 :
960 19 : download_keys (e);
961 : }
962 : else
963 : {
964 : e->retry_task
965 0 : = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
966 : &download_keys,
967 : e);
968 : }
969 : }
970 19 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
971 : "Exchange `%s' setup\n",
972 : e->exchange_url);
973 : }
974 : }
975 :
976 :
977 : /**
978 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
979 : *
980 : * @param cls closure (NULL)
981 : */
982 : static void
983 19 : shutdown_task (void *cls)
984 : {
985 : (void) cls;
986 19 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
987 : "Running shutdown\n");
988 38 : while (NULL != e_head)
989 : {
990 19 : struct Exchange *e = e_head;
991 :
992 19 : GNUNET_free (e->exchange_url);
993 19 : GNUNET_free (e->currency);
994 19 : if (NULL != e->conn)
995 : {
996 0 : TALER_EXCHANGE_get_keys_cancel (e->conn);
997 0 : e->conn = NULL;
998 : }
999 19 : if (NULL != e->keys)
1000 : {
1001 14 : TALER_EXCHANGE_keys_decref (e->keys);
1002 14 : e->keys = NULL;
1003 : }
1004 19 : if (NULL != e->retry_task)
1005 : {
1006 19 : GNUNET_SCHEDULER_cancel (e->retry_task);
1007 19 : e->retry_task = NULL;
1008 : }
1009 19 : GNUNET_CONTAINER_DLL_remove (e_head,
1010 : e_tail,
1011 : e);
1012 19 : GNUNET_free (e);
1013 : }
1014 19 : if (NULL != eh)
1015 : {
1016 19 : TALER_MERCHANTDB_event_listen_cancel (eh);
1017 19 : eh = NULL;
1018 : }
1019 19 : if (NULL != pg)
1020 : {
1021 19 : TALER_MERCHANTDB_disconnect (pg);
1022 19 : pg = NULL;
1023 : }
1024 19 : cfg = NULL;
1025 19 : if (NULL != ctx)
1026 : {
1027 19 : GNUNET_CURL_fini (ctx);
1028 19 : ctx = NULL;
1029 : }
1030 19 : if (NULL != rc)
1031 : {
1032 19 : GNUNET_CURL_gnunet_rc_destroy (rc);
1033 19 : rc = NULL;
1034 : }
1035 19 : }
1036 :
1037 :
1038 : /**
1039 : * First task.
1040 : *
1041 : * @param cls closure, NULL
1042 : * @param args remaining command-line arguments
1043 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1044 : * @param c configuration
1045 : */
1046 : static void
1047 19 : run (void *cls,
1048 : char *const *args,
1049 : const char *cfgfile,
1050 : const struct GNUNET_CONFIGURATION_Handle *c)
1051 : {
1052 : (void) args;
1053 : (void) cfgfile;
1054 :
1055 19 : cfg = c;
1056 19 : TALER_EXCHANGE_setup (enable_h3
1057 19 : ? TALER_EXCHANGE_GO_ENABLE_HTTP3
1058 : : TALER_EXCHANGE_GO_FORCE_HTTP1_1);
1059 19 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1060 : NULL);
1061 19 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1062 : &rc);
1063 19 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
1064 19 : if (NULL == ctx)
1065 : {
1066 0 : GNUNET_break (0);
1067 0 : GNUNET_SCHEDULER_shutdown ();
1068 0 : global_ret = EXIT_FAILURE;
1069 0 : return;
1070 : }
1071 19 : if (NULL ==
1072 19 : (pg = TALER_MERCHANTDB_connect (cfg)))
1073 : {
1074 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1075 : "Failed to initialize DB subsystem. Consider running taler-merchant-dbconfig!\n");
1076 0 : GNUNET_SCHEDULER_shutdown ();
1077 0 : global_ret = EXIT_FAILURE;
1078 0 : return;
1079 : }
1080 : {
1081 19 : struct GNUNET_DB_EventHeaderP es = {
1082 19 : .size = htons (sizeof (es)),
1083 19 : .type = htons (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
1084 : };
1085 :
1086 38 : eh = TALER_MERCHANTDB_event_listen (pg,
1087 : &es,
1088 19 : GNUNET_TIME_UNIT_FOREVER_REL,
1089 : &force_exchange_keys,
1090 : NULL);
1091 : }
1092 19 : GNUNET_CONFIGURATION_iterate_sections (cfg,
1093 : &accept_exchanges,
1094 : NULL);
1095 19 : if ( (0 == active_inquiries) &&
1096 : (test_mode) )
1097 : {
1098 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1099 : "No more open inquiries and in test mode. Existing.\n");
1100 0 : GNUNET_SCHEDULER_shutdown ();
1101 0 : return;
1102 : }
1103 : }
1104 :
1105 :
1106 : /**
1107 : * The main function of taler-merchant-exchangekeyupdate
1108 : *
1109 : * @param argc number of arguments from the command line
1110 : * @param argv command line arguments
1111 : * @return 0 ok, 1 on error
1112 : */
1113 : int
1114 19 : main (int argc,
1115 : char *const *argv)
1116 : {
1117 19 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1118 19 : GNUNET_GETOPT_option_flag ('3',
1119 : "http3",
1120 : "enable support for HTTP/2 and HTTP/3",
1121 : &enable_h3),
1122 19 : GNUNET_GETOPT_option_timetravel ('T',
1123 : "timetravel"),
1124 19 : GNUNET_GETOPT_option_flag ('t',
1125 : "test",
1126 : "run in test mode and exit when idle",
1127 : &test_mode),
1128 19 : GNUNET_GETOPT_option_version (VERSION),
1129 : GNUNET_GETOPT_OPTION_END
1130 : };
1131 : enum GNUNET_GenericReturnValue ret;
1132 :
1133 19 : ret = GNUNET_PROGRAM_run (
1134 : TALER_MERCHANT_project_data (),
1135 : argc, argv,
1136 : "taler-merchant-exchangekeyupdate",
1137 : gettext_noop (
1138 : "background process that ensures our key and configuration data on exchanges is up-to-date"),
1139 : options,
1140 : &run, NULL);
1141 19 : if (GNUNET_SYSERR == ret)
1142 0 : return EXIT_NOTCONFIGURED;
1143 19 : if (GNUNET_NO == ret)
1144 0 : return EXIT_SUCCESS;
1145 19 : return global_ret;
1146 : }
1147 :
1148 :
1149 : /* end of taler-merchant-exchangekeyupdate.c */
|