summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2022-02-27 22:35:51 +0100
committerChristian Grothoff <christian@grothoff.org>2022-02-27 22:35:51 +0100
commitfb3c4bb1885f40a84bd534cd38f631b93bfa4a87 (patch)
tree0b33b69075235f2e018dd2a118ea295bdfb0622c /src/backend
parentf6a64e01947c2e38d4dc3ae8b29cb7ecae70e3cd (diff)
downloadanastasis-fb3c4bb1885f40a84bd534cd38f631b93bfa4a87.tar.gz
anastasis-fb3c4bb1885f40a84bd534cd38f631b93bfa4a87.tar.bz2
anastasis-fb3c4bb1885f40a84bd534cd38f631b93bfa4a87.zip
-first rough cut towards implementing new /truths/ endpoint design (#7064)
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/Makefile.am6
-rw-r--r--src/backend/anastasis-httpd.c48
-rw-r--r--src/backend/anastasis-httpd_policy-upload.c (renamed from src/backend/anastasis-httpd_policy_upload.c)0
-rw-r--r--src/backend/anastasis-httpd_truth-challenge.c1389
-rw-r--r--src/backend/anastasis-httpd_truth-solve.c1430
-rw-r--r--src/backend/anastasis-httpd_truth-upload.c (renamed from src/backend/anastasis-httpd_truth_upload.c)0
-rw-r--r--src/backend/anastasis-httpd_truth.h60
7 files changed, 2921 insertions, 12 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 0f3a969..83877bc 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -19,11 +19,13 @@ anastasis_httpd_SOURCES = \
anastasis-httpd_mhd.c anastasis-httpd_mhd.h \
anastasis-httpd_policy.c anastasis-httpd_policy.h \
anastasis-httpd_policy-meta.c anastasis-httpd_policy-meta.h \
- anastasis-httpd_policy_upload.c \
+ anastasis-httpd_policy-upload.c \
anastasis-httpd_truth.c anastasis-httpd_truth.h \
anastasis-httpd_terms.c anastasis-httpd_terms.h \
anastasis-httpd_config.c anastasis-httpd_config.h \
- anastasis-httpd_truth_upload.c
+ anastasis-httpd_truth-challenge.c \
+ anastasis-httpd_truth-solve.c \
+ anastasis-httpd_truth-upload.c
anastasis_httpd_LDADD = \
$(top_builddir)/src/util/libanastasisutil.la \
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
index 4ef6087..0c9d957 100644
--- a/src/backend/anastasis-httpd.c
+++ b/src/backend/anastasis-httpd.c
@@ -1,6 +1,6 @@
/*
This file is part of Anastasis
- (C) 2020 Anastasis SARL
+ (C) 2020-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
@@ -416,12 +416,20 @@ url_handler (void *cls,
{
struct ANASTASIS_CRYPTO_TruthUUIDP tu;
const char *pub_key_str;
+ const char *end;
+ size_t len;
pub_key_str = &url[strlen ("/truth/")];
+ end = strchr (pub_key_str,
+ '/');
+ if (NULL == end)
+ len = strlen (pub_key_str);
+ else
+ len = end - pub_key_str;
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (
pub_key_str,
- strlen (pub_key_str),
+ len,
&tu,
sizeof(tu)))
{
@@ -431,15 +439,17 @@ url_handler (void *cls,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"truth UUID");
}
- if (0 == strcmp (method,
- MHD_HTTP_METHOD_GET))
+ if ( (NULL == end) &&
+ (0 == strcmp (method,
+ MHD_HTTP_METHOD_GET)) )
{
return AH_handler_truth_get (connection,
&tu,
hc);
}
- if (0 == strcmp (method,
- MHD_HTTP_METHOD_POST))
+ if ( (NULL == end) &&
+ (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST)) )
{
return AH_handler_truth_post (connection,
hc,
@@ -447,6 +457,30 @@ url_handler (void *cls,
upload_data,
upload_data_size);
}
+ if ( (NULL != end) &&
+ (0 == strcmp (end,
+ "/solve")) &&
+ (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ return AH_handler_truth_solve (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
+ if ( (NULL != end) &&
+ (0 == strcmp (end,
+ "/challenge")) &&
+ (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST)) )
+ {
+ return AH_handler_truth_challenge (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
if (0 == strcmp (method,
MHD_HTTP_METHOD_OPTIONS))
{
@@ -498,6 +532,8 @@ do_shutdown (void *cls)
(void) cls;
AH_resume_all_bc ();
AH_truth_shutdown ();
+ AH_truth_challenge_shutdown ();
+ AH_truth_solve_shutdown ();
AH_truth_upload_shutdown ();
if (NULL != mhd_task)
{
diff --git a/src/backend/anastasis-httpd_policy_upload.c b/src/backend/anastasis-httpd_policy-upload.c
index 2cc0389..2cc0389 100644
--- a/src/backend/anastasis-httpd_policy_upload.c
+++ b/src/backend/anastasis-httpd_policy-upload.c
diff --git a/src/backend/anastasis-httpd_truth-challenge.c b/src/backend/anastasis-httpd_truth-challenge.c
new file mode 100644
index 0000000..c583403
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth-challenge.c
@@ -0,0 +1,1389 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include "anastasis_authorization_lib.h"
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+
+/**
+ * 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 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;
+
+ /**
+ * How long do we wait at most for payment or
+ * authorization?
+ */
+ 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 provided a payment secret / order ID?
+ */
+ bool 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 hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+ struct ChallengeContext *gc = cls;
+
+ 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;
+ }
+ }
+
+ switch (osr->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_Result ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_assert (! gc->suspended);
+ ret = gc->authorization->process (gc->as,
+ gc->timeout,
+ connection);
+ switch (ret)
+ {
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+ /* Challenge sent successfully */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Authorization request sent successfully\n");
+ 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_RES_FAILED:
+ if (gc->payment_identifier_provided)
+ {
+ begin_refund (gc);
+ }
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+ /* connection was suspended */
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_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_RES_FAILED_REPLY_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+ /* Neither case should EVER happen here! */
+ GNUNET_break (0);
+ GNUNET_assert (! gc->suspended);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ if (gc->in_list)
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ }
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "authorization successful when we were only supposed to be challenging");
+ }
+ 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;
+ {
+ const char *pay_id;
+
+ pay_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ if (NULL != pay_id)
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pay_id,
+ strlen (pay_id),
+ &gc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ }
+ gc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client provided payment identifier `%s'\n",
+ pay_id);
+ }
+ }
+
+ {
+ const char *long_poll_timeout_ms;
+
+ long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_ms)
+ {
+ unsigned int timeout;
+ char dummy;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u%c",
+ &timeout,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ gc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ }
+ else
+ {
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ }
+ }
+ } /* 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_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 */
+ }
+ }
+ }
+
+ {
+ /* 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->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_EXPECTATION_FAILED,
+ 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_with_error (gc->connection,
+ MHD_HTTP_ALREADY_REPORTED,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_ACTIVE,
+ NULL);
+ }
+ }
+
+ 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);
+}
diff --git a/src/backend/anastasis-httpd_truth-solve.c b/src/backend/anastasis-httpd_truth-solve.c
new file mode 100644
index 0000000..577ec50
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth-solve.c
@@ -0,0 +1,1430 @@
+/*
+ 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 <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth-solve.c
+ * @brief functions to handle incoming requests on /truth/$TID/solve
+ * @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 <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include "anastasis_authorization_lib.h"
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_mhd_lib.h>
+
+/**
+ * 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 many retries do we allow per code?
+ */
+#define INITIAL_RETRY_COUNTER 3
+
+
+struct SolveContext
+{
+
+ /**
+ * 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 SolveContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct SolveContext *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;
+
+ /**
+ * Challenge response we got from the request.
+ */
+ struct GNUNET_HashCode challenge_response;
+
+ /**
+ * How long do we wait at most for payment or
+ * authorization?
+ */
+ 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 provided a payment secret / order ID?
+ */
+ bool 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;
+
+};
+
+
+/**
+ * Head of linked list over all authorization processes
+ */
+static struct SolveContext *gc_head;
+
+/**
+ * Tail of linked list over all authorization processes
+ */
+static struct SolveContext *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 SolveContext *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 SolveContext *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_solve_shutdown (void)
+{
+ struct SolveContext *gc;
+
+ 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 used to notify the application about completed requests.
+ * Cleans up the requests data structures.
+ *
+ * @param[in,out] hc
+ */
+static void
+request_done (struct TM_HandlerContext *hc)
+{
+ struct SolveContext *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 SolveContext *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 SolveContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct SolveContext *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 SolveContext`
+ * @param hr HTTP response details
+ * @param osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+ struct SolveContext *gc = cls;
+
+ 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;
+ }
+ }
+
+ switch (osr->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 SolveContext *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;
+}
+
+
+/**
+ * Load encrypted keyshare from db and return it to the client.
+ *
+ * @param truth_uuid UUID to the truth for the looup
+ * @param connection the connection to respond upon
+ * @return MHD status code
+ */
+static MHD_RESULT
+return_key_share (
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct MHD_Connection *connection)
+{
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->get_key_share (db->cls,
+ truth_uuid,
+ &encrypted_keyshare);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get key share");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key share\n");
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare),
+ &encrypted_keyshare,
+ MHD_RESPMEM_MUST_COPY);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/**
+ * 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 SolveContext *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 SolveContext *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 SolveContext *gc)
+{
+ enum ANASTASIS_AUTHORIZATION_Result ret;
+
+ GNUNET_assert (! gc->suspended);
+ ret = gc->authorization->process (gc->as,
+ gc->timeout,
+ connection);
+ switch (ret)
+ {
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+ /* Neither case should EVER happen here! */
+ GNUNET_break (0);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
+ "challenge sent when we were only supposed to be checking");
+ case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+ /* connection was suspended */
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+ GNUNET_assert (! gc->suspended);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Resuming with authorization successful!\n");
+ if (gc->in_list)
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ }
+ return MHD_YES;
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/**
+ * Use the database to rate-limit queries to the authentication
+ * procedure, but without actually storing 'real' challenge codes.
+ *
+ * @param[in,out] gc context to rate limit requests for
+ * @return #GNUNET_OK if rate-limiting passes,
+ * #GNUNET_NO if a reply was sent (rate limited)
+ * #GNUNET_SYSERR if we failed and no reply
+ * was queued
+ */
+static enum GNUNET_GenericReturnValue
+rate_limit (struct SolveContext *gc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Timestamp rt;
+ uint64_t code;
+ enum ANASTASIS_DB_CodeStatus cs;
+ struct GNUNET_HashCode hc;
+ bool satisfied;
+ uint64_t dummy;
+
+ rt = GNUNET_TIME_UNIT_FOREVER_TS;
+ qs = db->create_challenge_code (db->cls,
+ &gc->truth_uuid,
+ MAX_QUESTION_FREQ,
+ GNUNET_TIME_UNIT_HOURS,
+ INITIAL_RETRY_COUNTER,
+ &rt,
+ &code);
+ if (0 > qs)
+ {
+ GNUNET_break (0 < qs);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "create_challenge_code (for rate limiting)"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ return (MHD_YES ==
+ reply_rate_limited (gc))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ }
+ /* decrement trial counter */
+ ANASTASIS_hash_answer (code + 1, /* always use wrong answer */
+ &hc);
+ cs = db->verify_challenge_code (db->cls,
+ &gc->truth_uuid,
+ &hc,
+ &dummy,
+ &satisfied);
+ switch (cs)
+ {
+ case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+ /* good, what we wanted */
+ return GNUNET_OK;
+ case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_CODE_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return (MHD_YES ==
+ TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "verify_challenge_code"))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+ return (MHD_YES ==
+ reply_rate_limited (gc))
+ ? GNUNET_NO
+ : GNUNET_SYSERR;
+ case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ /* this should be impossible, we used code+1 */
+ GNUNET_assert (0);
+ }
+ return GNUNET_SYSERR;
+}
+
+
+/**
+ * Handle special case of a security question where we do not
+ * generate a code. Rate limits answers against brute forcing.
+ *
+ * @param[in,out] gc request to handle
+ * @param decrypted_truth hash to check against
+ * @param decrypted_truth_size number of bytes in @a decrypted_truth
+ * @return MHD status code
+ */
+static MHD_RESULT
+handle_security_question (struct SolveContext *gc,
+ const void *decrypted_truth,
+ size_t decrypted_truth_size)
+{
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling security question challenge\n");
+ /* rate limit */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = rate_limit (gc);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
+ }
+ /* check reply matches truth */
+ if ( (decrypted_truth_size != sizeof (struct GNUNET_HashCode)) ||
+ (0 != memcmp (&gc->challenge_response,
+ decrypted_truth,
+ decrypted_truth_size)) )
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Wrong answer provided to secure question had %u bytes, wanted %u\n",
+ (unsigned int) decrypted_truth_size,
+ (unsigned int) sizeof (struct GNUNET_HashCode));
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+ NULL);
+ }
+ /* good, return the key share */
+ return return_key_share (&gc->truth_uuid,
+ gc->connection);
+}
+
+
+/**
+ * Handle special case of an answer being directly checked by the
+ * plugin and not by our database. Rate limits answers against brute
+ * forcing.
+ *
+ * @param[in,out] gc request to handle
+ * @param decrypted_truth hash to check against
+ * @param decrypted_truth_size number of bytes in @a decrypted_truth
+ * @return MHD status code
+ */
+static MHD_RESULT
+direct_validation (struct SolveContext *gc,
+ const void *decrypted_truth,
+ size_t decrypted_truth_size)
+{
+ /* Non-random code, call plugin directly! */
+ enum ANASTASIS_AUTHORIZATION_Result aar;
+ enum GNUNET_GenericReturnValue res;
+
+ res = rate_limit (gc);
+ if (GNUNET_OK != res)
+ return (GNUNET_NO == res) ? MHD_YES : MHD_NO;
+ gc->as = gc->authorization->start (gc->authorization->cls,
+ &AH_trigger_daemon,
+ NULL,
+ &gc->truth_uuid,
+ 0LLU,
+ decrypted_truth,
+ decrypted_truth_size);
+ 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);
+ }
+ aar = gc->authorization->process (gc->as,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ gc->connection);
+ switch (aar)
+ {
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+ GNUNET_break (0);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED:
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+ GNUNET_break (0);
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FINISHED:
+ return return_key_share (&gc->truth_uuid,
+ gc->connection);
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+MHD_RESULT
+AH_handler_truth_solve (
+ 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 SolveContext *gc = hc->ctx;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+ void *decrypted_truth;
+ size_t decrypted_truth_size;
+ char *truth_mime = NULL;
+ bool is_question;
+
+ if (NULL == gc)
+ {
+ /* Fresh request, do initial setup */
+ gc = GNUNET_new (struct SolveContext);
+ gc->hc = hc;
+ hc->ctx = gc;
+ gc->connection = connection;
+ gc->truth_uuid = *truth_uuid;
+ gc->hc->cc = &request_done;
+ {
+ const char *pay_id;
+
+ pay_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ if (NULL != pay_id)
+ {
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pay_id,
+ strlen (pay_id),
+ &gc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
+ }
+ gc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client provided payment identifier `%s'\n",
+ pay_id);
+ }
+ }
+
+ {
+ const char *long_poll_timeout_ms;
+
+ long_poll_timeout_ms = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "timeout_ms");
+ if (NULL != long_poll_timeout_ms)
+ {
+ unsigned int timeout;
+ char dummy;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u%c",
+ &timeout,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ gc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ }
+ else
+ {
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ }
+ }
+ } /* 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! */
+ }
+
+ if (NULL == gc->root)
+ {
+ /* parse byte stream upload into JSON */
+ 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, parse JSON body */
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("truth_decryption_key",
+ &gc->truth_key),
+ GNUNET_JSON_spec_fixed_auto ("h_response",
+ &gc->challenge_response),
+ 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 */
+ }
+ }
+ }
+
+ {
+ /* load encrypted truth from DB; we may do this repeatedly
+ while handling the same request, if payment was checked
+ asynchronously! */
+ 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;
+ }
+ is_question = (0 == strcmp ("question",
+ method));
+ if (! is_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;
+ }
+ gc->challenge_cost = gc->authorization->cost;
+ }
+ else
+ {
+ gc->challenge_cost = AH_question_cost;
+ }
+ GNUNET_free (method);
+ }
+
+ /* check for payment */
+ if ( (is_question) ||
+ (! 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->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 the 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)
+ {
+ /* most likely, the decryption key is simply wrong */
+ GNUNET_break_op (0);
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+ NULL);
+ }
+
+ /* Special case for secure question: we do not generate a numeric challenge,
+ but check that the hash matches */
+ if (is_question)
+ {
+ MHD_RESULT ret;
+
+ ret = handle_security_question (gc,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (truth_mime);
+ GNUNET_free (decrypted_truth);
+ return ret;
+ }
+
+ /* Not security question, check for answer in DB */
+ {
+ enum ANASTASIS_DB_CodeStatus cs;
+ bool satisfied = false;
+ uint64_t code;
+
+ GNUNET_free (truth_mime);
+ if (gc->authorization->user_provided_code)
+ {
+ MHD_RESULT res;
+
+ if (GNUNET_TIME_absolute_is_past (gc->timeout))
+ {
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_AUTH_TIMEOUT,
+ "timeout awaiting validation");
+ }
+ res = direct_validation (gc,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (decrypted_truth);
+ return res;
+ }
+
+ /* random code, check against database */
+ // FIXME: check that this statement NEVER puts
+ // a new code INTO the DB (old style!)
+ cs = db->verify_challenge_code (db->cls,
+ &gc->truth_uuid,
+ &gc->challenge_response,
+ &code,
+ &satisfied);
+ switch (cs)
+ {
+ case ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH:
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Provided response does not match our stored challenge\n");
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
+ NULL);
+ case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_CODE_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,
+ "verify_challenge_code");
+ case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
+ NULL);
+ case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Response code valid (%s)\n",
+ satisfied ? "satisfied" : "unsatisfied");
+ if (! satisfied)
+ {
+ GNUNET_free (decrypted_truth);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
+ NULL);
+ }
+ GNUNET_free (decrypted_truth);
+ return return_key_share (&gc->truth_uuid,
+ connection);
+ default:
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ }
+}
diff --git a/src/backend/anastasis-httpd_truth_upload.c b/src/backend/anastasis-httpd_truth-upload.c
index fd14663..fd14663 100644
--- a/src/backend/anastasis-httpd_truth_upload.c
+++ b/src/backend/anastasis-httpd_truth-upload.c
diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h
index 87e570b..d0851ba 100644
--- a/src/backend/anastasis-httpd_truth.h
+++ b/src/backend/anastasis-httpd_truth.h
@@ -33,6 +33,19 @@ AH_truth_shutdown (void);
/**
+ * Prepare all active POST truth solve requests for system shutdown.
+ */
+void
+AH_truth_solve_shutdown (void);
+
+
+/**
+ * Prepare all active POST truth challenge requests for system shutdown.
+ */
+void
+AH_truth_challenge_shutdown (void);
+
+/**
* Prepare all active POST truth requests for system shutdown.
*/
void
@@ -42,9 +55,9 @@ AH_truth_upload_shutdown (void);
/**
* Handle a GET to /truth/$UUID
*
- * @param connection the MHD connection to handle
+ * @param[in,out] connection the MHD connection to handle
* @param truth_uuid the truth UUID
- * @param hc connection context
+ * @param[in,out] hc connection context
* @return MHD result code
*/
MHD_RESULT
@@ -57,8 +70,8 @@ AH_handler_truth_get (
/**
* Handle a POST to /truth/$UUID.
*
- * @param connection the MHD connection to handle
- * @param hc connection context
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
* @param truth_uuid the truth UUID
* @param truth_data truth data
* @param truth_data_size number of bytes (left) in @a truth_data
@@ -72,4 +85,43 @@ AH_handler_truth_post (
const char *truth_data,
size_t *truth_data_size);
+
+/**
+ * Handle a POST to /truth/$UUID/solve.
+ *
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
+ * @param truth_uuid the truth UUID
+ * @param truth_data truth data
+ * @param truth_data_size number of bytes (left) in @a truth_data
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_solve (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+/**
+ * Handle a POST to /truth/$UUID/challenge.
+ *
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
+ * @param truth_uuid the truth UUID
+ * @param truth_data truth data
+ * @param truth_data_size number of bytes (left) in @a truth_data
+ * @return MHD result code
+ */
+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);
+
+
#endif