Line data Source code
1 : /*
2 : This file is part of TALER
3 : Copyright (C) 2014-2022 Taler Systems SA
4 :
5 : TALER is free software; you can redistribute it and/or modify it under the
6 : terms of the GNU Affero General Public License as published by the Free Software
7 : Foundation; either version 3, or (at your option) any later version.
8 :
9 : TALER is distributed in the hope that it will be useful, but WITHOUT ANY
10 : WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 : A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
12 :
13 : You should have received a copy of the GNU Affero General Public License along with
14 : TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
15 : */
16 : /**
17 : * @file taler-exchange-httpd_reserves_close.c
18 : * @brief Handle /reserves/$RESERVE_PUB/close requests
19 : * @author Florian Dold
20 : * @author Benedikt Mueller
21 : * @author Christian Grothoff
22 : */
23 : #include "taler/platform.h"
24 : #include <gnunet/gnunet_util_lib.h>
25 : #include <jansson.h>
26 : #include "taler/taler_kyclogic_lib.h"
27 : #include "taler/taler_mhd_lib.h"
28 : #include "taler/taler_json_lib.h"
29 : #include "taler/taler_dbevents.h"
30 : #include "taler-exchange-httpd_common_kyc.h"
31 : #include "taler-exchange-httpd_keys.h"
32 : #include "taler-exchange-httpd_reserves_close.h"
33 : #include "taler-exchange-httpd_responses.h"
34 :
35 :
36 : /**
37 : * How far do we allow a client's time to be off when
38 : * checking the request timestamp?
39 : */
40 : #define TIMESTAMP_TOLERANCE \
41 : GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 15)
42 :
43 :
44 : /**
45 : * Closure for #reserve_close_transaction.
46 : */
47 : struct ReserveCloseContext
48 : {
49 :
50 : /**
51 : * Kept in a DLL.
52 : */
53 : struct ReserveCloseContext *next;
54 :
55 : /**
56 : * Kept in a DLL.
57 : */
58 : struct ReserveCloseContext *prev;
59 :
60 : /**
61 : * Our request context.
62 : */
63 : struct TEH_RequestContext *rc;
64 :
65 : /**
66 : * Handle for legitimization check.
67 : */
68 : struct TEH_LegitimizationCheckHandle *lch;
69 :
70 : /**
71 : * Where to wire the funds, may be NULL.
72 : */
73 : struct TALER_FullPayto payto_uri;
74 :
75 : /**
76 : * Response to return. Note that the response must
77 : * be queued or destroyed by the callee. NULL
78 : * if the legitimization check was successful and the handler should return
79 : * a handler-specific result.
80 : */
81 : struct MHD_Response *response;
82 :
83 : /**
84 : * Public key of the reserve the inquiry is about.
85 : */
86 : struct TALER_ReservePublicKeyP reserve_pub;
87 :
88 : /**
89 : * Timestamp of the request.
90 : */
91 : struct GNUNET_TIME_Timestamp timestamp;
92 :
93 : /**
94 : * Client signature approving the request.
95 : */
96 : struct TALER_ReserveSignatureP reserve_sig;
97 :
98 : /**
99 : * Amount that will be wired (after closing fees).
100 : */
101 : struct TALER_Amount wire_amount;
102 :
103 : /**
104 : * Current balance of the reserve.
105 : */
106 : struct TALER_Amount balance;
107 :
108 : /**
109 : * Hash of the @e payto_uri, if given (otherwise zero).
110 : */
111 : struct TALER_FullPaytoHashP h_payto;
112 :
113 : /**
114 : * KYC status for the request.
115 : */
116 : struct TALER_EXCHANGEDB_KycStatus kyc;
117 :
118 : /**
119 : * Hash of the payto-URI that was used for the KYC decision.
120 : */
121 : struct TALER_NormalizedPaytoHashP kyc_payto;
122 :
123 : /**
124 : * Query status from the amount_it() helper function.
125 : */
126 : enum GNUNET_DB_QueryStatus qs;
127 :
128 : /**
129 : * HTTP status code for @a response, or 0
130 : */
131 : unsigned int http_status;
132 :
133 : /**
134 : * Set to true if the request was suspended.
135 : */
136 : bool suspended;
137 :
138 : /**
139 : * Set to true if the request was suspended.
140 : */
141 : bool resumed;
142 : };
143 :
144 :
145 : /**
146 : * Kept in a DLL.
147 : */
148 : static struct ReserveCloseContext *rcc_head;
149 :
150 : /**
151 : * Kept in a DLL.
152 : */
153 : static struct ReserveCloseContext *rcc_tail;
154 :
155 :
156 : void
157 21 : TEH_reserves_close_cleanup ()
158 : {
159 : struct ReserveCloseContext *rcc;
160 :
161 21 : while (NULL != (rcc = rcc_head))
162 : {
163 0 : GNUNET_CONTAINER_DLL_remove (rcc_head,
164 : rcc_tail,
165 : rcc);
166 0 : MHD_resume_connection (rcc->rc->connection);
167 : }
168 21 : }
169 :
170 :
171 : /**
172 : * Send reserve close to client.
173 : *
174 : * @param rhc reserve close to return
175 : * @return MHD result code
176 : */
177 : static MHD_RESULT
178 2 : reply_reserve_close_success (
179 : const struct ReserveCloseContext *rhc)
180 : {
181 2 : struct MHD_Connection *connection = rhc->rc->connection;
182 2 : return TALER_MHD_REPLY_JSON_PACK (
183 : connection,
184 : MHD_HTTP_OK,
185 : TALER_JSON_pack_amount ("wire_amount",
186 : &rhc->wire_amount));
187 : }
188 :
189 :
190 : /**
191 : * Function called with the result of a legitimization
192 : * check.
193 : *
194 : * @param cls closure
195 : * @param lcr legitimization check result
196 : */
197 : static void
198 4 : reserve_close_legi_cb (
199 : void *cls,
200 : const struct TEH_LegitimizationCheckResult *lcr)
201 : {
202 4 : struct ReserveCloseContext *rcc = cls;
203 :
204 4 : rcc->lch = NULL;
205 4 : rcc->http_status = lcr->http_status;
206 4 : rcc->response = lcr->response;
207 4 : rcc->kyc = lcr->kyc;
208 4 : GNUNET_CONTAINER_DLL_remove (rcc_head,
209 : rcc_tail,
210 : rcc);
211 4 : MHD_resume_connection (rcc->rc->connection);
212 4 : rcc->resumed = true;
213 4 : rcc->suspended = false;
214 4 : TALER_MHD_daemon_trigger ();
215 4 : }
216 :
217 :
218 : /**
219 : * Function called to iterate over KYC-relevant
220 : * transaction amounts for a particular time range.
221 : * Called within a database transaction, so must
222 : * not start a new one.
223 : *
224 : * @param cls closure, identifies the event type and
225 : * account to iterate over events for
226 : * @param limit maximum time-range for which events
227 : * should be fetched (timestamp in the past)
228 : * @param cb function to call on each event found,
229 : * events must be returned in reverse chronological
230 : * order
231 : * @param cb_cls closure for @a cb
232 : * @return transaction status
233 : */
234 : static enum GNUNET_DB_QueryStatus
235 4 : amount_it (void *cls,
236 : struct GNUNET_TIME_Absolute limit,
237 : TALER_EXCHANGEDB_KycAmountCallback cb,
238 : void *cb_cls)
239 : {
240 4 : struct ReserveCloseContext *rcc = cls;
241 : enum GNUNET_GenericReturnValue ret;
242 :
243 4 : ret = cb (cb_cls,
244 4 : &rcc->balance,
245 : GNUNET_TIME_absolute_get ());
246 4 : GNUNET_break (GNUNET_SYSERR != ret);
247 4 : if (GNUNET_OK != ret)
248 0 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
249 : rcc->qs
250 8 : = TEH_plugin->iterate_reserve_close_info (
251 4 : TEH_plugin->cls,
252 4 : &rcc->kyc_payto,
253 : limit,
254 : cb,
255 : cb_cls);
256 4 : return rcc->qs;
257 : }
258 :
259 :
260 : /**
261 : * Function implementing /reserves/$RID/close transaction. Given the public
262 : * key of a reserve, return the associated transaction close. Runs the
263 : * transaction logic; IF it returns a non-error code, the transaction logic
264 : * MUST NOT queue a MHD response. IF it returns an hard error, the
265 : * transaction logic MUST queue a MHD response and set @a mhd_ret. IF it
266 : * returns the soft error code, the function MAY be called again to retry and
267 : * MUST not queue a MHD response.
268 : *
269 : * @param cls a `struct ReserveCloseContext *`
270 : * @param connection MHD request which triggered the transaction
271 : * @param[out] mhd_ret set to MHD response status for @a connection,
272 : * if transaction failed (!); unused
273 : * @return transaction status
274 : */
275 : static enum GNUNET_DB_QueryStatus
276 6 : reserve_close_transaction (
277 : void *cls,
278 : struct MHD_Connection *connection,
279 : MHD_RESULT *mhd_ret)
280 : {
281 6 : struct ReserveCloseContext *rcc = cls;
282 : enum GNUNET_DB_QueryStatus qs;
283 6 : struct TALER_FullPayto payto_uri = {
284 : .full_payto = NULL
285 : };
286 : const struct TALER_WireFeeSet *wf;
287 :
288 6 : qs = TEH_plugin->select_reserve_close_info (
289 6 : TEH_plugin->cls,
290 6 : &rcc->reserve_pub,
291 : &rcc->balance,
292 : &payto_uri);
293 6 : switch (qs)
294 : {
295 0 : case GNUNET_DB_STATUS_HARD_ERROR:
296 0 : GNUNET_break (0);
297 : *mhd_ret
298 0 : = TALER_MHD_reply_with_error (
299 : connection,
300 : MHD_HTTP_INTERNAL_SERVER_ERROR,
301 : TALER_EC_GENERIC_DB_FETCH_FAILED,
302 : "select_reserve_close_info");
303 0 : return qs;
304 0 : case GNUNET_DB_STATUS_SOFT_ERROR:
305 0 : return qs;
306 0 : case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
307 : *mhd_ret
308 0 : = TALER_MHD_reply_with_error (
309 : connection,
310 : MHD_HTTP_NOT_FOUND,
311 : TALER_EC_EXCHANGE_GENERIC_RESERVE_UNKNOWN,
312 : NULL);
313 0 : return GNUNET_DB_STATUS_HARD_ERROR;
314 6 : case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
315 6 : break;
316 : }
317 :
318 6 : if ( (NULL == rcc->payto_uri.full_payto) &&
319 0 : (NULL == payto_uri.full_payto) )
320 : {
321 : *mhd_ret
322 0 : = TALER_MHD_reply_with_error (
323 : connection,
324 : MHD_HTTP_CONFLICT,
325 : TALER_EC_EXCHANGE_RESERVES_CLOSE_NO_TARGET_ACCOUNT,
326 : NULL);
327 0 : return GNUNET_DB_STATUS_HARD_ERROR;
328 : }
329 :
330 6 : if ( (! rcc->resumed) &&
331 4 : (NULL != rcc->payto_uri.full_payto) &&
332 8 : ( (NULL == payto_uri.full_payto) ||
333 4 : (0 != TALER_full_payto_cmp (payto_uri,
334 : rcc->payto_uri)) ) )
335 : {
336 : /* KYC check may be needed: we're not returning
337 : the money to the account that funded the reserve
338 : in the first place. */
339 :
340 4 : TALER_full_payto_normalize_and_hash (rcc->payto_uri,
341 : &rcc->kyc_payto);
342 8 : rcc->lch = TEH_legitimization_check (
343 4 : &rcc->rc->async_scope_id,
344 : TALER_KYCLOGIC_KYC_TRIGGER_RESERVE_CLOSE,
345 : rcc->payto_uri,
346 4 : &rcc->kyc_payto,
347 : NULL, /* no account_pub: this is about the origin/destination account */
348 : &amount_it,
349 : rcc,
350 : &reserve_close_legi_cb,
351 : rcc);
352 4 : GNUNET_assert (NULL != rcc->lch);
353 4 : GNUNET_CONTAINER_DLL_insert (rcc_head,
354 : rcc_tail,
355 : rcc);
356 4 : MHD_suspend_connection (rcc->rc->connection);
357 4 : rcc->suspended = true;
358 4 : GNUNET_free (payto_uri.full_payto);
359 4 : return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
360 : }
361 2 : rcc->kyc.ok = true;
362 2 : if (NULL == rcc->payto_uri.full_payto)
363 0 : rcc->payto_uri = payto_uri;
364 :
365 : {
366 : char *method;
367 :
368 2 : method = TALER_payto_get_method (rcc->payto_uri.full_payto);
369 2 : wf = TEH_wire_fees_by_time (rcc->timestamp,
370 : method);
371 2 : if (NULL == wf)
372 : {
373 0 : GNUNET_break (0);
374 0 : *mhd_ret = TALER_MHD_reply_with_error (
375 : connection,
376 : MHD_HTTP_INTERNAL_SERVER_ERROR,
377 : TALER_EC_EXCHANGE_WIRE_FEES_NOT_CONFIGURED,
378 : method);
379 0 : GNUNET_free (method);
380 0 : GNUNET_free (payto_uri.full_payto);
381 0 : return GNUNET_DB_STATUS_HARD_ERROR;
382 : }
383 2 : GNUNET_free (method);
384 : }
385 :
386 2 : if (0 >
387 2 : TALER_amount_subtract (&rcc->wire_amount,
388 2 : &rcc->balance,
389 : &wf->closing))
390 : {
391 0 : GNUNET_log (GNUNET_ERROR_TYPE_INFO,
392 : "Client attempted to close reserve with insufficient balance.\n");
393 0 : GNUNET_assert (GNUNET_OK ==
394 : TALER_amount_set_zero (TEH_currency,
395 : &rcc->wire_amount));
396 0 : *mhd_ret = reply_reserve_close_success (rcc);
397 0 : GNUNET_free (payto_uri.full_payto);
398 0 : return GNUNET_DB_STATUS_HARD_ERROR;
399 : }
400 :
401 2 : qs = TEH_plugin->insert_close_request (
402 2 : TEH_plugin->cls,
403 2 : &rcc->reserve_pub,
404 : payto_uri,
405 2 : &rcc->reserve_sig,
406 : rcc->timestamp,
407 2 : &rcc->balance,
408 : &wf->closing);
409 2 : GNUNET_free (payto_uri.full_payto);
410 2 : rcc->payto_uri.full_payto = NULL;
411 2 : if (GNUNET_DB_STATUS_HARD_ERROR == qs)
412 : {
413 0 : GNUNET_break (0);
414 : *mhd_ret
415 0 : = TALER_MHD_reply_with_error (connection,
416 : MHD_HTTP_INTERNAL_SERVER_ERROR,
417 : TALER_EC_GENERIC_DB_FETCH_FAILED,
418 : "insert_close_request");
419 0 : return qs;
420 : }
421 2 : if (qs <= 0)
422 : {
423 0 : GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
424 0 : return qs;
425 : }
426 2 : return qs;
427 : }
428 :
429 :
430 : /**
431 : * Cleanup routine. Function called
432 : * upon completion of the request that should
433 : * clean up @a rh_ctx. Can be NULL.
434 : *
435 : * @param rc request to clean up context for
436 : */
437 : static void
438 4 : reserve_close_cleanup (struct TEH_RequestContext *rc)
439 : {
440 4 : struct ReserveCloseContext *rcc = rc->rh_ctx;
441 :
442 4 : if (NULL != rcc->lch)
443 : {
444 0 : TEH_legitimization_check_cancel (rcc->lch);
445 0 : rcc->lch = NULL;
446 : }
447 4 : GNUNET_free (rcc);
448 4 : }
449 :
450 :
451 : MHD_RESULT
452 8 : TEH_handler_reserves_close (
453 : struct TEH_RequestContext *rc,
454 : const struct TALER_ReservePublicKeyP *reserve_pub,
455 : const json_t *root)
456 : {
457 8 : struct ReserveCloseContext *rcc = rc->rh_ctx;
458 : MHD_RESULT mhd_ret;
459 :
460 8 : if (NULL == rcc)
461 : {
462 4 : rcc = GNUNET_new (struct ReserveCloseContext);
463 4 : rc->rh_ctx = rcc;
464 4 : rc->rh_cleaner = &reserve_close_cleanup;
465 4 : rcc->reserve_pub = *reserve_pub;
466 4 : rcc->rc = rc;
467 :
468 : {
469 : struct GNUNET_JSON_Specification spec[] = {
470 4 : GNUNET_JSON_spec_timestamp ("request_timestamp",
471 : &rcc->timestamp),
472 4 : GNUNET_JSON_spec_mark_optional (
473 : TALER_JSON_spec_full_payto_uri ("payto_uri",
474 : &rcc->payto_uri),
475 : NULL),
476 4 : GNUNET_JSON_spec_fixed_auto ("reserve_sig",
477 : &rcc->reserve_sig),
478 4 : GNUNET_JSON_spec_end ()
479 : };
480 :
481 : {
482 : enum GNUNET_GenericReturnValue res;
483 :
484 4 : res = TALER_MHD_parse_json_data (rc->connection,
485 : root,
486 : spec);
487 4 : if (GNUNET_SYSERR == res)
488 : {
489 0 : GNUNET_break (0);
490 0 : return MHD_NO; /* hard failure */
491 : }
492 4 : if (GNUNET_NO == res)
493 : {
494 0 : GNUNET_break_op (0);
495 0 : return MHD_YES; /* failure */
496 : }
497 : }
498 : }
499 :
500 : {
501 : struct GNUNET_TIME_Timestamp now;
502 :
503 4 : now = GNUNET_TIME_timestamp_get ();
504 4 : if (! GNUNET_TIME_absolute_approx_eq (
505 : now.abs_time,
506 : rcc->timestamp.abs_time,
507 : TIMESTAMP_TOLERANCE))
508 : {
509 0 : GNUNET_break_op (0);
510 0 : return TALER_MHD_reply_with_error (
511 : rc->connection,
512 : MHD_HTTP_BAD_REQUEST,
513 : TALER_EC_EXCHANGE_GENERIC_CLOCK_SKEW,
514 : NULL);
515 : }
516 : }
517 :
518 4 : if (NULL != rcc->payto_uri.full_payto)
519 4 : TALER_full_payto_hash (rcc->payto_uri,
520 : &rcc->h_payto);
521 4 : if (GNUNET_OK !=
522 4 : TALER_wallet_reserve_close_verify (
523 : rcc->timestamp,
524 4 : &rcc->h_payto,
525 4 : &rcc->reserve_pub,
526 4 : &rcc->reserve_sig))
527 : {
528 0 : GNUNET_break_op (0);
529 0 : return TALER_MHD_reply_with_error (
530 : rc->connection,
531 : MHD_HTTP_FORBIDDEN,
532 : TALER_EC_EXCHANGE_RESERVES_CLOSE_BAD_SIGNATURE,
533 : NULL);
534 : }
535 : }
536 8 : if (NULL != rcc->response)
537 0 : return MHD_queue_response (rc->connection,
538 : rcc->http_status,
539 : rcc->response);
540 8 : if (rcc->resumed &&
541 4 : (! rcc->kyc.ok) )
542 : {
543 2 : if (0 == rcc->kyc.requirement_row)
544 : {
545 0 : GNUNET_break (0);
546 0 : return TALER_MHD_reply_with_error (
547 : rc->connection,
548 : MHD_HTTP_INTERNAL_SERVER_ERROR,
549 : TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
550 : "requirement row not set");
551 : }
552 2 : return TEH_RESPONSE_reply_kyc_required (
553 : rc->connection,
554 2 : &rcc->kyc_payto,
555 2 : &rcc->kyc,
556 : false);
557 : }
558 :
559 6 : if (GNUNET_OK !=
560 6 : TEH_DB_run_transaction (rc->connection,
561 : "reserve close",
562 : TEH_MT_REQUEST_OTHER,
563 : &mhd_ret,
564 : &reserve_close_transaction,
565 : rcc))
566 : {
567 0 : return mhd_ret;
568 : }
569 6 : if (rcc->suspended)
570 4 : return MHD_YES;
571 2 : return reply_reserve_close_success (rcc);
572 : }
573 :
574 :
575 : /* end of taler-exchange-httpd_reserves_close.c */
|