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 44 : end_inquiry (void)
195 : {
196 44 : GNUNET_assert (active_inquiries > 0);
197 44 : active_inquiries--;
198 44 : 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 44 : if ( (! at_limit) &&
217 44 : (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 26 : add_restriction (json_t *restrictions,
237 : const struct TALER_EXCHANGE_AccountRestriction *r)
238 : {
239 : json_t *jr;
240 :
241 26 : jr = NULL;
242 26 : 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 26 : break;
253 26 : case TALER_EXCHANGE_AR_REGEX:
254 26 : 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 26 : break;
269 : }
270 26 : if (NULL == jr)
271 : {
272 0 : GNUNET_break_op (0);
273 0 : return GNUNET_SYSERR;
274 : }
275 26 : GNUNET_assert (0 ==
276 : json_array_append_new (restrictions,
277 : jr));
278 26 : 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 23 : 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 138 : for (unsigned int i = 0; i<keys->num_sign_keys; i++)
301 : {
302 115 : const struct TALER_EXCHANGE_SigningPublicKey *sign_key
303 115 : = &keys->sign_keys[i];
304 :
305 115 : qs = db_plugin->insert_exchange_signkey (
306 115 : 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 115 : if (0 > qs)
315 : {
316 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
317 0 : return qs;
318 : }
319 : }
320 :
321 23 : qs = db_plugin->insert_exchange_keys (db_plugin->cls,
322 : keys,
323 : first_retry);
324 23 : if (0 > qs)
325 : {
326 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
327 0 : return qs;
328 : }
329 :
330 23 : qs = db_plugin->delete_exchange_accounts (db_plugin->cls,
331 : &keys->master_pub);
332 23 : if (0 > qs)
333 : {
334 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
335 0 : return qs;
336 : }
337 :
338 46 : for (unsigned int i = 0; i<keys->accounts_len; i++)
339 : {
340 23 : const struct TALER_EXCHANGE_WireAccount *account
341 23 : = &keys->accounts[i];
342 : json_t *debit_restrictions;
343 : json_t *credit_restrictions;
344 :
345 23 : debit_restrictions = json_array ();
346 23 : GNUNET_assert (NULL != debit_restrictions);
347 23 : credit_restrictions = json_array ();
348 23 : GNUNET_assert (NULL != credit_restrictions);
349 36 : for (unsigned int j = 0; j<account->debit_restrictions_length; j++)
350 : {
351 13 : if (GNUNET_OK !=
352 13 : add_restriction (debit_restrictions,
353 13 : &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 36 : for (unsigned int j = 0; j<account->credit_restrictions_length; j++)
363 : {
364 13 : if (GNUNET_OK !=
365 13 : add_restriction (credit_restrictions,
366 13 : &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 23 : qs = db_plugin->insert_exchange_account (
376 23 : db_plugin->cls,
377 : &keys->master_pub,
378 : account->fpayto_uri,
379 23 : account->conversion_url,
380 : debit_restrictions,
381 : credit_restrictions,
382 : &account->master_sig);
383 23 : json_decref (debit_restrictions);
384 23 : json_decref (credit_restrictions);
385 23 : 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 46 : for (unsigned int i = 0; i<keys->fees_len; i++)
393 : {
394 23 : const struct TALER_EXCHANGE_WireFeesByMethod *fbm
395 23 : = &keys->fees[i];
396 23 : const char *wire_method = fbm->method;
397 23 : const struct TALER_EXCHANGE_WireAggregateFees *fees
398 : = fbm->fees_head;
399 :
400 59 : while (NULL != fees)
401 : {
402 : struct GNUNET_HashCode h_wire_method;
403 :
404 36 : GNUNET_CRYPTO_hash (wire_method,
405 36 : strlen (wire_method) + 1,
406 : &h_wire_method);
407 36 : qs = db_plugin->store_wire_fee_by_exchange (
408 36 : 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 36 : if (0 > qs)
416 : {
417 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
418 0 : return qs;
419 : }
420 36 : fees = fees->next;
421 : } /* all fees for this method */
422 : } /* for all methods (i) */
423 :
424 : {
425 23 : struct GNUNET_DB_EventHeaderP es = {
426 23 : .size = ntohs (sizeof (es)),
427 23 : .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_KEYS)
428 : };
429 :
430 23 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
431 : "Informing other processes about keys change for %s\n",
432 : keys->exchange_url);
433 23 : db_plugin->event_notify (db_plugin->cls,
434 : &es,
435 23 : keys->exchange_url,
436 23 : strlen (keys->exchange_url) + 1);
437 : }
438 23 : return qs;
439 : }
440 :
441 :
442 : /**
443 : * Run database transaction to store the @a keys in
444 : * the merchant database (and notify other processes
445 : * that may care about them).
446 : *
447 : * @param keys the keys to store
448 : * @param first_retry earliest we may retry fetching the keys
449 : * @return true on success
450 : */
451 : static bool
452 23 : store_keys (struct TALER_EXCHANGE_Keys *keys,
453 : struct GNUNET_TIME_Absolute first_retry)
454 : {
455 : enum GNUNET_DB_QueryStatus qs;
456 :
457 23 : db_plugin->preflight (db_plugin->cls);
458 23 : for (unsigned int r = 0; r<MAX_RETRIES; r++)
459 : {
460 23 : if (GNUNET_OK !=
461 23 : db_plugin->start (db_plugin->cls,
462 : "update exchange key data"))
463 : {
464 0 : db_plugin->rollback (db_plugin->cls);
465 0 : GNUNET_break (0);
466 0 : return false;
467 : }
468 :
469 23 : qs = insert_keys_data (keys,
470 : first_retry);
471 23 : if (0 > qs)
472 : {
473 0 : db_plugin->rollback (db_plugin->cls);
474 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
475 0 : continue;
476 0 : GNUNET_break (0);
477 0 : return false;
478 : }
479 :
480 23 : qs = db_plugin->commit (db_plugin->cls);
481 23 : if (0 > qs)
482 : {
483 0 : db_plugin->rollback (db_plugin->cls);
484 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
485 0 : continue;
486 0 : GNUNET_break (0);
487 0 : return false;
488 : }
489 23 : break;
490 : } /* end of retry loop */
491 23 : if (qs < 0)
492 : {
493 0 : GNUNET_break (0);
494 0 : return false;
495 : }
496 23 : return true;
497 : }
498 :
499 :
500 : /**
501 : * Function called with information about who is auditing
502 : * a particular exchange and what keys the exchange is using.
503 : *
504 : * @param cls closure with a `struct Exchange *`
505 : * @param kr response data
506 : * @param[in] keys the keys of the exchange
507 : */
508 : static void
509 44 : cert_cb (
510 : void *cls,
511 : const struct TALER_EXCHANGE_KeysResponse *kr,
512 : struct TALER_EXCHANGE_Keys *keys)
513 : {
514 44 : struct Exchange *e = cls;
515 : struct GNUNET_TIME_Absolute n;
516 : struct GNUNET_TIME_Absolute first_retry;
517 :
518 44 : e->conn = NULL;
519 44 : switch (kr->hr.http_status)
520 : {
521 23 : case MHD_HTTP_OK:
522 23 : TALER_EXCHANGE_keys_decref (e->keys);
523 23 : e->keys = NULL;
524 23 : if (0 != strcmp (e->currency,
525 23 : keys->currency))
526 : {
527 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
528 : "/keys response from `%s' is for currency `%s', but we expected `%s'. Ignoring response.\n",
529 : e->exchange_url,
530 : keys->currency,
531 : e->currency);
532 0 : TALER_EXCHANGE_keys_decref (keys);
533 0 : break;
534 : }
535 23 : if (0 != GNUNET_memcmp (&keys->master_pub,
536 : &e->master_pub))
537 : {
538 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
539 : "Master public key in %skeys response does not match. Ignoring response.\n",
540 : e->exchange_url);
541 0 : TALER_EXCHANGE_keys_decref (keys);
542 0 : break;
543 : }
544 23 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
545 : "Got new keys for %s, updating database\n",
546 : e->exchange_url);
547 23 : first_retry = GNUNET_TIME_relative_to_absolute (
548 : EXCHANGE_MAXFREQ);
549 23 : if (! store_keys (keys,
550 : first_retry))
551 : {
552 0 : GNUNET_break (0);
553 0 : TALER_EXCHANGE_keys_decref (keys);
554 0 : break;
555 : }
556 23 : e->keys = keys;
557 : /* Reset back-off */
558 23 : e->retry_delay = EXCHANGE_MAXFREQ;
559 : /* limit retry */
560 23 : e->first_retry = first_retry;
561 : /* Limit by expiration */
562 23 : n = GNUNET_TIME_absolute_max (e->first_retry,
563 : keys->key_data_expiration.abs_time);
564 23 : if (NULL != e->retry_task)
565 0 : GNUNET_SCHEDULER_cancel (e->retry_task);
566 23 : e->retry_task = GNUNET_SCHEDULER_add_at (n,
567 : &download_keys,
568 : e);
569 23 : end_inquiry ();
570 23 : return;
571 21 : default:
572 21 : GNUNET_break (NULL == keys);
573 21 : break;
574 : }
575 : /* Try again (soon-ish) */
576 : e->retry_delay
577 21 : = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
578 21 : n = GNUNET_TIME_absolute_max (
579 : e->first_retry,
580 : GNUNET_TIME_relative_to_absolute (e->retry_delay));
581 21 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
582 : "Will download %skeys in %s\n",
583 : e->exchange_url,
584 : GNUNET_TIME_relative2s (
585 : GNUNET_TIME_absolute_get_remaining (n),
586 : true));
587 21 : if (NULL != e->retry_task)
588 0 : GNUNET_SCHEDULER_cancel (e->retry_task);
589 : e->retry_task
590 21 : = GNUNET_SCHEDULER_add_at (n,
591 : &download_keys,
592 : e);
593 21 : end_inquiry ();
594 : }
595 :
596 :
597 : static void
598 44 : download_keys (void *cls)
599 : {
600 44 : struct Exchange *e = cls;
601 :
602 44 : e->retry_task = NULL;
603 44 : GNUNET_break (OPEN_INQUIRY_LIMIT >= active_inquiries);
604 44 : if (OPEN_INQUIRY_LIMIT <= active_inquiries)
605 : {
606 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
607 : "Cannot run job: at limit\n");
608 0 : e->limited = true;
609 0 : at_limit = true;
610 0 : return;
611 : }
612 : e->retry_delay
613 44 : = GNUNET_TIME_STD_BACKOFF (e->retry_delay);
614 44 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
615 : "Downloading keys from %s (%s)\n",
616 : e->exchange_url,
617 : e->force_retry ? "forced" : "regular");
618 44 : e->conn = TALER_EXCHANGE_get_keys (ctx,
619 44 : e->exchange_url,
620 44 : e->force_retry
621 : ? NULL
622 : : e->keys,
623 : &cert_cb,
624 : e);
625 44 : e->force_retry = false;
626 44 : if (NULL != e->conn)
627 : {
628 44 : active_inquiries++;
629 : }
630 : else
631 : {
632 : struct GNUNET_TIME_Relative n;
633 :
634 0 : n = GNUNET_TIME_relative_max (e->retry_delay,
635 : EXCHANGE_MAXFREQ);
636 : e->retry_task
637 0 : = GNUNET_SCHEDULER_add_delayed (n,
638 : &download_keys,
639 : e);
640 : }
641 : }
642 :
643 :
644 : /**
645 : * Lookup exchange by @a exchange_url. Create one
646 : * if it does not exist.
647 : *
648 : * @param exchange_url base URL to match against
649 : * @return NULL if not found
650 : */
651 : static struct Exchange *
652 31 : lookup_exchange (const char *exchange_url)
653 : {
654 31 : for (struct Exchange *e = e_head;
655 47 : NULL != e;
656 16 : e = e->next)
657 47 : if (0 == strcmp (e->exchange_url,
658 : exchange_url))
659 31 : return e;
660 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
661 : "Got notification about unknown exchange `%s'\n",
662 : exchange_url);
663 0 : return NULL;
664 : }
665 :
666 :
667 : /**
668 : * Force immediate (re)loading of /keys for an exchange.
669 : *
670 : * @param cls NULL
671 : * @param extra base URL of the exchange that changed
672 : * @param extra_len number of bytes in @a extra
673 : */
674 : static void
675 31 : force_exchange_keys (void *cls,
676 : const void *extra,
677 : size_t extra_len)
678 : {
679 31 : const char *url = extra;
680 : struct Exchange *e;
681 :
682 31 : if ( (NULL == extra) ||
683 : (0 == extra_len) )
684 : {
685 0 : GNUNET_break (0);
686 0 : return;
687 : }
688 31 : if ('\0' != url[extra_len - 1])
689 : {
690 0 : GNUNET_break (0);
691 0 : return;
692 : }
693 31 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
694 : "Received keys change notification: reload `%s'\n",
695 : url);
696 31 : e = lookup_exchange (url);
697 31 : if (NULL == e)
698 : {
699 0 : GNUNET_break (0);
700 0 : return;
701 : }
702 31 : if (NULL != e->conn)
703 : {
704 11 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
705 : "Already downloading %skeys\n",
706 : url);
707 11 : return;
708 : }
709 20 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
710 : "Will download %skeys in %s\n",
711 : url,
712 : GNUNET_TIME_relative2s (
713 : GNUNET_TIME_absolute_get_remaining (
714 : e->first_retry),
715 : true));
716 20 : if (NULL != e->retry_task)
717 20 : GNUNET_SCHEDULER_cancel (e->retry_task);
718 20 : e->force_retry = true;
719 : e->retry_task
720 20 : = GNUNET_SCHEDULER_add_at (e->first_retry,
721 : &download_keys,
722 : e);
723 : }
724 :
725 :
726 : /**
727 : * Function called on each configuration section. Finds sections
728 : * about exchanges, parses the entries.
729 : *
730 : * @param cls NULL
731 : * @param section name of the section
732 : */
733 : static void
734 545 : accept_exchanges (void *cls,
735 : const char *section)
736 : {
737 : char *url;
738 : char *mks;
739 : char *currency;
740 :
741 : (void) cls;
742 545 : if (0 !=
743 545 : strncasecmp (section,
744 : "merchant-exchange-",
745 : strlen ("merchant-exchange-")))
746 517 : return;
747 42 : if (GNUNET_YES ==
748 42 : GNUNET_CONFIGURATION_get_value_yesno (cfg,
749 : section,
750 : "DISABLED"))
751 14 : return;
752 28 : if (GNUNET_OK !=
753 28 : GNUNET_CONFIGURATION_get_value_string (cfg,
754 : section,
755 : "EXCHANGE_BASE_URL",
756 : &url))
757 : {
758 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
759 : section,
760 : "EXCHANGE_BASE_URL");
761 0 : global_ret = EXIT_NOTCONFIGURED;
762 0 : GNUNET_SCHEDULER_shutdown ();
763 0 : return;
764 : }
765 28 : for (struct Exchange *e = e_head;
766 42 : NULL != e;
767 14 : e = e->next)
768 : {
769 14 : if (0 == strcmp (url,
770 14 : e->exchange_url))
771 : {
772 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
773 : "Exchange `%s' configured in multiple sections, maybe set DISABLED=YES in section `%s'?\n",
774 : url,
775 : section);
776 0 : GNUNET_free (url);
777 0 : global_ret = EXIT_NOTCONFIGURED;
778 0 : GNUNET_SCHEDULER_shutdown ();
779 0 : return;
780 : }
781 : }
782 28 : if (GNUNET_OK !=
783 28 : GNUNET_CONFIGURATION_get_value_string (cfg,
784 : section,
785 : "CURRENCY",
786 : ¤cy))
787 : {
788 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
789 : section,
790 : "CURRENCY");
791 0 : GNUNET_free (url);
792 0 : global_ret = EXIT_NOTCONFIGURED;
793 0 : GNUNET_SCHEDULER_shutdown ();
794 0 : return;
795 : }
796 28 : if (GNUNET_OK !=
797 28 : GNUNET_CONFIGURATION_get_value_string (cfg,
798 : section,
799 : "MASTER_KEY",
800 : &mks))
801 : {
802 0 : GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
803 : section,
804 : "MASTER_KEY");
805 0 : global_ret = EXIT_NOTCONFIGURED;
806 0 : GNUNET_SCHEDULER_shutdown ();
807 0 : GNUNET_free (currency);
808 0 : GNUNET_free (url);
809 0 : return;
810 : }
811 :
812 : {
813 : struct Exchange *e;
814 :
815 28 : e = GNUNET_new (struct Exchange);
816 28 : e->exchange_url = url;
817 28 : e->currency = currency;
818 28 : GNUNET_CONTAINER_DLL_insert (e_head,
819 : e_tail,
820 : e);
821 28 : if (GNUNET_OK !=
822 28 : GNUNET_CRYPTO_eddsa_public_key_from_string (
823 : mks,
824 : strlen (mks),
825 : &e->master_pub.eddsa_pub))
826 : {
827 0 : GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
828 : section,
829 : "MASTER_KEY",
830 : "malformed EdDSA key");
831 0 : global_ret = EXIT_NOTCONFIGURED;
832 0 : GNUNET_SCHEDULER_shutdown ();
833 0 : GNUNET_free (mks);
834 0 : return;
835 : }
836 28 : GNUNET_free (mks);
837 :
838 : {
839 : enum GNUNET_DB_QueryStatus qs;
840 28 : struct TALER_EXCHANGE_Keys *keys = NULL;
841 :
842 28 : qs = db_plugin->select_exchange_keys (db_plugin->cls,
843 : url,
844 : &e->first_retry,
845 : &keys);
846 28 : if (qs < 0)
847 : {
848 0 : GNUNET_break (0);
849 0 : global_ret = EXIT_FAILURE;
850 0 : GNUNET_SCHEDULER_shutdown ();
851 0 : return;
852 : }
853 28 : if ( (NULL != keys) &&
854 1 : (0 != strcmp (keys->currency,
855 1 : e->currency)) )
856 : {
857 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
858 : "/keys cached in our database were for currency `%s', but we expected `%s'. Fetching /keys again.\n",
859 : keys->currency,
860 : e->currency);
861 0 : TALER_EXCHANGE_keys_decref (keys);
862 0 : keys = NULL;
863 : }
864 28 : if ( (NULL != keys) &&
865 1 : (0 != GNUNET_memcmp (&e->master_pub,
866 : &keys->master_pub)) )
867 : {
868 : /* master pub differs => fetch keys again */
869 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
870 : "Master public key of exchange `%s' differs from our configuration. Fetching /keys again.\n",
871 : e->exchange_url);
872 0 : TALER_EXCHANGE_keys_decref (keys);
873 0 : keys = NULL;
874 : }
875 28 : e->keys = keys;
876 28 : if (NULL == keys)
877 : {
878 : /* done synchronously so that the active_inquiries
879 : is updated immediately */
880 :
881 27 : download_keys (e);
882 : }
883 : else
884 : {
885 : e->retry_task
886 1 : = GNUNET_SCHEDULER_add_at (keys->key_data_expiration.abs_time,
887 : &download_keys,
888 : e);
889 : }
890 : }
891 28 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
892 : "Exchange `%s' setup\n",
893 : e->exchange_url);
894 : }
895 : }
896 :
897 :
898 : /**
899 : * We're being aborted with CTRL-C (or SIGTERM). Shut down.
900 : *
901 : * @param cls closure (NULL)
902 : */
903 : static void
904 14 : shutdown_task (void *cls)
905 : {
906 : (void) cls;
907 14 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
908 : "Running shutdown\n");
909 42 : while (NULL != e_head)
910 : {
911 28 : struct Exchange *e = e_head;
912 :
913 28 : GNUNET_free (e->exchange_url);
914 28 : GNUNET_free (e->currency);
915 28 : if (NULL != e->conn)
916 : {
917 0 : TALER_EXCHANGE_get_keys_cancel (e->conn);
918 0 : e->conn = NULL;
919 : }
920 28 : if (NULL != e->keys)
921 : {
922 24 : TALER_EXCHANGE_keys_decref (e->keys);
923 24 : e->keys = NULL;
924 : }
925 28 : if (NULL != e->retry_task)
926 : {
927 28 : GNUNET_SCHEDULER_cancel (e->retry_task);
928 28 : e->retry_task = NULL;
929 : }
930 28 : GNUNET_CONTAINER_DLL_remove (e_head,
931 : e_tail,
932 : e);
933 28 : GNUNET_free (e);
934 : }
935 14 : if (NULL != eh)
936 : {
937 14 : db_plugin->event_listen_cancel (eh);
938 14 : eh = NULL;
939 : }
940 14 : TALER_MERCHANTDB_plugin_unload (db_plugin);
941 14 : db_plugin = NULL;
942 14 : cfg = NULL;
943 14 : if (NULL != ctx)
944 : {
945 14 : GNUNET_CURL_fini (ctx);
946 14 : ctx = NULL;
947 : }
948 14 : if (NULL != rc)
949 : {
950 14 : GNUNET_CURL_gnunet_rc_destroy (rc);
951 14 : rc = NULL;
952 : }
953 14 : }
954 :
955 :
956 : /**
957 : * First task.
958 : *
959 : * @param cls closure, NULL
960 : * @param args remaining command-line arguments
961 : * @param cfgfile name of the configuration file used (for saving, can be NULL!)
962 : * @param c configuration
963 : */
964 : static void
965 14 : run (void *cls,
966 : char *const *args,
967 : const char *cfgfile,
968 : const struct GNUNET_CONFIGURATION_Handle *c)
969 : {
970 : (void) args;
971 : (void) cfgfile;
972 :
973 14 : cfg = c;
974 14 : GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
975 : NULL);
976 14 : ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
977 : &rc);
978 14 : rc = GNUNET_CURL_gnunet_rc_create (ctx);
979 14 : if (NULL == ctx)
980 : {
981 0 : GNUNET_break (0);
982 0 : GNUNET_SCHEDULER_shutdown ();
983 0 : global_ret = EXIT_FAILURE;
984 0 : return;
985 : }
986 14 : if (NULL ==
987 14 : (db_plugin = TALER_MERCHANTDB_plugin_load (cfg)))
988 : {
989 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
990 : "Failed to initialize DB subsystem\n");
991 0 : GNUNET_SCHEDULER_shutdown ();
992 0 : global_ret = EXIT_NOTCONFIGURED;
993 0 : return;
994 : }
995 14 : if (GNUNET_OK !=
996 14 : db_plugin->connect (db_plugin->cls))
997 : {
998 0 : GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
999 : "Failed to connect to database. Consider running taler-merchant-dbinit!\n");
1000 0 : GNUNET_SCHEDULER_shutdown ();
1001 0 : global_ret = EXIT_FAILURE;
1002 0 : return;
1003 : }
1004 : {
1005 14 : struct GNUNET_DB_EventHeaderP es = {
1006 14 : .size = ntohs (sizeof (es)),
1007 14 : .type = ntohs (TALER_DBEVENT_MERCHANT_EXCHANGE_FORCE_KEYS)
1008 : };
1009 :
1010 28 : eh = db_plugin->event_listen (db_plugin->cls,
1011 : &es,
1012 14 : GNUNET_TIME_UNIT_FOREVER_REL,
1013 : &force_exchange_keys,
1014 : NULL);
1015 : }
1016 14 : GNUNET_CONFIGURATION_iterate_sections (cfg,
1017 : &accept_exchanges,
1018 : NULL);
1019 14 : if ( (0 == active_inquiries) &&
1020 : (test_mode) )
1021 : {
1022 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
1023 : "No more open inquiries and in test mode. Existing.\n");
1024 0 : GNUNET_SCHEDULER_shutdown ();
1025 0 : return;
1026 : }
1027 : }
1028 :
1029 :
1030 : /**
1031 : * The main function of taler-merchant-exchangekeyupdate
1032 : *
1033 : * @param argc number of arguments from the command line
1034 : * @param argv command line arguments
1035 : * @return 0 ok, 1 on error
1036 : */
1037 : int
1038 14 : main (int argc,
1039 : char *const *argv)
1040 : {
1041 14 : struct GNUNET_GETOPT_CommandLineOption options[] = {
1042 14 : GNUNET_GETOPT_option_timetravel ('T',
1043 : "timetravel"),
1044 14 : GNUNET_GETOPT_option_flag ('t',
1045 : "test",
1046 : "run in test mode and exit when idle",
1047 : &test_mode),
1048 14 : GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION),
1049 : GNUNET_GETOPT_OPTION_END
1050 : };
1051 : enum GNUNET_GenericReturnValue ret;
1052 :
1053 14 : ret = GNUNET_PROGRAM_run (
1054 : TALER_MERCHANT_project_data (),
1055 : argc, argv,
1056 : "taler-merchant-exchangekeyupdate",
1057 : gettext_noop (
1058 : "background process that ensures our key and configuration data on exchanges is up-to-date"),
1059 : options,
1060 : &run, NULL);
1061 14 : if (GNUNET_SYSERR == ret)
1062 0 : return EXIT_INVALIDARGUMENT;
1063 14 : if (GNUNET_NO == ret)
1064 0 : return EXIT_SUCCESS;
1065 14 : return global_ret;
1066 : }
1067 :
1068 :
1069 : /* end of taler-merchant-exchangekeyupdate.c */
|