Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2022-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_purses_merge.c
18 : * @brief Handle /purses/$PID/merge requests; parses the POST and JSON and
19 : * verifies the reserve signature before handing things off
20 : * to the database.
21 : * @author Christian Grothoff
22 : */
23 : #include "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <gnunet/gnunet_json_lib.h>
26 : #include <jansson.h>
27 : #include <microhttpd.h>
28 : #include <pthread.h>
29 : #include "taler/taler_dbevents.h"
30 : #include "taler/taler_json_lib.h"
31 : #include "taler/taler_kyclogic_lib.h"
32 : #include "taler/taler_mhd_lib.h"
33 : #include "taler-exchange-httpd_common_kyc.h"
34 : #include "taler-exchange-httpd_purses_merge.h"
35 : #include "taler-exchange-httpd_responses.h"
36 : #include "taler/taler_exchangedb_lib.h"
37 : #include "taler-exchange-httpd_keys.h"
38 :
39 :
40 : /**
41 : * Closure for #merge_transaction.
42 : */
43 : struct PurseMergeContext
44 : {
45 :
46 : /**
47 : * Kept in a DLL.
48 : */
49 : struct PurseMergeContext *next;
50 :
51 : /**
52 : * Kept in a DLL.
53 : */
54 : struct PurseMergeContext *prev;
55 :
56 : /**
57 : * Our request.
58 : */
59 : struct TEH_RequestContext *rc;
60 :
61 : /**
62 : * Handle for the legitimization check.
63 : */
64 : struct TEH_LegitimizationCheckHandle *lch;
65 :
66 : /**
67 : * Fees that apply to this operation.
68 : */
69 : const struct TALER_WireFeeSet *wf;
70 :
71 : /**
72 : * Base URL of the exchange provider hosting the reserve.
73 : */
74 : char *provider_url;
75 :
76 : /**
77 : * URI of the account the purse is to be merged into.
78 : * Must be of the form 'payto://taler-reserve/$EXCHANGE_URL/RESERVE_PUB'.
79 : */
80 : struct TALER_NormalizedPayto payto_uri;
81 :
82 : /**
83 : * Response to return, if set.
84 : */
85 : struct MHD_Response *response;
86 :
87 : /**
88 : * Public key of the purse we are creating.
89 : */
90 : struct TALER_PurseContractPublicKeyP purse_pub;
91 :
92 : /**
93 : * Total amount to be put into the purse.
94 : */
95 : struct TALER_Amount target_amount;
96 :
97 : /**
98 : * Current amount in the purse.
99 : */
100 : struct TALER_Amount balance;
101 :
102 : /**
103 : * When should the purse expire.
104 : */
105 : struct GNUNET_TIME_Timestamp purse_expiration;
106 :
107 : /**
108 : * When the client signed the merge.
109 : */
110 : struct GNUNET_TIME_Timestamp merge_timestamp;
111 :
112 : /**
113 : * Our current time.
114 : */
115 : struct GNUNET_TIME_Timestamp exchange_timestamp;
116 :
117 : /**
118 : * Merge key for the purse.
119 : */
120 : struct TALER_PurseMergePublicKeyP merge_pub;
121 :
122 : /**
123 : * Signature of the reservce affiming this request.
124 : */
125 : struct TALER_ReserveSignatureP reserve_sig;
126 :
127 : /**
128 : * Signature of the client affiming the merge.
129 : */
130 : struct TALER_PurseMergeSignatureP merge_sig;
131 :
132 : /**
133 : * Public key of the reserve (account), as extracted from @e payto_uri.
134 : */
135 : union TALER_AccountPublicKeyP account_pub;
136 :
137 : /**
138 : * Hash of the contract terms of the purse.
139 : */
140 : struct TALER_PrivateContractHashP h_contract_terms;
141 :
142 : /**
143 : * Hash of the @e payto_uri.
144 : */
145 : struct TALER_NormalizedPaytoHashP h_payto;
146 :
147 : /**
148 : * KYC status of the operation.
149 : */
150 : struct TALER_EXCHANGEDB_KycStatus kyc;
151 :
152 : /**
153 : * HTTP status to return with @e response, or 0.
154 : */
155 : unsigned int http_status;
156 :
157 : /**
158 : * Minimum age for deposits into this purse.
159 : */
160 : uint32_t min_age;
161 :
162 : /**
163 : * Set to true if this request was suspended.
164 : */
165 : bool suspended;
166 : };
167 :
168 :
169 : /**
170 : * Kept in a DLL.
171 : */
172 : static struct PurseMergeContext *pmc_head;
173 :
174 : /**
175 : * Kept in a DLL.
176 : */
177 : static struct PurseMergeContext *pmc_tail;
178 :
179 :
180 : void
181 21 : TEH_purses_merge_cleanup ()
182 : {
183 : struct PurseMergeContext *pmc;
184 :
185 21 : while (NULL != (pmc = pmc_head))
186 : {
187 0 : GNUNET_CONTAINER_DLL_remove (pmc_head,
188 : pmc_tail,
189 : pmc);
190 0 : MHD_resume_connection (pmc->rc->connection);
191 : }
192 21 : }
193 :
194 :
195 : /**
196 : * Function called with the result of a legitimization
197 : * check.
198 : *
199 : * @param cls closure
200 : * @param lcr legitimization check result
201 : */
202 : static void
203 6 : legi_result_cb (
204 : void *cls,
205 : const struct TEH_LegitimizationCheckResult *lcr)
206 : {
207 6 : struct PurseMergeContext *pmc = cls;
208 :
209 6 : pmc->lch = NULL;
210 6 : MHD_resume_connection (pmc->rc->connection);
211 6 : GNUNET_CONTAINER_DLL_remove (pmc_head,
212 : pmc_tail,
213 : pmc);
214 6 : TALER_MHD_daemon_trigger ();
215 6 : if (NULL != lcr->response)
216 : {
217 0 : pmc->response = lcr->response;
218 0 : pmc->http_status = lcr->http_status;
219 0 : return;
220 : }
221 6 : pmc->kyc = lcr->kyc;
222 : }
223 :
224 :
225 : /**
226 : * Send confirmation of purse creation success to client.
227 : *
228 : * @param pmc details about the request that succeeded
229 : * @return MHD result code
230 : */
231 : static MHD_RESULT
232 3 : reply_merge_success (const struct PurseMergeContext *pmc)
233 : {
234 3 : struct MHD_Connection *connection = pmc->rc->connection;
235 : struct TALER_ExchangePublicKeyP pub;
236 : struct TALER_ExchangeSignatureP sig;
237 : enum TALER_ErrorCode ec;
238 : struct TALER_Amount merge_amount;
239 :
240 3 : if (0 <
241 3 : TALER_amount_cmp (&pmc->balance,
242 : &pmc->target_amount))
243 : {
244 0 : GNUNET_break (0);
245 0 : return TALER_MHD_REPLY_JSON_PACK (
246 : connection,
247 : MHD_HTTP_INTERNAL_SERVER_ERROR,
248 : TALER_JSON_pack_amount ("balance",
249 : &pmc->balance),
250 : TALER_JSON_pack_amount ("target_amount",
251 : &pmc->target_amount));
252 : }
253 3 : if ( (NULL == pmc->provider_url) ||
254 0 : (0 == strcmp (pmc->provider_url,
255 : TEH_base_url)) )
256 : {
257 : /* wad fee is always zero if we stay at our own exchange */
258 3 : merge_amount = pmc->target_amount;
259 : }
260 : else
261 : {
262 : #if WAD_NOT_IMPLEMENTED /* #7271 */
263 : /* FIXME: figure out partner, lookup wad fee by partner! #7271 */
264 : if (0 >
265 : TALER_amount_subtract (&merge_amount,
266 : &pmc->target_amount,
267 : &wad_fee))
268 : {
269 : GNUNET_assert (GNUNET_OK ==
270 : TALER_amount_set_zero (TEH_currency,
271 : &merge_amount));
272 : }
273 : #else
274 0 : merge_amount = pmc->target_amount;
275 : #endif
276 : }
277 3 : if (TALER_EC_NONE !=
278 3 : (ec = TALER_exchange_online_purse_merged_sign (
279 : &TEH_keys_exchange_sign_,
280 : pmc->exchange_timestamp,
281 : pmc->purse_expiration,
282 : &merge_amount,
283 : &pmc->purse_pub,
284 : &pmc->h_contract_terms,
285 : &pmc->account_pub.reserve_pub,
286 3 : (NULL != pmc->provider_url)
287 : ? pmc->provider_url
288 : : TEH_base_url,
289 : &pub,
290 : &sig)))
291 : {
292 0 : GNUNET_break (0);
293 0 : return TALER_MHD_reply_with_ec (connection,
294 : ec,
295 : NULL);
296 : }
297 3 : return TALER_MHD_REPLY_JSON_PACK (
298 : connection,
299 : MHD_HTTP_OK,
300 : TALER_JSON_pack_amount ("merge_amount",
301 : &merge_amount),
302 : GNUNET_JSON_pack_timestamp ("exchange_timestamp",
303 : pmc->exchange_timestamp),
304 : GNUNET_JSON_pack_data_auto ("exchange_sig",
305 : &sig),
306 : GNUNET_JSON_pack_data_auto ("exchange_pub",
307 : &pub));
308 : }
309 :
310 :
311 : /**
312 : * Function called to iterate over KYC-relevant
313 : * transaction amounts for a particular time range.
314 : * Called within a database transaction, so must
315 : * not start a new one.
316 : *
317 : * @param cls a `struct PurseMergeContext`
318 : * @param limit maximum time-range for which events
319 : * should be fetched (timestamp in the past)
320 : * @param cb function to call on each event found,
321 : * events must be returned in reverse chronological
322 : * order
323 : * @param cb_cls closure for @a cb
324 : * @return transaction status
325 : */
326 : static enum GNUNET_DB_QueryStatus
327 2 : amount_iterator (void *cls,
328 : struct GNUNET_TIME_Absolute limit,
329 : TALER_EXCHANGEDB_KycAmountCallback cb,
330 : void *cb_cls)
331 : {
332 2 : struct PurseMergeContext *pmc = cls;
333 : enum GNUNET_GenericReturnValue ret;
334 : enum GNUNET_DB_QueryStatus qs;
335 :
336 2 : ret = cb (cb_cls,
337 2 : &pmc->target_amount,
338 : GNUNET_TIME_absolute_get ());
339 2 : GNUNET_break (GNUNET_SYSERR != ret);
340 2 : if (GNUNET_OK != ret)
341 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
342 2 : qs = TEH_plugin->select_merge_amounts_for_kyc_check (
343 2 : TEH_plugin->cls,
344 2 : &pmc->h_payto,
345 : limit,
346 : cb,
347 : cb_cls);
348 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
349 : "Got %d additional transactions for this merge and limit %llu\n",
350 : qs,
351 : (unsigned long long) limit.abs_value_us);
352 2 : GNUNET_break (qs >= 0);
353 2 : return qs;
354 : }
355 :
356 :
357 : /**
358 : * Execute database transaction for /purses/$PID/merge. Runs the transaction
359 : * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
360 : * a MHD response. IF it returns an hard error, the transaction logic MUST
361 : * queue a MHD response and set @a mhd_ret. IF it returns the soft error
362 : * code, the function MAY be called again to retry and MUST not queue a MHD
363 : * response.
364 : *
365 : * @param cls a `struct PurseMergeContext`
366 : * @param connection MHD request context
367 : * @param[out] mhd_ret set to MHD status on error
368 : * @return transaction status
369 : */
370 : static enum GNUNET_DB_QueryStatus
371 5 : merge_transaction (void *cls,
372 : struct MHD_Connection *connection,
373 : MHD_RESULT *mhd_ret)
374 : {
375 5 : struct PurseMergeContext *pmc = cls;
376 : enum GNUNET_DB_QueryStatus qs;
377 5 : bool in_conflict = true;
378 5 : bool no_balance = true;
379 5 : bool no_partner = true;
380 :
381 5 : qs = TEH_plugin->do_purse_merge (
382 5 : TEH_plugin->cls,
383 5 : &pmc->purse_pub,
384 5 : &pmc->merge_sig,
385 : pmc->merge_timestamp,
386 5 : &pmc->reserve_sig,
387 5 : pmc->provider_url,
388 5 : &pmc->account_pub.reserve_pub,
389 : &no_partner,
390 : &no_balance,
391 : &in_conflict);
392 5 : if (qs < 0)
393 : {
394 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
395 0 : return qs;
396 0 : GNUNET_break (0);
397 0 : *mhd_ret =
398 0 : TALER_MHD_reply_with_error (connection,
399 : MHD_HTTP_INTERNAL_SERVER_ERROR,
400 : TALER_EC_GENERIC_DB_STORE_FAILED,
401 : "purse merge");
402 0 : return qs;
403 : }
404 5 : if (no_partner)
405 : {
406 0 : *mhd_ret =
407 0 : TALER_MHD_reply_with_error (connection,
408 : MHD_HTTP_NOT_FOUND,
409 : TALER_EC_EXCHANGE_MERGE_PURSE_PARTNER_UNKNOWN,
410 0 : pmc->provider_url);
411 0 : return GNUNET_DB_STATUS_HARD_ERROR;
412 : }
413 5 : if (no_balance)
414 : {
415 0 : *mhd_ret =
416 0 : TALER_MHD_reply_with_error (connection,
417 : MHD_HTTP_PAYMENT_REQUIRED,
418 : TALER_EC_EXCHANGE_PURSE_NOT_FULL,
419 : NULL);
420 0 : return GNUNET_DB_STATUS_HARD_ERROR;
421 : }
422 5 : if (in_conflict)
423 : {
424 : struct TALER_PurseMergeSignatureP merge_sig;
425 : struct GNUNET_TIME_Timestamp merge_timestamp;
426 2 : char *partner_url = NULL;
427 : struct TALER_ReservePublicKeyP reserve_pub;
428 : bool refunded;
429 :
430 2 : qs = TEH_plugin->select_purse_merge (TEH_plugin->cls,
431 2 : &pmc->purse_pub,
432 : &merge_sig,
433 : &merge_timestamp,
434 : &partner_url,
435 : &reserve_pub,
436 : &refunded);
437 2 : if (qs <= 0)
438 : {
439 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
440 0 : return qs;
441 0 : TALER_LOG_WARNING (
442 : "Failed to fetch merge purse information from database\n");
443 0 : *mhd_ret =
444 0 : TALER_MHD_reply_with_error (connection,
445 : MHD_HTTP_INTERNAL_SERVER_ERROR,
446 : TALER_EC_GENERIC_DB_FETCH_FAILED,
447 : "select purse merge");
448 0 : return qs;
449 : }
450 2 : if (refunded)
451 : {
452 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
453 : "Purse was already refunded\n");
454 0 : *mhd_ret = TALER_MHD_reply_with_error (connection,
455 : MHD_HTTP_GONE,
456 : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
457 : NULL);
458 0 : GNUNET_free (partner_url);
459 0 : return GNUNET_DB_STATUS_HARD_ERROR;
460 : }
461 2 : if (0 !=
462 2 : GNUNET_memcmp (&merge_sig,
463 : &pmc->merge_sig))
464 : {
465 2 : *mhd_ret = TALER_MHD_REPLY_JSON_PACK (
466 : connection,
467 : MHD_HTTP_CONFLICT,
468 : GNUNET_JSON_pack_timestamp ("merge_timestamp",
469 : merge_timestamp),
470 : GNUNET_JSON_pack_data_auto ("merge_sig",
471 : &merge_sig),
472 : GNUNET_JSON_pack_allow_null (
473 : GNUNET_JSON_pack_string ("partner_url",
474 : partner_url)),
475 : GNUNET_JSON_pack_data_auto ("reserve_pub",
476 : &reserve_pub));
477 2 : GNUNET_free (partner_url);
478 2 : return GNUNET_DB_STATUS_HARD_ERROR;
479 : }
480 : /* idempotent! */
481 0 : *mhd_ret = reply_merge_success (pmc);
482 0 : GNUNET_free (partner_url);
483 0 : return GNUNET_DB_STATUS_HARD_ERROR;
484 : }
485 :
486 3 : return qs;
487 : }
488 :
489 :
490 : /**
491 : * Purse-merge-specific cleanup routine. Function called
492 : * upon completion of the request that should
493 : * clean up @a rh_ctx. Can be NULL.
494 : *
495 : * @param rc request context to clean up
496 : */
497 : static void
498 6 : clean_purse_merge_rc (struct TEH_RequestContext *rc)
499 : {
500 6 : struct PurseMergeContext *pmc = rc->rh_ctx;
501 :
502 6 : if (NULL != pmc->lch)
503 : {
504 0 : TEH_legitimization_check_cancel (pmc->lch);
505 0 : pmc->lch = NULL;
506 : }
507 6 : GNUNET_free (pmc->provider_url);
508 6 : GNUNET_free (pmc);
509 6 : }
510 :
511 :
512 : MHD_RESULT
513 12 : TEH_handler_purses_merge (
514 : struct TEH_RequestContext *rc,
515 : const struct TALER_PurseContractPublicKeyP *purse_pub,
516 : const json_t *root)
517 : {
518 12 : struct PurseMergeContext *pmc = rc->rh_ctx;
519 :
520 12 : if (NULL == pmc)
521 : {
522 6 : pmc = GNUNET_new (struct PurseMergeContext);
523 6 : rc->rh_ctx = pmc;
524 6 : rc->rh_cleaner = &clean_purse_merge_rc;
525 6 : pmc->rc = rc;
526 6 : pmc->purse_pub = *purse_pub;
527 : pmc->exchange_timestamp
528 6 : = GNUNET_TIME_timestamp_get ();
529 :
530 : {
531 : struct GNUNET_JSON_Specification spec[] = {
532 6 : TALER_JSON_spec_normalized_payto_uri ("payto_uri",
533 : &pmc->payto_uri),
534 6 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
535 : &pmc->reserve_sig),
536 6 : GNUNET_JSON_spec_fixed_auto ("merge_sig",
537 : &pmc->merge_sig),
538 6 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
539 : &pmc->merge_timestamp),
540 6 : GNUNET_JSON_spec_end ()
541 : };
542 : enum GNUNET_GenericReturnValue res;
543 :
544 6 : res = TALER_MHD_parse_json_data (rc->connection,
545 : root,
546 : spec);
547 6 : if (GNUNET_SYSERR == res)
548 : {
549 0 : GNUNET_break (0);
550 0 : return MHD_NO; /* hard failure */
551 : }
552 6 : if (GNUNET_NO == res)
553 : {
554 0 : GNUNET_break_op (0);
555 0 : return MHD_YES; /* failure */
556 : }
557 : }
558 :
559 : {
560 : struct TALER_PurseContractSignatureP purse_sig;
561 : enum GNUNET_DB_QueryStatus qs;
562 :
563 : /* Fetch purse details */
564 6 : qs = TEH_plugin->get_purse_request (
565 6 : TEH_plugin->cls,
566 6 : &pmc->purse_pub,
567 : &pmc->merge_pub,
568 : &pmc->purse_expiration,
569 : &pmc->h_contract_terms,
570 : &pmc->min_age,
571 : &pmc->target_amount,
572 : &pmc->balance,
573 : &purse_sig);
574 6 : switch (qs)
575 : {
576 0 : case GNUNET_DB_STATUS_HARD_ERROR:
577 0 : GNUNET_break (0);
578 0 : return TALER_MHD_reply_with_error (
579 : rc->connection,
580 : MHD_HTTP_INTERNAL_SERVER_ERROR,
581 : TALER_EC_GENERIC_DB_FETCH_FAILED,
582 : "select purse request");
583 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
584 0 : GNUNET_break (0);
585 0 : return TALER_MHD_reply_with_error (
586 : rc->connection,
587 : MHD_HTTP_INTERNAL_SERVER_ERROR,
588 : TALER_EC_GENERIC_DB_FETCH_FAILED,
589 : "select purse request");
590 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
591 0 : return TALER_MHD_reply_with_error (
592 : rc->connection,
593 : MHD_HTTP_NOT_FOUND,
594 : TALER_EC_EXCHANGE_GENERIC_PURSE_UNKNOWN,
595 : NULL);
596 6 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
597 : /* continued below */
598 6 : break;
599 : }
600 : }
601 :
602 : /* check signatures */
603 6 : if (GNUNET_OK !=
604 6 : TALER_wallet_purse_merge_verify (
605 : pmc->payto_uri,
606 : pmc->merge_timestamp,
607 6 : &pmc->purse_pub,
608 6 : &pmc->merge_pub,
609 6 : &pmc->merge_sig))
610 : {
611 0 : GNUNET_break_op (0);
612 0 : return TALER_MHD_reply_with_error (
613 : rc->connection,
614 : MHD_HTTP_FORBIDDEN,
615 : TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_MERGE_SIGNATURE,
616 : NULL);
617 : }
618 :
619 : /* parse 'payto_uri' into pmc->account_pub and provider_url */
620 6 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
621 : "Received payto: `%s'\n",
622 : pmc->payto_uri.normalized_payto);
623 6 : if ( (0 != strncmp (pmc->payto_uri.normalized_payto,
624 : "payto://taler-reserve/",
625 6 : strlen ("payto://taler-reserve/"))) &&
626 6 : (0 != strncmp (pmc->payto_uri.normalized_payto,
627 : "payto://taler-reserve-http/",
628 : strlen ("payto://taler-reserve-http/"))) )
629 : {
630 0 : GNUNET_break_op (0);
631 0 : return TALER_MHD_reply_with_error (
632 : rc->connection,
633 : MHD_HTTP_BAD_REQUEST,
634 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
635 : "payto_uri");
636 : }
637 :
638 : {
639 : bool http;
640 : const char *host;
641 : const char *slash;
642 :
643 6 : http = (0 == strncmp (pmc->payto_uri.normalized_payto,
644 : "payto://taler-reserve-http/",
645 : strlen ("payto://taler-reserve-http/")));
646 12 : host = &pmc->payto_uri.normalized_payto[http
647 : ? strlen ("payto://taler-reserve-http/")
648 6 : : strlen ("payto://taler-reserve/")];
649 6 : slash = strchr (host,
650 : '/');
651 6 : if (NULL == slash)
652 : {
653 0 : GNUNET_break_op (0);
654 0 : return TALER_MHD_reply_with_error (
655 : rc->connection,
656 : MHD_HTTP_BAD_REQUEST,
657 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
658 : "payto_uri");
659 : }
660 6 : GNUNET_asprintf (&pmc->provider_url,
661 : "%s://%.*s/",
662 : http ? "http" : "https",
663 6 : (int) (slash - host),
664 : host);
665 6 : slash++;
666 6 : if (GNUNET_OK !=
667 6 : GNUNET_STRINGS_string_to_data (
668 : slash,
669 : strlen (slash),
670 6 : &pmc->account_pub.reserve_pub,
671 : sizeof (pmc->account_pub.reserve_pub)))
672 : {
673 0 : GNUNET_break_op (0);
674 0 : return TALER_MHD_reply_with_error (
675 : rc->connection,
676 : MHD_HTTP_BAD_REQUEST,
677 : TALER_EC_GENERIC_PARAMETER_MALFORMED,
678 : "payto_uri");
679 : }
680 : }
681 6 : TALER_normalized_payto_hash (pmc->payto_uri,
682 : &pmc->h_payto);
683 6 : if (0 == strcmp (pmc->provider_url,
684 : TEH_base_url))
685 : {
686 : /* we use NULL to represent 'self' as the provider */
687 6 : GNUNET_free (pmc->provider_url);
688 : }
689 : else
690 : {
691 0 : char *method = GNUNET_strdup ("FIXME-WAD #7271");
692 :
693 : /* FIXME-#7271: lookup wire method by pmc.provider_url! */
694 0 : pmc->wf = TEH_wire_fees_by_time (pmc->exchange_timestamp,
695 : method);
696 0 : if (NULL == pmc->wf)
697 : {
698 : MHD_RESULT res;
699 :
700 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
701 : "Cannot merge purse: wire fees not configured!\n");
702 0 : res = TALER_MHD_reply_with_error (
703 : rc->connection,
704 : MHD_HTTP_INTERNAL_SERVER_ERROR,
705 : TALER_EC_EXCHANGE_GENERIC_WIRE_FEES_MISSING,
706 : method);
707 0 : GNUNET_free (method);
708 0 : return res;
709 : }
710 0 : GNUNET_free (method);
711 : }
712 :
713 : {
714 : struct TALER_Amount zero_purse_fee;
715 :
716 6 : GNUNET_assert (GNUNET_OK ==
717 : TALER_amount_set_zero (
718 : pmc->target_amount.currency,
719 : &zero_purse_fee));
720 6 : if (GNUNET_OK !=
721 6 : TALER_wallet_account_merge_verify (
722 : pmc->merge_timestamp,
723 6 : &pmc->purse_pub,
724 : pmc->purse_expiration,
725 6 : &pmc->h_contract_terms,
726 6 : &pmc->target_amount,
727 : &zero_purse_fee,
728 : pmc->min_age,
729 : TALER_WAMF_MODE_MERGE_FULLY_PAID_PURSE,
730 6 : &pmc->account_pub.reserve_pub,
731 6 : &pmc->reserve_sig))
732 : {
733 0 : GNUNET_break_op (0);
734 0 : return TALER_MHD_reply_with_error (
735 : rc->connection,
736 : MHD_HTTP_FORBIDDEN,
737 : TALER_EC_EXCHANGE_PURSE_MERGE_INVALID_RESERVE_SIGNATURE,
738 : NULL);
739 : }
740 : }
741 : {
742 : struct TALER_FullPayto fake_full_payto;
743 :
744 6 : GNUNET_asprintf (&fake_full_payto.full_payto,
745 : "%s?receiver-name=wallet",
746 : pmc->payto_uri.normalized_payto);
747 12 : pmc->lch = TEH_legitimization_check (
748 6 : &rc->async_scope_id,
749 : TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
750 : fake_full_payto,
751 6 : &pmc->h_payto,
752 6 : &pmc->account_pub,
753 : &amount_iterator,
754 : pmc,
755 : &legi_result_cb,
756 : pmc);
757 6 : GNUNET_free (fake_full_payto.full_payto);
758 : }
759 6 : GNUNET_assert (NULL != pmc->lch);
760 6 : MHD_suspend_connection (rc->connection);
761 6 : GNUNET_CONTAINER_DLL_insert (pmc_head,
762 : pmc_tail,
763 : pmc);
764 6 : return MHD_YES;
765 : }
766 6 : if (NULL != pmc->response)
767 : {
768 0 : return MHD_queue_response (rc->connection,
769 : pmc->http_status,
770 : pmc->response);
771 : }
772 6 : if (! pmc->kyc.ok)
773 1 : return TEH_RESPONSE_reply_kyc_required (
774 : rc->connection,
775 1 : &pmc->h_payto,
776 1 : &pmc->kyc,
777 : false);
778 :
779 : /* execute merge transaction */
780 : {
781 : MHD_RESULT mhd_ret;
782 :
783 5 : if (GNUNET_OK !=
784 5 : TEH_DB_run_transaction (rc->connection,
785 : "execute purse merge",
786 : TEH_MT_REQUEST_PURSE_MERGE,
787 : &mhd_ret,
788 : &merge_transaction,
789 : pmc))
790 : {
791 2 : return mhd_ret;
792 : }
793 : }
794 :
795 : {
796 3 : struct TALER_PurseEventP rep = {
797 3 : .header.size = htons (sizeof (rep)),
798 3 : .header.type = htons (TALER_DBEVENT_EXCHANGE_PURSE_MERGED),
799 : .purse_pub = pmc->purse_pub
800 : };
801 :
802 3 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
803 : "Notifying about purse merge\n");
804 3 : TEH_plugin->event_notify (TEH_plugin->cls,
805 : &rep.header,
806 : NULL,
807 : 0);
808 : }
809 :
810 : /* generate regular response */
811 3 : return reply_merge_success (pmc);
812 : }
813 :
814 :
815 : /* end of taler-exchange-httpd_purses_merge.c */
|