diff options
Diffstat (limited to 'src/backend/anastasis-httpd_truth.c')
-rw-r--r-- | src/backend/anastasis-httpd_truth.c | 1428 |
1 files changed, 1428 insertions, 0 deletions
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c new file mode 100644 index 0000000..164c33a --- /dev/null +++ b/src/backend/anastasis-httpd_truth.c | |||
@@ -0,0 +1,1428 @@ | |||
1 | /* | ||
2 | This file is part of TALER | ||
3 | Copyright (C) 2019, 2021 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 anastasis-httpd_truth.c | ||
18 | * @brief functions to handle incoming requests on /truth | ||
19 | * @author Dennis Neufeld | ||
20 | * @author Dominik Meister | ||
21 | * @author Christian Grothoff | ||
22 | */ | ||
23 | #include "platform.h" | ||
24 | #include "anastasis-httpd.h" | ||
25 | #include "anastasis_service.h" | ||
26 | #include "anastasis-httpd_truth.h" | ||
27 | #include <gnunet/gnunet_util_lib.h> | ||
28 | #include <gnunet/gnunet_rest_lib.h> | ||
29 | #include "anastasis_authorization_lib.h" | ||
30 | #include <taler/taler_merchant_service.h> | ||
31 | #include <taler/taler_json_lib.h> | ||
32 | |||
33 | /** | ||
34 | * What is the maximum frequency at which we allow | ||
35 | * clients to attempt to answer security questions? | ||
36 | */ | ||
37 | #define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \ | ||
38 | GNUNET_TIME_UNIT_SECONDS, 30) | ||
39 | |||
40 | /** | ||
41 | * How long do we hold an HTTP client connection if | ||
42 | * we are awaiting payment before giving up? | ||
43 | */ | ||
44 | #define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \ | ||
45 | GNUNET_TIME_UNIT_MINUTES, 30) | ||
46 | |||
47 | /** | ||
48 | * How long should the wallet check for auto-refunds before giving up? | ||
49 | */ | ||
50 | #define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ | ||
51 | GNUNET_TIME_UNIT_MINUTES, 2) | ||
52 | |||
53 | |||
54 | /** | ||
55 | * How many retries do we allow per code? | ||
56 | */ | ||
57 | #define INITIAL_RETRY_COUNTER 3 | ||
58 | |||
59 | struct GetContext | ||
60 | { | ||
61 | |||
62 | /** | ||
63 | * Payment Identifier | ||
64 | */ | ||
65 | struct ANASTASIS_PaymentSecretP payment_identifier; | ||
66 | |||
67 | /** | ||
68 | * Public key of the challenge which is solved. | ||
69 | */ | ||
70 | struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; | ||
71 | |||
72 | /** | ||
73 | * Key to decrypt the truth. | ||
74 | */ | ||
75 | struct ANASTASIS_CRYPTO_TruthKeyP truth_key; | ||
76 | |||
77 | /** | ||
78 | * true if client provided a payment secret / order ID? | ||
79 | */ | ||
80 | struct TALER_Amount challenge_cost; | ||
81 | |||
82 | /** | ||
83 | * Our handler context. | ||
84 | */ | ||
85 | struct TM_HandlerContext *hc; | ||
86 | |||
87 | /** | ||
88 | * Kept in DLL for shutdown handling while suspended. | ||
89 | */ | ||
90 | struct GetContext *next; | ||
91 | |||
92 | /** | ||
93 | * Kept in DLL for shutdown handling while suspended. | ||
94 | */ | ||
95 | struct GetContext *prev; | ||
96 | |||
97 | /** | ||
98 | * Connection handle for closing or resuming | ||
99 | */ | ||
100 | struct MHD_Connection *connection; | ||
101 | |||
102 | /** | ||
103 | * Reference to the authorization plugin which was loaded | ||
104 | */ | ||
105 | struct ANASTASIS_AuthorizationPlugin *authorization; | ||
106 | |||
107 | /** | ||
108 | * Status of the authorization | ||
109 | */ | ||
110 | struct ANASTASIS_AUTHORIZATION_State *as; | ||
111 | |||
112 | /** | ||
113 | * Used while we are awaiting proposal creation. | ||
114 | */ | ||
115 | struct TALER_MERCHANT_PostOrdersHandle *po; | ||
116 | |||
117 | /** | ||
118 | * Used while we are waiting payment. | ||
119 | */ | ||
120 | struct TALER_MERCHANT_OrderMerchantGetHandle *cpo; | ||
121 | |||
122 | /** | ||
123 | * HTTP response code to use on resume, if non-NULL. | ||
124 | */ | ||
125 | struct MHD_Response *resp; | ||
126 | |||
127 | /** | ||
128 | * How long do we wait at most for payment? | ||
129 | */ | ||
130 | struct GNUNET_TIME_Absolute timeout; | ||
131 | |||
132 | /** | ||
133 | * Random authorization code we are using. | ||
134 | */ | ||
135 | uint64_t code; | ||
136 | |||
137 | /** | ||
138 | * HTTP response code to use on resume, if resp is set. | ||
139 | */ | ||
140 | unsigned int response_code; | ||
141 | |||
142 | /** | ||
143 | * true if client provided a payment secret / order ID? | ||
144 | */ | ||
145 | bool payment_identifier_provided; | ||
146 | |||
147 | /** | ||
148 | * True if this entry is in the #gc_head DLL. | ||
149 | */ | ||
150 | bool in_list; | ||
151 | |||
152 | /** | ||
153 | * True if this entry is currently suspended. | ||
154 | */ | ||
155 | bool suspended; | ||
156 | |||
157 | /** | ||
158 | * Did the request include a response? | ||
159 | */ | ||
160 | bool have_response; | ||
161 | |||
162 | }; | ||
163 | |||
164 | /** | ||
165 | * Information we track for refunds. | ||
166 | */ | ||
167 | struct RefundEntry | ||
168 | { | ||
169 | /** | ||
170 | * Kept in a DLL. | ||
171 | */ | ||
172 | struct RefundEntry *next; | ||
173 | |||
174 | /** | ||
175 | * Kept in a DLL. | ||
176 | */ | ||
177 | struct RefundEntry *prev; | ||
178 | |||
179 | /** | ||
180 | * Operation handle. | ||
181 | */ | ||
182 | struct TALER_MERCHANT_OrderRefundHandle *ro; | ||
183 | |||
184 | /** | ||
185 | * Which order is being refunded. | ||
186 | */ | ||
187 | char *order_id; | ||
188 | |||
189 | /** | ||
190 | * Payment Identifier | ||
191 | */ | ||
192 | struct ANASTASIS_PaymentSecretP payment_identifier; | ||
193 | |||
194 | /** | ||
195 | * Public key of the challenge which is solved. | ||
196 | */ | ||
197 | struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; | ||
198 | }; | ||
199 | |||
200 | |||
201 | /** | ||
202 | * Head of linked list of active refund operations. | ||
203 | */ | ||
204 | static struct RefundEntry *re_head; | ||
205 | |||
206 | /** | ||
207 | * Tail of linked list of active refund operations. | ||
208 | */ | ||
209 | static struct RefundEntry *re_tail; | ||
210 | |||
211 | /** | ||
212 | * Head of linked list over all authorization processes | ||
213 | */ | ||
214 | static struct GetContext *gc_head; | ||
215 | |||
216 | /** | ||
217 | * Tail of linked list over all authorization processes | ||
218 | */ | ||
219 | static struct GetContext *gc_tail; | ||
220 | |||
221 | |||
222 | void | ||
223 | AH_truth_shutdown (void) | ||
224 | { | ||
225 | struct GetContext *gc; | ||
226 | struct RefundEntry *re; | ||
227 | |||
228 | while (NULL != (re = re_head)) | ||
229 | { | ||
230 | GNUNET_CONTAINER_DLL_remove (re_head, | ||
231 | re_tail, | ||
232 | re); | ||
233 | if (NULL != re->ro) | ||
234 | { | ||
235 | TALER_MERCHANT_post_order_refund_cancel (re->ro); | ||
236 | re->ro = NULL; | ||
237 | } | ||
238 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
239 | "Refund `%s' failed due to shutdown\n", | ||
240 | re->order_id); | ||
241 | GNUNET_free (re->order_id); | ||
242 | GNUNET_free (re); | ||
243 | } | ||
244 | |||
245 | while (NULL != (gc = gc_head)) | ||
246 | { | ||
247 | GNUNET_CONTAINER_DLL_remove (gc_head, | ||
248 | gc_tail, | ||
249 | gc); | ||
250 | gc->in_list = false; | ||
251 | if (NULL != gc->cpo) | ||
252 | { | ||
253 | TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); | ||
254 | gc->cpo = NULL; | ||
255 | } | ||
256 | if (NULL != gc->po) | ||
257 | { | ||
258 | TALER_MERCHANT_orders_post_cancel (gc->po); | ||
259 | gc->po = NULL; | ||
260 | } | ||
261 | if (gc->suspended) | ||
262 | { | ||
263 | MHD_resume_connection (gc->connection); | ||
264 | gc->suspended = false; | ||
265 | } | ||
266 | if (NULL != gc->as) | ||
267 | { | ||
268 | gc->authorization->cleanup (gc->as); | ||
269 | gc->as = NULL; | ||
270 | gc->authorization = NULL; | ||
271 | } | ||
272 | } | ||
273 | ANASTASIS_authorization_plugin_shutdown (); | ||
274 | } | ||
275 | |||
276 | |||
277 | /** | ||
278 | * Callback to process a POST /orders/ID/refund request | ||
279 | * | ||
280 | * @param cls closure | ||
281 | * @param http_status HTTP status code for this request | ||
282 | * @param ec taler-specific error code | ||
283 | * @param taler_refund_uri the refund uri offered to the wallet | ||
284 | * @param h_contract hash of the contract a Browser may need to authorize | ||
285 | * obtaining the HTTP response. | ||
286 | */ | ||
287 | static void | ||
288 | refund_cb ( | ||
289 | void *cls, | ||
290 | const struct TALER_MERCHANT_HttpResponse *hr, | ||
291 | const char *taler_refund_uri, | ||
292 | const struct GNUNET_HashCode *h_contract) | ||
293 | { | ||
294 | struct RefundEntry *re = cls; | ||
295 | |||
296 | re->ro = NULL; | ||
297 | switch (hr->http_status) | ||
298 | { | ||
299 | case MHD_HTTP_OK: | ||
300 | { | ||
301 | enum GNUNET_DB_QueryStatus qs; | ||
302 | |||
303 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
304 | "Refund `%s' succeeded\n", | ||
305 | re->order_id); | ||
306 | qs = db->record_challenge_refund (db->cls, | ||
307 | &re->truth_uuid, | ||
308 | &re->payment_identifier); | ||
309 | switch (qs) | ||
310 | { | ||
311 | case GNUNET_DB_STATUS_HARD_ERROR: | ||
312 | GNUNET_break (0); | ||
313 | break; | ||
314 | case GNUNET_DB_STATUS_SOFT_ERROR: | ||
315 | GNUNET_break (0); | ||
316 | break; | ||
317 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | ||
318 | GNUNET_break (0); | ||
319 | break; | ||
320 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | ||
321 | break; | ||
322 | } | ||
323 | } | ||
324 | break; | ||
325 | default: | ||
326 | GNUNET_log (GNUNET_ERROR_TYPE_ERROR, | ||
327 | "Refund `%s' failed with HTTP status %u: %s (#%u)\n", | ||
328 | re->order_id, | ||
329 | hr->http_status, | ||
330 | hr->hint, | ||
331 | (unsigned int) hr->ec); | ||
332 | break; | ||
333 | } | ||
334 | GNUNET_CONTAINER_DLL_remove (re_head, | ||
335 | re_tail, | ||
336 | re); | ||
337 | GNUNET_free (re->order_id); | ||
338 | GNUNET_free (re); | ||
339 | } | ||
340 | |||
341 | |||
342 | /** | ||
343 | * Start to give a refund for the challenge created by @a gc. | ||
344 | * | ||
345 | * @param gc request where we failed and should now grant a refund for | ||
346 | */ | ||
347 | static void | ||
348 | begin_refund (const struct GetContext *gc) | ||
349 | { | ||
350 | struct RefundEntry *re; | ||
351 | |||
352 | re = GNUNET_new (struct RefundEntry); | ||
353 | re->order_id = GNUNET_STRINGS_data_to_string_alloc ( | ||
354 | &gc->payment_identifier, | ||
355 | sizeof (gc->payment_identifier)); | ||
356 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
357 | "Challenge execution failed, triggering refund for order `%s'\n", | ||
358 | re->order_id); | ||
359 | re->payment_identifier = gc->payment_identifier; | ||
360 | re->truth_uuid = gc->truth_uuid; | ||
361 | re->ro = TALER_MERCHANT_post_order_refund (AH_ctx, | ||
362 | AH_backend_url, | ||
363 | re->order_id, | ||
364 | &gc->challenge_cost, | ||
365 | "failed to issue challenge", | ||
366 | &refund_cb, | ||
367 | re); | ||
368 | if (NULL == re->ro) | ||
369 | { | ||
370 | GNUNET_break (0); | ||
371 | GNUNET_free (re->order_id); | ||
372 | GNUNET_free (re); | ||
373 | return; | ||
374 | } | ||
375 | GNUNET_CONTAINER_DLL_insert (re_head, | ||
376 | re_tail, | ||
377 | re); | ||
378 | } | ||
379 | |||
380 | |||
381 | /** | ||
382 | * Callback used to notify the application about completed requests. | ||
383 | * Cleans up the requests data structures. | ||
384 | * | ||
385 | * @param hc | ||
386 | */ | ||
387 | static void | ||
388 | request_done (struct TM_HandlerContext *hc) | ||
389 | { | ||
390 | struct GetContext *gc = hc->ctx; | ||
391 | |||
392 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
393 | "Request completed\n"); | ||
394 | if (NULL == gc) | ||
395 | return; | ||
396 | hc->cc = NULL; | ||
397 | GNUNET_assert (! gc->suspended); | ||
398 | if (gc->in_list) | ||
399 | { | ||
400 | GNUNET_CONTAINER_DLL_remove (gc_head, | ||
401 | gc_tail, | ||
402 | gc); | ||
403 | gc->in_list = false; | ||
404 | } | ||
405 | if (NULL != gc->as) | ||
406 | { | ||
407 | gc->authorization->cleanup (gc->as); | ||
408 | gc->authorization = NULL; | ||
409 | gc->as = NULL; | ||
410 | } | ||
411 | if (NULL != gc->cpo) | ||
412 | { | ||
413 | TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); | ||
414 | gc->cpo = NULL; | ||
415 | } | ||
416 | if (NULL != gc->po) | ||
417 | { | ||
418 | TALER_MERCHANT_orders_post_cancel (gc->po); | ||
419 | gc->po = NULL; | ||
420 | } | ||
421 | GNUNET_free (gc); | ||
422 | hc->ctx = NULL; | ||
423 | } | ||
424 | |||
425 | |||
426 | /** | ||
427 | * Transmit a payment request for @a order_id on @a connection | ||
428 | * | ||
429 | * @param gc context to make payment request for | ||
430 | */ | ||
431 | static void | ||
432 | make_payment_request (struct GetContext *gc) | ||
433 | { | ||
434 | struct MHD_Response *resp; | ||
435 | |||
436 | resp = MHD_create_response_from_buffer (0, | ||
437 | NULL, | ||
438 | MHD_RESPMEM_PERSISTENT); | ||
439 | GNUNET_assert (NULL != resp); | ||
440 | TALER_MHD_add_global_headers (resp); | ||
441 | { | ||
442 | char *hdr; | ||
443 | char *order_id; | ||
444 | const char *pfx; | ||
445 | const char *hn; | ||
446 | |||
447 | if (0 == strncasecmp ("https://", | ||
448 | AH_backend_url, | ||
449 | strlen ("https://"))) | ||
450 | { | ||
451 | pfx = "taler://"; | ||
452 | hn = &AH_backend_url[strlen ("https://")]; | ||
453 | } | ||
454 | else if (0 == strncasecmp ("http://", | ||
455 | AH_backend_url, | ||
456 | strlen ("http://"))) | ||
457 | { | ||
458 | pfx = "taler+http://"; | ||
459 | hn = &AH_backend_url[strlen ("http://")]; | ||
460 | } | ||
461 | else | ||
462 | { | ||
463 | /* This invariant holds as per check in anastasis-httpd.c */ | ||
464 | GNUNET_assert (0); | ||
465 | } | ||
466 | /* This invariant holds as per check in anastasis-httpd.c */ | ||
467 | GNUNET_assert (0 != strlen (hn)); | ||
468 | |||
469 | order_id = GNUNET_STRINGS_data_to_string_alloc ( | ||
470 | &gc->payment_identifier, | ||
471 | sizeof (gc->payment_identifier)); | ||
472 | GNUNET_asprintf (&hdr, | ||
473 | "%spay/%s%s/", | ||
474 | pfx, | ||
475 | hn, | ||
476 | order_id); | ||
477 | GNUNET_free (order_id); | ||
478 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
479 | "Sending payment request `%s'\n", | ||
480 | hdr); | ||
481 | GNUNET_break (MHD_YES == | ||
482 | MHD_add_response_header (resp, | ||
483 | ANASTASIS_HTTP_HEADER_TALER, | ||
484 | hdr)); | ||
485 | GNUNET_free (hdr); | ||
486 | } | ||
487 | gc->resp = resp; | ||
488 | gc->response_code = MHD_HTTP_PAYMENT_REQUIRED; | ||
489 | } | ||
490 | |||
491 | |||
492 | /** | ||
493 | * Callbacks of this type are used to serve the result of submitting a | ||
494 | * /contract request to a merchant. | ||
495 | * | ||
496 | * @param cls our `struct GetContext` | ||
497 | * @param por response details | ||
498 | */ | ||
499 | static void | ||
500 | proposal_cb (void *cls, | ||
501 | const struct TALER_MERCHANT_PostOrdersReply *por) | ||
502 | { | ||
503 | struct GetContext *gc = cls; | ||
504 | enum GNUNET_DB_QueryStatus qs; | ||
505 | |||
506 | gc->po = NULL; | ||
507 | GNUNET_assert (gc->in_list); | ||
508 | GNUNET_CONTAINER_DLL_remove (gc_head, | ||
509 | gc_tail, | ||
510 | gc); | ||
511 | gc->in_list = false; | ||
512 | GNUNET_assert (gc->suspended); | ||
513 | MHD_resume_connection (gc->connection); | ||
514 | gc->suspended = false; | ||
515 | AH_trigger_daemon (NULL); | ||
516 | if (MHD_HTTP_OK != por->hr.http_status) | ||
517 | { | ||
518 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
519 | "Backend returned status %u/%d\n", | ||
520 | por->hr.http_status, | ||
521 | (int) por->hr.ec); | ||
522 | GNUNET_break (0); | ||
523 | gc->resp = TALER_MHD_make_json_pack ( | ||
524 | "{s:I, s:s, s:I, s:I, s:O?}", | ||
525 | "code", | ||
526 | (json_int_t) TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR, | ||
527 | "hint", | ||
528 | "Failed to setup order with merchant backend", | ||
529 | "backend-ec", | ||
530 | (json_int_t) por->hr.ec, | ||
531 | "backend-http-status", | ||
532 | (json_int_t) por->hr.http_status, | ||
533 | "backend-reply", | ||
534 | por->hr.reply); | ||
535 | GNUNET_assert (NULL != gc->resp); | ||
536 | gc->response_code = MHD_HTTP_BAD_GATEWAY; | ||
537 | return; | ||
538 | } | ||
539 | qs = db->record_challenge_payment (db->cls, | ||
540 | &gc->truth_uuid, | ||
541 | &gc->payment_identifier, | ||
542 | &gc->challenge_cost); | ||
543 | if (0 >= qs) | ||
544 | { | ||
545 | GNUNET_break (0); | ||
546 | gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, | ||
547 | "record challenge payment"); | ||
548 | gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
549 | return; | ||
550 | } | ||
551 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
552 | "Setup fresh order, creating payment request\n"); | ||
553 | make_payment_request (gc); | ||
554 | } | ||
555 | |||
556 | |||
557 | /** | ||
558 | * Callback to process a GET /check-payment request | ||
559 | * | ||
560 | * @param cls our `struct GetContext` | ||
561 | * @param hr HTTP response details | ||
562 | * @param osr order status | ||
563 | */ | ||
564 | static void | ||
565 | check_payment_cb (void *cls, | ||
566 | const struct TALER_MERCHANT_HttpResponse *hr, | ||
567 | const struct TALER_MERCHANT_OrderStatusResponse *osr) | ||
568 | |||
569 | { | ||
570 | struct GetContext *gc = cls; | ||
571 | |||
572 | gc->cpo = NULL; | ||
573 | GNUNET_assert (gc->in_list); | ||
574 | GNUNET_CONTAINER_DLL_remove (gc_head, | ||
575 | gc_tail, | ||
576 | gc); | ||
577 | gc->in_list = false; | ||
578 | GNUNET_assert (gc->suspended); | ||
579 | MHD_resume_connection (gc->connection); | ||
580 | gc->suspended = false; | ||
581 | AH_trigger_daemon (NULL); | ||
582 | |||
583 | switch (hr->http_status) | ||
584 | { | ||
585 | case MHD_HTTP_OK: | ||
586 | GNUNET_assert (NULL != osr); | ||
587 | break; | ||
588 | case MHD_HTTP_NOT_FOUND: | ||
589 | /* We created this order before, how can it be not found now? */ | ||
590 | GNUNET_break (0); | ||
591 | gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED, | ||
592 | NULL); | ||
593 | gc->response_code = MHD_HTTP_BAD_GATEWAY; | ||
594 | return; | ||
595 | case MHD_HTTP_BAD_GATEWAY: | ||
596 | gc->resp = TALER_MHD_make_error ( | ||
597 | TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD, | ||
598 | NULL); | ||
599 | gc->response_code = MHD_HTTP_BAD_GATEWAY; | ||
600 | return; | ||
601 | case MHD_HTTP_GATEWAY_TIMEOUT: | ||
602 | gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT, | ||
603 | "Timeout check payment status"); | ||
604 | GNUNET_assert (NULL != gc->resp); | ||
605 | gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; | ||
606 | return; | ||
607 | default: | ||
608 | { | ||
609 | char status[14]; | ||
610 | |||
611 | GNUNET_snprintf (status, | ||
612 | sizeof (status), | ||
613 | "%u", | ||
614 | hr->http_status); | ||
615 | gc->resp = TALER_MHD_make_error ( | ||
616 | TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS, | ||
617 | status); | ||
618 | GNUNET_assert (NULL != gc->resp); | ||
619 | gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
620 | return; | ||
621 | } | ||
622 | } | ||
623 | |||
624 | switch (osr->status) | ||
625 | { | ||
626 | case TALER_MERCHANT_OSC_PAID: | ||
627 | { | ||
628 | enum GNUNET_DB_QueryStatus qs; | ||
629 | |||
630 | qs = db->update_challenge_payment (db->cls, | ||
631 | &gc->truth_uuid, | ||
632 | &gc->payment_identifier); | ||
633 | if (0 <= qs) | ||
634 | { | ||
635 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
636 | "Order has been paid, continuing with request processing\n"); | ||
637 | return; /* continue as planned */ | ||
638 | } | ||
639 | GNUNET_break (0); | ||
640 | gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, | ||
641 | "update challenge payment"); | ||
642 | gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; | ||
643 | return; /* continue as planned */ | ||
644 | } | ||
645 | case TALER_MERCHANT_OSC_CLAIMED: | ||
646 | case TALER_MERCHANT_OSC_UNPAID: | ||
647 | /* repeat payment request */ | ||
648 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
649 | "Order remains unpaid, sending payment request again\n"); | ||
650 | make_payment_request (gc); | ||
651 | return; | ||
652 | } | ||
653 | /* should never get here */ | ||
654 | GNUNET_break (0); | ||
655 | } | ||
656 | |||
657 | |||
658 | /** | ||
659 | * Helper function used to ask our backend to begin processing a | ||
660 | * payment for the user's account. May perform asynchronous | ||
661 | * operations by suspending the connection if required. | ||
662 | * | ||
663 | * @param gc context to begin payment for. | ||
664 | * @return MHD status code | ||
665 | */ | ||
666 | static MHD_RESULT | ||
667 | begin_payment (struct GetContext *gc) | ||
668 | { | ||
669 | enum GNUNET_DB_QueryStatus qs; | ||
670 | char *order_id; | ||
671 | |||
672 | qs = db->lookup_challenge_payment (db->cls, | ||
673 | &gc->truth_uuid, | ||
674 | &gc->payment_identifier); | ||
675 | if (qs < 0) | ||
676 | { | ||
677 | GNUNET_break (0); | ||
678 | return TALER_MHD_reply_with_error (gc->connection, | ||
679 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
680 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
681 | "lookup challenge payment"); | ||
682 | } | ||
683 | GNUNET_assert (! gc->in_list); | ||
684 | gc->in_list = true; | ||
685 | GNUNET_CONTAINER_DLL_insert (gc_tail, | ||
686 | gc_head, | ||
687 | gc); | ||
688 | GNUNET_assert (! gc->suspended); | ||
689 | gc->suspended = true; | ||
690 | MHD_suspend_connection (gc->connection); | ||
691 | if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) | ||
692 | { | ||
693 | /* We already created the order, check if it was paid */ | ||
694 | struct GNUNET_TIME_Relative timeout; | ||
695 | |||
696 | order_id = GNUNET_STRINGS_data_to_string_alloc ( | ||
697 | &gc->payment_identifier, | ||
698 | sizeof (gc->payment_identifier)); | ||
699 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
700 | "Order exists, checking payment status for order `%s'\n", | ||
701 | order_id); | ||
702 | timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout); | ||
703 | gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx, | ||
704 | AH_backend_url, | ||
705 | order_id, | ||
706 | NULL /* NOT session-bound */, | ||
707 | false, | ||
708 | timeout, | ||
709 | &check_payment_cb, | ||
710 | gc); | ||
711 | } | ||
712 | else | ||
713 | { | ||
714 | /* Create a fresh order */ | ||
715 | json_t *order; | ||
716 | struct GNUNET_TIME_Absolute pay_deadline; | ||
717 | |||
718 | GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, | ||
719 | &gc->payment_identifier, | ||
720 | sizeof (struct ANASTASIS_PaymentSecretP)); | ||
721 | order_id = GNUNET_STRINGS_data_to_string_alloc ( | ||
722 | &gc->payment_identifier, | ||
723 | sizeof (gc->payment_identifier)); | ||
724 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
725 | "Creating fresh order `%s'\n", | ||
726 | order_id); | ||
727 | pay_deadline = GNUNET_TIME_relative_to_absolute ( | ||
728 | ANASTASIS_CHALLENGE_OFFER_LIFETIME); | ||
729 | GNUNET_TIME_round_abs (&pay_deadline); | ||
730 | order = json_pack ("{s:o, s:s, s:s, s:o, s:o}", | ||
731 | "amount", TALER_JSON_from_amount (&gc->challenge_cost), | ||
732 | "summary", "challenge fee for anastasis service", | ||
733 | "order_id", order_id, | ||
734 | "auto_refund", GNUNET_JSON_from_time_rel ( | ||
735 | AUTO_REFUND_TIMEOUT), | ||
736 | "pay_deadline", GNUNET_JSON_from_time_abs ( | ||
737 | pay_deadline)); | ||
738 | gc->po = TALER_MERCHANT_orders_post2 (AH_ctx, | ||
739 | AH_backend_url, | ||
740 | order, | ||
741 | AUTO_REFUND_TIMEOUT, | ||
742 | NULL, /* no payment target */ | ||
743 | 0, | ||
744 | NULL, /* no inventory products */ | ||
745 | 0, | ||
746 | NULL, /* no uuids */ | ||
747 | false, /* do NOT require claim token */ | ||
748 | &proposal_cb, | ||
749 | gc); | ||
750 | json_decref (order); | ||
751 | } | ||
752 | GNUNET_free (order_id); | ||
753 | AH_trigger_curl (); | ||
754 | return MHD_YES; | ||
755 | } | ||
756 | |||
757 | |||
758 | /** | ||
759 | * Load encrypted keyshare from db and return it to the client. | ||
760 | * | ||
761 | * @param truth_uuid UUID to the truth for the looup | ||
762 | * @param connection the connection to respond upon | ||
763 | * @return MHD status code | ||
764 | */ | ||
765 | static MHD_RESULT | ||
766 | return_key_share ( | ||
767 | const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, | ||
768 | struct MHD_Connection *connection) | ||
769 | { | ||
770 | struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare; | ||
771 | |||
772 | { | ||
773 | enum GNUNET_DB_QueryStatus qs; | ||
774 | |||
775 | qs = db->get_key_share (db->cls, | ||
776 | truth_uuid, | ||
777 | &encrypted_keyshare); | ||
778 | switch (qs) | ||
779 | { | ||
780 | case GNUNET_DB_STATUS_HARD_ERROR: | ||
781 | case GNUNET_DB_STATUS_SOFT_ERROR: | ||
782 | GNUNET_break (0); | ||
783 | return TALER_MHD_reply_with_error (connection, | ||
784 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
785 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
786 | "get key share"); | ||
787 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | ||
788 | return TALER_MHD_reply_with_error (connection, | ||
789 | MHD_HTTP_NOT_FOUND, | ||
790 | TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE, | ||
791 | NULL); | ||
792 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | ||
793 | break; | ||
794 | } | ||
795 | } | ||
796 | |||
797 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
798 | "Returning key share\n"); | ||
799 | { | ||
800 | struct MHD_Response *resp; | ||
801 | MHD_RESULT ret; | ||
802 | |||
803 | resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare), | ||
804 | &encrypted_keyshare, | ||
805 | MHD_RESPMEM_MUST_COPY); | ||
806 | TALER_MHD_add_global_headers (resp); | ||
807 | ret = MHD_queue_response (connection, | ||
808 | MHD_HTTP_OK, | ||
809 | resp); | ||
810 | MHD_destroy_response (resp); | ||
811 | return ret; | ||
812 | } | ||
813 | } | ||
814 | |||
815 | |||
816 | /** | ||
817 | * Run the authorization method-specific 'process' function and continue | ||
818 | * based on its result with generating an HTTP response. | ||
819 | * | ||
820 | * @param connection the connection we are handling | ||
821 | * @param gc our overall handler context | ||
822 | */ | ||
823 | static MHD_RESULT | ||
824 | run_authorization_process (struct MHD_Connection *connection, | ||
825 | struct GetContext *gc) | ||
826 | { | ||
827 | enum ANASTASIS_AUTHORIZATION_Result ret; | ||
828 | enum GNUNET_DB_QueryStatus qs; | ||
829 | |||
830 | ret = gc->authorization->process (gc->as, | ||
831 | connection); | ||
832 | switch (ret) | ||
833 | { | ||
834 | case ANASTASIS_AUTHORIZATION_RES_SUCCESS: | ||
835 | /* Challenge sent successfully */ | ||
836 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
837 | "Authorization request sent successfully\n"); | ||
838 | qs = db->mark_challenge_sent (db->cls, | ||
839 | &gc->payment_identifier, | ||
840 | &gc->truth_uuid, | ||
841 | gc->code); | ||
842 | GNUNET_break (0 < qs); | ||
843 | gc->authorization->cleanup (gc->as); | ||
844 | gc->as = NULL; | ||
845 | return MHD_YES; | ||
846 | case ANASTASIS_AUTHORIZATION_RES_FAILED: | ||
847 | if (gc->payment_identifier_provided) | ||
848 | { | ||
849 | begin_refund (gc); | ||
850 | } | ||
851 | gc->authorization->cleanup (gc->as); | ||
852 | gc->as = NULL; | ||
853 | return MHD_YES; | ||
854 | case ANASTASIS_AUTHORIZATION_RES_SUSPENDED: | ||
855 | /* connection was suspended again, odd that this happens */ | ||
856 | return MHD_YES; | ||
857 | case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED: | ||
858 | /* Challenge sent successfully */ | ||
859 | qs = db->mark_challenge_sent (db->cls, | ||
860 | &gc->payment_identifier, | ||
861 | &gc->truth_uuid, | ||
862 | gc->code); | ||
863 | GNUNET_break (0 < qs); | ||
864 | gc->authorization->cleanup (gc->as); | ||
865 | gc->as = NULL; | ||
866 | return MHD_NO; | ||
867 | case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED: | ||
868 | gc->authorization->cleanup (gc->as); | ||
869 | gc->as = NULL; | ||
870 | return MHD_NO; | ||
871 | } | ||
872 | GNUNET_break (0); | ||
873 | return MHD_NO; | ||
874 | } | ||
875 | |||
876 | |||
877 | /** | ||
878 | * @param connection the MHD connection to handle | ||
879 | * @param url handles a URL of the format "/truth/$UUID[&response=$RESPONSE]" | ||
880 | * @param hc | ||
881 | * @return MHD result code | ||
882 | */ | ||
883 | MHD_RESULT | ||
884 | AH_handler_truth_get ( | ||
885 | struct MHD_Connection *connection, | ||
886 | const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, | ||
887 | struct TM_HandlerContext *hc) | ||
888 | { | ||
889 | struct GetContext *gc = hc->ctx; | ||
890 | struct GNUNET_HashCode challenge_response; | ||
891 | void *encrypted_truth; | ||
892 | size_t encrypted_truth_size; | ||
893 | void *decrypted_truth; | ||
894 | size_t decrypted_truth_size; | ||
895 | char *truth_mime = NULL; | ||
896 | bool is_question; | ||
897 | |||
898 | if (NULL == gc) | ||
899 | { | ||
900 | /* Fresh request, do initial setup */ | ||
901 | gc = GNUNET_new (struct GetContext); | ||
902 | gc->hc = hc; | ||
903 | hc->ctx = gc; | ||
904 | gc->connection = connection; | ||
905 | gc->truth_uuid = *truth_uuid; | ||
906 | gc->hc->cc = &request_done; | ||
907 | { | ||
908 | const char *pay_id; | ||
909 | |||
910 | pay_id = MHD_lookup_connection_value (connection, | ||
911 | MHD_HEADER_KIND, | ||
912 | ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); | ||
913 | if (NULL != pay_id) | ||
914 | { | ||
915 | if (GNUNET_OK != | ||
916 | GNUNET_STRINGS_string_to_data ( | ||
917 | pay_id, | ||
918 | strlen (pay_id), | ||
919 | &gc->payment_identifier, | ||
920 | sizeof (struct ANASTASIS_PaymentSecretP))) | ||
921 | { | ||
922 | GNUNET_break_op (0); | ||
923 | return TALER_MHD_reply_with_error (connection, | ||
924 | MHD_HTTP_BAD_REQUEST, | ||
925 | TALER_EC_GENERIC_PARAMETER_MALFORMED, | ||
926 | ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER); | ||
927 | } | ||
928 | gc->payment_identifier_provided = true; | ||
929 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
930 | "Client provided payment identifier `%s'\n", | ||
931 | pay_id); | ||
932 | } | ||
933 | } | ||
934 | |||
935 | { | ||
936 | /* check if header contains Truth-Decryption-Key */ | ||
937 | const char *tdk; | ||
938 | |||
939 | tdk = MHD_lookup_connection_value (connection, | ||
940 | MHD_HEADER_KIND, | ||
941 | ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); | ||
942 | if (NULL == tdk) | ||
943 | { | ||
944 | GNUNET_break_op (0); | ||
945 | return TALER_MHD_reply_with_error (connection, | ||
946 | MHD_HTTP_BAD_REQUEST, | ||
947 | TALER_EC_GENERIC_PARAMETER_MISSING, | ||
948 | ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); | ||
949 | } | ||
950 | |||
951 | if (GNUNET_OK != | ||
952 | GNUNET_STRINGS_string_to_data ( | ||
953 | tdk, | ||
954 | strlen (tdk), | ||
955 | &gc->truth_key, | ||
956 | sizeof (struct ANASTASIS_CRYPTO_TruthKeyP))) | ||
957 | { | ||
958 | GNUNET_break_op (0); | ||
959 | return TALER_MHD_reply_with_error (connection, | ||
960 | MHD_HTTP_BAD_REQUEST, | ||
961 | TALER_EC_GENERIC_PARAMETER_MALFORMED, | ||
962 | ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY); | ||
963 | } | ||
964 | } | ||
965 | |||
966 | { | ||
967 | const char *challenge_response_s; | ||
968 | |||
969 | challenge_response_s = MHD_lookup_connection_value (connection, | ||
970 | MHD_GET_ARGUMENT_KIND, | ||
971 | "response"); | ||
972 | if ( (NULL != challenge_response_s) && | ||
973 | (GNUNET_OK != | ||
974 | GNUNET_CRYPTO_hash_from_string (challenge_response_s, | ||
975 | &challenge_response)) ) | ||
976 | { | ||
977 | GNUNET_break_op (0); | ||
978 | return TALER_MHD_reply_with_error (connection, | ||
979 | MHD_HTTP_BAD_REQUEST, | ||
980 | TALER_EC_GENERIC_PARAMETER_MALFORMED, | ||
981 | "response"); | ||
982 | } | ||
983 | gc->have_response = (NULL != challenge_response_s); | ||
984 | } | ||
985 | |||
986 | { | ||
987 | const char *long_poll_timeout_ms; | ||
988 | |||
989 | long_poll_timeout_ms = MHD_lookup_connection_value (connection, | ||
990 | MHD_GET_ARGUMENT_KIND, | ||
991 | "timeout_ms"); | ||
992 | if (NULL != long_poll_timeout_ms) | ||
993 | { | ||
994 | unsigned int timeout; | ||
995 | |||
996 | if (1 != sscanf (long_poll_timeout_ms, | ||
997 | "%u", | ||
998 | &timeout)) | ||
999 | { | ||
1000 | GNUNET_break_op (0); | ||
1001 | return TALER_MHD_reply_with_error (connection, | ||
1002 | MHD_HTTP_BAD_REQUEST, | ||
1003 | TALER_EC_GENERIC_PARAMETER_MALFORMED, | ||
1004 | "timeout_ms (must be non-negative number)"); | ||
1005 | } | ||
1006 | gc->timeout | ||
1007 | = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply ( | ||
1008 | GNUNET_TIME_UNIT_MILLISECONDS, | ||
1009 | timeout)); | ||
1010 | } | ||
1011 | else | ||
1012 | { | ||
1013 | gc->timeout = GNUNET_TIME_relative_to_absolute ( | ||
1014 | GNUNET_TIME_UNIT_SECONDS); | ||
1015 | } | ||
1016 | } | ||
1017 | |||
1018 | } /* end of first-time initialization (if NULL == gc) */ | ||
1019 | else | ||
1020 | { | ||
1021 | if (NULL != gc->resp) | ||
1022 | { | ||
1023 | MHD_RESULT ret; | ||
1024 | |||
1025 | /* We generated a response asynchronously, queue that */ | ||
1026 | ret = MHD_queue_response (connection, | ||
1027 | gc->response_code, | ||
1028 | gc->resp); | ||
1029 | GNUNET_break (MHD_YES == ret); | ||
1030 | MHD_destroy_response (gc->resp); | ||
1031 | gc->resp = NULL; | ||
1032 | return ret; | ||
1033 | } | ||
1034 | if (NULL != gc->as) | ||
1035 | { | ||
1036 | /* Authorization process is "running", check what is going on */ | ||
1037 | GNUNET_assert (NULL != gc->authorization); | ||
1038 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1039 | "Continuing with running the authorization process\n"); | ||
1040 | return run_authorization_process (connection, | ||
1041 | gc); | ||
1042 | |||
1043 | } | ||
1044 | /* We get here if the async check for payment said this request | ||
1045 | was indeed paid! */ | ||
1046 | } | ||
1047 | |||
1048 | { | ||
1049 | /* load encrypted truth from DB */ | ||
1050 | enum GNUNET_DB_QueryStatus qs; | ||
1051 | char *method; | ||
1052 | |||
1053 | qs = db->get_escrow_challenge (db->cls, | ||
1054 | &gc->truth_uuid, | ||
1055 | &encrypted_truth, | ||
1056 | &encrypted_truth_size, | ||
1057 | &truth_mime, | ||
1058 | &method); | ||
1059 | switch (qs) | ||
1060 | { | ||
1061 | case GNUNET_DB_STATUS_HARD_ERROR: | ||
1062 | case GNUNET_DB_STATUS_SOFT_ERROR: | ||
1063 | GNUNET_break (0); | ||
1064 | return TALER_MHD_reply_with_error (gc->connection, | ||
1065 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1066 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1067 | "get escrow challenge"); | ||
1068 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | ||
1069 | return TALER_MHD_reply_with_error (connection, | ||
1070 | MHD_HTTP_NOT_FOUND, | ||
1071 | TALER_EC_ANASTASIS_TRUTH_UNKNOWN, | ||
1072 | NULL); | ||
1073 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | ||
1074 | break; | ||
1075 | } | ||
1076 | is_question = (0 == strcmp ("question", | ||
1077 | method)); | ||
1078 | if (! is_question) | ||
1079 | { | ||
1080 | gc->authorization | ||
1081 | = ANASTASIS_authorization_plugin_load (method, | ||
1082 | AH_cfg, | ||
1083 | &gc->challenge_cost); | ||
1084 | if (NULL == gc->authorization) | ||
1085 | { | ||
1086 | MHD_RESULT ret; | ||
1087 | |||
1088 | ret = TALER_MHD_reply_with_error ( | ||
1089 | connection, | ||
1090 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1091 | TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, | ||
1092 | method); | ||
1093 | GNUNET_free (encrypted_truth); | ||
1094 | GNUNET_free (truth_mime); | ||
1095 | GNUNET_free (method); | ||
1096 | return ret; | ||
1097 | } | ||
1098 | } | ||
1099 | else | ||
1100 | { | ||
1101 | gc->challenge_cost = AH_question_cost; | ||
1102 | } | ||
1103 | GNUNET_free (method); | ||
1104 | } | ||
1105 | |||
1106 | { | ||
1107 | struct TALER_Amount zero_amount; | ||
1108 | |||
1109 | TALER_amount_set_zero (AH_currency, | ||
1110 | &zero_amount); | ||
1111 | if (0 != TALER_amount_cmp (&gc->challenge_cost, | ||
1112 | &zero_amount)) | ||
1113 | { | ||
1114 | /* Check database to see if the transaction is paid for */ | ||
1115 | enum GNUNET_DB_QueryStatus qs; | ||
1116 | bool paid; | ||
1117 | |||
1118 | if (! gc->payment_identifier_provided) | ||
1119 | { | ||
1120 | GNUNET_free (truth_mime); | ||
1121 | GNUNET_free (encrypted_truth); | ||
1122 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1123 | "Beginning payment, client did not provide payment identifier\n"); | ||
1124 | return begin_payment (gc); | ||
1125 | } | ||
1126 | qs = db->check_challenge_payment (db->cls, | ||
1127 | &gc->payment_identifier, | ||
1128 | &gc->truth_uuid, | ||
1129 | &paid); | ||
1130 | switch (qs) | ||
1131 | { | ||
1132 | case GNUNET_DB_STATUS_HARD_ERROR: | ||
1133 | case GNUNET_DB_STATUS_SOFT_ERROR: | ||
1134 | GNUNET_break (0); | ||
1135 | GNUNET_free (truth_mime); | ||
1136 | GNUNET_free (encrypted_truth); | ||
1137 | return TALER_MHD_reply_with_error (gc->connection, | ||
1138 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1139 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1140 | "check challenge payment"); | ||
1141 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | ||
1142 | /* Create fresh payment identifier (cannot trust client) */ | ||
1143 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1144 | "Client-provided payment identifier is unknown.\n"); | ||
1145 | GNUNET_free (truth_mime); | ||
1146 | GNUNET_free (encrypted_truth); | ||
1147 | return begin_payment (gc); | ||
1148 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | ||
1149 | if (! paid) | ||
1150 | { | ||
1151 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1152 | "Payment identifier known. Checking payment with client's payment identifier\n"); | ||
1153 | GNUNET_free (truth_mime); | ||
1154 | GNUNET_free (encrypted_truth); | ||
1155 | return begin_payment (gc); | ||
1156 | } | ||
1157 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1158 | "Payment confirmed\n"); | ||
1159 | break; | ||
1160 | } | ||
1161 | } | ||
1162 | else | ||
1163 | { | ||
1164 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1165 | "Request is free of charge\n"); | ||
1166 | } | ||
1167 | } | ||
1168 | |||
1169 | /* We've been paid, now validate response */ | ||
1170 | { | ||
1171 | /* decrypt encrypted_truth */ | ||
1172 | ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, | ||
1173 | encrypted_truth, | ||
1174 | encrypted_truth_size, | ||
1175 | &decrypted_truth, | ||
1176 | &decrypted_truth_size); | ||
1177 | GNUNET_free (encrypted_truth); | ||
1178 | } | ||
1179 | if (NULL == decrypted_truth) | ||
1180 | { | ||
1181 | GNUNET_free (truth_mime); | ||
1182 | return TALER_MHD_reply_with_error (connection, | ||
1183 | MHD_HTTP_EXPECTATION_FAILED, | ||
1184 | TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, | ||
1185 | NULL); | ||
1186 | } | ||
1187 | |||
1188 | /* Special case for secure question: we do not generate a numeric challenge, | ||
1189 | but check that the hash matches */ | ||
1190 | if (is_question) | ||
1191 | { | ||
1192 | if (! gc->have_response) | ||
1193 | { | ||
1194 | GNUNET_free (decrypted_truth); | ||
1195 | GNUNET_free (truth_mime); | ||
1196 | return TALER_MHD_reply_with_error (connection, | ||
1197 | MHD_HTTP_FORBIDDEN, | ||
1198 | TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED, | ||
1199 | NULL); | ||
1200 | } | ||
1201 | |||
1202 | { | ||
1203 | enum GNUNET_DB_QueryStatus qs; | ||
1204 | struct GNUNET_TIME_Absolute rt; | ||
1205 | uint64_t code; | ||
1206 | enum ANASTASIS_DB_CodeStatus cs; | ||
1207 | struct GNUNET_HashCode hc; | ||
1208 | |||
1209 | rt = GNUNET_TIME_UNIT_FOREVER_ABS; | ||
1210 | qs = db->create_challenge_code (db->cls, | ||
1211 | &gc->truth_uuid, | ||
1212 | MAX_QUESTION_FREQ, | ||
1213 | GNUNET_TIME_UNIT_HOURS, | ||
1214 | INITIAL_RETRY_COUNTER, | ||
1215 | &rt, | ||
1216 | &code); | ||
1217 | if (0 > qs) | ||
1218 | { | ||
1219 | GNUNET_break (0 < qs); | ||
1220 | GNUNET_free (decrypted_truth); | ||
1221 | GNUNET_free (truth_mime); | ||
1222 | return TALER_MHD_reply_with_error (connection, | ||
1223 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1224 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1225 | "create_challenge_code (for rate limiting)"); | ||
1226 | } | ||
1227 | if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) | ||
1228 | { | ||
1229 | GNUNET_free (decrypted_truth); | ||
1230 | GNUNET_free (truth_mime); | ||
1231 | return TALER_MHD_reply_with_error (connection, | ||
1232 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1233 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1234 | NULL); | ||
1235 | } | ||
1236 | /* decrement trial counter */ | ||
1237 | ANASTASIS_hash_answer (code + 1, /* always use wrong answer */ | ||
1238 | &hc); | ||
1239 | cs = db->verify_challenge_code (db->cls, | ||
1240 | &gc->truth_uuid, | ||
1241 | &hc); | ||
1242 | switch (cs) | ||
1243 | { | ||
1244 | case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: | ||
1245 | /* good, what we wanted */ | ||
1246 | break; | ||
1247 | case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: | ||
1248 | case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: | ||
1249 | GNUNET_break (0); | ||
1250 | return TALER_MHD_reply_with_error (gc->connection, | ||
1251 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1252 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1253 | "verify_challenge_code"); | ||
1254 | case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: | ||
1255 | return TALER_MHD_reply_with_error (connection, | ||
1256 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1257 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1258 | NULL); | ||
1259 | case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: | ||
1260 | /* this should be impossible, we used code+1 */ | ||
1261 | GNUNET_assert (0); | ||
1262 | } | ||
1263 | } | ||
1264 | if ( (decrypted_truth_size != sizeof (challenge_response)) || | ||
1265 | (0 != memcmp (&challenge_response, | ||
1266 | decrypted_truth, | ||
1267 | decrypted_truth_size)) ) | ||
1268 | { | ||
1269 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1270 | "Wrong answer provided to secure question had %u bytes, wanted %u\n", | ||
1271 | (unsigned int) decrypted_truth_size, | ||
1272 | (unsigned int) sizeof (challenge_response)); | ||
1273 | GNUNET_free (decrypted_truth); | ||
1274 | GNUNET_free (truth_mime); | ||
1275 | return TALER_MHD_reply_with_error (connection, | ||
1276 | MHD_HTTP_FORBIDDEN, | ||
1277 | TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, | ||
1278 | NULL); | ||
1279 | } | ||
1280 | GNUNET_free (decrypted_truth); | ||
1281 | GNUNET_free (truth_mime); | ||
1282 | return return_key_share (&gc->truth_uuid, | ||
1283 | connection); | ||
1284 | } | ||
1285 | |||
1286 | /* Not security question, check for answer in DB */ | ||
1287 | if (gc->have_response) | ||
1288 | { | ||
1289 | enum ANASTASIS_DB_CodeStatus cs; | ||
1290 | |||
1291 | GNUNET_free (decrypted_truth); | ||
1292 | GNUNET_free (truth_mime); | ||
1293 | cs = db->verify_challenge_code (db->cls, | ||
1294 | &gc->truth_uuid, | ||
1295 | &challenge_response); | ||
1296 | switch (cs) | ||
1297 | { | ||
1298 | case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH: | ||
1299 | GNUNET_log (GNUNET_ERROR_TYPE_WARNING, | ||
1300 | "Provided response does not match our stored challenge\n"); | ||
1301 | return TALER_MHD_reply_with_error (connection, | ||
1302 | MHD_HTTP_FORBIDDEN, | ||
1303 | TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED, | ||
1304 | NULL); | ||
1305 | case ANASTASIS_DB_CODE_STATUS_HARD_ERROR: | ||
1306 | case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR: | ||
1307 | GNUNET_break (0); | ||
1308 | return TALER_MHD_reply_with_error (gc->connection, | ||
1309 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1310 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1311 | "verify_challenge_code"); | ||
1312 | case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: | ||
1313 | GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, | ||
1314 | "No challenge known (challenge is invalidated after %u requests)\n", | ||
1315 | INITIAL_RETRY_COUNTER); | ||
1316 | return TALER_MHD_reply_with_error (connection, | ||
1317 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1318 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1319 | NULL); | ||
1320 | case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: | ||
1321 | return return_key_share (&gc->truth_uuid, | ||
1322 | connection); | ||
1323 | } | ||
1324 | GNUNET_break (0); | ||
1325 | return MHD_NO; | ||
1326 | } | ||
1327 | |||
1328 | /* Not security question and no answer: use plugin to check if | ||
1329 | decrypted truth is a valid challenge! */ | ||
1330 | { | ||
1331 | enum GNUNET_GenericReturnValue ret; | ||
1332 | |||
1333 | ret = gc->authorization->validate (gc->authorization->cls, | ||
1334 | connection, | ||
1335 | truth_mime, | ||
1336 | decrypted_truth, | ||
1337 | decrypted_truth_size); | ||
1338 | GNUNET_free (truth_mime); | ||
1339 | switch (ret) | ||
1340 | { | ||
1341 | case GNUNET_OK: | ||
1342 | /* data valid, continued below */ | ||
1343 | break; | ||
1344 | case GNUNET_NO: | ||
1345 | /* data invalid, reply was queued */ | ||
1346 | GNUNET_free (decrypted_truth); | ||
1347 | return MHD_YES; | ||
1348 | case GNUNET_SYSERR: | ||
1349 | /* data invalid, reply was NOT queued */ | ||
1350 | GNUNET_free (decrypted_truth); | ||
1351 | return MHD_NO; | ||
1352 | } | ||
1353 | } | ||
1354 | |||
1355 | /* Setup challenge and begin authorization process */ | ||
1356 | { | ||
1357 | struct GNUNET_TIME_Absolute transmission_date; | ||
1358 | enum GNUNET_DB_QueryStatus qs; | ||
1359 | |||
1360 | qs = db->create_challenge_code (db->cls, | ||
1361 | &gc->truth_uuid, | ||
1362 | gc->authorization->code_rotation_period, | ||
1363 | gc->authorization->code_validity_period, | ||
1364 | INITIAL_RETRY_COUNTER, | ||
1365 | &transmission_date, | ||
1366 | &gc->code); | ||
1367 | switch (qs) | ||
1368 | { | ||
1369 | case GNUNET_DB_STATUS_HARD_ERROR: | ||
1370 | case GNUNET_DB_STATUS_SOFT_ERROR: | ||
1371 | GNUNET_break (0); | ||
1372 | GNUNET_free (decrypted_truth); | ||
1373 | return TALER_MHD_reply_with_error (gc->connection, | ||
1374 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1375 | TALER_EC_GENERIC_DB_FETCH_FAILED, | ||
1376 | "store_challenge_code"); | ||
1377 | case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: | ||
1378 | /* 0 == retry_counter of existing challenge => rate limit exceeded */ | ||
1379 | GNUNET_free (decrypted_truth); | ||
1380 | return TALER_MHD_reply_with_error (connection, | ||
1381 | MHD_HTTP_TOO_MANY_REQUESTS, | ||
1382 | TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED, | ||
1383 | NULL); | ||
1384 | case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: | ||
1385 | /* challenge code was stored successfully*/ | ||
1386 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1387 | "Created fresh challenge\n"); | ||
1388 | break; | ||
1389 | } | ||
1390 | |||
1391 | if (GNUNET_TIME_absolute_get_duration (transmission_date).rel_value_us < | ||
1392 | gc->authorization->code_retransmission_frequency.rel_value_us) | ||
1393 | { | ||
1394 | /* Too early for a retransmission! */ | ||
1395 | GNUNET_free (decrypted_truth); | ||
1396 | return TALER_MHD_reply_with_error (gc->connection, | ||
1397 | MHD_HTTP_ALREADY_REPORTED, | ||
1398 | TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE, | ||
1399 | NULL); | ||
1400 | } | ||
1401 | } | ||
1402 | |||
1403 | GNUNET_log (GNUNET_ERROR_TYPE_INFO, | ||
1404 | "Beginning authorization process\n"); | ||
1405 | gc->as = gc->authorization->start (gc->authorization->cls, | ||
1406 | &AH_trigger_daemon, | ||
1407 | NULL, | ||
1408 | &gc->truth_uuid, | ||
1409 | gc->code, | ||
1410 | decrypted_truth, | ||
1411 | decrypted_truth_size); | ||
1412 | GNUNET_free (decrypted_truth); | ||
1413 | if (NULL == gc->as) | ||
1414 | { | ||
1415 | GNUNET_break (0); | ||
1416 | return TALER_MHD_reply_with_error (gc->connection, | ||
1417 | MHD_HTTP_INTERNAL_SERVER_ERROR, | ||
1418 | TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, | ||
1419 | NULL); | ||
1420 | } | ||
1421 | GNUNET_assert (! gc->in_list); | ||
1422 | gc->in_list = true; | ||
1423 | GNUNET_CONTAINER_DLL_insert (gc_head, | ||
1424 | gc_tail, | ||
1425 | gc); | ||
1426 | return run_authorization_process (connection, | ||
1427 | gc); | ||
1428 | } | ||