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_reserves_purse.c
18 : * @brief Handle /reserves/$RID/purse requests; parses the POST and JSON and
19 : * verifies the coin 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_json_lib.h"
30 : #include "taler/taler_kyclogic_lib.h"
31 : #include "taler/taler_mhd_lib.h"
32 : #include "taler-exchange-httpd_common_kyc.h"
33 : #include "taler-exchange-httpd_reserves_purse.h"
34 : #include "taler-exchange-httpd_responses.h"
35 : #include "taler/taler_exchangedb_lib.h"
36 : #include "taler-exchange-httpd_keys.h"
37 :
38 :
39 : /**
40 : * Closure for #purse_transaction.
41 : */
42 : struct ReservePurseContext
43 : {
44 :
45 : /**
46 : * Kept in a DLL.
47 : */
48 : struct ReservePurseContext *next;
49 :
50 : /**
51 : * Kept in a DLL.
52 : */
53 : struct ReservePurseContext *prev;
54 :
55 : /**
56 : * Our request context.
57 : */
58 : struct TEH_RequestContext *rc;
59 :
60 : /**
61 : * Handle for legitimization check.
62 : */
63 : struct TEH_LegitimizationCheckHandle *lch;
64 :
65 : /**
66 : * Response to return. Note that the response must
67 : * be queued or destroyed by the callee. NULL
68 : * if the legitimization check was successful and the handler should return
69 : * a handler-specific result.
70 : */
71 : struct MHD_Response *response;
72 :
73 : /**
74 : * Payto URI for the reserve.
75 : */
76 : struct TALER_NormalizedPayto payto_uri;
77 :
78 : /**
79 : * Public key of the account (reserve) we are creating a purse for.
80 : */
81 : union TALER_AccountPublicKeyP account_pub;
82 :
83 : /**
84 : * Signature of the reserve affirming the merge.
85 : */
86 : struct TALER_ReserveSignatureP reserve_sig;
87 :
88 : /**
89 : * Purse fee the client is willing to pay.
90 : */
91 : struct TALER_Amount purse_fee;
92 :
93 : /**
94 : * Total amount already put into the purse.
95 : */
96 : struct TALER_Amount deposit_total;
97 :
98 : /**
99 : * Merge time.
100 : */
101 : struct GNUNET_TIME_Timestamp merge_timestamp;
102 :
103 : /**
104 : * Our current time.
105 : */
106 : struct GNUNET_TIME_Timestamp exchange_timestamp;
107 :
108 : /**
109 : * Details about an encrypted contract, if any.
110 : */
111 : struct TALER_EncryptedContract econtract;
112 :
113 : /**
114 : * Merge key for the purse.
115 : */
116 : struct TALER_PurseMergePublicKeyP merge_pub;
117 :
118 : /**
119 : * Merge affirmation by the @e merge_pub.
120 : */
121 : struct TALER_PurseMergeSignatureP merge_sig;
122 :
123 : /**
124 : * Signature of the client affiming this request.
125 : */
126 : struct TALER_PurseContractSignatureP purse_sig;
127 :
128 : /**
129 : * Fundamental details about the purse.
130 : */
131 : struct TEH_PurseDetails pd;
132 :
133 : /**
134 : * Hash of the @e payto_uri.
135 : */
136 : struct TALER_NormalizedPaytoHashP h_payto;
137 :
138 : /**
139 : * KYC status of the operation.
140 : */
141 : struct TALER_EXCHANGEDB_KycStatus kyc;
142 :
143 : /**
144 : * Minimum age for deposits into this purse.
145 : */
146 : uint32_t min_age;
147 :
148 : /**
149 : * Flags for the operation.
150 : */
151 : enum TALER_WalletAccountMergeFlags flags;
152 :
153 : /**
154 : * HTTP status code for @a response, or 0
155 : */
156 : unsigned int http_status;
157 :
158 : /**
159 : * Do we lack an @e econtract?
160 : */
161 : bool no_econtract;
162 :
163 : /**
164 : * Set to true if the purse_fee was not given in the REST request.
165 : */
166 : bool no_purse_fee;
167 :
168 : };
169 :
170 : /**
171 : * Kept in a DLL.
172 : */
173 : static struct ReservePurseContext *rpc_head;
174 :
175 : /**
176 : * Kept in a DLL.
177 : */
178 : static struct ReservePurseContext *rpc_tail;
179 :
180 :
181 : void
182 21 : TEH_reserves_purse_cleanup ()
183 : {
184 : struct ReservePurseContext *rpc;
185 :
186 21 : while (NULL != (rpc = rpc_head))
187 : {
188 0 : GNUNET_CONTAINER_DLL_remove (rpc_head,
189 : rpc_tail,
190 : rpc);
191 0 : MHD_resume_connection (rpc->rc->connection);
192 : }
193 21 : }
194 :
195 :
196 : /**
197 : * Function called to iterate over KYC-relevant
198 : * transaction amounts for a particular time range.
199 : * Called within a database transaction, so must
200 : * not start a new one.
201 : *
202 : * @param cls a `struct ReservePurseContext`
203 : * @param limit maximum time-range for which events
204 : * should be fetched (timestamp in the past)
205 : * @param cb function to call on each event found,
206 : * events must be returned in reverse chronological
207 : * order
208 : * @param cb_cls closure for @a cb
209 : * @return transaction status
210 : */
211 : static enum GNUNET_DB_QueryStatus
212 2 : amount_iterator (void *cls,
213 : struct GNUNET_TIME_Absolute limit,
214 : TALER_EXCHANGEDB_KycAmountCallback cb,
215 : void *cb_cls)
216 : {
217 2 : struct ReservePurseContext *rpc = cls;
218 : enum GNUNET_DB_QueryStatus qs;
219 : enum GNUNET_GenericReturnValue ret;
220 :
221 2 : ret = cb (cb_cls,
222 2 : &rpc->pd.target_amount,
223 : GNUNET_TIME_absolute_get ());
224 2 : GNUNET_break (GNUNET_SYSERR != ret);
225 2 : if (GNUNET_OK != ret)
226 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
227 2 : qs = TEH_plugin->select_merge_amounts_for_kyc_check (
228 2 : TEH_plugin->cls,
229 2 : &rpc->h_payto,
230 : limit,
231 : cb,
232 : cb_cls);
233 2 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
234 : "Got %d additional transactions for this merge and limit %llu\n",
235 : qs,
236 : (unsigned long long) limit.abs_value_us);
237 2 : GNUNET_break (qs >= 0);
238 2 : return qs;
239 : }
240 :
241 :
242 : /**
243 : * Function called with the result of a legitimization
244 : * check.
245 : *
246 : * @param cls closure
247 : * @param lcr legitimization check result
248 : */
249 : static void
250 14 : reserve_purse_legi_cb (
251 : void *cls,
252 : const struct TEH_LegitimizationCheckResult *lcr)
253 : {
254 14 : struct ReservePurseContext *rpc = cls;
255 :
256 14 : rpc->lch = NULL;
257 14 : rpc->http_status = lcr->http_status;
258 14 : rpc->response = lcr->response;
259 14 : rpc->kyc = lcr->kyc;
260 14 : GNUNET_CONTAINER_DLL_remove (rpc_head,
261 : rpc_tail,
262 : rpc);
263 14 : MHD_resume_connection (rpc->rc->connection);
264 14 : TALER_MHD_daemon_trigger ();
265 14 : }
266 :
267 :
268 : /**
269 : * Execute database transaction for /reserves/$PID/purse. Runs the transaction
270 : * logic; IF it returns a non-error code, the transaction logic MUST NOT queue
271 : * a MHD response. IF it returns an hard error, the transaction logic MUST
272 : * queue a MHD response and set @a mhd_ret. IF it returns the soft error
273 : * code, the function MAY be called again to retry and MUST not queue a MHD
274 : * response.
275 : *
276 : * @param cls a `struct ReservePurseContext`
277 : * @param connection MHD request context
278 : * @param[out] mhd_ret set to MHD status on error
279 : * @return transaction status
280 : */
281 : static enum GNUNET_DB_QueryStatus
282 13 : purse_transaction (void *cls,
283 : struct MHD_Connection *connection,
284 : MHD_RESULT *mhd_ret)
285 : {
286 13 : struct ReservePurseContext *rpc = cls;
287 : enum GNUNET_DB_QueryStatus qs;
288 :
289 : {
290 13 : bool in_conflict = true;
291 :
292 : /* 1) store purse */
293 13 : qs = TEH_plugin->insert_purse_request (
294 13 : TEH_plugin->cls,
295 13 : &rpc->pd.purse_pub,
296 13 : &rpc->merge_pub,
297 : rpc->pd.purse_expiration,
298 13 : &rpc->pd.h_contract_terms,
299 : rpc->min_age,
300 : rpc->flags,
301 13 : &rpc->purse_fee,
302 13 : &rpc->pd.target_amount,
303 13 : &rpc->purse_sig,
304 : &in_conflict);
305 13 : if (qs < 0)
306 : {
307 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
308 0 : return qs;
309 0 : GNUNET_break (0);
310 0 : *mhd_ret =
311 0 : TALER_MHD_reply_with_error (
312 : connection,
313 : MHD_HTTP_INTERNAL_SERVER_ERROR,
314 : TALER_EC_GENERIC_DB_STORE_FAILED,
315 : "insert purse request");
316 0 : return GNUNET_DB_STATUS_HARD_ERROR;
317 : }
318 13 : if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
319 0 : return qs;
320 13 : if (in_conflict)
321 : {
322 : struct TALER_PurseMergePublicKeyP merge_pub;
323 : struct GNUNET_TIME_Timestamp purse_expiration;
324 : struct TALER_PrivateContractHashP h_contract_terms;
325 : struct TALER_Amount target_amount;
326 : struct TALER_Amount balance;
327 : struct TALER_PurseContractSignatureP purse_sig;
328 : uint32_t min_age;
329 :
330 0 : TEH_plugin->rollback (TEH_plugin->cls);
331 0 : qs = TEH_plugin->get_purse_request (
332 0 : TEH_plugin->cls,
333 0 : &rpc->pd.purse_pub,
334 : &merge_pub,
335 : &purse_expiration,
336 : &h_contract_terms,
337 : &min_age,
338 : &target_amount,
339 : &balance,
340 : &purse_sig);
341 0 : if (qs <= 0)
342 : {
343 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
344 0 : GNUNET_break (0 != qs);
345 0 : TALER_LOG_WARNING ("Failed to fetch purse information from database\n");
346 0 : *mhd_ret = TALER_MHD_reply_with_error (
347 : connection,
348 : MHD_HTTP_INTERNAL_SERVER_ERROR,
349 : TALER_EC_GENERIC_DB_FETCH_FAILED,
350 : "select purse request");
351 0 : return GNUNET_DB_STATUS_HARD_ERROR;
352 : }
353 : *mhd_ret
354 0 : = TALER_MHD_REPLY_JSON_PACK (
355 : connection,
356 : MHD_HTTP_CONFLICT,
357 : TALER_JSON_pack_ec (
358 : TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_CONFLICTING_META_DATA),
359 : TALER_JSON_pack_amount ("amount",
360 : &target_amount),
361 : GNUNET_JSON_pack_uint64 ("min_age",
362 : min_age),
363 : GNUNET_JSON_pack_timestamp ("purse_expiration",
364 : purse_expiration),
365 : GNUNET_JSON_pack_data_auto ("purse_sig",
366 : &purse_sig),
367 : GNUNET_JSON_pack_data_auto ("h_contract_terms",
368 : &h_contract_terms),
369 : GNUNET_JSON_pack_data_auto ("merge_pub",
370 : &merge_pub));
371 0 : return GNUNET_DB_STATUS_HARD_ERROR;
372 : }
373 :
374 : }
375 :
376 : /* 2) create purse with reserve (and debit reserve for purse creation!) */
377 : {
378 13 : bool in_conflict = true;
379 13 : bool insufficient_funds = true;
380 13 : bool no_reserve = true;
381 :
382 13 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
383 : "Creating purse with flags %d\n",
384 : rpc->flags);
385 13 : qs = TEH_plugin->do_reserve_purse (
386 13 : TEH_plugin->cls,
387 13 : &rpc->pd.purse_pub,
388 13 : &rpc->merge_sig,
389 : rpc->merge_timestamp,
390 13 : &rpc->reserve_sig,
391 : (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
392 13 : == rpc->flags)
393 : ? NULL
394 : : &rpc->purse_fee,
395 13 : &rpc->account_pub.reserve_pub,
396 : &in_conflict,
397 : &no_reserve,
398 : &insufficient_funds);
399 13 : if (qs < 0)
400 : {
401 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
402 2 : return qs;
403 0 : TALER_LOG_WARNING (
404 : "Failed to store purse merge information in database\n");
405 0 : *mhd_ret =
406 0 : TALER_MHD_reply_with_error (
407 : connection,
408 : MHD_HTTP_INTERNAL_SERVER_ERROR,
409 : TALER_EC_GENERIC_DB_STORE_FAILED,
410 : "do reserve purse");
411 0 : return GNUNET_DB_STATUS_HARD_ERROR;
412 : }
413 13 : if (in_conflict)
414 : {
415 : /* same purse already merged into a different reserve!? */
416 : struct TALER_PurseContractPublicKeyP purse_pub;
417 : struct TALER_PurseMergeSignatureP merge_sig;
418 : struct GNUNET_TIME_Timestamp merge_timestamp;
419 : char *partner_url;
420 : struct TALER_ReservePublicKeyP reserve_pub;
421 : bool refunded;
422 :
423 0 : TEH_plugin->rollback (TEH_plugin->cls);
424 0 : qs = TEH_plugin->select_purse_merge (
425 0 : TEH_plugin->cls,
426 : &purse_pub,
427 : &merge_sig,
428 : &merge_timestamp,
429 : &partner_url,
430 : &reserve_pub,
431 : &refunded);
432 0 : if (qs <= 0)
433 : {
434 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
435 0 : GNUNET_break (0 != qs);
436 0 : TALER_LOG_WARNING (
437 : "Failed to fetch purse merge information from database\n");
438 0 : *mhd_ret = TALER_MHD_reply_with_error (
439 : connection,
440 : MHD_HTTP_INTERNAL_SERVER_ERROR,
441 : TALER_EC_GENERIC_DB_FETCH_FAILED,
442 : "select purse merge");
443 0 : return GNUNET_DB_STATUS_HARD_ERROR;
444 : }
445 0 : if (refunded)
446 : {
447 : /* This is a bit of a strange case ... */
448 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
449 : "Purse was already refunded\n");
450 0 : *mhd_ret = TALER_MHD_reply_with_error (
451 : connection,
452 : MHD_HTTP_GONE,
453 : TALER_EC_EXCHANGE_GENERIC_PURSE_EXPIRED,
454 : NULL);
455 0 : GNUNET_free (partner_url);
456 0 : return GNUNET_DB_STATUS_HARD_ERROR;
457 : }
458 : *mhd_ret
459 0 : = TALER_MHD_REPLY_JSON_PACK (
460 : connection,
461 : MHD_HTTP_CONFLICT,
462 : TALER_JSON_pack_ec (
463 : TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_CONFLICTING_META_DATA),
464 : GNUNET_JSON_pack_string ("partner_url",
465 : NULL == partner_url
466 : ? TEH_base_url
467 : : partner_url),
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_data_auto ("reserve_pub",
473 : &reserve_pub));
474 0 : GNUNET_free (partner_url);
475 0 : return GNUNET_DB_STATUS_HARD_ERROR;
476 : }
477 13 : if ( (no_reserve) &&
478 : ( (TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA
479 0 : == rpc->flags) ||
480 0 : (! TALER_amount_is_zero (&rpc->purse_fee)) ) )
481 : {
482 : *mhd_ret
483 0 : = TALER_MHD_REPLY_JSON_PACK (
484 : connection,
485 : MHD_HTTP_NOT_FOUND,
486 : TALER_JSON_pack_ec (
487 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN));
488 0 : return GNUNET_DB_STATUS_HARD_ERROR;
489 : }
490 13 : if (insufficient_funds)
491 : {
492 : *mhd_ret
493 2 : = TALER_MHD_REPLY_JSON_PACK (
494 : connection,
495 : MHD_HTTP_CONFLICT,
496 : TALER_JSON_pack_ec (
497 : TALER_EC_EXCHANGE_RESERVES_PURSE_CREATE_INSUFFICIENT_FUNDS));
498 2 : return GNUNET_DB_STATUS_HARD_ERROR;
499 : }
500 : }
501 : /* 3) if present, persist contract */
502 11 : if (! rpc->no_econtract)
503 : {
504 11 : bool in_conflict = true;
505 :
506 11 : qs = TEH_plugin->insert_contract (TEH_plugin->cls,
507 11 : &rpc->pd.purse_pub,
508 11 : &rpc->econtract,
509 : &in_conflict);
510 11 : if (qs < 0)
511 : {
512 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
513 0 : return qs;
514 0 : TALER_LOG_WARNING ("Failed to store purse information in database\n");
515 0 : *mhd_ret = TALER_MHD_reply_with_error (
516 : connection,
517 : MHD_HTTP_INTERNAL_SERVER_ERROR,
518 : TALER_EC_GENERIC_DB_STORE_FAILED,
519 : "purse purse contract");
520 0 : return GNUNET_DB_STATUS_HARD_ERROR;
521 : }
522 11 : if (in_conflict)
523 : {
524 : struct TALER_EncryptedContract econtract;
525 : struct GNUNET_HashCode h_econtract;
526 :
527 0 : qs = TEH_plugin->select_contract_by_purse (
528 0 : TEH_plugin->cls,
529 0 : &rpc->pd.purse_pub,
530 : &econtract);
531 0 : if (qs <= 0)
532 : {
533 0 : if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
534 0 : return qs;
535 0 : GNUNET_break (0 != qs);
536 0 : TALER_LOG_WARNING (
537 : "Failed to store fetch contract information from database\n");
538 0 : *mhd_ret = TALER_MHD_reply_with_error (
539 : connection,
540 : MHD_HTTP_INTERNAL_SERVER_ERROR,
541 : TALER_EC_GENERIC_DB_FETCH_FAILED,
542 : "select contract");
543 0 : return GNUNET_DB_STATUS_HARD_ERROR;
544 : }
545 0 : GNUNET_CRYPTO_hash (econtract.econtract,
546 : econtract.econtract_size,
547 : &h_econtract);
548 : *mhd_ret
549 0 : = TALER_MHD_REPLY_JSON_PACK (
550 : connection,
551 : MHD_HTTP_CONFLICT,
552 : TALER_JSON_pack_ec (
553 : TALER_EC_EXCHANGE_PURSE_ECONTRACT_CONFLICTING_META_DATA),
554 : GNUNET_JSON_pack_data_auto ("h_econtract",
555 : &h_econtract),
556 : GNUNET_JSON_pack_data_auto ("econtract_sig",
557 : &econtract.econtract_sig),
558 : GNUNET_JSON_pack_data_auto ("contract_pub",
559 : &econtract.contract_pub));
560 0 : return GNUNET_DB_STATUS_HARD_ERROR;
561 : }
562 : }
563 11 : return qs;
564 : }
565 :
566 :
567 : /**
568 : * Function to clean up our rh_ctx in @a rc
569 : *
570 : * @param[in,out] rc context to clean up
571 : */
572 : static void
573 14 : rpc_cleaner (struct TEH_RequestContext *rc)
574 : {
575 14 : struct ReservePurseContext *rpc = rc->rh_ctx;
576 :
577 14 : if (NULL != rpc->lch)
578 : {
579 0 : TEH_legitimization_check_cancel (rpc->lch);
580 0 : rpc->lch = NULL;
581 : }
582 14 : GNUNET_free (rpc->econtract.econtract);
583 14 : GNUNET_free (rpc->payto_uri.normalized_payto);
584 14 : GNUNET_free (rpc);
585 14 : }
586 :
587 :
588 : MHD_RESULT
589 28 : TEH_handler_reserves_purse (
590 : struct TEH_RequestContext *rc,
591 : const struct TALER_ReservePublicKeyP *reserve_pub,
592 : const json_t *root)
593 : {
594 28 : struct ReservePurseContext *rpc = rc->rh_ctx;
595 28 : struct MHD_Connection *connection = rc->connection;
596 :
597 28 : if (NULL == rpc)
598 : {
599 14 : rpc = GNUNET_new (struct ReservePurseContext);
600 14 : rc->rh_ctx = rpc;
601 14 : rc->rh_cleaner = &rpc_cleaner;
602 14 : rpc->rc = rc;
603 14 : rpc->account_pub.reserve_pub = *reserve_pub;
604 : rpc->exchange_timestamp
605 14 : = GNUNET_TIME_timestamp_get ();
606 14 : rpc->no_purse_fee = true;
607 :
608 :
609 : {
610 : struct GNUNET_JSON_Specification spec[] = {
611 14 : TALER_JSON_spec_amount ("purse_value",
612 : TEH_currency,
613 : &rpc->pd.target_amount),
614 14 : GNUNET_JSON_spec_uint32 ("min_age",
615 : &rpc->min_age),
616 14 : GNUNET_JSON_spec_mark_optional (
617 : TALER_JSON_spec_amount ("purse_fee",
618 : TEH_currency,
619 : &rpc->purse_fee),
620 : &rpc->no_purse_fee),
621 14 : GNUNET_JSON_spec_mark_optional (
622 : TALER_JSON_spec_econtract ("econtract",
623 : &rpc->econtract),
624 : &rpc->no_econtract),
625 14 : GNUNET_JSON_spec_fixed_auto ("merge_pub",
626 : &rpc->merge_pub),
627 14 : GNUNET_JSON_spec_fixed_auto ("merge_sig",
628 : &rpc->merge_sig),
629 14 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
630 : &rpc->reserve_sig),
631 14 : GNUNET_JSON_spec_fixed_auto ("purse_pub",
632 : &rpc->pd.purse_pub),
633 14 : GNUNET_JSON_spec_fixed_auto ("purse_sig",
634 : &rpc->purse_sig),
635 14 : GNUNET_JSON_spec_fixed_auto ("h_contract_terms",
636 : &rpc->pd.h_contract_terms),
637 14 : GNUNET_JSON_spec_timestamp ("merge_timestamp",
638 : &rpc->merge_timestamp),
639 14 : GNUNET_JSON_spec_timestamp ("purse_expiration",
640 : &rpc->pd.purse_expiration),
641 14 : GNUNET_JSON_spec_end ()
642 : };
643 :
644 : {
645 : enum GNUNET_GenericReturnValue res;
646 :
647 14 : res = TALER_MHD_parse_json_data (connection,
648 : root,
649 : spec);
650 14 : if (GNUNET_SYSERR == res)
651 : {
652 0 : GNUNET_break (0);
653 0 : return MHD_NO; /* hard failure */
654 : }
655 14 : if (GNUNET_NO == res)
656 : {
657 0 : GNUNET_break_op (0);
658 0 : return MHD_YES; /* failure */
659 : }
660 : }
661 : }
662 :
663 : rpc->payto_uri
664 14 : = TALER_reserve_make_payto (TEH_base_url,
665 14 : &rpc->account_pub.reserve_pub);
666 14 : TALER_normalized_payto_hash (rpc->payto_uri,
667 : &rpc->h_payto);
668 14 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
669 :
670 :
671 14 : if (GNUNET_OK !=
672 14 : TALER_wallet_purse_merge_verify (rpc->payto_uri,
673 : rpc->merge_timestamp,
674 14 : &rpc->pd.purse_pub,
675 14 : &rpc->merge_pub,
676 14 : &rpc->merge_sig))
677 : {
678 : MHD_RESULT ret;
679 :
680 0 : GNUNET_break_op (0);
681 0 : ret = TALER_MHD_reply_with_error (
682 : connection,
683 : MHD_HTTP_FORBIDDEN,
684 : TALER_EC_EXCHANGE_RESERVES_PURSE_MERGE_SIGNATURE_INVALID,
685 0 : rpc->payto_uri.normalized_payto);
686 0 : return ret;
687 : }
688 14 : GNUNET_assert (GNUNET_OK ==
689 : TALER_amount_set_zero (TEH_currency,
690 : &rpc->deposit_total));
691 14 : if (GNUNET_TIME_timestamp_cmp (rpc->pd.purse_expiration,
692 : <,
693 : rpc->exchange_timestamp))
694 : {
695 0 : GNUNET_break_op (0);
696 0 : return TALER_MHD_reply_with_error (
697 : connection,
698 : MHD_HTTP_BAD_REQUEST,
699 : TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_BEFORE_NOW,
700 : NULL);
701 : }
702 14 : if (GNUNET_TIME_absolute_is_never (
703 : rpc->pd.purse_expiration.abs_time))
704 : {
705 0 : GNUNET_break_op (0);
706 0 : return TALER_MHD_reply_with_error (
707 : connection,
708 : MHD_HTTP_BAD_REQUEST,
709 : TALER_EC_EXCHANGE_RESERVES_PURSE_EXPIRATION_IS_NEVER,
710 : NULL);
711 : }
712 :
713 : {
714 : struct TEH_KeyStateHandle *keys;
715 : const struct TEH_GlobalFee *gf;
716 :
717 14 : keys = TEH_keys_get_state ();
718 14 : if (NULL == keys)
719 : {
720 0 : GNUNET_break (0);
721 0 : return TALER_MHD_reply_with_error (
722 : connection,
723 : MHD_HTTP_INTERNAL_SERVER_ERROR,
724 : TALER_EC_EXCHANGE_GENERIC_KEYS_MISSING,
725 : NULL);
726 : }
727 14 : gf = TEH_keys_global_fee_by_time (keys,
728 : rpc->exchange_timestamp);
729 14 : if (NULL == gf)
730 : {
731 0 : GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
732 : "Cannot create purse: global fees not configured!\n");
733 0 : return TALER_MHD_reply_with_error (
734 : connection,
735 : MHD_HTTP_INTERNAL_SERVER_ERROR,
736 : TALER_EC_EXCHANGE_GENERIC_GLOBAL_FEES_MISSING,
737 : NULL);
738 : }
739 14 : if (rpc->no_purse_fee)
740 : {
741 6 : rpc->flags = TALER_WAMF_MODE_CREATE_FROM_PURSE_QUOTA;
742 6 : GNUNET_assert (GNUNET_OK ==
743 : TALER_amount_set_zero (TEH_currency,
744 : &rpc->purse_fee));
745 : }
746 : else
747 : {
748 8 : rpc->flags = TALER_WAMF_MODE_CREATE_WITH_PURSE_FEE;
749 8 : if (-1 ==
750 8 : TALER_amount_cmp (&rpc->purse_fee,
751 : &gf->fees.purse))
752 : {
753 : /* rpc->purse_fee is below gf.fees.purse! */
754 0 : GNUNET_break_op (0);
755 0 : return TALER_MHD_reply_with_error (
756 : connection,
757 : MHD_HTTP_BAD_REQUEST,
758 : TALER_EC_EXCHANGE_RESERVES_PURSE_FEE_TOO_LOW,
759 : TALER_amount2s (&gf->fees.purse));
760 : }
761 : }
762 : }
763 :
764 14 : TEH_METRICS_num_verifications[TEH_MT_SIGNATURE_EDDSA]++;
765 14 : if (GNUNET_OK !=
766 14 : TALER_wallet_purse_create_verify (
767 : rpc->pd.purse_expiration,
768 14 : &rpc->pd.h_contract_terms,
769 14 : &rpc->merge_pub,
770 : rpc->min_age,
771 14 : &rpc->pd.target_amount,
772 14 : &rpc->pd.purse_pub,
773 14 : &rpc->purse_sig))
774 : {
775 0 : GNUNET_break_op (0);
776 0 : return TALER_MHD_reply_with_error (
777 : connection,
778 : MHD_HTTP_FORBIDDEN,
779 : TALER_EC_EXCHANGE_PURSE_CREATE_SIGNATURE_INVALID,
780 : NULL);
781 : }
782 14 : if (GNUNET_OK !=
783 14 : TALER_wallet_account_merge_verify (
784 : rpc->merge_timestamp,
785 14 : &rpc->pd.purse_pub,
786 : rpc->pd.purse_expiration,
787 14 : &rpc->pd.h_contract_terms,
788 14 : &rpc->pd.target_amount,
789 14 : &rpc->purse_fee,
790 : rpc->min_age,
791 : rpc->flags,
792 14 : &rpc->account_pub.reserve_pub,
793 14 : &rpc->reserve_sig))
794 : {
795 0 : GNUNET_break_op (0);
796 0 : return TALER_MHD_reply_with_error (
797 : connection,
798 : MHD_HTTP_FORBIDDEN,
799 : TALER_EC_EXCHANGE_RESERVES_RESERVE_MERGE_SIGNATURE_INVALID,
800 : NULL);
801 : }
802 28 : if ( (! rpc->no_econtract) &&
803 : (GNUNET_OK !=
804 14 : TALER_wallet_econtract_upload_verify (
805 14 : rpc->econtract.econtract,
806 : rpc->econtract.econtract_size,
807 14 : &rpc->econtract.contract_pub,
808 14 : &rpc->pd.purse_pub,
809 14 : &rpc->econtract.econtract_sig))
810 : )
811 : {
812 0 : TALER_LOG_WARNING ("Invalid signature on /reserves/$PID/purse request\n");
813 0 : return TALER_MHD_reply_with_error (connection,
814 : MHD_HTTP_FORBIDDEN,
815 : TALER_EC_EXCHANGE_PURSE_ECONTRACT_SIGNATURE_INVALID,
816 : NULL);
817 : }
818 : {
819 : struct TALER_FullPayto fake_full_payto;
820 :
821 14 : GNUNET_asprintf (&fake_full_payto.full_payto,
822 : "%s?receiver-name=wallet",
823 : rpc->payto_uri.normalized_payto);
824 28 : rpc->lch = TEH_legitimization_check (
825 14 : &rpc->rc->async_scope_id,
826 : TALER_KYCLOGIC_KYC_TRIGGER_P2P_RECEIVE,
827 : fake_full_payto,
828 14 : &rpc->h_payto,
829 14 : &rpc->account_pub,
830 : &amount_iterator,
831 : rpc,
832 : &reserve_purse_legi_cb,
833 : rpc);
834 14 : GNUNET_free (fake_full_payto.full_payto);
835 : }
836 14 : GNUNET_assert (NULL != rpc->lch);
837 14 : MHD_suspend_connection (rc->connection);
838 14 : GNUNET_CONTAINER_DLL_insert (rpc_head,
839 : rpc_tail,
840 : rpc);
841 14 : return MHD_YES;
842 : }
843 14 : if (NULL != rpc->response)
844 0 : return MHD_queue_response (connection,
845 : rpc->http_status,
846 : rpc->response);
847 14 : if (! rpc->kyc.ok)
848 : {
849 1 : if (0 == rpc->kyc.requirement_row)
850 : {
851 0 : GNUNET_break (0);
852 0 : return TALER_MHD_reply_with_error (
853 : connection,
854 : MHD_HTTP_INTERNAL_SERVER_ERROR,
855 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
856 : "requirement row not set");
857 : }
858 1 : return TEH_RESPONSE_reply_kyc_required (
859 : connection,
860 1 : &rpc->h_payto,
861 1 : &rpc->kyc,
862 : false);
863 : }
864 :
865 : {
866 : MHD_RESULT mhd_ret;
867 :
868 13 : if (GNUNET_OK !=
869 13 : TEH_DB_run_transaction (connection,
870 : "execute reserve purse",
871 : TEH_MT_REQUEST_RESERVE_PURSE,
872 : &mhd_ret,
873 : &purse_transaction,
874 : rpc))
875 : {
876 2 : return mhd_ret;
877 : }
878 : }
879 :
880 : /* generate regular response */
881 : {
882 : MHD_RESULT res;
883 :
884 11 : res = TEH_RESPONSE_reply_purse_created (
885 : connection,
886 : rpc->exchange_timestamp,
887 11 : &rpc->deposit_total,
888 11 : &rpc->pd);
889 11 : return res;
890 : }
891 : }
892 :
893 :
894 : /* end of taler-exchange-httpd_reserves_purse.c */
|