Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-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-exchange-httpd_deposits_get.c
18 : * @brief Handle wire deposit tracking-related requests
19 : * @author Christian Grothoff
20 : */
21 : #include "taler/platform.h"
22 : #include <gnunet/gnunet_util_lib.h>
23 : #include <jansson.h>
24 : #include <microhttpd.h>
25 : #include <pthread.h>
26 : #include "taler/taler_dbevents.h"
27 : #include "taler/taler_json_lib.h"
28 : #include "taler/taler_util.h"
29 : #include "taler/taler_mhd_lib.h"
30 : #include "taler/taler_signatures.h"
31 : #include "taler-exchange-httpd_keys.h"
32 : #include "taler-exchange-httpd_deposits_get.h"
33 : #include "taler-exchange-httpd_responses.h"
34 :
35 :
36 : /**
37 : * Closure for #handle_wtid_data.
38 : */
39 : struct DepositWtidContext
40 : {
41 :
42 : /**
43 : * Kept in a DLL.
44 : */
45 : struct DepositWtidContext *next;
46 :
47 : /**
48 : * Kept in a DLL.
49 : */
50 : struct DepositWtidContext *prev;
51 :
52 : /**
53 : * Context for the request we are processing.
54 : */
55 : struct TEH_RequestContext *rc;
56 :
57 : /**
58 : * Subscription for the database event we are waiting for.
59 : */
60 : struct GNUNET_DB_EventHandler *eh;
61 :
62 : /**
63 : * Hash over the proposal data of the contract for which this deposit is made.
64 : */
65 : struct TALER_PrivateContractHashP h_contract_terms;
66 :
67 : /**
68 : * Hash over the wiring information of the merchant.
69 : */
70 : struct TALER_MerchantWireHashP h_wire;
71 :
72 : /**
73 : * The Merchant's public key. The deposit inquiry request is to be
74 : * signed by the corresponding private key (using EdDSA).
75 : */
76 : struct TALER_MerchantPublicKeyP merchant;
77 :
78 : /**
79 : * Public key for KYC operations on the target bank
80 : * account for the wire transfer. All zero if no
81 : * public key is accepted yet. In that case, the
82 : * client should use the @e merchant public key for
83 : * the KYC auth wire transfer.
84 : */
85 : union TALER_AccountPublicKeyP account_pub;
86 :
87 : /**
88 : * The coin's public key. This is the value that must have been
89 : * signed (blindly) by the Exchange.
90 : */
91 : struct TALER_CoinSpendPublicKeyP coin_pub;
92 :
93 : /**
94 : * Set by #handle_wtid data to the wire transfer ID.
95 : */
96 : struct TALER_WireTransferIdentifierRawP wtid;
97 :
98 : /**
99 : * Signature by the merchant.
100 : */
101 : struct TALER_MerchantSignatureP merchant_sig;
102 :
103 : /**
104 : * Set by #handle_wtid data to the coin's contribution to the wire transfer.
105 : */
106 : struct TALER_Amount coin_contribution;
107 :
108 : /**
109 : * Set by #handle_wtid data to the fee charged to the coin.
110 : */
111 : struct TALER_Amount coin_fee;
112 :
113 : /**
114 : * Set by #handle_wtid data to the wire transfer execution time.
115 : */
116 : struct GNUNET_TIME_Timestamp execution_time;
117 :
118 : /**
119 : * Timeout of the request, for long-polling.
120 : */
121 : struct GNUNET_TIME_Absolute timeout;
122 :
123 : /**
124 : * Set by #handle_wtid to the coin contribution to the transaction
125 : * (that is, @e coin_contribution minus @e coin_fee).
126 : */
127 : struct TALER_Amount coin_delta;
128 :
129 : /**
130 : * KYC status information for the receiving account.
131 : */
132 : struct TALER_EXCHANGEDB_KycStatus kyc;
133 :
134 : /**
135 : * #GNUNET_YES if we were suspended, #GNUNET_SYSERR
136 : * if we were woken up due to shutdown.
137 : */
138 : enum GNUNET_GenericReturnValue suspended;
139 :
140 : /**
141 : * What do we long-poll for? Defaults to
142 : * #TALER_DGLPT_OK if not given.
143 : */
144 : enum TALER_DepositGetLongPollTarget lpt;
145 : };
146 :
147 :
148 : /**
149 : * Head of DLL of suspended requests.
150 : */
151 : static struct DepositWtidContext *dwc_head;
152 :
153 : /**
154 : * Tail of DLL of suspended requests.
155 : */
156 : static struct DepositWtidContext *dwc_tail;
157 :
158 :
159 : void
160 21 : TEH_deposits_get_cleanup ()
161 : {
162 : struct DepositWtidContext *n;
163 :
164 21 : for (struct DepositWtidContext *ctx = dwc_head;
165 21 : NULL != ctx;
166 0 : ctx = n)
167 : {
168 0 : n = ctx->next;
169 0 : GNUNET_assert (GNUNET_YES == ctx->suspended);
170 0 : ctx->suspended = GNUNET_SYSERR;
171 0 : MHD_resume_connection (ctx->rc->connection);
172 0 : GNUNET_CONTAINER_DLL_remove (dwc_head,
173 : dwc_tail,
174 : ctx);
175 : }
176 21 : }
177 :
178 :
179 : /**
180 : * A merchant asked for details about a deposit. Provide
181 : * them. Generates the 200 reply.
182 : *
183 : * @param ctx details to respond with
184 : * @return MHD result code
185 : */
186 : static MHD_RESULT
187 2 : reply_deposit_details (
188 : const struct DepositWtidContext *ctx)
189 : {
190 2 : struct MHD_Connection *connection = ctx->rc->connection;
191 : struct TALER_ExchangePublicKeyP pub;
192 : struct TALER_ExchangeSignatureP sig;
193 : enum TALER_ErrorCode ec;
194 :
195 2 : if (TALER_EC_NONE !=
196 2 : (ec = TALER_exchange_online_confirm_wire_sign (
197 : &TEH_keys_exchange_sign_,
198 : &ctx->h_wire,
199 : &ctx->h_contract_terms,
200 : &ctx->wtid,
201 : &ctx->coin_pub,
202 : ctx->execution_time,
203 : &ctx->coin_delta,
204 : &pub,
205 : &sig)))
206 : {
207 0 : GNUNET_break (0);
208 0 : return TALER_MHD_reply_with_ec (connection,
209 : ec,
210 : NULL);
211 : }
212 2 : return TALER_MHD_REPLY_JSON_PACK (
213 : connection,
214 : MHD_HTTP_OK,
215 : GNUNET_JSON_pack_data_auto ("wtid",
216 : &ctx->wtid),
217 : GNUNET_JSON_pack_timestamp ("execution_time",
218 : ctx->execution_time),
219 : TALER_JSON_pack_amount ("coin_contribution",
220 : &ctx->coin_delta),
221 : GNUNET_JSON_pack_data_auto ("exchange_sig",
222 : &sig),
223 : GNUNET_JSON_pack_data_auto ("exchange_pub",
224 : &pub));
225 : }
226 :
227 :
228 : /**
229 : * Function called on events received from Postgres.
230 : * Wakes up long pollers.
231 : *
232 : * @param cls the `struct DepositWtidContext *`
233 : * @param extra additional event data provided
234 : * @param extra_size number of bytes in @a extra
235 : */
236 : static void
237 0 : db_event_cb (void *cls,
238 : const void *extra,
239 : size_t extra_size)
240 : {
241 0 : struct DepositWtidContext *ctx = cls;
242 : struct GNUNET_AsyncScopeSave old_scope;
243 :
244 : (void) extra;
245 : (void) extra_size;
246 0 : if (GNUNET_YES != ctx->suspended)
247 0 : return; /* might get multiple wake-up events */
248 0 : GNUNET_CONTAINER_DLL_remove (dwc_head,
249 : dwc_tail,
250 : ctx);
251 0 : GNUNET_async_scope_enter (&ctx->rc->async_scope_id,
252 : &old_scope);
253 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
254 : "Resuming request handling\n");
255 0 : TEH_check_invariants ();
256 0 : ctx->suspended = GNUNET_NO;
257 0 : MHD_resume_connection (ctx->rc->connection);
258 0 : TALER_MHD_daemon_trigger ();
259 0 : TEH_check_invariants ();
260 0 : GNUNET_async_scope_restore (&old_scope);
261 : }
262 :
263 :
264 : /**
265 : * Lookup and return the wire transfer identifier.
266 : *
267 : * @param ctx context of the signed request to execute
268 : * @return MHD result code
269 : */
270 : static MHD_RESULT
271 8 : handle_track_transaction_request (
272 : struct DepositWtidContext *ctx)
273 : {
274 8 : struct MHD_Connection *connection = ctx->rc->connection;
275 : enum GNUNET_DB_QueryStatus qs;
276 : bool pending;
277 : struct TALER_Amount fee;
278 :
279 8 : qs = TEH_plugin->lookup_transfer_by_deposit (
280 8 : TEH_plugin->cls,
281 8 : &ctx->h_contract_terms,
282 8 : &ctx->h_wire,
283 8 : &ctx->coin_pub,
284 8 : &ctx->merchant,
285 : &pending,
286 : &ctx->wtid,
287 : &ctx->execution_time,
288 : &ctx->coin_contribution,
289 : &fee,
290 : &ctx->kyc,
291 : &ctx->account_pub);
292 8 : if (0 > qs)
293 : {
294 0 : GNUNET_break (GNUNET_DB_STATUS_HARD_ERROR == qs);
295 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
296 0 : return TALER_MHD_reply_with_error (
297 : connection,
298 : MHD_HTTP_INTERNAL_SERVER_ERROR,
299 : TALER_EC_GENERIC_DB_FETCH_FAILED,
300 : "lookup_transfer_by_deposit");
301 : }
302 8 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
303 : {
304 2 : return TALER_MHD_reply_with_error (
305 : connection,
306 : MHD_HTTP_NOT_FOUND,
307 : TALER_EC_EXCHANGE_DEPOSITS_GET_NOT_FOUND,
308 : NULL);
309 : }
310 :
311 6 : if (0 >
312 6 : TALER_amount_subtract (&ctx->coin_delta,
313 6 : &ctx->coin_contribution,
314 : &fee))
315 : {
316 0 : GNUNET_break (0);
317 0 : return TALER_MHD_reply_with_error (
318 : connection,
319 : MHD_HTTP_INTERNAL_SERVER_ERROR,
320 : TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
321 : "wire fees exceed aggregate in database");
322 : }
323 6 : if (pending)
324 : {
325 4 : if (GNUNET_TIME_absolute_is_future (ctx->timeout))
326 : {
327 0 : bool do_suspend = false;
328 0 : switch (ctx->lpt)
329 : {
330 0 : case TALER_DGLPT_NONE:
331 0 : break;
332 0 : case TALER_DGLPT_KYC_REQUIRED_OR_OK:
333 0 : do_suspend = ctx->kyc.ok;
334 0 : break;
335 0 : case TALER_DGLPT_OK:
336 0 : do_suspend = true;
337 0 : break;
338 : }
339 0 : if (do_suspend)
340 : {
341 0 : GNUNET_assert (GNUNET_NO == ctx->suspended);
342 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
343 : "Suspending request handling\n");
344 0 : GNUNET_CONTAINER_DLL_insert (dwc_head,
345 : dwc_tail,
346 : ctx);
347 0 : ctx->suspended = GNUNET_YES;
348 0 : MHD_suspend_connection (connection);
349 0 : return MHD_YES;
350 : }
351 : }
352 4 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
353 : "KYC required with row %llu\n",
354 : (unsigned long long) ctx->kyc.requirement_row);
355 4 : return TALER_MHD_REPLY_JSON_PACK (
356 : connection,
357 : MHD_HTTP_ACCEPTED,
358 : GNUNET_JSON_pack_allow_null (
359 : (0 == ctx->kyc.requirement_row)
360 : ? GNUNET_JSON_pack_string ("requirement_row",
361 : NULL)
362 : : GNUNET_JSON_pack_uint64 ("requirement_row",
363 : ctx->kyc.requirement_row)),
364 : GNUNET_JSON_pack_allow_null (
365 : (GNUNET_is_zero (&ctx->account_pub))
366 : ? GNUNET_JSON_pack_string ("account_pub",
367 : NULL)
368 : : GNUNET_JSON_pack_data_auto ("account_pub",
369 : &ctx->account_pub)),
370 : GNUNET_JSON_pack_bool ("kyc_ok",
371 : ctx->kyc.ok),
372 : GNUNET_JSON_pack_timestamp ("execution_time",
373 : ctx->execution_time));
374 : }
375 2 : return reply_deposit_details (ctx);
376 : }
377 :
378 :
379 : /**
380 : * Function called to clean up a context.
381 : *
382 : * @param rc request context with data to clean up
383 : */
384 : static void
385 8 : dwc_cleaner (struct TEH_RequestContext *rc)
386 : {
387 8 : struct DepositWtidContext *ctx = rc->rh_ctx;
388 :
389 8 : GNUNET_assert (GNUNET_YES != ctx->suspended);
390 8 : if (NULL != ctx->eh)
391 : {
392 0 : TEH_plugin->event_listen_cancel (TEH_plugin->cls,
393 : ctx->eh);
394 0 : ctx->eh = NULL;
395 : }
396 8 : GNUNET_free (ctx);
397 8 : }
398 :
399 :
400 : MHD_RESULT
401 8 : TEH_handler_deposits_get (struct TEH_RequestContext *rc,
402 : const char *const args[4])
403 : {
404 8 : struct DepositWtidContext *ctx = rc->rh_ctx;
405 :
406 8 : if (NULL == ctx)
407 : {
408 8 : ctx = GNUNET_new (struct DepositWtidContext);
409 8 : ctx->rc = rc;
410 8 : ctx->lpt = TALER_DGLPT_OK; /* default */
411 8 : rc->rh_ctx = ctx;
412 8 : rc->rh_cleaner = &dwc_cleaner;
413 :
414 8 : if (GNUNET_OK !=
415 8 : GNUNET_STRINGS_string_to_data (args[0],
416 : strlen (args[0]),
417 8 : &ctx->h_wire,
418 : sizeof (ctx->h_wire)))
419 : {
420 0 : GNUNET_break_op (0);
421 0 : return TALER_MHD_reply_with_error (
422 : rc->connection,
423 : MHD_HTTP_BAD_REQUEST,
424 : TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_WIRE,
425 : args[0]);
426 : }
427 8 : if (GNUNET_OK !=
428 8 : GNUNET_STRINGS_string_to_data (args[1],
429 8 : strlen (args[1]),
430 8 : &ctx->merchant,
431 : sizeof (ctx->merchant)))
432 : {
433 0 : GNUNET_break_op (0);
434 0 : return TALER_MHD_reply_with_error (
435 : rc->connection,
436 : MHD_HTTP_BAD_REQUEST,
437 : TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_MERCHANT_PUB,
438 0 : args[1]);
439 : }
440 8 : if (GNUNET_OK !=
441 8 : GNUNET_STRINGS_string_to_data (args[2],
442 8 : strlen (args[2]),
443 8 : &ctx->h_contract_terms,
444 : sizeof (ctx->h_contract_terms)))
445 : {
446 0 : GNUNET_break_op (0);
447 0 : return TALER_MHD_reply_with_error (
448 : rc->connection,
449 : MHD_HTTP_BAD_REQUEST,
450 : TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_H_CONTRACT_TERMS,
451 0 : args[2]);
452 : }
453 8 : if (GNUNET_OK !=
454 8 : GNUNET_STRINGS_string_to_data (args[3],
455 8 : strlen (args[3]),
456 8 : &ctx->coin_pub,
457 : sizeof (ctx->coin_pub)))
458 : {
459 0 : GNUNET_break_op (0);
460 0 : return TALER_MHD_reply_with_error (
461 : rc->connection,
462 : MHD_HTTP_BAD_REQUEST,
463 : TALER_EC_EXCHANGE_DEPOSITS_GET_INVALID_COIN_PUB,
464 0 : args[3]);
465 : }
466 8 : TALER_MHD_parse_request_arg_auto_t (rc->connection,
467 : "merchant_sig",
468 : &ctx->merchant_sig);
469 8 : TALER_MHD_parse_request_timeout (rc->connection,
470 : &ctx->timeout);
471 : {
472 8 : uint64_t num = 0;
473 : int val;
474 :
475 8 : TALER_MHD_parse_request_number (rc->connection,
476 : "lpt",
477 : &num);
478 8 : val = (int) num;
479 8 : if ( (val < 0) ||
480 : (val > TALER_DGLPT_MAX) )
481 : {
482 : /* Protocol violation, but we can be graceful and
483 : just ignore the long polling! */
484 0 : GNUNET_break_op (0);
485 0 : val = TALER_DGLPT_NONE;
486 : }
487 8 : ctx->lpt = (enum TALER_DepositGetLongPollTarget) val;
488 8 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
489 : "Long polling for target %d with timeout %s\n",
490 : val,
491 : GNUNET_TIME_relative2s (
492 : GNUNET_TIME_absolute_get_remaining (
493 : ctx->timeout),
494 : true));
495 : }
496 8 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
497 : {
498 8 : if (GNUNET_OK !=
499 8 : TALER_merchant_deposit_verify (&ctx->merchant,
500 8 : &ctx->coin_pub,
501 8 : &ctx->h_contract_terms,
502 8 : &ctx->h_wire,
503 8 : &ctx->merchant_sig))
504 : {
505 0 : GNUNET_break_op (0);
506 0 : return TALER_MHD_reply_with_error (
507 : rc->connection,
508 : MHD_HTTP_FORBIDDEN,
509 : TALER_EC_EXCHANGE_DEPOSITS_GET_MERCHANT_SIGNATURE_INVALID,
510 : NULL);
511 : }
512 : }
513 8 : if ( (GNUNET_TIME_absolute_is_future (ctx->timeout)) &&
514 0 : (TALER_DGLPT_NONE != ctx->lpt) )
515 : {
516 0 : struct TALER_CoinDepositEventP rep = {
517 0 : .header.size = htons (sizeof (rep)),
518 0 : .header.type = htons (TALER_DBEVENT_EXCHANGE_DEPOSIT_STATUS_CHANGED),
519 : .merchant_pub = ctx->merchant
520 : };
521 :
522 0 : ctx->eh = TEH_plugin->event_listen (
523 0 : TEH_plugin->cls,
524 : GNUNET_TIME_absolute_get_remaining (ctx->timeout),
525 : &rep.header,
526 : &db_event_cb,
527 : ctx);
528 0 : GNUNET_break (NULL != ctx->eh);
529 : }
530 : }
531 :
532 8 : return handle_track_transaction_request (ctx);
533 : }
534 :
535 :
536 : /* end of taler-exchange-httpd_deposits_get.c */
|