Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2024, 2025 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-donaukeyupdate.c
18 : * @brief Process that ensures our /keys data for all Donau instances is current
19 : * @author Bohdan Potuzhnyi
20 : * @author Christian Grothoff
21 : */
22 : #include "platform.h"
23 : #include "microhttpd.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include <pthread.h>
27 : #include <taler/taler_dbevents.h>
28 : #include "donau/donau_service.h"
29 : #include "taler/taler_merchant_util.h"
30 : #include "merchantdb_lib.h"
31 : #include "merchantdb_lib.h"
32 : #include "taler/taler_merchant_bank_lib.h"
33 : #include "merchant-database/iterate_all_donau_instances.h"
34 : #include "merchant-database/get_donau_instance_by_serial.h"
35 : #include "merchant-database/update_donau_instance.h"
36 : #include "merchant-database/insert_donau_keys.h"
37 : #include "merchant-database/event_listen.h"
38 : #include "merchant-database/preflight.h"
39 : #include "merchant-database/start.h"
40 :
41 : /**
42 : * Maximum frequency for the Donau interaction.
43 : */
44 : #define DONAU_MAXFREQ GNUNET_TIME_relative_multiply ( \
45 : GNUNET_TIME_UNIT_MINUTES, \
46 : 5)
47 :
48 : /**
49 : * How many inquiries do we process concurrently at most.
50 : */
51 : #define OPEN_INQUIRY_LIMIT 1024
52 :
53 : /**
54 : * How often do we retry after DB serialization errors (at most)?
55 : */
56 : #define MAX_RETRIES 3
57 :
58 : /**
59 : * Information about a Donau instance.
60 : */
61 : struct Donau
62 : {
63 : /**
64 : * Pointer to the next Donau instance in the doubly linked list.
65 : */
66 : struct Donau *next;
67 :
68 : /**
69 : * Pointer to the previous Donau instance in the doubly linked list.
70 : */
71 : struct Donau *prev;
72 :
73 : /**
74 : * Base URL of the Donau instance being tracked.
75 : * This URL is used to query the Donau service for keys and other resources.
76 : */
77 : char *donau_url;
78 :
79 : /**
80 : * Expected currency of the donau.
81 : */
82 : char *currency;
83 :
84 : /**
85 : * Pointer to the keys obtained from the Donau instance.
86 : * This structure holds the cryptographic keys for the Donau instance.
87 : */
88 : struct DONAU_Keys *keys;
89 :
90 : /**
91 : * A handle for an ongoing /keys request to the Donau instance.
92 : * This is NULL when there is no active request.
93 : */
94 : struct DONAU_GetKeysHandle *conn;
95 :
96 : /**
97 : * Scheduler task for retrying a failed /keys request.
98 : * This task will trigger the next attempt to download the Donau keys if the previous request failed or needs to be retried.
99 : */
100 : struct GNUNET_SCHEDULER_Task *retry_task;
101 :
102 : /**
103 : * The earliest time at which the Donau instance can attempt another /keys request.
104 : * This is used to manage the timing between requests and ensure compliance with rate-limiting rules.
105 : */
106 : struct GNUNET_TIME_Absolute first_retry;
107 :
108 : /**
109 : * The delay between the next retry for fetching /keys.
110 : * Used to implement exponential backoff strategies for retries in case of failures.
111 : */
112 : struct GNUNET_TIME_Relative retry_delay;
113 :
114 : /**
115 : * A flag indicating whether this Donau instance is currently rate-limited.
116 : * If true, the instance is temporarily paused from making further requests due to reaching a limit.
117 : */
118 : bool limited;
119 :
120 : /**
121 : * Are we force-retrying a /keys download because some keys
122 : * were missing?
123 : */
124 : bool force_retry;
125 : };
126 :
127 :
128 : /**
129 : * Head of known Donau instances.
130 : */
131 : static struct Donau *d_head;
132 :
133 : /**
134 : * Tail of known Donau instances.
135 : */
136 : static struct Donau *d_tail;
137 :
138 : /**
139 : * Context for the charity force download.
140 : */
141 : struct ForceCharityCtx
142 : {
143 : /**
144 : * Pointer to the next ForceCharityCtx in the doubly linked list.
145 : */
146 : struct ForceCharityCtx *next;
147 :
148 : /**
149 : * Pointer to the previous ForceCharityCtx in the doubly linked list.
150 : */
151 : struct ForceCharityCtx *prev;
152 :
153 : /**
154 : * Serial of the Donau instance in our DB for which we running the force update.
155 : */
156 : uint64_t di_serial;
157 :
158 : /**
159 : * Base URL of the Donau instance for which we are running the force update.
160 : */
161 : char *donau_url;
162 :
163 : /**
164 : * ID of the charity for which we are running the force update.
165 : */
166 : uint64_t charity_id;
167 :
168 : /**
169 : * Handle to the charity update request.
170 : */
171 : struct DONAU_CharityGetHandle *h;
172 :
173 : /**
174 : * Scheduler task for retrying a failed /charity_id request.
175 : * This task will trigger the next attempt to download the charity
176 : * details if the previous request could not be started.
177 : */
178 : struct GNUNET_SCHEDULER_Task *retry_task;
179 :
180 : /**
181 : * The delay before the next retry for fetching /charity_id.
182 : * Used to implement exponential backoff in case of failures.
183 : */
184 : struct GNUNET_TIME_Relative retry_delay;
185 :
186 : /**
187 : * A flag indicating whether this charity inquiry is currently
188 : * rate-limited. If true, the inquiry was postponed because we
189 : * were at the #OPEN_INQUIRY_LIMIT and must be re-driven from
190 : * end_inquiry() once a slot frees up.
191 : */
192 : bool limited;
193 : };
194 :
195 : /**
196 : * Head of the list of charity force updates.
197 : */
198 : static struct ForceCharityCtx *fcc_head;
199 :
200 : /**
201 : * Tail of the list of charity force updates.
202 : */
203 : static struct ForceCharityCtx *fcc_tail;
204 :
205 : /**
206 : * The merchant's configuration.
207 : */
208 : static const struct GNUNET_CONFIGURATION_Handle *cfg;
209 :
210 : /**
211 : * Our database connection.
212 : */
213 : static struct TALER_MERCHANTDB_PostgresContext *pg;
214 :
215 : /**
216 : * Our event handler listening for /keys forced downloads.
217 : */
218 : static struct GNUNET_DB_EventHandler *eh;
219 :
220 : /**
221 : * Our event handler listening for /charity_id forced downloads.
222 : */
223 : static struct GNUNET_DB_EventHandler *eh_charity;
224 :
225 : /**
226 : * Handle to the context for interacting with the Donau services.
227 : */
228 : static struct GNUNET_CURL_Context *ctx;
229 :
230 : /**
231 : * Scheduler context for running the @e ctx.
232 : */
233 : static struct GNUNET_CURL_RescheduleContext *rc;
234 :
235 : /**
236 : * How many active inquiries do we have right now.
237 : */
238 : static unsigned int active_inquiries;
239 :
240 : /**
241 : * Value to return from main(). 0 on success, non-zero on errors.
242 : */
243 : static int global_ret;
244 :
245 : /**
246 : * Should we enable HTTP/2 and HTTP/3 when talking to the Donau?
247 : * Those are not expected to be terribly beneficial for a client with
248 : * stable connections to a few servers, but they could cause stability
249 : * issues with libcurl. Hence we *default* to HTTP/1.1-only, as that
250 : * is the conservative and most tested code path.
251 : */
252 : static int enable_h3;
253 :
254 : /**
255 : * #GNUNET_YES if we are in test mode and should exit when idle.
256 : */
257 : static int test_mode;
258 :
259 : /**
260 : * True if the last DB query was limited by the
261 : * #OPEN_INQUIRY_LIMIT and we thus should check again
262 : * as soon as we are substantially below that limit,
263 : * and not only when we get a DB notification.
264 : */
265 : static bool at_limit;
266 :
267 :
268 : /**
269 : * Function that initiates a /keys download for a Donau instance.
270 : *
271 : * @param cls closure with a `struct Donau *`
272 : */
273 : static void
274 : download_keys (void *cls);
275 :
276 :
277 : /**
278 : * Function that initiates a /charity_id download for a Donau instance.
279 : *
280 : * @param cls closure with a `struct ForceCharityCtx *`
281 : */
282 : static void
283 : download_charity_id (void *cls);
284 :
285 :
286 : /**
287 : * An inquiry finished, check if we need to start more.
288 : */
289 : static void
290 2 : end_inquiry (void)
291 : {
292 2 : GNUNET_assert (active_inquiries > 0);
293 2 : active_inquiries--;
294 2 : if ( (active_inquiries < OPEN_INQUIRY_LIMIT / 2) &&
295 : (at_limit) )
296 : {
297 0 : at_limit = false;
298 0 : for (struct Donau *d = d_head;
299 0 : NULL != d;
300 0 : d = d->next)
301 : {
302 0 : if (! d->limited)
303 0 : continue;
304 0 : d->limited = false;
305 : /* done synchronously so that the active_inquiries
306 : is updated immediately */
307 0 : download_keys (d);
308 0 : if (at_limit)
309 0 : break;
310 : }
311 0 : for (struct ForceCharityCtx *fcc = fcc_head;
312 0 : NULL != fcc;
313 0 : fcc = fcc->next)
314 : {
315 0 : if (at_limit)
316 0 : break;
317 0 : if (! fcc->limited)
318 0 : continue;
319 0 : fcc->limited = false;
320 : /* done synchronously so that the active_inquiries
321 : is updated immediately */
322 0 : download_charity_id (fcc);
323 : }
324 : }
325 2 : if ( (! at_limit) &&
326 2 : (0 == active_inquiries) &&
327 : (test_mode) )
328 : {
329 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
330 : "No more open inquiries and in test mode. Exiting.\n");
331 2 : GNUNET_SCHEDULER_shutdown ();
332 2 : return;
333 : }
334 : }
335 :
336 :
337 : /**
338 : * Update Donau keys in the database.
339 : *
340 : * @param keys Donau keys to persist
341 : * @param first_retry earliest we may retry fetching the keys
342 : * @return transaction status
343 : */
344 : static enum GNUNET_DB_QueryStatus
345 2 : insert_donau_keys_data (const struct DONAU_Keys *keys,
346 : struct GNUNET_TIME_Absolute first_retry)
347 : {
348 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
349 : "Inserting Donau keys into the database %s\n",
350 : keys->donau_url);
351 2 : return TALER_MERCHANTDB_insert_donau_keys (pg,
352 : keys,
353 : first_retry);
354 : }
355 :
356 :
357 : /**
358 : * Store Donau keys in the database and handle retries.
359 : *
360 : * @param keys the keys to store
361 : * @param first_retry earliest time we may retry fetching the keys
362 : * @return true on success
363 : */
364 : static bool
365 2 : store_donau_keys (struct DONAU_Keys *keys,
366 : struct GNUNET_TIME_Absolute first_retry)
367 : {
368 : enum GNUNET_DB_QueryStatus qs;
369 2 : TALER_MERCHANTDB_preflight (pg);
370 2 : for (unsigned int r = 0; r < MAX_RETRIES; r++)
371 : {
372 2 : if (GNUNET_OK !=
373 2 : TALER_MERCHANTDB_start (pg,
374 : "update donau key data"))
375 : {
376 0 : TALER_MERCHANTDB_rollback (pg);
377 0 : GNUNET_break (0);
378 0 : return false;
379 : }
380 :
381 2 : qs = insert_donau_keys_data (keys,
382 : first_retry);
383 2 : if (0 > qs)
384 : {
385 0 : TALER_MERCHANTDB_rollback (pg);
386 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
387 : "Error while inserting Donau keys into the database: status %d",
388 : qs);
389 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
390 0 : continue;
391 0 : GNUNET_break (0);
392 0 : return false;
393 : }
394 :
395 2 : qs = TALER_MERCHANTDB_commit (pg);
396 2 : if (0 > qs)
397 : {
398 0 : TALER_MERCHANTDB_rollback (pg);
399 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
400 : "Failed to commit Donau keys to the database: status %d",
401 : qs);
402 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
403 0 : continue;
404 0 : GNUNET_break (0);
405 0 : return false;
406 : }
407 2 : break;
408 : }
409 2 : if (qs < 0)
410 : {
411 0 : GNUNET_break (0);
412 0 : return false;
413 : }
414 2 : return true;
415 : }
416 :
417 :
418 : /**
419 : * Store Donau charity in the database and handle retries.
420 : *
421 : * @param charity_id the charity ID to store
422 : * @param donau_url the base URL of the Donau instance
423 : * @param charity the charity structure to store
424 : */
425 : static bool
426 0 : store_donau_charity (uint64_t charity_id,
427 : const char *donau_url,
428 : const struct DONAU_Charity *charity)
429 : {
430 : enum GNUNET_DB_QueryStatus qs;
431 :
432 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
433 : "Inserting/updating charity %llu for Donau `%s'\n",
434 : (unsigned long long) charity_id,
435 : donau_url);
436 :
437 0 : TALER_MERCHANTDB_preflight (pg);
438 :
439 0 : for (unsigned int r = 0; r < MAX_RETRIES; r++)
440 : {
441 0 : if (GNUNET_OK !=
442 0 : TALER_MERCHANTDB_start (pg,
443 : "update donau charity data"))
444 : {
445 0 : TALER_MERCHANTDB_rollback (pg);
446 0 : GNUNET_break (0);
447 0 : return false;
448 : }
449 :
450 0 : qs = TALER_MERCHANTDB_update_donau_instance (pg,
451 : donau_url,
452 : charity,
453 : charity_id);
454 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
455 : {
456 0 : TALER_MERCHANTDB_rollback (pg);
457 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
458 : "Error while updating charity into the database: status %d",
459 : qs);
460 0 : continue;
461 : }
462 0 : if (0 >= qs)
463 : {
464 0 : TALER_MERCHANTDB_rollback (pg);
465 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
466 : "Error while updating charity into the database: status %d",
467 : qs);
468 0 : return false;
469 : }
470 :
471 0 : qs = TALER_MERCHANTDB_commit (pg);
472 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
473 : {
474 0 : TALER_MERCHANTDB_rollback (pg);
475 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
476 : "Failed to commit charity data to the database: status %d",
477 : qs);
478 0 : continue;
479 : }
480 0 : if (0 > qs)
481 : {
482 0 : TALER_MERCHANTDB_rollback (pg);
483 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
484 : "Failed to commit charity data to the database: status %d",
485 : qs);
486 0 : return false;
487 : }
488 0 : break;
489 : }
490 0 : if (0 >= qs)
491 : {
492 0 : GNUNET_break (0);
493 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
494 : "Retries exhausted while inserting charity %llu for Donau `%s': last status %d",
495 : (unsigned long long) charity_id,
496 : donau_url,
497 : qs);
498 0 : return false;
499 : }
500 0 : return true;
501 : }
502 :
503 :
504 : /**
505 : * Callback after Donau keys are fetched.
506 : *
507 : * @param cls closure with a `struct Donau *`
508 : * @param kr response data
509 : * @param keys the keys of the Donau instance
510 : */
511 : static void
512 2 : donau_cert_cb (
513 : void *cls,
514 : const struct DONAU_KeysResponse *kr,
515 : struct DONAU_Keys *keys)
516 : {
517 2 : struct Donau *d = cls;
518 : struct GNUNET_TIME_Absolute n;
519 : struct GNUNET_TIME_Absolute first_retry;
520 :
521 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
522 : "Starting donau cert with object \n");
523 :
524 2 : d->conn = NULL;
525 2 : switch (kr->hr.http_status)
526 : {
527 2 : case MHD_HTTP_OK:
528 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
529 : "Got new keys for %s, updating database\n",
530 : d->donau_url);
531 2 : first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
532 2 : if (! store_donau_keys (keys,
533 : first_retry))
534 : {
535 0 : GNUNET_break (0);
536 0 : DONAU_keys_decref (keys);
537 0 : break;
538 : }
539 :
540 2 : DONAU_keys_decref (d->keys);
541 2 : d->keys = keys;
542 : /* Reset back-off */
543 2 : d->retry_delay = DONAU_MAXFREQ;
544 : /* limit retry */
545 2 : d->first_retry = first_retry;
546 :
547 2 : if (0 < keys->num_sign_keys)
548 2 : n = GNUNET_TIME_absolute_max (d->first_retry,
549 2 : keys->sign_keys[0].expire_sign.abs_time);
550 : else
551 0 : n = d->first_retry;
552 2 : if (NULL != d->retry_task)
553 0 : GNUNET_SCHEDULER_cancel (d->retry_task);
554 2 : d->retry_task = GNUNET_SCHEDULER_add_at (n,
555 : &download_keys,
556 : d);
557 2 : end_inquiry ();
558 2 : return;
559 0 : default:
560 0 : GNUNET_break (NULL == keys);
561 0 : break;
562 : }
563 :
564 : d->retry_delay
565 0 : = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
566 0 : n = GNUNET_TIME_absolute_max (
567 : d->first_retry,
568 : GNUNET_TIME_relative_to_absolute (d->retry_delay));
569 :
570 0 : if (NULL != d->retry_task)
571 0 : GNUNET_SCHEDULER_cancel (d->retry_task);
572 : d->retry_task
573 0 : = GNUNET_SCHEDULER_add_at (n,
574 : &download_keys,
575 : d);
576 0 : end_inquiry ();
577 : }
578 :
579 :
580 : /**
581 : * Initiate the download of Donau keys.
582 : *
583 : * @param cls closure with a `struct Donau *`
584 : */
585 : static void
586 2 : download_keys (void *cls)
587 : {
588 2 : struct Donau *d = cls;
589 :
590 2 : d->retry_task = NULL;
591 2 : GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
592 2 : if (OPEN_INQUIRY_LIMIT <= active_inquiries)
593 : {
594 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
595 : "Cannot start more donaukeys inquiries, already at limit\n");
596 0 : d->limited = true;
597 0 : at_limit = true;
598 0 : return;
599 : }
600 : d->retry_delay
601 2 : = GNUNET_TIME_STD_BACKOFF (d->retry_delay);
602 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
603 : "Downloading keys from %s (%s)\n",
604 : d->donau_url,
605 : d->force_retry ? "forced" : "regular");
606 4 : d->conn = DONAU_get_keys (ctx,
607 2 : d->donau_url,
608 : &donau_cert_cb,
609 : d);
610 2 : d->force_retry = false;
611 2 : if (NULL != d->conn)
612 : {
613 2 : active_inquiries++;
614 : }
615 : else
616 : {
617 : struct GNUNET_TIME_Relative n;
618 :
619 0 : n = GNUNET_TIME_relative_max (d->retry_delay,
620 : DONAU_MAXFREQ);
621 :
622 : d->retry_task
623 0 : = GNUNET_SCHEDULER_add_delayed (n,
624 : &download_keys,
625 : d);
626 : }
627 : }
628 :
629 :
630 : /**
631 : * Callback for DONAU_charity_get() that stores the charity
632 : * information in the DB and finishes the inquiry.
633 : *
634 : * @param cls closure with `struct ForceCharityCtx *`
635 : * @param gcr response from DONAU
636 : */
637 : static void
638 0 : donau_charity_cb (void *cls,
639 : const struct DONAU_GetCharityResponse *gcr)
640 : {
641 0 : struct ForceCharityCtx *fcc = cls;
642 0 : fcc->h = NULL;
643 :
644 0 : switch (gcr->hr.http_status)
645 : {
646 0 : case MHD_HTTP_OK:
647 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
648 : "Got charity_id `%llu' details for donau `%s', updating DB\n",
649 : (unsigned long long) fcc->charity_id,
650 : fcc->donau_url);
651 :
652 0 : if (! store_donau_charity (fcc->charity_id,
653 0 : fcc->donau_url,
654 : &gcr->details.ok.charity))
655 : {
656 0 : GNUNET_break (0);
657 : }
658 0 : break;
659 :
660 0 : default:
661 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
662 : "DONAU charity_get for `%s' failed with HTTP %u / ec %u\n",
663 : fcc->donau_url,
664 : gcr->hr.http_status,
665 : gcr->hr.ec);
666 0 : break;
667 : }
668 :
669 0 : end_inquiry ();
670 0 : }
671 :
672 :
673 : /**
674 : * Download the charity_id for a Donau instance.
675 : *
676 : * @param cls closure with a `struct ForceCharityCtx *`
677 : */
678 : static void
679 0 : download_charity_id (void *cls)
680 : {
681 0 : struct ForceCharityCtx *fcc = cls;
682 :
683 0 : fcc->retry_task = NULL;
684 : /* nothing to do if a request is already outstanding */
685 0 : if (NULL != fcc->h)
686 0 : return;
687 :
688 : /* respect global inquiry limit */
689 0 : GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
690 0 : if (OPEN_INQUIRY_LIMIT <= active_inquiries)
691 : {
692 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
693 : "Cannot start more charity inquiries, already at limit\n");
694 : /* remember to re-drive this inquiry from end_inquiry()
695 : once we are substantially below the limit again */
696 0 : fcc->limited = true;
697 0 : at_limit = true;
698 0 : return;
699 : }
700 :
701 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
702 : "Downloading charity `%llu' from `%s'\n",
703 : (unsigned long long) fcc->charity_id,
704 : fcc->donau_url);
705 :
706 0 : fcc->h = DONAU_charity_get (ctx,
707 0 : fcc->donau_url,
708 : fcc->charity_id,
709 : NULL, /* bearer token -- not needed */
710 : &donau_charity_cb,
711 : fcc);
712 :
713 0 : if (NULL != fcc->h)
714 : {
715 0 : fcc->retry_delay = GNUNET_TIME_UNIT_ZERO;
716 0 : active_inquiries++;
717 : }
718 : else
719 : {
720 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
721 : "Failed to initiate DONAU_charity_get() for `%s'\n",
722 : fcc->donau_url);
723 : /* no inquiry was started, so we must not call end_inquiry() here;
724 : retry with exponential backoff instead */
725 : fcc->retry_delay
726 0 : = GNUNET_TIME_STD_BACKOFF (fcc->retry_delay);
727 : fcc->retry_task
728 0 : = GNUNET_SCHEDULER_add_delayed (fcc->retry_delay,
729 : &download_charity_id,
730 : fcc);
731 : }
732 : }
733 :
734 :
735 : /**
736 : * Lookup donau by @a donau_url. Create one
737 : * if it does not exist.
738 : *
739 : * @param donau_url base URL to match against
740 : * @return NULL if not found
741 : */
742 : static struct Donau *
743 0 : lookup_donau (const char *donau_url)
744 : {
745 0 : for (struct Donau *d = d_head;
746 0 : NULL != d;
747 0 : d = d->next)
748 0 : if (0 == strcmp (d->donau_url,
749 : donau_url))
750 0 : return d;
751 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
752 : "Got notification about unknown Donau `%s'\n",
753 : donau_url);
754 0 : return NULL;
755 : }
756 :
757 :
758 : /**
759 : * Lookup a ForceCharityCtx by donau-instance serial.
760 : *
761 : * @param di_serial serial to search for
762 : * @return matching context or NULL
763 : */
764 : static struct ForceCharityCtx *
765 0 : lookup_donau_charity (uint64_t di_serial)
766 : {
767 0 : for (struct ForceCharityCtx *fcc = fcc_head;
768 0 : NULL != fcc;
769 0 : fcc = fcc->next)
770 0 : if (fcc->di_serial == di_serial)
771 0 : return fcc;
772 0 : return NULL;
773 : }
774 :
775 :
776 : /**
777 : * Force immediate (re)loading of /charity_id for an donau.
778 : *
779 : * @param cls NULL
780 : * @param extra base URL of the donau that changed
781 : * @param extra_len number of bytes in @a extra
782 : */
783 : static void
784 0 : force_donau_charity_id (void *cls,
785 : const void *extra,
786 : size_t extra_len)
787 : {
788 : uint64_t di_serial;
789 0 : char *donau_url = NULL;
790 0 : uint64_t charity_id = -1;
791 : enum GNUNET_DB_QueryStatus qs;
792 : struct ForceCharityCtx *fcc;
793 :
794 0 : if ( (sizeof(uint64_t) != extra_len) ||
795 : (NULL == extra) )
796 : {
797 0 : GNUNET_break (0);
798 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
799 : "Incorrect extra for the force_donau_charity_id");
800 0 : return;
801 : }
802 0 : GNUNET_memcpy (&di_serial,
803 : extra,
804 : sizeof(uint64_t));
805 0 : di_serial = GNUNET_ntohll (di_serial);
806 0 : qs = TALER_MERCHANTDB_get_donau_instance_by_serial (pg,
807 : di_serial,
808 : &donau_url,
809 : &charity_id);
810 :
811 0 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
812 : {
813 0 : GNUNET_break (0);
814 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
815 : "force_donau_charity_id: instance serial %llu not found (status %d)\n",
816 : (unsigned long long) di_serial,
817 : qs);
818 0 : return;
819 : }
820 :
821 0 : fcc = lookup_donau_charity (di_serial);
822 0 : if (NULL == fcc)
823 : {
824 0 : fcc = GNUNET_new (struct ForceCharityCtx);
825 0 : fcc->di_serial = di_serial;
826 0 : fcc->donau_url = donau_url; /* take ownership */
827 0 : fcc->charity_id = charity_id;
828 0 : GNUNET_CONTAINER_DLL_insert (fcc_head, fcc_tail, fcc);
829 :
830 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
831 : "Created new ForceCharityCtx for donau `%s' "
832 : "(serial %llu, charity %llu)\n",
833 : donau_url,
834 : (unsigned long long) di_serial,
835 : (unsigned long long) charity_id);
836 : }
837 : else
838 : {
839 0 : GNUNET_free (donau_url);
840 0 : if (NULL != fcc->h)
841 : {
842 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
843 : "Already downloading charity_id for donau `%s'\n",
844 : fcc->donau_url);
845 0 : return;
846 : }
847 0 : if (NULL != fcc->retry_task)
848 : {
849 0 : GNUNET_SCHEDULER_cancel (fcc->retry_task);
850 0 : fcc->retry_task = NULL;
851 : }
852 0 : fcc->charity_id = charity_id;
853 : }
854 0 : download_charity_id (fcc);
855 : }
856 :
857 :
858 : /**
859 : * Force immediate (re)loading of /keys for an donau.
860 : *
861 : * @param cls NULL
862 : * @param extra base URL of the donau that changed
863 : * @param extra_len number of bytes in @a extra
864 : */
865 : static void
866 0 : force_donau_keys (void *cls,
867 : const void *extra,
868 : size_t extra_len)
869 : {
870 0 : const char *url = extra;
871 : struct Donau *d;
872 :
873 0 : if ( (NULL == extra) ||
874 : (0 == extra_len) )
875 : {
876 0 : GNUNET_break (0);
877 0 : return;
878 : }
879 0 : if ('\0' != url[extra_len - 1])
880 : {
881 0 : GNUNET_break (0);
882 0 : return;
883 : }
884 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
885 : "Received keys update notification: reload `%s'\n",
886 : url);
887 :
888 0 : d = lookup_donau (url);
889 0 : if (NULL == d)
890 : {
891 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
892 : "Donau instance `%s' not found. Creating new instance.\n",
893 : url);
894 :
895 0 : d = GNUNET_new (struct Donau);
896 0 : d->donau_url = GNUNET_strdup (url);
897 0 : d->retry_delay = DONAU_MAXFREQ;
898 0 : d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
899 :
900 0 : GNUNET_CONTAINER_DLL_insert (d_head,
901 : d_tail,
902 : d);
903 0 : download_keys (d);
904 : }
905 :
906 0 : if (NULL != d->conn)
907 : {
908 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
909 : "Already downloading %skeys\n",
910 : url);
911 0 : return;
912 : }
913 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
914 : "Will download %skeys in %s\n",
915 : url,
916 : GNUNET_TIME_relative2s (
917 : GNUNET_TIME_absolute_get_remaining (
918 : d->first_retry),
919 : true));
920 0 : if (NULL != d->retry_task)
921 0 : GNUNET_SCHEDULER_cancel (d->retry_task);
922 0 : d->force_retry = true;
923 : d->retry_task
924 0 : = GNUNET_SCHEDULER_add_at (d->first_retry,
925 : &download_keys,
926 : d);
927 : }
928 :
929 :
930 : /**
931 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
932 : *
933 : * @param cls closure (NULL)
934 : */
935 : static void
936 2 : shutdown_task (void *cls)
937 : {
938 : (void) cls;
939 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
940 : "Running shutdown\n");
941 4 : while (NULL != d_head)
942 : {
943 2 : struct Donau *d = d_head;
944 :
945 2 : GNUNET_free (d->donau_url);
946 2 : GNUNET_free (d->currency);
947 2 : if (NULL != d->conn)
948 : {
949 0 : DONAU_get_keys_cancel (d->conn);
950 0 : d->conn = NULL;
951 : }
952 2 : if (NULL != d->keys)
953 : {
954 2 : DONAU_keys_decref (d->keys);
955 2 : d->keys = NULL;
956 : }
957 2 : if (NULL != d->retry_task)
958 : {
959 2 : GNUNET_SCHEDULER_cancel (d->retry_task);
960 2 : d->retry_task = NULL;
961 : }
962 2 : GNUNET_CONTAINER_DLL_remove (d_head,
963 : d_tail,
964 : d);
965 2 : GNUNET_free (d);
966 : }
967 2 : while (NULL != fcc_head)
968 : {
969 0 : struct ForceCharityCtx *fcc = fcc_head;
970 :
971 0 : if (NULL != fcc->h)
972 : {
973 0 : DONAU_charity_get_cancel (fcc->h);
974 0 : fcc->h = NULL;
975 : }
976 0 : if (NULL != fcc->retry_task)
977 : {
978 0 : GNUNET_SCHEDULER_cancel (fcc->retry_task);
979 0 : fcc->retry_task = NULL;
980 : }
981 0 : GNUNET_CONTAINER_DLL_remove (fcc_head,
982 : fcc_tail,
983 : fcc);
984 0 : GNUNET_free (fcc->donau_url);
985 0 : GNUNET_free (fcc);
986 : }
987 2 : if (NULL != eh)
988 : {
989 2 : TALER_MERCHANTDB_event_listen_cancel (eh);
990 2 : eh = NULL;
991 : }
992 2 : if (NULL != eh_charity)
993 : {
994 2 : TALER_MERCHANTDB_event_listen_cancel (eh_charity);
995 2 : eh_charity = NULL;
996 : }
997 2 : if (NULL != pg)
998 : {
999 2 : TALER_MERCHANTDB_disconnect (pg);
1000 2 : pg = NULL;
1001 : }
1002 2 : cfg = NULL;
1003 2 : if (NULL != ctx)
1004 : {
1005 2 : GNUNET_CURL_fini (ctx);
1006 2 : ctx = NULL;
1007 : }
1008 2 : if (NULL != rc)
1009 : {
1010 2 : GNUNET_CURL_gnunet_rc_destroy (rc);
1011 2 : rc = NULL;
1012 : }
1013 2 : }
1014 :
1015 :
1016 : /**
1017 : * Callback function typically used by `iterate_donau_instances` to handle
1018 : * the details of each Donau instance retrieved from the database.
1019 : *
1020 : * @param cls Closure to pass additional context or data to the callback function.
1021 : * @param donau_instance_serial Serial number of the Donau instance in the merchant database.
1022 : * @param donau_url The URL of the Donau instance.
1023 : * @param charity_name The name of the charity associated with the Donau instance.
1024 : * @param charity_pub_key Pointer to the charity's public key used for cryptographic operations.
1025 : * @param charity_id The unique identifier for the charity within the Donau instance.
1026 : * @param charity_max_per_year Maximum allowed donations to the charity for the current year.
1027 : * @param charity_receipts_to_date Total donations received by the charity so far in the current year.
1028 : * @param current_year The year for which the donation data is being tracked.
1029 : * @param donau_keys_json JSON object containing additional key-related information for the Donau instance.
1030 : */
1031 : static void
1032 2 : accept_donau (
1033 : void *cls,
1034 : uint64_t donau_instance_serial,
1035 : const char *donau_url,
1036 : const char *charity_name,
1037 : const struct DONAU_CharityPublicKeyP *charity_pub_key,
1038 : uint64_t charity_id,
1039 : const struct TALER_Amount *charity_max_per_year,
1040 : const struct TALER_Amount *charity_receipts_to_date,
1041 : int64_t current_year,
1042 : const json_t *donau_keys_json
1043 : )
1044 : {
1045 : struct Donau *d;
1046 :
1047 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1048 : "Donau instance `%s' not found. Creating new instance.\n",
1049 : donau_url);
1050 2 : d = GNUNET_new (struct Donau);
1051 2 : d->donau_url = GNUNET_strdup (donau_url);
1052 2 : d->retry_delay = DONAU_MAXFREQ;
1053 2 : d->first_retry = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_ZERO);
1054 2 : GNUNET_CONTAINER_DLL_insert (d_head,
1055 : d_tail,
1056 : d);
1057 2 : if (NULL == donau_keys_json)
1058 : {
1059 2 : download_keys (d);
1060 2 : return;
1061 : }
1062 0 : d->keys = DONAU_keys_from_json (donau_keys_json);
1063 0 : if (NULL == d->keys)
1064 : {
1065 0 : GNUNET_break (0);
1066 0 : download_keys (d);
1067 0 : return;
1068 : }
1069 0 : d->retry_delay = DONAU_MAXFREQ;
1070 0 : d->first_retry = GNUNET_TIME_relative_to_absolute (DONAU_MAXFREQ);
1071 :
1072 : {
1073 : struct GNUNET_TIME_Absolute n;
1074 :
1075 0 : if (0 < d->keys->num_sign_keys)
1076 0 : n = GNUNET_TIME_absolute_min (
1077 : d->first_retry,
1078 0 : d->keys->sign_keys[0].expire_sign.abs_time);
1079 : else
1080 0 : n = d->first_retry;
1081 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1082 : "Will download %skeys in %s\n",
1083 : donau_url,
1084 : GNUNET_TIME_relative2s (
1085 : GNUNET_TIME_absolute_get_remaining (n),
1086 : true));
1087 0 : d->retry_task = GNUNET_SCHEDULER_add_at (n,
1088 : &download_keys,
1089 : d);
1090 : }
1091 : }
1092 :
1093 :
1094 : /**
1095 : * First task.
1096 : *
1097 : * @param cls closure, NULL
1098 : * @param args remaining command-line arguments
1099 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
1100 : * @param c configuration
1101 : */
1102 : static void
1103 2 : run (void *cls,
1104 : char *const *args,
1105 : const char *cfgfile,
1106 : const struct GNUNET_CONFIGURATION_Handle *c)
1107 : {
1108 : (void) args;
1109 : (void) cfgfile;
1110 :
1111 2 : cfg = c;
1112 2 : DONAU_setup (enable_h3
1113 2 : ? DONAU_GO_ENABLE_HTTP3
1114 : : DONAU_GO_FORCE_HTTP1_1);
1115 2 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
1116 : NULL);
1117 2 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
1118 : &rc);
1119 2 : if (NULL == ctx)
1120 : {
1121 0 : GNUNET_break (0);
1122 0 : GNUNET_SCHEDULER_shutdown ();
1123 0 : global_ret = EXIT_FAILURE;
1124 0 : return;
1125 : }
1126 2 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
1127 2 : if (NULL ==
1128 2 : (pg = TALER_MERCHANTDB_connect (cfg)) )
1129 : {
1130 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
1131 : "Failed to initialize DB subsystem\n");
1132 0 : GNUNET_SCHEDULER_shutdown ();
1133 0 : global_ret = EXIT_FAILURE;
1134 0 : return;
1135 : }
1136 : {
1137 2 : struct GNUNET_DB_EventHeaderP es = {
1138 2 : .size = htons (sizeof(es)),
1139 2 : .type = htons (TALER_DBEVENT_MERCHANT_DONAU_KEYS)
1140 : };
1141 :
1142 4 : eh = TALER_MERCHANTDB_event_listen (pg,
1143 : &es,
1144 2 : GNUNET_TIME_UNIT_FOREVER_REL,
1145 : &force_donau_keys,
1146 : NULL);
1147 : }
1148 : {
1149 2 : struct GNUNET_DB_EventHeaderP es = {
1150 2 : .size = htons (sizeof(es)),
1151 2 : .type = htons (TALER_DBEVENT_MERCHANT_DONAU_CHARITY_ID)
1152 : };
1153 :
1154 4 : eh_charity = TALER_MERCHANTDB_event_listen (
1155 : pg,
1156 : &es,
1157 2 : GNUNET_TIME_UNIT_FOREVER_REL,
1158 : &force_donau_charity_id,
1159 : NULL);
1160 : }
1161 :
1162 : {
1163 : enum GNUNET_DB_QueryStatus qs;
1164 :
1165 2 : qs = TALER_MERCHANTDB_iterate_all_donau_instances (pg,
1166 : &accept_donau,
1167 : NULL);
1168 2 : if (qs < 0)
1169 : {
1170 0 : GNUNET_break (0);
1171 0 : GNUNET_SCHEDULER_shutdown ();
1172 0 : return;
1173 : }
1174 : }
1175 2 : if ( (0 == active_inquiries) &&
1176 : (test_mode) )
1177 : {
1178 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1179 : "No donau keys inquiries to start, exiting.\n");
1180 0 : GNUNET_SCHEDULER_shutdown ();
1181 0 : return;
1182 : }
1183 : }
1184 :
1185 :
1186 : /**
1187 : * The main function of taler-merchant-donaukeyupdate
1188 : *
1189 : * @param argc number of arguments from the command line
1190 : * @param argv command line arguments
1191 : * @return 0 ok, 1 on error
1192 : */
1193 : int
1194 2 : main (int argc,
1195 : char *const *argv)
1196 : {
1197 2 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1198 2 : GNUNET_GETOPT_option_flag ('3',
1199 : "http3",
1200 : "enable support for HTTP/2 and HTTP/3",
1201 : &enable_h3),
1202 2 : GNUNET_GETOPT_option_timetravel ('T',
1203 : "timetravel"),
1204 2 : GNUNET_GETOPT_option_flag ('t',
1205 : "test",
1206 : "run in test mode and exit when idle",
1207 : &test_mode),
1208 2 : GNUNET_GETOPT_option_version (VERSION),
1209 : GNUNET_GETOPT_OPTION_END
1210 : };
1211 : enum GNUNET_GenericReturnValue ret;
1212 :
1213 2 : ret = GNUNET_PROGRAM_run (
1214 : TALER_MERCHANT_project_data (),
1215 : argc, argv,
1216 : "taler-merchant-donaukeyupdate",
1217 : gettext_noop (
1218 : "background process that ensures our key and configuration data on Donau is up-to-date"),
1219 : options,
1220 : &run, NULL);
1221 2 : if (GNUNET_SYSERR == ret)
1222 0 : return EXIT_INVALIDARGUMENT;
1223 2 : if (GNUNET_NO == ret)
1224 0 : return EXIT_SUCCESS;
1225 2 : return global_ret;
1226 : }
1227 :
1228 :
1229 : /* end of taler-merchant-donaukeyupdate.c */
|