/* This file is part of Anastasis Copyright (C) 2019-2022 Anastasis SARL Anastasis is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Anastasis is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Anastasis; see the file COPYING. If not, see */ /** * @file anastasis-httpd_truth-challenge.c * @brief functions to handle incoming requests on /truth/$TID/challenge * @author Dennis Neufeld * @author Dominik Meister * @author Christian Grothoff */ #include "platform.h" #include "anastasis-httpd.h" #include "anastasis_service.h" #include "anastasis-httpd_truth.h" #include #include #include "anastasis_authorization_lib.h" #include #include #include /** * What is the maximum frequency at which we allow * clients to attempt to answer security questions? */ #define MAX_QUESTION_FREQ GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_SECONDS, 30) /** * How long should the wallet check for auto-refunds before giving up? */ #define AUTO_REFUND_TIMEOUT GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_MINUTES, 2) /** * How long should the wallet check for payment before giving up? */ #define PAYMENT_TIMEOUT GNUNET_TIME_relative_multiply ( \ GNUNET_TIME_UNIT_SECONDS, 15) /** * How many retries do we allow per code? */ #define INITIAL_RETRY_COUNTER 3 struct ChallengeContext { /** * Payment Identifier */ struct ANASTASIS_PaymentSecretP payment_identifier; /** * Public key of the challenge which is solved. */ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; /** * Key to decrypt the truth. */ struct ANASTASIS_CRYPTO_TruthKeyP truth_key; /** * Cost for paying the challenge. */ struct TALER_Amount challenge_cost; /** * Our handler context. */ struct TM_HandlerContext *hc; /** * Opaque parsing context. */ void *opaque_post_parsing_context; /** * Uploaded JSON data, NULL if upload is not yet complete. */ json_t *root; /** * Kept in DLL for shutdown handling while suspended. */ struct ChallengeContext *next; /** * Kept in DLL for shutdown handling while suspended. */ struct ChallengeContext *prev; /** * Connection handle for closing or resuming */ struct MHD_Connection *connection; /** * Reference to the authorization plugin which was loaded */ struct ANASTASIS_AuthorizationPlugin *authorization; /** * Status of the authorization */ struct ANASTASIS_AUTHORIZATION_State *as; /** * Used while we are awaiting proposal creation. */ struct TALER_MERCHANT_PostOrdersHandle *po; /** * Used while we are waiting payment. */ struct TALER_MERCHANT_OrderMerchantGetHandle *cpo; /** * HTTP response code to use on resume, if non-NULL. */ struct MHD_Response *resp; /** * Our entry in the #to_heap, or NULL. */ struct GNUNET_CONTAINER_HeapNode *hn; /** * When should this request time out? */ struct GNUNET_TIME_Absolute timeout; /** * Random authorization code we are using. */ uint64_t code; /** * HTTP response code to use on resume, if resp is set. */ unsigned int response_code; /** * true if client did not provide a payment secret / order ID. */ bool no_payment_identifier_provided; /** * True if this entry is in the #gc_head DLL. */ bool in_list; /** * True if this entry is currently suspended. */ bool suspended; }; /** * Information we track for refunds. */ struct RefundEntry { /** * Kept in a DLL. */ struct RefundEntry *next; /** * Kept in a DLL. */ struct RefundEntry *prev; /** * Operation handle. */ struct TALER_MERCHANT_OrderRefundHandle *ro; /** * Which order is being refunded. */ char *order_id; /** * Payment Identifier */ struct ANASTASIS_PaymentSecretP payment_identifier; /** * Public key of the challenge which is solved. */ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid; }; /** * Head of linked list of active refund operations. */ static struct RefundEntry *re_head; /** * Tail of linked list of active refund operations. */ static struct RefundEntry *re_tail; /** * Head of linked list over all authorization processes */ static struct ChallengeContext *gc_head; /** * Tail of linked list over all authorization processes */ static struct ChallengeContext *gc_tail; /** * Task running #do_timeout(). */ static struct GNUNET_SCHEDULER_Task *to_task; /** * Generate a response telling the client that answering this * challenge failed because the rate limit has been exceeded. * * @param gc request to answer for * @return MHD status code */ static MHD_RESULT reply_rate_limited (const struct ChallengeContext *gc) { return TALER_MHD_REPLY_JSON_PACK ( gc->connection, MHD_HTTP_TOO_MANY_REQUESTS, TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED), GNUNET_JSON_pack_uint64 ("request_limit", gc->authorization->retry_counter), GNUNET_JSON_pack_time_rel ("request_frequency", gc->authorization->code_rotation_period)); } /** * Timeout requests that are past their due date. * * @param cls NULL */ static void do_timeout (void *cls) { struct ChallengeContext *gc; (void) cls; to_task = NULL; while (NULL != (gc = GNUNET_CONTAINER_heap_peek (AH_to_heap))) { if (GNUNET_TIME_absolute_is_future (gc->timeout)) break; if (gc->suspended) { /* Test needed as we may have a "concurrent" wakeup from another task that did not clear this entry from the heap before the response process concluded. */ gc->suspended = false; MHD_resume_connection (gc->connection); } GNUNET_assert (NULL != gc->hn); gc->hn = NULL; GNUNET_assert (gc == GNUNET_CONTAINER_heap_remove_root (AH_to_heap)); } if (NULL == gc) return; to_task = GNUNET_SCHEDULER_add_at (gc->timeout, &do_timeout, NULL); } void AH_truth_challenge_shutdown (void) { struct ChallengeContext *gc; struct RefundEntry *re; while (NULL != (re = re_head)) { GNUNET_CONTAINER_DLL_remove (re_head, re_tail, re); if (NULL != re->ro) { TALER_MERCHANT_post_order_refund_cancel (re->ro); re->ro = NULL; } GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Refund `%s' failed due to shutdown\n", re->order_id); GNUNET_free (re->order_id); GNUNET_free (re); } while (NULL != (gc = gc_head)) { GNUNET_CONTAINER_DLL_remove (gc_head, gc_tail, gc); gc->in_list = false; if (NULL != gc->cpo) { TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); gc->cpo = NULL; } if (NULL != gc->po) { TALER_MERCHANT_orders_post_cancel (gc->po); gc->po = NULL; } if (gc->suspended) { gc->suspended = false; MHD_resume_connection (gc->connection); } if (NULL != gc->as) { gc->authorization->cleanup (gc->as); gc->as = NULL; gc->authorization = NULL; } } ANASTASIS_authorization_plugin_shutdown (); if (NULL != to_task) { GNUNET_SCHEDULER_cancel (to_task); to_task = NULL; } } /** * Callback to process a POST /orders/ID/refund request * * @param cls closure with a `struct RefundEntry *` * @param hr HTTP response details * @param taler_refund_uri the refund uri offered to the wallet * @param h_contract hash of the contract a Browser may need to authorize * obtaining the HTTP response. */ static void refund_cb ( void *cls, const struct TALER_MERCHANT_HttpResponse *hr, const char *taler_refund_uri, const struct TALER_PrivateContractHashP *h_contract) { struct RefundEntry *re = cls; re->ro = NULL; switch (hr->http_status) { case MHD_HTTP_OK: { enum GNUNET_DB_QueryStatus qs; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Refund `%s' succeeded\n", re->order_id); qs = db->record_challenge_refund (db->cls, &re->truth_uuid, &re->payment_identifier); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: GNUNET_break (0); break; case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); break; case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: GNUNET_break (0); break; case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } } break; default: GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Refund `%s' failed with HTTP status %u: %s (#%u)\n", re->order_id, hr->http_status, hr->hint, (unsigned int) hr->ec); break; } GNUNET_CONTAINER_DLL_remove (re_head, re_tail, re); GNUNET_free (re->order_id); GNUNET_free (re); } /** * Start to give a refund for the challenge created by @a gc. * * @param gc request where we failed and should now grant a refund for */ static void begin_refund (const struct ChallengeContext *gc) { struct RefundEntry *re; re = GNUNET_new (struct RefundEntry); re->order_id = GNUNET_STRINGS_data_to_string_alloc ( &gc->payment_identifier, sizeof (gc->payment_identifier)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Challenge execution failed, triggering refund for order `%s'\n", re->order_id); re->payment_identifier = gc->payment_identifier; re->truth_uuid = gc->truth_uuid; re->ro = TALER_MERCHANT_post_order_refund (AH_ctx, AH_backend_url, re->order_id, &gc->challenge_cost, "failed to issue challenge", &refund_cb, re); if (NULL == re->ro) { GNUNET_break (0); GNUNET_free (re->order_id); GNUNET_free (re); return; } GNUNET_CONTAINER_DLL_insert (re_head, re_tail, re); } /** * Callback used to notify the application about completed requests. * Cleans up the requests data structures. * * @param hc */ static void request_done (struct TM_HandlerContext *hc) { struct ChallengeContext *gc = hc->ctx; GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Request completed\n"); if (NULL == gc) return; hc->cc = NULL; GNUNET_assert (! gc->suspended); if (gc->in_list) { GNUNET_CONTAINER_DLL_remove (gc_head, gc_tail, gc); gc->in_list = false; } if (NULL != gc->hn) { GNUNET_assert (gc == GNUNET_CONTAINER_heap_remove_node (gc->hn)); gc->hn = NULL; } if (NULL != gc->as) { gc->authorization->cleanup (gc->as); gc->authorization = NULL; gc->as = NULL; } if (NULL != gc->cpo) { TALER_MERCHANT_merchant_order_get_cancel (gc->cpo); gc->cpo = NULL; } if (NULL != gc->po) { TALER_MERCHANT_orders_post_cancel (gc->po); gc->po = NULL; } if (NULL != gc->root) { json_decref (gc->root); gc->root = NULL; } TALER_MHD_parse_post_cleanup_callback (gc->opaque_post_parsing_context); GNUNET_free (gc); hc->ctx = NULL; } /** * Transmit a payment request for @a order_id on @a connection * * @param gc context to make payment request for */ static void make_payment_request (struct ChallengeContext *gc) { struct MHD_Response *resp; resp = MHD_create_response_from_buffer (0, NULL, MHD_RESPMEM_PERSISTENT); GNUNET_assert (NULL != resp); TALER_MHD_add_global_headers (resp); { char *hdr; char *order_id; const char *pfx; const char *hn; if (0 == strncasecmp ("https://", AH_backend_url, strlen ("https://"))) { pfx = "taler://"; hn = &AH_backend_url[strlen ("https://")]; } else if (0 == strncasecmp ("http://", AH_backend_url, strlen ("http://"))) { pfx = "taler+http://"; hn = &AH_backend_url[strlen ("http://")]; } else { /* This invariant holds as per check in anastasis-httpd.c */ GNUNET_assert (0); } /* This invariant holds as per check in anastasis-httpd.c */ GNUNET_assert (0 != strlen (hn)); order_id = GNUNET_STRINGS_data_to_string_alloc ( &gc->payment_identifier, sizeof (gc->payment_identifier)); GNUNET_asprintf (&hdr, "%spay/%s%s/", pfx, hn, order_id); GNUNET_free (order_id); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Sending payment request `%s'\n", hdr); GNUNET_break (MHD_YES == MHD_add_response_header (resp, ANASTASIS_HTTP_HEADER_TALER, hdr)); GNUNET_free (hdr); } gc->resp = resp; gc->response_code = MHD_HTTP_PAYMENT_REQUIRED; } /** * Callbacks of this type are used to serve the result of submitting a * /contract request to a merchant. * * @param cls our `struct ChallengeContext` * @param por response details */ static void proposal_cb (void *cls, const struct TALER_MERCHANT_PostOrdersReply *por) { struct ChallengeContext *gc = cls; enum GNUNET_DB_QueryStatus qs; gc->po = NULL; GNUNET_assert (gc->in_list); GNUNET_CONTAINER_DLL_remove (gc_head, gc_tail, gc); gc->in_list = false; GNUNET_assert (gc->suspended); gc->suspended = false; MHD_resume_connection (gc->connection); AH_trigger_daemon (NULL); if (MHD_HTTP_OK != por->hr.http_status) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Backend returned status %u/%d\n", por->hr.http_status, (int) por->hr.ec); GNUNET_break (0); gc->resp = TALER_MHD_MAKE_JSON_PACK ( GNUNET_JSON_pack_uint64 ("code", TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR), GNUNET_JSON_pack_string ("hint", "Failed to setup order with merchant backend"), GNUNET_JSON_pack_uint64 ("backend-ec", por->hr.ec), GNUNET_JSON_pack_uint64 ("backend-http-status", por->hr.http_status), GNUNET_JSON_pack_allow_null ( GNUNET_JSON_pack_object_steal ("backend-reply", (json_t *) por->hr.reply))); gc->response_code = MHD_HTTP_BAD_GATEWAY; return; } qs = db->record_challenge_payment (db->cls, &gc->truth_uuid, &gc->payment_identifier, &gc->challenge_cost); if (0 >= qs) { GNUNET_break (0); gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, "record challenge payment"); gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Setup fresh order, creating payment request\n"); make_payment_request (gc); } /** * Callback to process a GET /check-payment request * * @param cls our `struct ChallengeContext` * @param osr order status */ static void check_payment_cb (void *cls, const struct TALER_MERCHANT_OrderStatusResponse *osr) { struct ChallengeContext *gc = cls; const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr; gc->cpo = NULL; GNUNET_assert (gc->in_list); GNUNET_CONTAINER_DLL_remove (gc_head, gc_tail, gc); gc->in_list = false; GNUNET_assert (gc->suspended); gc->suspended = false; MHD_resume_connection (gc->connection); AH_trigger_daemon (NULL); switch (hr->http_status) { case MHD_HTTP_OK: GNUNET_assert (NULL != osr); break; case MHD_HTTP_NOT_FOUND: /* We created this order before, how can it be not found now? */ GNUNET_break (0); gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED, NULL); gc->response_code = MHD_HTTP_BAD_GATEWAY; return; case MHD_HTTP_BAD_GATEWAY: gc->resp = TALER_MHD_make_error ( TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD, NULL); gc->response_code = MHD_HTTP_BAD_GATEWAY; return; case MHD_HTTP_GATEWAY_TIMEOUT: gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT, "Timeout check payment status"); GNUNET_assert (NULL != gc->resp); gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT; return; default: { char status[14]; GNUNET_snprintf (status, sizeof (status), "%u", hr->http_status); gc->resp = TALER_MHD_make_error ( TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS, status); GNUNET_assert (NULL != gc->resp); gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; } } GNUNET_assert (MHD_HTTP_OK == hr->http_status); switch (osr->details.ok.status) { case TALER_MERCHANT_OSC_PAID: { enum GNUNET_DB_QueryStatus qs; qs = db->update_challenge_payment (db->cls, &gc->truth_uuid, &gc->payment_identifier); if (0 <= qs) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order has been paid, continuing with request processing\n"); return; /* continue as planned */ } GNUNET_break (0); gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED, "update challenge payment"); gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR; return; /* continue as planned */ } case TALER_MERCHANT_OSC_CLAIMED: case TALER_MERCHANT_OSC_UNPAID: /* repeat payment request */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order remains unpaid, sending payment request again\n"); make_payment_request (gc); return; } /* should never get here */ GNUNET_break (0); } /** * Helper function used to ask our backend to begin processing a * payment for the user's account. May perform asynchronous * operations by suspending the connection if required. * * @param gc context to begin payment for. * @return MHD status code */ static MHD_RESULT begin_payment (struct ChallengeContext *gc) { enum GNUNET_DB_QueryStatus qs; char *order_id; qs = db->lookup_challenge_payment (db->cls, &gc->truth_uuid, &gc->payment_identifier); if (qs < 0) { GNUNET_break (0); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "lookup challenge payment"); } GNUNET_assert (! gc->in_list); gc->in_list = true; GNUNET_CONTAINER_DLL_insert (gc_tail, gc_head, gc); GNUNET_assert (! gc->suspended); gc->suspended = true; MHD_suspend_connection (gc->connection); if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs) { /* We already created the order, check if it was paid */ struct GNUNET_TIME_Relative timeout; order_id = GNUNET_STRINGS_data_to_string_alloc ( &gc->payment_identifier, sizeof (gc->payment_identifier)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Order exists, checking payment status for order `%s'\n", order_id); timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout); gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx, AH_backend_url, order_id, NULL /* NOT session-bound */, false, timeout, &check_payment_cb, gc); } else { /* Create a fresh order */ json_t *order; struct GNUNET_TIME_Timestamp pay_deadline; GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, &gc->payment_identifier, sizeof (struct ANASTASIS_PaymentSecretP)); order_id = GNUNET_STRINGS_data_to_string_alloc ( &gc->payment_identifier, sizeof (gc->payment_identifier)); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Creating fresh order `%s'\n", order_id); pay_deadline = GNUNET_TIME_relative_to_timestamp ( ANASTASIS_CHALLENGE_OFFER_LIFETIME); order = GNUNET_JSON_PACK ( TALER_JSON_pack_amount ("amount", &gc->challenge_cost), GNUNET_JSON_pack_string ("summary", "challenge fee for anastasis service"), GNUNET_JSON_pack_string ("order_id", order_id), GNUNET_JSON_pack_time_rel ("auto_refund", AUTO_REFUND_TIMEOUT), GNUNET_JSON_pack_timestamp ("pay_deadline", pay_deadline)); gc->po = TALER_MERCHANT_orders_post2 (AH_ctx, AH_backend_url, order, AUTO_REFUND_TIMEOUT, NULL, /* no payment target */ 0, NULL, /* no inventory products */ 0, NULL, /* no uuids */ false, /* do NOT require claim token */ &proposal_cb, gc); json_decref (order); } GNUNET_free (order_id); AH_trigger_curl (); return MHD_YES; } /** * Mark @a gc as suspended and update the respective * data structures and jobs. * * @param[in,out] gc context of the suspended operation */ static void gc_suspended (struct ChallengeContext *gc) { gc->suspended = true; if (NULL == AH_to_heap) AH_to_heap = GNUNET_CONTAINER_heap_create ( GNUNET_CONTAINER_HEAP_ORDER_MIN); gc->hn = GNUNET_CONTAINER_heap_insert (AH_to_heap, gc, gc->timeout.abs_value_us); if (NULL != to_task) { GNUNET_SCHEDULER_cancel (to_task); to_task = NULL; } { struct ChallengeContext *rn; rn = GNUNET_CONTAINER_heap_peek (AH_to_heap); to_task = GNUNET_SCHEDULER_add_at (rn->timeout, &do_timeout, NULL); } } /** * Run the authorization method-specific 'process' function and continue * based on its result with generating an HTTP response. * * @param connection the connection we are handling * @param gc our overall handler context */ static MHD_RESULT run_authorization_process (struct MHD_Connection *connection, struct ChallengeContext *gc) { enum ANASTASIS_AUTHORIZATION_ChallengeResult ret; enum GNUNET_DB_QueryStatus qs; GNUNET_assert (! gc->suspended); if (NULL == gc->authorization->challenge) { GNUNET_break (0); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, "challenge method not implemented for authorization method"); } ret = gc->authorization->challenge (gc->as, connection); switch (ret) { case ANASTASIS_AUTHORIZATION_CRES_SUCCESS: /* Challenge sent successfully */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Authorization request %llu for %s sent successfully\n", (unsigned long long) gc->code, TALER_B2S (&gc->truth_uuid)); qs = db->mark_challenge_sent (db->cls, &gc->payment_identifier, &gc->truth_uuid, gc->code); GNUNET_break (0 < qs); gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_YES; case ANASTASIS_AUTHORIZATION_CRES_FAILED: if (! gc->no_payment_identifier_provided) { begin_refund (gc); } gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_YES; case ANASTASIS_AUTHORIZATION_CRES_SUSPENDED: /* connection was suspended */ gc_suspended (gc); return MHD_YES; case ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED: /* Challenge sent successfully */ qs = db->mark_challenge_sent (db->cls, &gc->payment_identifier, &gc->truth_uuid, gc->code); GNUNET_break (0 < qs); gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_NO; case ANASTASIS_AUTHORIZATION_CRES_FAILED_REPLY_FAILED: gc->authorization->cleanup (gc->as); gc->as = NULL; return MHD_NO; } GNUNET_break (0); return MHD_NO; } MHD_RESULT AH_handler_truth_challenge ( struct MHD_Connection *connection, struct TM_HandlerContext *hc, const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid, const char *upload_data, size_t *upload_data_size) { struct ChallengeContext *gc = hc->ctx; void *encrypted_truth; size_t encrypted_truth_size; void *decrypted_truth; size_t decrypted_truth_size; char *truth_mime = NULL; if (NULL == gc) { /* Fresh request, do initial setup */ gc = GNUNET_new (struct ChallengeContext); gc->hc = hc; hc->ctx = gc; gc->connection = connection; gc->truth_uuid = *truth_uuid; gc->hc->cc = &request_done; gc->timeout = GNUNET_TIME_relative_to_absolute ( PAYMENT_TIMEOUT); } /* end of first-time initialization (if NULL == gc) */ else { /* might have been woken up by authorization plugin, so clear the flag. MDH called us, so we are clearly no longer suspended */ gc->suspended = false; if (NULL != gc->resp) { MHD_RESULT ret; /* We generated a response asynchronously, queue that */ ret = MHD_queue_response (connection, gc->response_code, gc->resp); GNUNET_break (MHD_YES == ret); MHD_destroy_response (gc->resp); gc->resp = NULL; return ret; } if (NULL != gc->as) { /* Authorization process is "running", check what is going on */ GNUNET_assert (NULL != gc->authorization); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Continuing with running the authorization process\n"); GNUNET_assert (! gc->suspended); return run_authorization_process (connection, gc); } /* We get here if the async check for payment said this request was indeed paid! */ } /* parse byte stream upload into JSON */ if (NULL == gc->root) { enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_post_json (connection, &gc->opaque_post_parsing_context, upload_data, upload_data_size, &gc->root); if (GNUNET_SYSERR == res) { GNUNET_assert (NULL == gc->root); return MHD_NO; /* bad upload, could not even generate error */ } if ( (GNUNET_NO == res) || (NULL == gc->root) ) { GNUNET_assert (NULL == gc->root); return MHD_YES; /* so far incomplete upload or parser error */ } /* 'root' is now initialized */ { struct GNUNET_JSON_Specification spec[] = { GNUNET_JSON_spec_fixed_auto ("truth_decryption_key", &gc->truth_key), GNUNET_JSON_spec_mark_optional ( GNUNET_JSON_spec_fixed_auto ("payment_secret", &gc->payment_identifier), &gc->no_payment_identifier_provided), GNUNET_JSON_spec_end () }; enum GNUNET_GenericReturnValue res; res = TALER_MHD_parse_json_data (connection, gc->root, spec); if (GNUNET_SYSERR == res) { GNUNET_break (0); return MHD_NO; /* hard failure */ } if (GNUNET_NO == res) { GNUNET_break_op (0); return MHD_YES; /* failure */ } if (! gc->no_payment_identifier_provided) GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client provided payment identifier `%s'\n", TALER_B2S (&gc->payment_identifier)); } } { /* load encrypted truth from DB */ enum GNUNET_DB_QueryStatus qs; char *method; qs = db->get_escrow_challenge (db->cls, &gc->truth_uuid, &encrypted_truth, &encrypted_truth_size, &truth_mime, &method); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "get escrow challenge"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_ANASTASIS_TRUTH_UNKNOWN, NULL); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: break; } if (0 == strcmp ("question", method)) { GNUNET_break_op (0); GNUNET_free (encrypted_truth); GNUNET_free (truth_mime); GNUNET_free (method); return TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, "question"); } gc->authorization = ANASTASIS_authorization_plugin_load (method, db, AH_cfg); if (NULL == gc->authorization) { MHD_RESULT ret; ret = TALER_MHD_reply_with_error ( connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_METHOD_NO_LONGER_SUPPORTED, method); GNUNET_free (encrypted_truth); GNUNET_free (truth_mime); GNUNET_free (method); return ret; } if (gc->authorization->user_provided_code) { MHD_RESULT ret; GNUNET_break_op (0); ret = TALER_MHD_reply_with_error (connection, MHD_HTTP_BAD_REQUEST, TALER_EC_ANASTASIS_TRUTH_CHALLENGE_WRONG_METHOD, method); GNUNET_free (encrypted_truth); GNUNET_free (truth_mime); GNUNET_free (method); return ret; } gc->challenge_cost = gc->authorization->cost; GNUNET_free (method); } if (! gc->authorization->payment_plugin_managed) { if (! TALER_amount_is_zero (&gc->challenge_cost)) { /* Check database to see if the transaction is paid for */ enum GNUNET_DB_QueryStatus qs; bool paid; if (gc->no_payment_identifier_provided) { GNUNET_free (truth_mime); GNUNET_free (encrypted_truth); GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Beginning payment, client did not provide payment identifier\n"); return begin_payment (gc); } qs = db->check_challenge_payment (db->cls, &gc->payment_identifier, &gc->truth_uuid, &paid); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); GNUNET_free (truth_mime); GNUNET_free (encrypted_truth); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "check challenge payment"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* Create fresh payment identifier (cannot trust client) */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Client-provided payment identifier is unknown.\n"); GNUNET_free (truth_mime); GNUNET_free (encrypted_truth); return begin_payment (gc); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: if (! paid) { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment identifier known. Checking payment with client's payment identifier\n"); GNUNET_free (truth_mime); GNUNET_free (encrypted_truth); return begin_payment (gc); } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Payment confirmed\n"); break; } } else { GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Request is free of charge\n"); } } /* We've been paid, now validate response */ { /* decrypt encrypted_truth */ ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key, encrypted_truth, encrypted_truth_size, &decrypted_truth, &decrypted_truth_size); GNUNET_free (encrypted_truth); } if (NULL == decrypted_truth) { GNUNET_free (truth_mime); return TALER_MHD_reply_with_error (connection, MHD_HTTP_CONFLICT, TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED, NULL); } /* Not security question and no answer: use plugin to check if decrypted truth is a valid challenge! */ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "No challenge provided, creating fresh challenge\n"); { enum GNUNET_GenericReturnValue ret; ret = gc->authorization->validate (gc->authorization->cls, connection, truth_mime, decrypted_truth, decrypted_truth_size); GNUNET_free (truth_mime); switch (ret) { case GNUNET_OK: /* data valid, continued below */ break; case GNUNET_NO: /* data invalid, reply was queued */ GNUNET_free (decrypted_truth); return MHD_YES; case GNUNET_SYSERR: /* data invalid, reply was NOT queued */ GNUNET_free (decrypted_truth); return MHD_NO; } } /* Setup challenge and begin authorization process */ { struct GNUNET_TIME_Timestamp transmission_date; enum GNUNET_DB_QueryStatus qs; qs = db->create_challenge_code (db->cls, &gc->truth_uuid, gc->authorization->code_rotation_period, gc->authorization->code_validity_period, gc->authorization->retry_counter, &transmission_date, &gc->code); switch (qs) { case GNUNET_DB_STATUS_HARD_ERROR: case GNUNET_DB_STATUS_SOFT_ERROR: GNUNET_break (0); GNUNET_free (decrypted_truth); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_GENERIC_DB_FETCH_FAILED, "create_challenge_code"); case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: /* 0 == retry_counter of existing challenge => rate limit exceeded */ GNUNET_free (decrypted_truth); return reply_rate_limited (gc); case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: /* challenge code was stored successfully*/ GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Created fresh challenge\n"); break; } if (GNUNET_TIME_relative_cmp ( GNUNET_TIME_absolute_get_duration ( transmission_date.abs_time), <, gc->authorization->code_retransmission_frequency) ) { /* Too early for a retransmission! */ GNUNET_free (decrypted_truth); return TALER_MHD_REPLY_JSON_PACK ( gc->connection, MHD_HTTP_OK, GNUNET_JSON_pack_string ("challenge_type", "TAN_ALREADY_SENT")); } } GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Beginning authorization process\n"); gc->as = gc->authorization->start (gc->authorization->cls, &AH_trigger_daemon, NULL, &gc->truth_uuid, gc->code, decrypted_truth, decrypted_truth_size); GNUNET_free (decrypted_truth); if (NULL == gc->as) { GNUNET_break (0); return TALER_MHD_reply_with_error (gc->connection, MHD_HTTP_INTERNAL_SERVER_ERROR, TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED, NULL); } if (! gc->in_list) { gc->in_list = true; GNUNET_CONTAINER_DLL_insert (gc_head, gc_tail, gc); } GNUNET_assert (! gc->suspended); return run_authorization_process (connection, gc); }