Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2015-2022 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-exchange-httpd_wire.c
18 : * @brief Handle /wire requests
19 : * @author Christian Grothoff
20 : */
21 : #include "platform.h"
22 : #include <gnunet/gnunet_json_lib.h>
23 : #include "taler_dbevents.h"
24 : #include "taler-exchange-httpd_responses.h"
25 : #include "taler-exchange-httpd_keys.h"
26 : #include "taler-exchange-httpd_wire.h"
27 : #include "taler_json_lib.h"
28 : #include "taler_mhd_lib.h"
29 : #include <jansson.h>
30 :
31 : /**
32 : * Information we track about wire fees.
33 : */
34 : struct WireFeeSet
35 : {
36 :
37 : /**
38 : * Kept in a DLL.
39 : */
40 : struct WireFeeSet *next;
41 :
42 : /**
43 : * Kept in a DLL.
44 : */
45 : struct WireFeeSet *prev;
46 :
47 : /**
48 : * Actual fees.
49 : */
50 : struct TALER_WireFeeSet fees;
51 :
52 : /**
53 : * Start date of fee validity (inclusive).
54 : */
55 : struct GNUNET_TIME_Timestamp start_date;
56 :
57 : /**
58 : * End date of fee validity (exclusive).
59 : */
60 : struct GNUNET_TIME_Timestamp end_date;
61 :
62 : /**
63 : * Wire method the fees apply to.
64 : */
65 : char *method;
66 : };
67 :
68 :
69 : /**
70 : * State we keep per thread to cache the /wire response.
71 : */
72 : struct WireStateHandle
73 : {
74 : /**
75 : * Cached reply for /wire response.
76 : */
77 : struct MHD_Response *wire_reply;
78 :
79 : /**
80 : * ETag for this response (if any).
81 : */
82 : char *etag;
83 :
84 : /**
85 : * head of DLL of wire fees.
86 : */
87 : struct WireFeeSet *wfs_head;
88 :
89 : /**
90 : * Tail of DLL of wire fees.
91 : */
92 : struct WireFeeSet *wfs_tail;
93 :
94 : /**
95 : * Earliest timestamp of all the wire methods when we have no more fees.
96 : */
97 : struct GNUNET_TIME_Absolute cache_expiration;
98 :
99 : /**
100 : * @e cache_expiration time, formatted.
101 : */
102 : char dat[128];
103 :
104 : /**
105 : * For which (global) wire_generation was this data structure created?
106 : * Used to check when we are outdated and need to be re-generated.
107 : */
108 : uint64_t wire_generation;
109 :
110 : /**
111 : * HTTP status to return with this response.
112 : */
113 : unsigned int http_status;
114 :
115 : };
116 :
117 :
118 : /**
119 : * Stores the latest generation of our wire response.
120 : */
121 : static struct WireStateHandle *wire_state;
122 :
123 : /**
124 : * Handler listening for wire updates by other exchange
125 : * services.
126 : */
127 : static struct GNUNET_DB_EventHandler *wire_eh;
128 :
129 : /**
130 : * Counter incremented whenever we have a reason to re-build the #wire_state
131 : * because something external changed.
132 : */
133 : static uint64_t wire_generation;
134 :
135 :
136 : /**
137 : * Free memory associated with @a wsh
138 : *
139 : * @param[in] wsh wire state to destroy
140 : */
141 : static void
142 0 : destroy_wire_state (struct WireStateHandle *wsh)
143 : {
144 : struct WireFeeSet *wfs;
145 :
146 0 : while (NULL != (wfs = wsh->wfs_head))
147 : {
148 0 : GNUNET_CONTAINER_DLL_remove (wsh->wfs_head,
149 : wsh->wfs_tail,
150 : wfs);
151 0 : GNUNET_free (wfs->method);
152 0 : GNUNET_free (wfs);
153 : }
154 0 : MHD_destroy_response (wsh->wire_reply);
155 0 : GNUNET_free (wsh->etag);
156 0 : GNUNET_free (wsh);
157 0 : }
158 :
159 :
160 : /**
161 : * Function called whenever another exchange process has updated
162 : * the wire data in the database.
163 : *
164 : * @param cls NULL
165 : * @param extra unused
166 : * @param extra_size number of bytes in @a extra unused
167 : */
168 : static void
169 0 : wire_update_event_cb (void *cls,
170 : const void *extra,
171 : size_t extra_size)
172 : {
173 : (void) cls;
174 : (void) extra;
175 : (void) extra_size;
176 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
177 : "Received /wire update event\n");
178 0 : TEH_check_invariants ();
179 0 : wire_generation++;
180 0 : }
181 :
182 :
183 : enum GNUNET_GenericReturnValue
184 0 : TEH_wire_init ()
185 : {
186 0 : struct GNUNET_DB_EventHeaderP es = {
187 0 : .size = htons (sizeof (es)),
188 0 : .type = htons (TALER_DBEVENT_EXCHANGE_KEYS_UPDATED),
189 : };
190 :
191 0 : wire_eh = TEH_plugin->event_listen (TEH_plugin->cls,
192 0 : GNUNET_TIME_UNIT_FOREVER_REL,
193 : &es,
194 : &wire_update_event_cb,
195 : NULL);
196 0 : if (NULL == wire_eh)
197 : {
198 0 : GNUNET_break (0);
199 0 : return GNUNET_SYSERR;
200 : }
201 0 : return GNUNET_OK;
202 : }
203 :
204 :
205 : void
206 0 : TEH_wire_done ()
207 : {
208 0 : if (NULL != wire_state)
209 : {
210 0 : destroy_wire_state (wire_state);
211 0 : wire_state = NULL;
212 : }
213 0 : if (NULL != wire_eh)
214 : {
215 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
216 : wire_eh);
217 0 : wire_eh = NULL;
218 : }
219 0 : }
220 :
221 :
222 : /**
223 : * Add information about a wire account to @a cls.
224 : *
225 : * @param cls a `json_t *` object to expand with wire account details
226 : * @param payto_uri the exchange bank account URI to add
227 : * @param master_sig master key signature affirming that this is a bank
228 : * account of the exchange (of purpose #TALER_SIGNATURE_MASTER_WIRE_DETAILS)
229 : */
230 : static void
231 0 : add_wire_account (void *cls,
232 : const char *payto_uri,
233 : const struct TALER_MasterSignatureP *master_sig)
234 : {
235 0 : json_t *a = cls;
236 :
237 0 : if (0 !=
238 0 : json_array_append_new (
239 : a,
240 0 : GNUNET_JSON_PACK (
241 : GNUNET_JSON_pack_string ("payto_uri",
242 : payto_uri),
243 : GNUNET_JSON_pack_data_auto ("master_sig",
244 : master_sig))))
245 : {
246 0 : GNUNET_break (0); /* out of memory!? */
247 0 : return;
248 : }
249 : }
250 :
251 :
252 : /**
253 : * Closure for #add_wire_fee().
254 : */
255 : struct AddContext
256 : {
257 : /**
258 : * Wire method the fees are for.
259 : */
260 : char *wire_method;
261 :
262 : /**
263 : * Wire state we are building.
264 : */
265 : struct WireStateHandle *wsh;
266 :
267 : /**
268 : * Array to append the fee to.
269 : */
270 : json_t *a;
271 :
272 : /**
273 : * Context we hash "everything" we add into. This is used
274 : * to compute the etag. Technically, we only hash the
275 : * master_sigs, as they imply the rest.
276 : */
277 : struct GNUNET_HashContext *hc;
278 :
279 : /**
280 : * Set to the maximum end-date seen.
281 : */
282 : struct GNUNET_TIME_Absolute max_seen;
283 : };
284 :
285 :
286 : /**
287 : * Add information about a wire account to @a cls.
288 : *
289 : * @param cls a `struct AddContext`
290 : * @param fees the wire fees we charge
291 : * @param start_date from when are these fees valid (start date)
292 : * @param end_date until when are these fees valid (end date, exclusive)
293 : * @param master_sig master key signature affirming that this is the correct
294 : * fee (of purpose #TALER_SIGNATURE_MASTER_WIRE_FEES)
295 : */
296 : static void
297 0 : add_wire_fee (void *cls,
298 : const struct TALER_WireFeeSet *fees,
299 : struct GNUNET_TIME_Timestamp start_date,
300 : struct GNUNET_TIME_Timestamp end_date,
301 : const struct TALER_MasterSignatureP *master_sig)
302 : {
303 0 : struct AddContext *ac = cls;
304 : struct WireFeeSet *wfs;
305 :
306 0 : GNUNET_CRYPTO_hash_context_read (ac->hc,
307 : master_sig,
308 : sizeof (*master_sig));
309 0 : ac->max_seen = GNUNET_TIME_absolute_max (ac->max_seen,
310 : end_date.abs_time);
311 0 : wfs = GNUNET_new (struct WireFeeSet);
312 0 : wfs->start_date = start_date;
313 0 : wfs->end_date = end_date;
314 0 : wfs->fees = *fees;
315 0 : wfs->method = GNUNET_strdup (ac->wire_method);
316 0 : GNUNET_CONTAINER_DLL_insert (ac->wsh->wfs_head,
317 : ac->wsh->wfs_tail,
318 : wfs);
319 0 : if (0 !=
320 0 : json_array_append_new (
321 : ac->a,
322 0 : GNUNET_JSON_PACK (
323 : TALER_JSON_pack_amount ("wire_fee",
324 : &fees->wire),
325 : TALER_JSON_pack_amount ("wad_fee",
326 : &fees->wad),
327 : TALER_JSON_pack_amount ("closing_fee",
328 : &fees->closing),
329 : GNUNET_JSON_pack_timestamp ("start_date",
330 : start_date),
331 : GNUNET_JSON_pack_timestamp ("end_date",
332 : end_date),
333 : GNUNET_JSON_pack_data_auto ("sig",
334 : master_sig))))
335 : {
336 0 : GNUNET_break (0); /* out of memory!? */
337 0 : return;
338 : }
339 : }
340 :
341 :
342 : /**
343 : * Create the /wire response from our database state.
344 : *
345 : * @return NULL on error
346 : */
347 : static struct WireStateHandle *
348 0 : build_wire_state (void)
349 : {
350 : json_t *wire_accounts_array;
351 : json_t *wire_fee_object;
352 0 : uint64_t wg = wire_generation; /* must be obtained FIRST */
353 : enum GNUNET_DB_QueryStatus qs;
354 : struct WireStateHandle *wsh;
355 : struct GNUNET_HashContext *hc;
356 :
357 0 : wsh = GNUNET_new (struct WireStateHandle);
358 0 : wsh->wire_generation = wg;
359 0 : wire_accounts_array = json_array ();
360 0 : GNUNET_assert (NULL != wire_accounts_array);
361 0 : qs = TEH_plugin->get_wire_accounts (TEH_plugin->cls,
362 : &add_wire_account,
363 : wire_accounts_array);
364 0 : if (0 > qs)
365 : {
366 0 : GNUNET_break (0);
367 0 : json_decref (wire_accounts_array);
368 0 : wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
369 : wsh->wire_reply
370 0 : = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
371 : "get_wire_accounts");
372 0 : return wsh;
373 : }
374 0 : if (0 == json_array_size (wire_accounts_array))
375 : {
376 0 : json_decref (wire_accounts_array);
377 0 : wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
378 : wsh->wire_reply
379 0 : = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_NO_ACCOUNTS_CONFIGURED,
380 : NULL);
381 0 : return wsh;
382 : }
383 0 : wire_fee_object = json_object ();
384 0 : GNUNET_assert (NULL != wire_fee_object);
385 0 : wsh->cache_expiration = GNUNET_TIME_UNIT_FOREVER_ABS;
386 0 : hc = GNUNET_CRYPTO_hash_context_start ();
387 : {
388 : json_t *account;
389 : size_t index;
390 :
391 0 : json_array_foreach (wire_accounts_array, index, account) {
392 : char *wire_method;
393 0 : const char *payto_uri = json_string_value (json_object_get (account,
394 : "payto_uri"));
395 :
396 0 : GNUNET_assert (NULL != payto_uri);
397 0 : wire_method = TALER_payto_get_method (payto_uri);
398 0 : if (NULL == wire_method)
399 : {
400 0 : wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
401 : wsh->wire_reply
402 0 : = TALER_MHD_make_error (
403 : TALER_EC_EXCHANGE_WIRE_INVALID_PAYTO_CONFIGURED,
404 : payto_uri);
405 0 : json_decref (wire_accounts_array);
406 0 : json_decref (wire_fee_object);
407 0 : GNUNET_CRYPTO_hash_context_abort (hc);
408 0 : return wsh;
409 : }
410 0 : if (NULL == json_object_get (wire_fee_object,
411 : wire_method))
412 : {
413 0 : struct AddContext ac = {
414 : .wire_method = wire_method,
415 : .wsh = wsh,
416 0 : .a = json_array (),
417 : .hc = hc
418 : };
419 :
420 0 : GNUNET_assert (NULL != ac.a);
421 0 : qs = TEH_plugin->get_wire_fees (TEH_plugin->cls,
422 : wire_method,
423 : &add_wire_fee,
424 : &ac);
425 0 : if (0 > qs)
426 : {
427 0 : GNUNET_break (0);
428 0 : json_decref (ac.a);
429 0 : json_decref (wire_fee_object);
430 0 : json_decref (wire_accounts_array);
431 0 : GNUNET_free (wire_method);
432 0 : wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
433 : wsh->wire_reply
434 0 : = TALER_MHD_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
435 : "get_wire_fees");
436 0 : GNUNET_CRYPTO_hash_context_abort (hc);
437 0 : return wsh;
438 : }
439 0 : if (0 == json_array_size (ac.a))
440 : {
441 0 : json_decref (ac.a);
442 0 : json_decref (wire_accounts_array);
443 0 : json_decref (wire_fee_object);
444 0 : wsh->http_status = MHD_HTTP_INTERNAL_SERVER_ERROR;
445 : wsh->wire_reply
446 0 : = TALER_MHD_make_error (TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
447 : wire_method);
448 0 : GNUNET_free (wire_method);
449 0 : GNUNET_CRYPTO_hash_context_abort (hc);
450 0 : return wsh;
451 : }
452 0 : wsh->cache_expiration = GNUNET_TIME_absolute_min (ac.max_seen,
453 : wsh->cache_expiration);
454 0 : GNUNET_assert (0 ==
455 : json_object_set_new (wire_fee_object,
456 : wire_method,
457 : ac.a));
458 : }
459 0 : GNUNET_free (wire_method);
460 : }
461 : }
462 :
463 :
464 0 : wsh->wire_reply = TALER_MHD_MAKE_JSON_PACK (
465 : GNUNET_JSON_pack_array_steal ("accounts",
466 : wire_accounts_array),
467 : GNUNET_JSON_pack_object_steal ("fees",
468 : wire_fee_object),
469 : GNUNET_JSON_pack_data_auto ("master_public_key",
470 : &TEH_master_public_key));
471 : {
472 : struct GNUNET_TIME_Timestamp m;
473 :
474 0 : m = GNUNET_TIME_absolute_to_timestamp (wsh->cache_expiration);
475 0 : TALER_MHD_get_date_string (m.abs_time,
476 0 : wsh->dat);
477 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
478 : "Setting 'Expires' header for '/wire' to '%s'\n",
479 : wsh->dat);
480 0 : GNUNET_break (MHD_YES ==
481 : MHD_add_response_header (wsh->wire_reply,
482 : MHD_HTTP_HEADER_EXPIRES,
483 : wsh->dat));
484 : }
485 : /* Set cache control headers: our response varies depending on these headers */
486 0 : GNUNET_break (MHD_YES ==
487 : MHD_add_response_header (wsh->wire_reply,
488 : MHD_HTTP_HEADER_VARY,
489 : MHD_HTTP_HEADER_ACCEPT_ENCODING));
490 : /* Information is always public, revalidate after 1 day */
491 0 : GNUNET_break (MHD_YES ==
492 : MHD_add_response_header (wsh->wire_reply,
493 : MHD_HTTP_HEADER_CACHE_CONTROL,
494 : "public,max-age=86400"));
495 :
496 : {
497 : struct GNUNET_HashCode h;
498 : char etag[sizeof (h) * 2];
499 : char *end;
500 :
501 0 : GNUNET_CRYPTO_hash_context_finish (hc,
502 : &h);
503 0 : end = GNUNET_STRINGS_data_to_string (&h,
504 : sizeof (h),
505 : etag,
506 : sizeof (etag));
507 0 : *end = '\0';
508 0 : wsh->etag = GNUNET_strdup (etag);
509 0 : GNUNET_break (MHD_YES ==
510 : MHD_add_response_header (wsh->wire_reply,
511 : MHD_HTTP_HEADER_ETAG,
512 : etag));
513 : }
514 0 : wsh->http_status = MHD_HTTP_OK;
515 0 : return wsh;
516 : }
517 :
518 :
519 : void
520 0 : TEH_wire_update_state (void)
521 : {
522 0 : struct GNUNET_DB_EventHeaderP es = {
523 0 : .size = htons (sizeof (es)),
524 0 : .type = htons (TALER_DBEVENT_EXCHANGE_WIRE_UPDATED),
525 : };
526 :
527 0 : TEH_plugin->event_notify (TEH_plugin->cls,
528 : &es,
529 : NULL,
530 : 0);
531 0 : wire_generation++;
532 0 : }
533 :
534 :
535 : /**
536 : * Return the current key state for this thread. Possibly
537 : * re-builds the key state if we have reason to believe
538 : * that something changed.
539 : *
540 : * @return NULL on error
541 : */
542 : struct WireStateHandle *
543 0 : get_wire_state (void)
544 : {
545 : struct WireStateHandle *old_wsh;
546 :
547 0 : old_wsh = wire_state;
548 0 : if ( (NULL == old_wsh) ||
549 0 : (old_wsh->wire_generation < wire_generation) )
550 : {
551 : struct WireStateHandle *wsh;
552 :
553 0 : TEH_check_invariants ();
554 0 : wsh = build_wire_state ();
555 0 : wire_state = wsh;
556 0 : if (NULL != old_wsh)
557 0 : destroy_wire_state (old_wsh);
558 0 : TEH_check_invariants ();
559 0 : return wsh;
560 : }
561 0 : return old_wsh;
562 : }
563 :
564 :
565 : MHD_RESULT
566 0 : TEH_handler_wire (struct TEH_RequestContext *rc,
567 : const char *const args[])
568 : {
569 : struct WireStateHandle *wsh;
570 :
571 : (void) args;
572 0 : wsh = get_wire_state ();
573 0 : if (NULL == wsh)
574 0 : return TALER_MHD_reply_with_error (rc->connection,
575 : MHD_HTTP_INTERNAL_SERVER_ERROR,
576 : TALER_EC_EXCHANGE_GENERIC_BAD_CONFIGURATION,
577 : NULL);
578 : {
579 : const char *etag;
580 :
581 0 : etag = MHD_lookup_connection_value (rc->connection,
582 : MHD_HEADER_KIND,
583 : MHD_HTTP_HEADER_IF_NONE_MATCH);
584 0 : if ( (NULL != etag) &&
585 0 : (MHD_HTTP_OK == wsh->http_status) &&
586 0 : (NULL != wsh->etag) &&
587 0 : (0 == strcmp (etag,
588 0 : wsh->etag)) )
589 : {
590 : MHD_RESULT ret;
591 : struct MHD_Response *resp;
592 :
593 0 : resp = MHD_create_response_from_buffer (0,
594 : NULL,
595 : MHD_RESPMEM_PERSISTENT);
596 0 : TALER_MHD_add_global_headers (resp);
597 0 : GNUNET_break (MHD_YES ==
598 : MHD_add_response_header (resp,
599 : MHD_HTTP_HEADER_EXPIRES,
600 : wsh->dat));
601 0 : GNUNET_break (MHD_YES ==
602 : MHD_add_response_header (resp,
603 : MHD_HTTP_HEADER_ETAG,
604 : wsh->etag));
605 0 : ret = MHD_queue_response (rc->connection,
606 : MHD_HTTP_NOT_MODIFIED,
607 : resp);
608 0 : GNUNET_break (MHD_YES == ret);
609 0 : MHD_destroy_response (resp);
610 0 : return ret;
611 : }
612 : }
613 0 : return MHD_queue_response (rc->connection,
614 : wsh->http_status,
615 : wsh->wire_reply);
616 : }
617 :
618 :
619 : const struct TALER_WireFeeSet *
620 0 : TEH_wire_fees_by_time (
621 : struct GNUNET_TIME_Timestamp ts,
622 : const char *method)
623 : {
624 0 : struct WireStateHandle *wsh = get_wire_state ();
625 :
626 0 : for (struct WireFeeSet *wfs = wsh->wfs_head;
627 : NULL != wfs;
628 0 : wfs = wfs->next)
629 : {
630 0 : if (0 != strcmp (method,
631 0 : wfs->method))
632 0 : continue;
633 0 : if ( (GNUNET_TIME_timestamp_cmp (wfs->start_date,
634 : >,
635 0 : ts)) ||
636 0 : (GNUNET_TIME_timestamp_cmp (ts,
637 : >=,
638 : wfs->end_date)) )
639 0 : continue;
640 0 : return &wfs->fees;
641 : }
642 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
643 : "No wire fees for method `%s' at %s configured\n",
644 : method,
645 : GNUNET_TIME_timestamp2s (ts));
646 0 : return NULL;
647 : }
648 :
649 :
650 : /* end of taler-exchange-httpd_wire.c */
|