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_batch-deposit.c
18 : * @brief Handle /batch-deposit requests; parses the POST and JSON and
19 : * verifies the coin signatures before handing things off
20 : * to the database.
21 : * @author Florian Dold
22 : * @author Benedikt Mueller
23 : * @author Christian Grothoff
24 : */
25 : #include "taler/platform.h"
26 : #include <gnunet/gnunet_util_lib.h>
27 : #include <gnunet/gnunet_json_lib.h>
28 : #include <jansson.h>
29 : #include <microhttpd.h>
30 : #include <pthread.h>
31 : #include "taler/taler_extensions_policy.h"
32 : #include "taler/taler_json_lib.h"
33 : #include "taler/taler_mhd_lib.h"
34 : #include "taler-exchange-httpd_common_kyc.h"
35 : #include "taler-exchange-httpd_batch-deposit.h"
36 : #include "taler-exchange-httpd_responses.h"
37 : #include "taler/taler_exchangedb_lib.h"
38 : #include "taler-exchange-httpd_keys.h"
39 :
40 :
41 : /**
42 : * Closure for #batch_deposit_transaction.
43 : */
44 : struct BatchDepositContext
45 : {
46 :
47 : /**
48 : * Kept in a DLL.
49 : */
50 : struct BatchDepositContext *next;
51 :
52 : /**
53 : * Kept in a DLL.
54 : */
55 : struct BatchDepositContext *prev;
56 :
57 : /**
58 : * The request we are working on.
59 : */
60 : struct TEH_RequestContext *rc;
61 :
62 : /**
63 : * Handle for the legitimization check.
64 : */
65 : struct TEH_LegitimizationCheckHandle *lch;
66 :
67 : /**
68 : * Array with the individual coin deposit fees.
69 : */
70 : struct TALER_Amount *deposit_fees;
71 :
72 : /**
73 : * Information about deposited coins.
74 : */
75 : struct TALER_EXCHANGEDB_CoinDepositInformation *cdis;
76 :
77 : /**
78 : * Additional details for policy extension relevant for this
79 : * deposit operation, possibly NULL!
80 : */
81 : json_t *policy_json;
82 :
83 : /**
84 : * Response to return, if set.
85 : */
86 : struct MHD_Response *response;
87 :
88 : /**
89 : * KYC status of the reserve used for the operation.
90 : */
91 : struct TALER_EXCHANGEDB_KycStatus kyc;
92 :
93 : /**
94 : * Hash over @e policy_details, might be all zero
95 : */
96 : struct TALER_ExtensionPolicyHashP h_policy;
97 :
98 : /**
99 : * Hash over the merchant's payto://-URI with the wire salt.
100 : */
101 : struct TALER_MerchantWireHashP h_wire;
102 :
103 : /**
104 : * When @e policy_details are persisted, this contains the id of the record
105 : * in the policy_details table.
106 : */
107 : uint64_t policy_details_serial_id;
108 :
109 : /**
110 : * Hash over the normalized payto://-URI of the account we are
111 : * depositing into.
112 : */
113 : struct TALER_NormalizedPaytoHashP nph;
114 :
115 : /**
116 : * Our timestamp (when we received the request).
117 : * Possibly updated by the transaction if the
118 : * request is idempotent (was repeated).
119 : */
120 : struct GNUNET_TIME_Timestamp exchange_timestamp;
121 :
122 : /**
123 : * Total amount that is accumulated with this deposit,
124 : * without fee.
125 : */
126 : struct TALER_Amount accumulated_total_without_fee;
127 :
128 : /**
129 : * Details about the batch deposit operation.
130 : */
131 : struct TALER_EXCHANGEDB_BatchDeposit bd;
132 :
133 : /**
134 : * If @e policy_json was present, the corresponding policy extension
135 : * calculates these details. These will be persisted in the policy_details
136 : * table.
137 : */
138 : struct TALER_PolicyDetails policy_details;
139 :
140 : /**
141 : * HTTP status to return with @e response, or 0.
142 : */
143 : unsigned int http_status;
144 :
145 : /**
146 : * Our current state in the state machine.
147 : */
148 : enum
149 : {
150 : BDC_PHASE_INIT = 0,
151 : BDC_PHASE_PARSE = 1,
152 : BDC_PHASE_POLICY = 2,
153 : BDC_PHASE_KYC = 3,
154 : BDC_PHASE_TRANSACT = 4,
155 : BDC_PHASE_REPLY_SUCCESS = 5,
156 : BDC_PHASE_SUSPENDED,
157 : BDC_PHASE_CHECK_KYC_RESULT,
158 : BDC_PHASE_GENERATE_REPLY_FAILURE,
159 : BDC_PHASE_RETURN_YES,
160 : BDC_PHASE_RETURN_NO,
161 : } phase;
162 :
163 : /**
164 : * True, if no policy was present in the request. Then
165 : * @e policy_json is NULL and @e h_policy will be all zero.
166 : */
167 : bool has_no_policy;
168 :
169 : /**
170 : * KYC failed because a KYC auth transfer is needed
171 : * to establish the merchant_pub.
172 : */
173 : bool bad_kyc_auth;
174 : };
175 :
176 :
177 : /**
178 : * Head of list of suspended batch deposit operations.
179 : */
180 : static struct BatchDepositContext *bdc_head;
181 :
182 : /**
183 : * Tail of list of suspended batch deposit operations.
184 : */
185 : static struct BatchDepositContext *bdc_tail;
186 :
187 :
188 : void
189 21 : TEH_batch_deposit_cleanup ()
190 : {
191 : struct BatchDepositContext *bdc;
192 :
193 21 : while (NULL != (bdc = bdc_head))
194 : {
195 0 : GNUNET_assert (BDC_PHASE_SUSPENDED == bdc->phase);
196 0 : bdc->phase = BDC_PHASE_RETURN_NO;
197 0 : MHD_resume_connection (bdc->rc->connection);
198 0 : GNUNET_CONTAINER_DLL_remove (bdc_head,
199 : bdc_tail,
200 : bdc);
201 : }
202 21 : }
203 :
204 :
205 : /**
206 : * Terminate the main loop by returning the final
207 : * result.
208 : *
209 : * @param[in,out] bdc context to update phase for
210 : * @param mres MHD status to return
211 : */
212 : static void
213 104 : finish_loop (struct BatchDepositContext *bdc,
214 : MHD_RESULT mres)
215 : {
216 104 : bdc->phase = (MHD_YES == mres)
217 : ? BDC_PHASE_RETURN_YES
218 104 : : BDC_PHASE_RETURN_NO;
219 104 : }
220 :
221 :
222 : /**
223 : * Send confirmation of batch deposit success to client. This function will
224 : * create a signed message affirming the given information and return it to
225 : * the client. By this, the exchange affirms that the coins had sufficient
226 : * (residual) value for the specified transaction and that it will execute the
227 : * requested batch deposit operation with the given wiring details.
228 : *
229 : * @param[in,out] bdc information about the batch deposit
230 : */
231 : static void
232 90 : bdc_phase_reply_success (
233 : struct BatchDepositContext *bdc)
234 90 : {
235 90 : const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
236 90 : const struct TALER_CoinSpendSignatureP *csigs[GNUNET_NZL (bd->num_cdis)];
237 : enum TALER_ErrorCode ec;
238 : struct TALER_ExchangePublicKeyP pub;
239 : struct TALER_ExchangeSignatureP sig;
240 :
241 182 : for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
242 92 : csigs[i] = &bd->cdis[i].csig;
243 90 : if (TALER_EC_NONE !=
244 90 : (ec = TALER_exchange_online_deposit_confirmation_sign (
245 : &TEH_keys_exchange_sign_,
246 : &bd->h_contract_terms,
247 90 : &bdc->h_wire,
248 90 : bdc->has_no_policy ? NULL : &bdc->h_policy,
249 : bdc->exchange_timestamp,
250 : bd->wire_deadline,
251 : bd->refund_deadline,
252 90 : &bdc->accumulated_total_without_fee,
253 90 : bd->num_cdis,
254 : csigs,
255 90 : &bdc->bd.merchant_pub,
256 : &pub,
257 : &sig)))
258 : {
259 0 : GNUNET_break (0);
260 0 : finish_loop (bdc,
261 0 : TALER_MHD_reply_with_ec (bdc->rc->connection,
262 : ec,
263 : NULL));
264 0 : return;
265 : }
266 180 : finish_loop (bdc,
267 180 : TALER_MHD_REPLY_JSON_PACK (
268 : bdc->rc->connection,
269 : MHD_HTTP_OK,
270 : GNUNET_JSON_pack_timestamp ("exchange_timestamp",
271 : bdc->exchange_timestamp),
272 : GNUNET_JSON_pack_data_auto ("exchange_pub",
273 : &pub),
274 : GNUNET_JSON_pack_data_auto ("exchange_sig",
275 : &sig)));
276 : }
277 :
278 :
279 : /**
280 : * Execute database transaction for /batch-deposit. Runs the transaction
281 : * logic; IF it returns a non-error code, the transaction logic MUST
282 : * NOT queue a MHD response. IF it returns an hard error, the
283 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF
284 : * it returns the soft error code, the function MAY be called again to
285 : * retry and MUST not queue a MHD response.
286 : *
287 : * @param cls a `struct BatchDepositContext`
288 : * @param connection MHD request context
289 : * @param[out] mhd_ret set to MHD status on error
290 : * @return transaction status
291 : */
292 : static enum GNUNET_DB_QueryStatus
293 99 : batch_deposit_transaction (void *cls,
294 : struct MHD_Connection *connection,
295 : MHD_RESULT *mhd_ret)
296 : {
297 99 : struct BatchDepositContext *bdc = cls;
298 99 : const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
299 99 : enum GNUNET_DB_QueryStatus qs = GNUNET_DB_STATUS_HARD_ERROR;
300 99 : uint32_t bad_balance_coin_index = UINT32_MAX;
301 : bool balance_ok;
302 : bool in_conflict;
303 :
304 : /* If the deposit has a policy associated to it, persist it. This will
305 : * insert or update the record. */
306 99 : if (! bdc->has_no_policy)
307 : {
308 0 : qs = TEH_plugin->persist_policy_details (
309 0 : TEH_plugin->cls,
310 0 : &bdc->policy_details,
311 : &bdc->bd.policy_details_serial_id,
312 : &bdc->accumulated_total_without_fee,
313 : &bdc->policy_details.fulfillment_state);
314 0 : if (qs < 0)
315 0 : return qs;
316 :
317 0 : bdc->bd.policy_blocked =
318 0 : bdc->policy_details.fulfillment_state != TALER_PolicyFulfillmentSuccess;
319 : }
320 :
321 : /* FIXME-#9373: replace by batch insert! */
322 199 : for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
323 : {
324 101 : const struct TALER_EXCHANGEDB_CoinDepositInformation *cdi
325 101 : = &bdc->cdis[i];
326 : uint64_t known_coin_id;
327 :
328 101 : qs = TEH_make_coin_known (&cdi->coin,
329 : connection,
330 : &known_coin_id,
331 : mhd_ret);
332 101 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
333 : "make coin known (%s) returned %d\n",
334 : TALER_B2S (&cdi->coin.coin_pub),
335 : qs);
336 101 : if (qs < 0)
337 1 : return qs;
338 : }
339 :
340 98 : qs = TEH_plugin->do_deposit (
341 98 : TEH_plugin->cls,
342 : bd,
343 : &bdc->exchange_timestamp,
344 : &balance_ok,
345 : &bad_balance_coin_index,
346 : &in_conflict);
347 98 : if (qs <= 0)
348 : {
349 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
350 0 : return qs;
351 0 : TALER_LOG_WARNING (
352 : "Failed to store /batch-deposit information in database\n");
353 0 : *mhd_ret = TALER_MHD_reply_with_error (
354 : connection,
355 : MHD_HTTP_INTERNAL_SERVER_ERROR,
356 : TALER_EC_GENERIC_DB_STORE_FAILED,
357 : "batch-deposit");
358 0 : return qs;
359 : }
360 98 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
361 : "do_deposit returned: %d / %s[%u] / %s\n",
362 : qs,
363 : balance_ok ? "balance ok" : "balance insufficient",
364 : (unsigned int) bad_balance_coin_index,
365 : in_conflict ? "in conflict" : "no conflict");
366 98 : if (in_conflict)
367 : {
368 : struct TALER_MerchantWireHashP h_wire;
369 :
370 4 : if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
371 4 : TEH_plugin->get_wire_hash_for_contract (
372 4 : TEH_plugin->cls,
373 : &bd->merchant_pub,
374 : &bd->h_contract_terms,
375 : &h_wire))
376 : {
377 0 : TALER_LOG_WARNING (
378 : "Failed to retrieve conflicting contract details from database\n");
379 0 : *mhd_ret = TALER_MHD_reply_with_error (
380 : connection,
381 : MHD_HTTP_INTERNAL_SERVER_ERROR,
382 : TALER_EC_GENERIC_DB_STORE_FAILED,
383 : "batch-deposit");
384 0 : return qs;
385 : }
386 :
387 : *mhd_ret
388 4 : = TEH_RESPONSE_reply_coin_conflicting_contract (
389 : connection,
390 : TALER_EC_EXCHANGE_DEPOSIT_CONFLICTING_CONTRACT,
391 : &h_wire);
392 4 : return GNUNET_DB_STATUS_HARD_ERROR;
393 : }
394 94 : if (! balance_ok)
395 : {
396 4 : GNUNET_assert (bad_balance_coin_index < bdc->bd.num_cdis);
397 4 : GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
398 : "returning history of conflicting coin (%s)\n",
399 : TALER_B2S (&bdc->cdis[bad_balance_coin_index].coin.coin_pub));
400 : *mhd_ret
401 8 : = TEH_RESPONSE_reply_coin_insufficient_funds (
402 : connection,
403 : TALER_EC_EXCHANGE_GENERIC_INSUFFICIENT_FUNDS,
404 4 : &bdc->cdis[bad_balance_coin_index].coin.denom_pub_hash,
405 4 : &bdc->cdis[bad_balance_coin_index].coin.coin_pub);
406 4 : return GNUNET_DB_STATUS_HARD_ERROR;
407 : }
408 90 : TEH_METRICS_num_success[TEH_MT_SUCCESS_DEPOSIT]++;
409 90 : return qs;
410 : }
411 :
412 :
413 : /**
414 : * Run database transaction.
415 : *
416 : * @param[in,out] bdc request context
417 : */
418 : static void
419 99 : bdc_phase_transact (struct BatchDepositContext *bdc)
420 : {
421 : MHD_RESULT mhd_ret;
422 :
423 99 : if (GNUNET_SYSERR ==
424 99 : TEH_plugin->preflight (TEH_plugin->cls))
425 : {
426 0 : GNUNET_break (0);
427 0 : finish_loop (bdc,
428 : TALER_MHD_reply_with_error (
429 0 : bdc->rc->connection,
430 : MHD_HTTP_INTERNAL_SERVER_ERROR,
431 : TALER_EC_GENERIC_DB_START_FAILED,
432 : "preflight failure"));
433 9 : return;
434 : }
435 :
436 99 : if (GNUNET_OK !=
437 99 : TEH_DB_run_transaction (bdc->rc->connection,
438 : "execute batch deposit",
439 : TEH_MT_REQUEST_BATCH_DEPOSIT,
440 : &mhd_ret,
441 : &batch_deposit_transaction,
442 : bdc))
443 : {
444 9 : finish_loop (bdc,
445 : mhd_ret);
446 9 : return;
447 : }
448 90 : bdc->phase++;
449 : }
450 :
451 :
452 : /**
453 : * Check if the @a bdc is replayed and we already have an
454 : * answer. If so, replay the existing answer and return the
455 : * HTTP response.
456 : *
457 : * @param bdc parsed request data
458 : * @return true if the request is idempotent with an existing request
459 : * false if we did not find the request in the DB and did not set @a mret
460 : */
461 : static bool
462 5 : check_request_idempotent (
463 : struct BatchDepositContext *bdc)
464 : {
465 5 : const struct TEH_RequestContext *rc = bdc->rc;
466 : enum GNUNET_DB_QueryStatus qs;
467 : bool is_idempotent;
468 :
469 5 : qs = TEH_plugin->do_check_deposit_idempotent (
470 5 : TEH_plugin->cls,
471 5 : &bdc->bd,
472 : &bdc->exchange_timestamp,
473 : &is_idempotent);
474 5 : if (0 > qs)
475 : {
476 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
477 0 : finish_loop (bdc,
478 : TALER_MHD_reply_with_error (
479 0 : rc->connection,
480 : MHD_HTTP_INTERNAL_SERVER_ERROR,
481 : TALER_EC_GENERIC_DB_FETCH_FAILED,
482 : "do_check_deposit_idempotent"));
483 0 : return true;
484 : }
485 5 : if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) ||
486 5 : (! is_idempotent) )
487 5 : return false;
488 0 : bdc->phase = BDC_PHASE_REPLY_SUCCESS;
489 0 : return true;
490 : }
491 :
492 :
493 : /**
494 : * Check the KYC result.
495 : *
496 : * @param bdc storage for request processing
497 : */
498 : static void
499 66 : bdc_phase_check_kyc_result (struct BatchDepositContext *bdc)
500 : {
501 : /* return final positive response */
502 66 : if ( (! bdc->kyc.ok) ||
503 66 : (bdc->bad_kyc_auth) )
504 : {
505 5 : if (check_request_idempotent (bdc))
506 : {
507 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
508 : "Request is idempotent!\n");
509 0 : return;
510 : }
511 : /* KYC required */
512 5 : finish_loop (bdc,
513 : TEH_RESPONSE_reply_kyc_required (
514 5 : bdc->rc->connection,
515 5 : &bdc->nph,
516 5 : &bdc->kyc,
517 5 : bdc->bad_kyc_auth));
518 5 : return;
519 : }
520 61 : bdc->phase = BDC_PHASE_TRANSACT;
521 : }
522 :
523 :
524 : /**
525 : * Function called with the result of a legitimization
526 : * check.
527 : *
528 : * @param cls closure
529 : * @param lcr legitimization check result
530 : */
531 : static void
532 66 : deposit_legi_cb (
533 : void *cls,
534 : const struct TEH_LegitimizationCheckResult *lcr)
535 : {
536 66 : struct BatchDepositContext *bdc = cls;
537 :
538 66 : bdc->lch = NULL;
539 66 : GNUNET_assert (BDC_PHASE_SUSPENDED ==
540 : bdc->phase);
541 66 : MHD_resume_connection (bdc->rc->connection);
542 66 : GNUNET_CONTAINER_DLL_remove (bdc_head,
543 : bdc_tail,
544 : bdc);
545 66 : TALER_MHD_daemon_trigger ();
546 66 : if (NULL != lcr->response)
547 : {
548 0 : bdc->response = lcr->response;
549 0 : bdc->http_status = lcr->http_status;
550 0 : bdc->phase = BDC_PHASE_GENERATE_REPLY_FAILURE;
551 0 : return;
552 : }
553 66 : bdc->kyc = lcr->kyc;
554 66 : bdc->bad_kyc_auth = lcr->bad_kyc_auth;
555 66 : bdc->phase = BDC_PHASE_CHECK_KYC_RESULT;
556 : }
557 :
558 :
559 : /**
560 : * Function called to iterate over KYC-relevant transaction amounts for a
561 : * particular time range. Called within a database transaction, so must
562 : * not start a new one.
563 : *
564 : * @param cls closure, identifies the event type and account to iterate
565 : * over events for
566 : * @param limit maximum time-range for which events should be fetched
567 : * (timestamp in the past)
568 : * @param cb function to call on each event found, events must be returned
569 : * in reverse chronological order
570 : * @param cb_cls closure for @a cb, of type struct AgeWithdrawContext
571 : * @return transaction status
572 : */
573 : static enum GNUNET_DB_QueryStatus
574 0 : deposit_amount_cb (
575 : void *cls,
576 : struct GNUNET_TIME_Absolute limit,
577 : TALER_EXCHANGEDB_KycAmountCallback cb,
578 : void *cb_cls)
579 : {
580 0 : struct BatchDepositContext *bdc = cls;
581 : enum GNUNET_GenericReturnValue ret;
582 : enum GNUNET_DB_QueryStatus qs;
583 :
584 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
585 : "Signaling amount %s for KYC check during deposit\n",
586 : TALER_amount2s (&bdc->accumulated_total_without_fee));
587 0 : ret = cb (cb_cls,
588 0 : &bdc->accumulated_total_without_fee,
589 : bdc->exchange_timestamp.abs_time);
590 0 : GNUNET_break (GNUNET_SYSERR != ret);
591 0 : if (GNUNET_OK != ret)
592 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
593 0 : qs = TEH_plugin->select_deposit_amounts_for_kyc_check (
594 0 : TEH_plugin->cls,
595 0 : &bdc->nph,
596 : limit,
597 : cb,
598 : cb_cls);
599 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
600 : "Got %d additional transactions for this deposit and limit %llu\n",
601 : qs,
602 : (unsigned long long) limit.abs_value_us);
603 0 : GNUNET_break (qs >= 0);
604 0 : return qs;
605 : }
606 :
607 :
608 : /**
609 : * Run KYC check.
610 : *
611 : * @param[in,out] bdc request context
612 : */
613 : static void
614 104 : bdc_phase_kyc (struct BatchDepositContext *bdc)
615 : {
616 104 : if (GNUNET_YES != TEH_enable_kyc)
617 : {
618 38 : bdc->phase++;
619 38 : return;
620 : }
621 66 : TALER_full_payto_normalize_and_hash (bdc->bd.receiver_wire_account,
622 : &bdc->nph);
623 132 : bdc->lch = TEH_legitimization_check2 (
624 66 : &bdc->rc->async_scope_id,
625 : TALER_KYCLOGIC_KYC_TRIGGER_DEPOSIT,
626 : bdc->bd.receiver_wire_account,
627 66 : &bdc->nph,
628 66 : &bdc->bd.merchant_pub,
629 : &deposit_amount_cb,
630 : bdc,
631 : &deposit_legi_cb,
632 : bdc);
633 66 : GNUNET_assert (NULL != bdc->lch);
634 66 : GNUNET_CONTAINER_DLL_insert (bdc_head,
635 : bdc_tail,
636 : bdc);
637 66 : MHD_suspend_connection (bdc->rc->connection);
638 66 : bdc->phase = BDC_PHASE_SUSPENDED;
639 : }
640 :
641 :
642 : /**
643 : * Handle policy.
644 : *
645 : * @param[in,out] bdc request context
646 : */
647 : static void
648 104 : bdc_phase_policy (struct BatchDepositContext *bdc)
649 : {
650 104 : const char *error_hint = NULL;
651 :
652 104 : if (bdc->has_no_policy)
653 : {
654 104 : bdc->phase++;
655 104 : return;
656 : }
657 0 : if (GNUNET_OK !=
658 0 : TALER_extensions_create_policy_details (
659 : TEH_currency,
660 0 : bdc->policy_json,
661 : &bdc->policy_details,
662 : &error_hint))
663 : {
664 0 : GNUNET_break_op (0);
665 0 : finish_loop (bdc,
666 : TALER_MHD_reply_with_error (
667 0 : bdc->rc->connection,
668 : MHD_HTTP_BAD_REQUEST,
669 : TALER_EC_EXCHANGE_DEPOSITS_POLICY_NOT_ACCEPTED,
670 : error_hint));
671 0 : return;
672 : }
673 :
674 0 : TALER_deposit_policy_hash (bdc->policy_json,
675 : &bdc->h_policy);
676 0 : bdc->phase++;
677 : }
678 :
679 :
680 : /**
681 : * Parse per-coin deposit information from @a jcoin
682 : * into @a deposit. Fill in generic information from
683 : * @a ctx.
684 : *
685 : * @param bdc information about the overall batch
686 : * @param jcoin coin data to parse
687 : * @param[out] cdi where to store the result
688 : * @param[out] deposit_fee where to write the deposit fee
689 : * @return #GNUNET_OK on success, #GNUNET_NO if an error was returned,
690 : * #GNUNET_SYSERR on failure and no error could be returned
691 : */
692 : static enum GNUNET_GenericReturnValue
693 106 : parse_coin (const struct BatchDepositContext *bdc,
694 : json_t *jcoin,
695 : struct TALER_EXCHANGEDB_CoinDepositInformation *cdi,
696 : struct TALER_Amount *deposit_fee)
697 : {
698 106 : const struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
699 : struct GNUNET_JSON_Specification spec[] = {
700 106 : TALER_JSON_spec_amount ("contribution",
701 : TEH_currency,
702 : &cdi->amount_with_fee),
703 106 : GNUNET_JSON_spec_fixed_auto ("denom_pub_hash",
704 : &cdi->coin.denom_pub_hash),
705 106 : TALER_JSON_spec_denom_sig ("ub_sig",
706 : &cdi->coin.denom_sig),
707 106 : GNUNET_JSON_spec_fixed_auto ("coin_pub",
708 : &cdi->coin.coin_pub),
709 106 : GNUNET_JSON_spec_mark_optional (
710 106 : GNUNET_JSON_spec_fixed_auto ("h_age_commitment",
711 : &cdi->coin.h_age_commitment),
712 : &cdi->coin.no_age_commitment),
713 106 : GNUNET_JSON_spec_fixed_auto ("coin_sig",
714 : &cdi->csig),
715 106 : GNUNET_JSON_spec_end ()
716 : };
717 : enum GNUNET_GenericReturnValue res;
718 :
719 106 : if (GNUNET_OK !=
720 106 : (res = TALER_MHD_parse_json_data (bdc->rc->connection,
721 : jcoin,
722 : spec)))
723 0 : return res;
724 : /* check denomination exists and is valid */
725 : {
726 : struct TEH_DenominationKey *dk;
727 : MHD_RESULT mret;
728 :
729 106 : dk = TEH_keys_denomination_by_hash (
730 106 : &cdi->coin.denom_pub_hash,
731 106 : bdc->rc->connection,
732 : &mret);
733 106 : if (NULL == dk)
734 : {
735 0 : GNUNET_JSON_parse_free (spec);
736 0 : return (MHD_YES == mret)
737 : ? GNUNET_NO
738 0 : : GNUNET_SYSERR;
739 : }
740 106 : if (0 > TALER_amount_cmp (&dk->meta.value,
741 106 : &cdi->amount_with_fee))
742 : {
743 0 : GNUNET_break_op (0);
744 0 : GNUNET_JSON_parse_free (spec);
745 : return (MHD_YES ==
746 0 : TALER_MHD_reply_with_error (
747 0 : bdc->rc->connection,
748 : MHD_HTTP_BAD_REQUEST,
749 : TALER_EC_EXCHANGE_GENERIC_AMOUNT_EXCEEDS_DENOMINATION_VALUE,
750 : NULL))
751 : ? GNUNET_NO
752 0 : : GNUNET_SYSERR;
753 : }
754 106 : if (GNUNET_TIME_absolute_is_past (dk->meta.expire_deposit.abs_time))
755 : {
756 : /* This denomination is past the expiration time for deposits */
757 0 : GNUNET_JSON_parse_free (spec);
758 : return (MHD_YES ==
759 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
760 0 : bdc->rc->connection,
761 0 : &cdi->coin.denom_pub_hash,
762 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_EXPIRED,
763 : "DEPOSIT"))
764 : ? GNUNET_NO
765 0 : : GNUNET_SYSERR;
766 : }
767 106 : if (GNUNET_TIME_absolute_is_future (dk->meta.start.abs_time))
768 : {
769 : /* This denomination is not yet valid */
770 0 : GNUNET_JSON_parse_free (spec);
771 : return (MHD_YES ==
772 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
773 0 : bdc->rc->connection,
774 0 : &cdi->coin.denom_pub_hash,
775 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_VALIDITY_IN_FUTURE,
776 : "DEPOSIT"))
777 : ? GNUNET_NO
778 0 : : GNUNET_SYSERR;
779 : }
780 106 : if (dk->recoup_possible)
781 : {
782 : /* This denomination has been revoked */
783 0 : GNUNET_JSON_parse_free (spec);
784 : return (MHD_YES ==
785 0 : TEH_RESPONSE_reply_expired_denom_pub_hash (
786 0 : bdc->rc->connection,
787 0 : &cdi->coin.denom_pub_hash,
788 : TALER_EC_EXCHANGE_GENERIC_DENOMINATION_REVOKED,
789 : "DEPOSIT"))
790 : ? GNUNET_NO
791 0 : : GNUNET_SYSERR;
792 : }
793 106 : if (dk->denom_pub.bsign_pub_key->cipher !=
794 106 : cdi->coin.denom_sig.unblinded_sig->cipher)
795 : {
796 : /* denomination cipher and denomination signature cipher not the same */
797 0 : GNUNET_JSON_parse_free (spec);
798 : return (MHD_YES ==
799 0 : TALER_MHD_reply_with_error (
800 0 : bdc->rc->connection,
801 : MHD_HTTP_BAD_REQUEST,
802 : TALER_EC_EXCHANGE_GENERIC_CIPHER_MISMATCH,
803 : NULL))
804 : ? GNUNET_NO
805 0 : : GNUNET_SYSERR;
806 : }
807 :
808 106 : *deposit_fee = dk->meta.fees.deposit;
809 : /* check coin signature */
810 106 : switch (dk->denom_pub.bsign_pub_key->cipher)
811 : {
812 57 : case GNUNET_CRYPTO_BSA_RSA:
813 57 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_RSA]++;
814 57 : break;
815 49 : case GNUNET_CRYPTO_BSA_CS:
816 49 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_CS]++;
817 49 : break;
818 0 : default:
819 0 : break;
820 : }
821 106 : if (GNUNET_YES !=
822 106 : TALER_test_coin_valid (&cdi->coin,
823 106 : &dk->denom_pub))
824 : {
825 0 : TALER_LOG_WARNING ("Invalid coin passed for /batch-deposit\n");
826 0 : GNUNET_JSON_parse_free (spec);
827 : return (MHD_YES ==
828 0 : TALER_MHD_reply_with_error (
829 0 : bdc->rc->connection,
830 : MHD_HTTP_FORBIDDEN,
831 : TALER_EC_EXCHANGE_DENOMINATION_SIGNATURE_INVALID,
832 : NULL))
833 : ? GNUNET_NO
834 0 : : GNUNET_SYSERR;
835 : }
836 : }
837 106 : if (0 < TALER_amount_cmp (deposit_fee,
838 106 : &cdi->amount_with_fee))
839 : {
840 0 : GNUNET_break_op (0);
841 0 : GNUNET_JSON_parse_free (spec);
842 : return (MHD_YES ==
843 0 : TALER_MHD_reply_with_error (
844 0 : bdc->rc->connection,
845 : MHD_HTTP_BAD_REQUEST,
846 : TALER_EC_EXCHANGE_DEPOSIT_NEGATIVE_VALUE_AFTER_FEE,
847 : NULL))
848 : ? GNUNET_NO
849 0 : : GNUNET_SYSERR;
850 : }
851 :
852 106 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
853 106 : if (GNUNET_OK !=
854 212 : TALER_wallet_deposit_verify (
855 106 : &cdi->amount_with_fee,
856 : deposit_fee,
857 : &bdc->h_wire,
858 : &bd->h_contract_terms,
859 : &bd->wallet_data_hash,
860 106 : cdi->coin.no_age_commitment
861 : ? NULL
862 : : &cdi->coin.h_age_commitment,
863 106 : NULL != bdc->policy_json ? &bdc->h_policy : NULL,
864 106 : &cdi->coin.denom_pub_hash,
865 : bd->wallet_timestamp,
866 : &bd->merchant_pub,
867 : bd->refund_deadline,
868 106 : &cdi->coin.coin_pub,
869 106 : &cdi->csig))
870 : {
871 0 : TALER_LOG_WARNING ("Invalid signature on /batch-deposit request\n");
872 0 : GNUNET_JSON_parse_free (spec);
873 : return (MHD_YES ==
874 0 : TALER_MHD_reply_with_error (
875 0 : bdc->rc->connection,
876 : MHD_HTTP_FORBIDDEN,
877 : TALER_EC_EXCHANGE_DEPOSIT_COIN_SIGNATURE_INVALID,
878 0 : TALER_B2S (&cdi->coin.coin_pub)))
879 : ? GNUNET_NO
880 0 : : GNUNET_SYSERR;
881 : }
882 106 : return GNUNET_OK;
883 : }
884 :
885 :
886 : /**
887 : * Run processing phase that parses the request.
888 : *
889 : * @param[in,out] bdc request context
890 : * @param root JSON object that was POSTed
891 : */
892 : static void
893 104 : bdc_phase_parse (struct BatchDepositContext *bdc,
894 : const json_t *root)
895 : {
896 104 : struct TALER_EXCHANGEDB_BatchDeposit *bd = &bdc->bd;
897 : const json_t *coins;
898 : const json_t *policy_json;
899 104 : bool no_refund_deadline = true;
900 : struct GNUNET_JSON_Specification spec[] = {
901 104 : TALER_JSON_spec_full_payto_uri ("merchant_payto_uri",
902 : &bd->receiver_wire_account),
903 104 : GNUNET_JSON_spec_fixed_auto ("wire_salt",
904 : &bd->wire_salt),
905 104 : GNUNET_JSON_spec_fixed_auto ("merchant_pub",
906 : &bd->merchant_pub),
907 104 : GNUNET_JSON_spec_fixed_auto ("merchant_sig",
908 : &bd->merchant_sig),
909 104 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
910 : &bd->h_contract_terms),
911 104 : GNUNET_JSON_spec_mark_optional (
912 104 : GNUNET_JSON_spec_fixed_auto ("wallet_data_hash",
913 : &bd->wallet_data_hash),
914 : &bd->no_wallet_data_hash),
915 104 : GNUNET_JSON_spec_array_const ("coins",
916 : &coins),
917 104 : GNUNET_JSON_spec_mark_optional (
918 : GNUNET_JSON_spec_object_const ("policy",
919 : &policy_json),
920 : &bdc->has_no_policy),
921 104 : GNUNET_JSON_spec_timestamp ("timestamp",
922 : &bd->wallet_timestamp),
923 104 : GNUNET_JSON_spec_mark_optional (
924 : GNUNET_JSON_spec_timestamp ("refund_deadline",
925 : &bd->refund_deadline),
926 : &no_refund_deadline),
927 104 : GNUNET_JSON_spec_timestamp ("wire_transfer_deadline",
928 : &bd->wire_deadline),
929 104 : GNUNET_JSON_spec_end ()
930 : };
931 :
932 : {
933 : enum GNUNET_GenericReturnValue res;
934 :
935 104 : res = TALER_MHD_parse_json_data (bdc->rc->connection,
936 : root,
937 : spec);
938 104 : if (GNUNET_SYSERR == res)
939 : {
940 : /* hard failure */
941 0 : GNUNET_break (0);
942 0 : finish_loop (bdc,
943 : MHD_NO);
944 0 : return;
945 : }
946 104 : if (GNUNET_NO == res)
947 : {
948 : /* failure */
949 0 : GNUNET_break_op (0);
950 0 : finish_loop (bdc,
951 : MHD_YES);
952 0 : return;
953 : }
954 : }
955 104 : if (GNUNET_OK !=
956 104 : TALER_merchant_contract_verify (
957 104 : &bd->h_contract_terms,
958 104 : &bd->merchant_pub,
959 : &bd->merchant_sig))
960 : {
961 0 : GNUNET_break_op (0);
962 0 : GNUNET_JSON_parse_free (spec);
963 0 : finish_loop (bdc,
964 : TALER_MHD_reply_with_error (
965 0 : bdc->rc->connection,
966 : MHD_HTTP_BAD_REQUEST,
967 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
968 : "merchant_sig"));
969 0 : return;
970 : }
971 : bdc->policy_json
972 104 : = json_incref ((json_t *) policy_json);
973 104 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
974 : "Batch deposit into contract %s\n",
975 : GNUNET_h2s (&bd->h_contract_terms.hash));
976 :
977 : /* validate merchant's wire details (as far as we can) */
978 : {
979 : char *emsg;
980 :
981 104 : emsg = TALER_payto_validate (bd->receiver_wire_account);
982 104 : if (NULL != emsg)
983 : {
984 : MHD_RESULT ret;
985 :
986 0 : GNUNET_break_op (0);
987 0 : GNUNET_JSON_parse_free (spec);
988 0 : ret = TALER_MHD_reply_with_error (bdc->rc->connection,
989 : MHD_HTTP_BAD_REQUEST,
990 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
991 : emsg);
992 0 : GNUNET_free (emsg);
993 0 : finish_loop (bdc,
994 : ret);
995 0 : return;
996 : }
997 : }
998 104 : if (GNUNET_TIME_timestamp_cmp (bd->refund_deadline,
999 : >,
1000 : bd->wire_deadline))
1001 : {
1002 0 : GNUNET_break_op (0);
1003 0 : GNUNET_JSON_parse_free (spec);
1004 0 : finish_loop (bdc,
1005 : TALER_MHD_reply_with_error (
1006 0 : bdc->rc->connection,
1007 : MHD_HTTP_BAD_REQUEST,
1008 : TALER_EC_EXCHANGE_DEPOSIT_REFUND_DEADLINE_AFTER_WIRE_DEADLINE,
1009 : NULL));
1010 0 : return;
1011 : }
1012 104 : if (GNUNET_TIME_absolute_is_never (bd->wire_deadline.abs_time))
1013 : {
1014 0 : GNUNET_break_op (0);
1015 0 : GNUNET_JSON_parse_free (spec);
1016 0 : finish_loop (bdc,
1017 : TALER_MHD_reply_with_error (
1018 0 : bdc->rc->connection,
1019 : MHD_HTTP_BAD_REQUEST,
1020 : TALER_EC_EXCHANGE_DEPOSIT_WIRE_DEADLINE_IS_NEVER,
1021 : NULL));
1022 0 : return;
1023 : }
1024 104 : TALER_full_payto_hash (bd->receiver_wire_account,
1025 : &bd->wire_target_h_payto);
1026 104 : TALER_merchant_wire_signature_hash (bd->receiver_wire_account,
1027 104 : &bd->wire_salt,
1028 : &bdc->h_wire);
1029 104 : bd->num_cdis = json_array_size (coins);
1030 104 : if (0 == bd->num_cdis)
1031 : {
1032 0 : GNUNET_break_op (0);
1033 0 : GNUNET_JSON_parse_free (spec);
1034 0 : finish_loop (bdc,
1035 : TALER_MHD_reply_with_error (
1036 0 : bdc->rc->connection,
1037 : MHD_HTTP_BAD_REQUEST,
1038 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1039 : "coins"));
1040 0 : return;
1041 : }
1042 104 : if (TALER_MAX_COINS < bd->num_cdis)
1043 : {
1044 0 : GNUNET_break_op (0);
1045 0 : GNUNET_JSON_parse_free (spec);
1046 0 : finish_loop (bdc,
1047 : TALER_MHD_reply_with_error (
1048 0 : bdc->rc->connection,
1049 : MHD_HTTP_BAD_REQUEST,
1050 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
1051 : "coins"));
1052 0 : return;
1053 : }
1054 :
1055 : bdc->cdis
1056 104 : = GNUNET_new_array (bd->num_cdis,
1057 : struct TALER_EXCHANGEDB_CoinDepositInformation);
1058 : bdc->deposit_fees
1059 104 : = GNUNET_new_array (bd->num_cdis,
1060 : struct TALER_Amount);
1061 104 : bd->cdis = bdc->cdis;
1062 210 : for (unsigned i = 0; i<bd->num_cdis; i++)
1063 : {
1064 : struct TALER_Amount amount_without_fee;
1065 : enum GNUNET_GenericReturnValue res;
1066 :
1067 106 : res = parse_coin (bdc,
1068 : json_array_get (coins,
1069 : i),
1070 106 : &bdc->cdis[i],
1071 106 : &bdc->deposit_fees[i]);
1072 106 : if (GNUNET_OK != res)
1073 : {
1074 0 : finish_loop (bdc,
1075 : (GNUNET_NO == res)
1076 : ? MHD_YES
1077 : : MHD_NO);
1078 0 : return;
1079 : }
1080 106 : GNUNET_assert (0 <=
1081 : TALER_amount_subtract (
1082 : &amount_without_fee,
1083 : &bdc->cdis[i].amount_with_fee,
1084 : &bdc->deposit_fees[i]));
1085 :
1086 106 : GNUNET_assert (0 <=
1087 : TALER_amount_add (
1088 : &bdc->accumulated_total_without_fee,
1089 : &bdc->accumulated_total_without_fee,
1090 : &amount_without_fee));
1091 : }
1092 :
1093 104 : GNUNET_JSON_parse_free (spec);
1094 104 : bdc->phase++;
1095 : }
1096 :
1097 :
1098 : /**
1099 : * Function called to clean up a context.
1100 : *
1101 : * @param rc request context with data to clean up
1102 : */
1103 : static void
1104 104 : bdc_cleaner (struct TEH_RequestContext *rc)
1105 : {
1106 104 : struct BatchDepositContext *bdc = rc->rh_ctx;
1107 :
1108 104 : if (NULL != bdc->lch)
1109 : {
1110 0 : TEH_legitimization_check_cancel (bdc->lch);
1111 0 : bdc->lch = NULL;
1112 : }
1113 210 : for (unsigned int i = 0; i<bdc->bd.num_cdis; i++)
1114 106 : TALER_denom_sig_free (&bdc->cdis[i].coin.denom_sig);
1115 104 : GNUNET_free (bdc->cdis);
1116 104 : GNUNET_free (bdc->deposit_fees);
1117 104 : json_decref (bdc->policy_json);
1118 104 : GNUNET_free (bdc);
1119 104 : }
1120 :
1121 :
1122 : MHD_RESULT
1123 170 : TEH_handler_batch_deposit (struct TEH_RequestContext *rc,
1124 : const json_t *root,
1125 : const char *const args[])
1126 : {
1127 170 : struct BatchDepositContext *bdc = rc->rh_ctx;
1128 :
1129 : (void) args;
1130 170 : if (NULL == bdc)
1131 : {
1132 104 : bdc = GNUNET_new (struct BatchDepositContext);
1133 104 : bdc->rc = rc;
1134 104 : rc->rh_ctx = bdc;
1135 104 : rc->rh_cleaner = &bdc_cleaner;
1136 104 : bdc->phase = BDC_PHASE_PARSE;
1137 104 : bdc->exchange_timestamp = GNUNET_TIME_timestamp_get ();
1138 104 : GNUNET_assert (GNUNET_OK ==
1139 : TALER_amount_set_zero (TEH_currency,
1140 : &bdc->accumulated_total_without_fee));
1141 : }
1142 : while (1)
1143 : {
1144 737 : switch (bdc->phase)
1145 : {
1146 0 : case BDC_PHASE_INIT:
1147 0 : GNUNET_break (0);
1148 0 : bdc->phase = BDC_PHASE_RETURN_NO;
1149 0 : break;
1150 104 : case BDC_PHASE_PARSE:
1151 104 : bdc_phase_parse (bdc,
1152 : root);
1153 104 : break;
1154 104 : case BDC_PHASE_POLICY:
1155 104 : bdc_phase_policy (bdc);
1156 104 : break;
1157 104 : case BDC_PHASE_KYC:
1158 104 : bdc_phase_kyc (bdc);
1159 104 : break;
1160 99 : case BDC_PHASE_TRANSACT:
1161 99 : bdc_phase_transact (bdc);
1162 99 : break;
1163 90 : case BDC_PHASE_REPLY_SUCCESS:
1164 90 : bdc_phase_reply_success (bdc);
1165 90 : break;
1166 66 : case BDC_PHASE_SUSPENDED:
1167 66 : return MHD_YES;
1168 66 : case BDC_PHASE_CHECK_KYC_RESULT:
1169 66 : bdc_phase_check_kyc_result (bdc);
1170 66 : break;
1171 0 : case BDC_PHASE_GENERATE_REPLY_FAILURE:
1172 0 : return MHD_queue_response (bdc->rc->connection,
1173 : bdc->http_status,
1174 : bdc->response);
1175 104 : case BDC_PHASE_RETURN_YES:
1176 104 : return MHD_YES;
1177 0 : case BDC_PHASE_RETURN_NO:
1178 0 : return MHD_NO;
1179 : }
1180 : }
1181 : }
1182 :
1183 :
1184 : /* end of taler-exchange-httpd_batch-deposit.c */
|