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