summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/Makefile.am11
-rw-r--r--src/backend/anastasis-httpd.c152
-rw-r--r--src/backend/anastasis-httpd.h9
-rw-r--r--src/backend/anastasis-httpd_config.c115
-rw-r--r--src/backend/anastasis-httpd_policy-meta.c192
-rw-r--r--src/backend/anastasis-httpd_policy-meta.h41
-rw-r--r--src/backend/anastasis-httpd_policy-upload.c (renamed from src/backend/anastasis-httpd_policy_upload.c)222
-rw-r--r--src/backend/anastasis-httpd_policy.c36
-rw-r--r--src/backend/anastasis-httpd_policy.h2
-rw-r--r--src/backend/anastasis-httpd_terms.c23
-rw-r--r--src/backend/anastasis-httpd_terms.h2
-rw-r--r--src/backend/anastasis-httpd_truth-challenge.c (renamed from src/backend/anastasis-httpd_truth.c)801
-rw-r--r--src/backend/anastasis-httpd_truth-solve.c1474
-rw-r--r--src/backend/anastasis-httpd_truth-upload.c (renamed from src/backend/anastasis-httpd_truth_upload.c)249
-rw-r--r--src/backend/anastasis-httpd_truth.h59
-rw-r--r--src/backend/anastasis.conf6
16 files changed, 2420 insertions, 974 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
index 1046810..c1b0931 100644
--- a/src/backend/Makefile.am
+++ b/src/backend/Makefile.am
@@ -18,11 +18,14 @@ anastasis_httpd_SOURCES = \
anastasis-httpd.c anastasis-httpd.h \
anastasis-httpd_mhd.c anastasis-httpd_mhd.h \
anastasis-httpd_policy.c anastasis-httpd_policy.h \
- anastasis-httpd_policy_upload.c \
- anastasis-httpd_truth.c anastasis-httpd_truth.h \
+ anastasis-httpd_policy-meta.c anastasis-httpd_policy-meta.h \
+ anastasis-httpd_policy-upload.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 \
@@ -34,11 +37,9 @@ anastasis_httpd_LDADD = \
-ltalerjson \
-ltalerutil \
-lgnunetcurl \
- -lgnunetrest \
-lgnunetjson \
-lgnunetutil \
-lmicrohttpd \
- -luuid \
$(XLIB)
EXTRA_DIST = \
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
index 9f5c87b..ebfb0ae 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
@@ -26,17 +26,13 @@
#include "anastasis-httpd_mhd.h"
#include "anastasis_database_lib.h"
#include "anastasis-httpd_policy.h"
+#include "anastasis-httpd_policy-meta.h"
#include "anastasis-httpd_truth.h"
#include "anastasis-httpd_terms.h"
#include "anastasis-httpd_config.h"
/**
- * Backlog for listen operation on unix-domain sockets.
- */
-#define UNIX_BACKLOG 500
-
-/**
* Upload limit to the service, in megabytes.
*/
unsigned long long int AH_upload_limit_mb;
@@ -72,11 +68,6 @@ const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
char *AH_backend_url;
/**
- * Taler currency.
- */
-char *AH_currency;
-
-/**
* Our fulfillment URL.
*/
char *AH_fulfillment_url;
@@ -87,9 +78,9 @@ char *AH_fulfillment_url;
char *AH_business_name;
/**
- * Our server salt.
+ * Our provider salt.
*/
-struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt;
/**
* Number of policy uploads permitted per annual fee payment.
@@ -295,7 +286,7 @@ url_handler (void *cls,
&TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
{ "/terms", MHD_HTTP_METHOD_GET, NULL,
NULL, 0,
- &AH_handler_terms, MHD_HTTP_OK },
+ &AH_handler_privacy, MHD_HTTP_OK },
{ "/privacy", MHD_HTTP_METHOD_GET, NULL,
NULL, 0,
&AH_handler_terms, MHD_HTTP_OK },
@@ -360,12 +351,15 @@ url_handler (void *cls,
strlen ("/policy/")))
{
const char *account = url + strlen ("/policy/");
+ const char *end = strchr (account, '/');
struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;
if (GNUNET_OK !=
GNUNET_STRINGS_string_to_data (
account,
- strlen (account),
+ (NULL == end)
+ ? strlen (account)
+ : end - account,
&account_pub,
sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP)))
{
@@ -374,14 +368,23 @@ url_handler (void *cls,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"account public key");
}
+ if ( (NULL != end) &&
+ (0 != strcmp (end,
+ "/meta")) )
+ return TMH_MHD_handler_static_response (&h404,
+ connection);
if (0 == strcmp (method,
MHD_HTTP_METHOD_GET))
{
- return AH_policy_get (connection,
- &account_pub);
+ if (NULL == end)
+ return AH_policy_get (connection,
+ &account_pub);
+ return AH_policy_meta_get (connection,
+ &account_pub);
}
- if (0 == strcmp (method,
- MHD_HTTP_METHOD_POST))
+ if ( (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST)) &&
+ (NULL == end) )
{
return AH_handler_policy_post (connection,
hc,
@@ -389,6 +392,11 @@ url_handler (void *cls,
upload_data,
upload_data_size);
}
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
+ {
+ return TALER_MHD_reply_cors_preflight (connection);
+ }
return TMH_MHD_handler_static_response (&h405,
connection);
}
@@ -398,12 +406,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)))
{
@@ -413,15 +429,19 @@ url_handler (void *cls,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"truth UUID");
}
+ if ( (NULL != end) &&
+ (0 != strcmp (end, "/solve")) &&
+ (0 != strcmp (end, "/challenge")) )
+ return TMH_MHD_handler_static_response (&h404,
+ connection);
if (0 == strcmp (method,
- MHD_HTTP_METHOD_GET))
- {
- return AH_handler_truth_get (connection,
- &tu,
- hc);
- }
- if (0 == strcmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
+ return TALER_MHD_reply_cors_preflight (connection);
+ if (0 != strcmp (method,
MHD_HTTP_METHOD_POST))
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ if (NULL == end)
{
return AH_handler_truth_post (connection,
hc,
@@ -429,9 +449,27 @@ url_handler (void *cls,
upload_data,
upload_data_size);
}
- return TMH_MHD_handler_static_response (&h405,
- connection);
- }
+ if (0 == strcmp (end,
+ "/solve"))
+ {
+ return AH_handler_truth_solve (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
+ if (0 == strcmp (end,
+ "/challenge"))
+ {
+ return AH_handler_truth_challenge (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
+ /* should be impossible to get here */
+ GNUNET_assert (0);
+ } /* end of "/truth/" prefix */
path_matched = false;
for (unsigned int i = 0; NULL != handlers[i].url; i++)
{
@@ -474,7 +512,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)
{
@@ -712,34 +751,6 @@ run (void *cls,
return;
}
if (GNUNET_OK !=
- TALER_config_get_currency (config,
- &AH_currency))
- {
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (0 != strcasecmp (AH_currency,
- AH_annual_fee.currency))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "anastasis",
- "ANNUAL_FEE",
- "currency mismatch");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (GNUNET_OK !=
- TALER_amount_cmp_currency (&AH_insurance,
- &AH_annual_fee))
- {
- GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
- "anastasis",
- "INSURANCE",
- "currency mismatch");
- GNUNET_SCHEDULER_shutdown ();
- return;
- }
- if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (config,
"anastasis-merchant-backend",
"PAYMENT_BACKEND_URL",
@@ -817,30 +828,30 @@ run (void *cls,
return;
}
{
- char *server_salt;
+ char *provider_salt;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (config,
"anastasis",
- "SERVER_SALT",
- &server_salt))
+ "PROVIDER_SALT",
+ &provider_salt))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"anastasis",
- "SERVER_SALT");
+ "PROVIDER_SALT");
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_assert (GNUNET_YES ==
- GNUNET_CRYPTO_kdf (&AH_server_salt,
- sizeof (AH_server_salt),
- "anastasis-server-salt",
- strlen ("anastasis-server-salt"),
- server_salt,
- strlen (server_salt),
+ GNUNET_CRYPTO_kdf (&AH_provider_salt,
+ sizeof (AH_provider_salt),
+ "anastasis-provider-salt",
+ strlen ("anastasis-provider-salt"),
+ provider_salt,
+ strlen (provider_salt),
NULL,
0));
- GNUNET_free (server_salt);
+ GNUNET_free (provider_salt);
}
/* setup HTTP client event loop */
@@ -971,7 +982,6 @@ main (int argc,
"CERTTYPE",
"type of the TLS client certificate, defaults to PEM if not specified",
&certtype),
-
GNUNET_GETOPT_OPTION_END
};
diff --git a/src/backend/anastasis-httpd.h b/src/backend/anastasis-httpd.h
index 33e0504..1a87921 100644
--- a/src/backend/anastasis-httpd.h
+++ b/src/backend/anastasis-httpd.h
@@ -175,11 +175,6 @@ extern struct TALER_Amount AH_question_cost;
extern char *AH_backend_url;
/**
- * Taler currency.
- */
-extern char *AH_currency;
-
-/**
* Heap for processing timeouts of requests.
*/
extern struct GNUNET_CONTAINER_Heap *AH_to_heap;
@@ -205,9 +200,9 @@ extern char *AH_fulfillment_url;
extern char *AH_business_name;
/**
- * Our server salt.
+ * Our provider salt.
*/
-extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_provider_salt;
/**
* Our context for making HTTP requests.
diff --git a/src/backend/anastasis-httpd_config.c b/src/backend/anastasis-httpd_config.c
index d96d57e..315419e 100644
--- a/src/backend/anastasis-httpd_config.c
+++ b/src/backend/anastasis-httpd_config.c
@@ -1,9 +1,9 @@
/*
This file is part of Anastasis
- Copyright (C) 2020, 2021 Anastasis SARL
+ Copyright (C) 2020, 2021, 2024 Anastasis SARL
Anastasis is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
+ 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
@@ -78,48 +78,83 @@ MHD_RESULT
AH_handler_config (struct AH_RequestHandler *rh,
struct MHD_Connection *connection)
{
- json_t *method_arr = json_array ();
+ static struct MHD_Response *response;
+ static struct GNUNET_TIME_Absolute a;
- GNUNET_assert (NULL != method_arr);
+ if ( (GNUNET_TIME_absolute_is_past (a)) &&
+ (NULL != response) )
{
- json_t *method;
+ MHD_destroy_response (response);
+ response = NULL;
+ }
+ if (NULL == response)
+ {
+ json_t *method_arr = json_array ();
+ struct GNUNET_TIME_Timestamp km;
+ char dat[128];
+
+ GNUNET_assert (NULL != method_arr);
+ a = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_DAYS);
+ /* Round up to next full day to ensure the expiration
+ time does not become a fingerprint! */
+ a = GNUNET_TIME_absolute_round_down (a,
+ GNUNET_TIME_UNIT_DAYS);
+ a = GNUNET_TIME_absolute_add (a,
+ GNUNET_TIME_UNIT_DAYS);
+ /* => /config response stays at most 48h in caches! */
+ km = GNUNET_TIME_absolute_to_timestamp (a);
+ TALER_MHD_get_date_string (km.abs_time,
+ dat);
+ {
+ json_t *method;
+
+ method = GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_string ("type",
+ "question"),
+ TALER_JSON_pack_amount ("cost",
+ &AH_question_cost));
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (method_arr,
+ method));
+ }
+ GNUNET_CONFIGURATION_iterate_sections (AH_cfg,
+ &add_methods,
+ method_arr);
- method = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("type",
- "question"),
- TALER_JSON_pack_amount ("cost",
- &AH_question_cost));
- GNUNET_assert (
- 0 ==
- json_array_append_new (method_arr,
- method));
+ response = TALER_MHD_MAKE_JSON_PACK (
+ GNUNET_JSON_pack_string ("name",
+ "anastasis"),
+ GNUNET_JSON_pack_string ("version",
+ "0:2:0"),
+ GNUNET_JSON_pack_string ("implementation",
+ "urn:net:taler:specs:anastasis:c-reference"),
+ GNUNET_JSON_pack_string ("business_name",
+ AH_business_name),
+ GNUNET_JSON_pack_array_steal ("methods",
+ method_arr),
+ GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes",
+ AH_upload_limit_mb),
+ TALER_JSON_pack_amount ("annual_fee",
+ &AH_annual_fee),
+ TALER_JSON_pack_amount ("truth_upload_fee",
+ &AH_truth_upload_fee),
+ TALER_JSON_pack_amount ("liability_limit",
+ &AH_insurance),
+ GNUNET_JSON_pack_data_auto ("provider_salt",
+ &AH_provider_salt));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_EXPIRES,
+ dat));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (response,
+ MHD_HTTP_HEADER_CACHE_CONTROL,
+ "public,max-age=21600")); /* 6h */
}
- GNUNET_CONFIGURATION_iterate_sections (AH_cfg,
- &add_methods,
- method_arr);
- return TALER_MHD_REPLY_JSON_PACK (
- connection,
- MHD_HTTP_OK,
- GNUNET_JSON_pack_string ("name",
- "anastasis"),
- GNUNET_JSON_pack_string ("version",
- "0:0:0"),
- GNUNET_JSON_pack_string ("business_name",
- AH_business_name),
- GNUNET_JSON_pack_string ("currency",
- (char *) AH_currency),
- GNUNET_JSON_pack_array_steal ("methods",
- method_arr),
- GNUNET_JSON_pack_uint64 ("storage_limit_in_megabytes",
- AH_upload_limit_mb),
- TALER_JSON_pack_amount ("annual_fee",
- &AH_annual_fee),
- TALER_JSON_pack_amount ("truth_upload_fee",
- &AH_truth_upload_fee),
- TALER_JSON_pack_amount ("liability_limit",
- &AH_insurance),
- GNUNET_JSON_pack_data_auto ("server_salt",
- &AH_server_salt));
+ return MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ response);
}
diff --git a/src/backend/anastasis-httpd_policy-meta.c b/src/backend/anastasis-httpd_policy-meta.c
new file mode 100644
index 0000000..67acc52
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy-meta.c
@@ -0,0 +1,192 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 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_policy-meta.c
+ * @brief functions to handle incoming requests on /policy/$PID/meta
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis-httpd_policy-meta.h"
+#include "anastasis_service.h"
+#include <gnunet/gnunet_util_lib.h>
+#include <gnunet/gnunet_rest_lib.h>
+#include <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_signatures.h>
+
+
+/**
+ * Function called on matching meta data. Note that if the client did
+ * not provide meta data for @a version, the function will be called
+ * with @a recovery_meta_data being NULL.
+ *
+ * @param cls closure with a `json_t *` to build up
+ * @param version the version of the recovery document
+ * @param ts timestamp when the document was created
+ * @param recovery_meta_data contains meta data about the encrypted recovery document
+ * @param recovery_meta_data_size size of @a recovery_meta_data blob
+ * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration
+ */
+static enum GNUNET_GenericReturnValue
+build_meta_result (void *cls,
+ uint32_t version,
+ struct GNUNET_TIME_Timestamp ts,
+ const void *recovery_meta_data,
+ size_t recovery_meta_data_size)
+{
+ json_t *result = cls;
+ char version_s[14];
+
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ (unsigned int) version);
+ GNUNET_assert (0 ==
+ json_object_set_new (
+ result,
+ version_s,
+ GNUNET_JSON_PACK (
+ GNUNET_JSON_pack_data_varsize (
+ "meta",
+ recovery_meta_data,
+ recovery_meta_data_size),
+ GNUNET_JSON_pack_timestamp (
+ "upload_time",
+ ts))));
+ return GNUNET_OK;
+}
+
+
+/**
+ * Return the meta data on recovery documents of @a account on @a
+ * connection.
+ *
+ * @param connection MHD connection to use
+ * @param account_pub account to query
+ * @return MHD result code
+ */
+static MHD_RESULT
+return_policy_meta (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ uint32_t max_version = INT32_MAX; /* Postgres is using signed ints... */
+ json_t *result;
+
+ {
+ const char *version_s;
+
+ version_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "max_version");
+ if (NULL != version_s)
+ {
+ char dummy;
+
+ if (1 != sscanf (version_s,
+ "%u%c",
+ &max_version,
+ &dummy))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "version");
+ }
+ if (max_version > INT32_MAX)
+ max_version = INT32_MAX; /* cap to signed range */
+ }
+ }
+ result = json_object ();
+ GNUNET_assert (NULL != result);
+ qs = db->get_recovery_meta_data (db->cls,
+ account_pub,
+ max_version,
+ &build_meta_result,
+ result);
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get_recovery_document");
+ 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_SOFT_FAILURE,
+ "get_recovery_document");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
+ NULL);
+ default:
+ /* interesting case below */
+ break;
+ }
+
+ return TALER_MHD_reply_json_steal (connection,
+ result,
+ MHD_HTTP_OK);
+}
+
+
+MHD_RESULT
+AH_policy_meta_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ struct GNUNET_HashCode recovery_data_hash;
+ enum ANASTASIS_DB_AccountStatus as;
+ uint32_t version;
+ struct GNUNET_TIME_Timestamp expiration;
+
+ as = db->lookup_account (db->cls,
+ account_pub,
+ &expiration,
+ &recovery_data_hash,
+ &version);
+ switch (as)
+ {
+ case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
+ "account expired: payment required");
+ case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup account");
+ case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
+ "no such account");
+ case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
+ /* We have results, should fetch and return them! */
+ break;
+ }
+ return return_policy_meta (connection,
+ account_pub);
+}
diff --git a/src/backend/anastasis-httpd_policy-meta.h b/src/backend/anastasis-httpd_policy-meta.h
new file mode 100644
index 0000000..8c48fc6
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy-meta.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 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_policy-meta.h
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_POLICY_META_H
+#define ANASTASIS_HTTPD_POLICY_META_H
+#include <microhttpd.h>
+
+
+/**
+ * Handle GET /policy/$ACCOUNT_PUB/meta request.
+ *
+ * @param connection the MHD connection to handle
+ * @param account_pub public key of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_policy_meta_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub);
+
+
+#endif
diff --git a/src/backend/anastasis-httpd_policy_upload.c b/src/backend/anastasis-httpd_policy-upload.c
index c36cc17..83e8117 100644
--- a/src/backend/anastasis-httpd_policy_upload.c
+++ b/src/backend/anastasis-httpd_policy-upload.c
@@ -35,7 +35,7 @@
* we are awaiting payment before giving up?
*/
#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 30)
+ GNUNET_TIME_UNIT_SECONDS, 30)
/**
@@ -86,6 +86,16 @@ struct PolicyUploadContext
char *upload;
/**
+ * Meta data uploaded by the client, or NULL for none.
+ */
+ void *meta_data;
+
+ /**
+ * Number of bytes in @e meta_data.
+ */
+ size_t meta_data_size;
+
+ /**
* Used while we are awaiting proposal creation.
*/
struct TALER_MERCHANT_PostOrdersHandle *po;
@@ -114,7 +124,7 @@ struct PolicyUploadContext
* Timestamp of the order in @e payment_identifier. Used to
* select the most recent unpaid offer.
*/
- struct GNUNET_TIME_Absolute existing_pi_timestamp;
+ struct GNUNET_TIME_Timestamp existing_pi_timestamp;
/**
* When does the operation timeout?
@@ -125,13 +135,13 @@ struct PolicyUploadContext
* How long must the account be valid? Determines whether we should
* trigger payment, and if so how much.
*/
- struct GNUNET_TIME_Absolute end_date;
+ struct GNUNET_TIME_Timestamp end_date;
/**
* How long is the account already valid?
* Determines how much the user needs to pay.
*/
- struct GNUNET_TIME_Absolute paid_until;
+ struct GNUNET_TIME_Timestamp paid_until;
/**
* Expected total upload size.
@@ -220,6 +230,7 @@ cleanup_ctx (struct TM_HandlerContext *hc)
if (NULL != puc->resp)
MHD_destroy_response (puc->resp);
GNUNET_free (puc->upload);
+ GNUNET_free (puc->meta_data);
GNUNET_free (puc);
}
@@ -323,11 +334,10 @@ proposal_cb (void *cls,
AH_trigger_daemon (NULL);
if (MHD_HTTP_OK != por->hr.http_status)
{
- GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
- "Backend returned status %u/%d\n",
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Backend returned status %u/%d when trying to setup order\n",
por->hr.http_status,
(int) por->hr.ec);
- GNUNET_break (0);
puc->resp = TALER_MHD_MAKE_JSON_PACK (
GNUNET_JSON_pack_uint64 ("code",
TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR),
@@ -340,7 +350,7 @@ proposal_cb (void *cls,
GNUNET_JSON_pack_allow_null (
GNUNET_JSON_pack_object_incref ("backend-reply",
(json_t *) por->hr.reply)));
- puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ puc->response_code = MHD_HTTP_BAD_GATEWAY;
return;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -377,15 +387,14 @@ proposal_cb (void *cls,
* Callback to process a GET /check-payment request
*
* @param cls our `struct PolicyUploadContext`
- * @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 PolicyUploadContext *puc = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
/* refunds are not supported, verify */
puc->cpo = NULL;
@@ -412,10 +421,12 @@ check_payment_cb (void *cls,
puc->response_code = MHD_HTTP_BAD_GATEWAY;
return;
}
+
+ GNUNET_assert (MHD_HTTP_OK == hr->http_status);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Payment status checked: %s\n",
- osr->status ? "paid" : "unpaid");
- switch (osr->status)
+ "Payment status checked: %d\n",
+ osr->details.ok.status);
+ switch (osr->details.ok.status)
{
case TALER_MERCHANT_OSC_PAID:
{
@@ -425,13 +436,12 @@ check_payment_cb (void *cls,
const json_t *contract;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification cspec[] = {
- TALER_JSON_spec_amount ("amount",
- AH_currency,
- &amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
GNUNET_JSON_spec_end ()
};
- contract = osr->details.paid.contract_terms;
+ contract = osr->details.ok.details.paid.contract_terms;
if (GNUNET_OK !=
GNUNET_JSON_parse (contract,
cspec,
@@ -476,7 +486,7 @@ check_payment_cb (void *cls,
case TALER_MERCHANT_OSC_CLAIMED:
break;
}
- if (0 != puc->existing_pi_timestamp.abs_value_us)
+ if (! GNUNET_TIME_absolute_is_zero (puc->existing_pi_timestamp.abs_time))
{
/* repeat payment request */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -527,7 +537,6 @@ await_payment (struct PolicyUploadContext *puc)
AH_backend_url,
order_id,
NULL /* our payments are NOT session-bound */,
- false,
timeout,
&check_payment_cb,
puc);
@@ -548,6 +557,7 @@ await_payment (struct PolicyUploadContext *puc)
static MHD_RESULT
begin_payment (struct PolicyUploadContext *puc)
{
+ static const char *no_uuids[1] = { NULL };
json_t *order;
GNUNET_CONTAINER_DLL_insert (puc_head,
@@ -575,6 +585,10 @@ begin_payment (struct PolicyUploadContext *puc)
order_id = GNUNET_STRINGS_data_to_string_alloc (
&puc->payment_identifier,
sizeof(struct ANASTASIS_PaymentSecretP));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Creating order for %u years with payment of %s\n",
+ puc->years_to_pay,
+ TALER_amount2s (&upload_fee));
order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s }",
"amount", TALER_JSON_from_amount (&upload_fee),
"summary", "Anastasis policy storage fee",
@@ -594,7 +608,7 @@ begin_payment (struct PolicyUploadContext *puc)
0,
NULL, /* no inventory products */
0,
- NULL, /* no uuids */
+ no_uuids, /* no uuids */
false, /* do NOT require claim token */
&proposal_cb,
puc);
@@ -648,35 +662,44 @@ AH_handler_policy_post (
hc->cc = &cleanup_ctx;
puc->con = connection;
+ TALER_MHD_parse_request_header_auto (connection,
+ ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER,
+ &puc->payment_identifier,
+ puc->payment_identifier_provided);
+ puc->account = *account_pub;
+
+ /* check for meta-data */
{
- const char *pay_id;
+ const char *metas;
- pay_id = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- ANASTASIS_HTTP_HEADER_PAYMENT_IDENTIFIER);
- if (NULL != pay_id)
+ metas = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ ANASTASIS_HTTP_HEADER_POLICY_META_DATA);
+ if (NULL == metas)
{
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (
- pay_id,
- strlen (pay_id),
- &puc->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
- " header must be a base32-encoded Payment-Secret");
- }
- puc->payment_identifier_provided = true;
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Policy upload started with payment identifier `%s'\n",
- pay_id);
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ ANASTASIS_HTTP_HEADER_POLICY_META_DATA
+ " header must be present");
+ }
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data_alloc (metas,
+ strlen (metas),
+ &puc->meta_data,
+ &puc->meta_data_size))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (
+ connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_HTTP_HEADERS_MALFORMED,
+ ANASTASIS_HTTP_HEADER_POLICY_META_DATA
+ " header must include a base32-encoded value");
}
}
- puc->account = *account_pub;
/* now setup 'puc' */
{
const char *lens;
@@ -721,28 +744,10 @@ AH_handler_policy_post (
}
puc->upload_size = (size_t) len;
}
- {
- /* Check if header contains Anastasis-Policy-Signature */
- const char *sig_s;
- sig_s = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE);
- if ( (NULL == sig_s) ||
- (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (sig_s,
- strlen (sig_s),
- &puc->account_sig,
- sizeof (puc->account_sig))) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_ANASTASIS_POLICY_BAD_SIGNATURE,
- ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE
- " header must include a base32-encoded EdDSA signature");
- }
- }
+ TALER_MHD_parse_request_header_auto_t (connection,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
+ &puc->account_sig);
{
/* Check if header contains an ETAG */
const char *etag;
@@ -751,9 +756,12 @@ AH_handler_policy_post (
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
if ( (NULL == etag) ||
+ (2 >= strlen (etag)) ||
+ ('"' != etag[0]) ||
+ ('"' != etag[strlen (etag) - 1]) ||
(GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (etag,
- strlen (etag),
+ GNUNET_STRINGS_string_to_data (etag + 1,
+ strlen (etag) - 2,
&puc->new_policy_upload_hash,
sizeof (puc->new_policy_upload_hash))) )
{
@@ -787,39 +795,10 @@ AH_handler_policy_post (
}
}
- {
- 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)");
- }
- puc->timeout
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout));
- }
- else
- {
- puc->timeout = GNUNET_TIME_relative_to_absolute
- (CHECK_PAYMENT_GENERIC_TIMEOUT);
- }
- }
+ puc->timeout = GNUNET_TIME_relative_to_absolute (
+ CHECK_PAYMENT_GENERIC_TIMEOUT);
+ TALER_MHD_parse_request_timeout (connection,
+ &puc->timeout);
/* check if the client insists on paying */
{
@@ -847,9 +826,9 @@ AH_handler_policy_post (
}
else
{
- years = 0;
+ years = 1;
}
- puc->end_date = GNUNET_TIME_relative_to_absolute (
+ puc->end_date = GNUNET_TIME_relative_to_timestamp (
GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
years));
}
@@ -861,11 +840,15 @@ AH_handler_policy_post (
{
struct GNUNET_TIME_Relative rem;
- rem = GNUNET_TIME_absolute_get_remaining (puc->end_date);
+ rem = GNUNET_TIME_absolute_get_remaining (puc->end_date.abs_time);
puc->years_to_pay = rem.rel_value_us
/ GNUNET_TIME_UNIT_YEARS.rel_value_us;
if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
puc->years_to_pay++;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calculated years to pay to be %u until %s\n",
+ puc->years_to_pay,
+ GNUNET_TIME_absolute2s (puc->end_date.abs_time));
if (puc->payment_identifier_provided)
{
@@ -906,18 +889,14 @@ AH_handler_policy_post (
if (! puc->payment_identifier_provided)
{
- struct TALER_Amount zero_amount;
enum GNUNET_DB_QueryStatus qs;
struct GNUNET_TIME_Relative rel;
- TALER_amount_set_zero (AH_currency,
- &zero_amount);
/* generate fresh payment identifier */
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
&puc->payment_identifier,
sizeof (struct ANASTASIS_PaymentSecretP));
- if (0 != TALER_amount_cmp (&AH_annual_fee,
- &zero_amount))
+ if (! TALER_amount_is_zero (&AH_annual_fee))
{
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"No payment identifier, requesting payment\n");
@@ -941,10 +920,10 @@ AH_handler_policy_post (
ANASTASIS_MAX_YEARS_STORAGE);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Policy lifetime is %s (%u years)\n",
- GNUNET_STRINGS_relative_time_to_string (rel,
- GNUNET_YES),
+ GNUNET_TIME_relative2s (rel,
+ true),
ANASTASIS_MAX_YEARS_STORAGE);
- puc->paid_until = GNUNET_TIME_relative_to_absolute (rel);
+ puc->paid_until = GNUNET_TIME_relative_to_timestamp (rel);
qs = db->update_lifetime (db->cls,
account_pub,
&puc->payment_identifier,
@@ -965,7 +944,7 @@ AH_handler_policy_post (
struct GNUNET_HashCode hc;
enum ANASTASIS_DB_AccountStatus as;
uint32_t version;
- struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Timestamp now;
struct GNUNET_TIME_Relative rem;
as = db->lookup_account (db->cls,
@@ -973,16 +952,21 @@ AH_handler_policy_post (
&puc->paid_until,
&hc,
&version);
- now = GNUNET_TIME_absolute_get ();
- if (puc->paid_until.abs_value_us < now.abs_value_us)
+ now = GNUNET_TIME_timestamp_get ();
+ if (GNUNET_TIME_timestamp_cmp (puc->paid_until,
+ <,
+ now))
puc->paid_until = now;
- rem = GNUNET_TIME_absolute_get_difference (puc->paid_until,
- puc->end_date);
+ rem = GNUNET_TIME_absolute_get_difference (puc->paid_until.abs_time,
+ puc->end_date.abs_time);
puc->years_to_pay = rem.rel_value_us
/ GNUNET_TIME_UNIT_YEARS.rel_value_us;
if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
puc->years_to_pay++;
-
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Calculated years to pay to be %u until %s\n",
+ puc->years_to_pay,
+ GNUNET_TIME_absolute2s (puc->end_date.abs_time));
if ( (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED == as) &&
(0 != puc->years_to_pay) )
{
@@ -1113,6 +1097,8 @@ AH_handler_policy_post (
&puc->new_policy_upload_hash,
puc->upload,
puc->upload_size,
+ puc->meta_data,
+ puc->meta_data_size,
&puc->payment_identifier,
&version);
GNUNET_snprintf (version_s,
@@ -1123,7 +1109,7 @@ AH_handler_policy_post (
sizeof (expir_s),
"%llu",
(unsigned long long)
- (puc->paid_until.abs_value_us
+ (puc->paid_until.abs_time.abs_value_us
/ GNUNET_TIME_UNIT_SECONDS.rel_value_us));
switch (ss)
{
diff --git a/src/backend/anastasis-httpd_policy.c b/src/backend/anastasis-httpd_policy.c
index 165be44..177cc06 100644
--- a/src/backend/anastasis-httpd_policy.c
+++ b/src/backend/anastasis-httpd_policy.c
@@ -30,17 +30,9 @@
#include <taler/taler_merchant_service.h>
#include <taler/taler_signatures.h>
-/**
- * How long do we hold an HTTP client connection if
- * we are awaiting payment before giving up?
- */
-#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 30)
-
/**
- * Return the current recoverydocument of @a account on @a connection
- * using @a default_http_status on success.
+ * Return the current recoverydocument of @a account on @a connection.
*
* @param connection MHD connection to use
* @param account_pub account to query
@@ -130,25 +122,30 @@ return_policy (struct MHD_Connection *connection,
{
char *sig_s;
char *etag;
+ char *etagq;
sig_s = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
sizeof (account_sig));
- etag = GNUNET_STRINGS_data_to_string_alloc (&recovery_data_hash,
- sizeof (recovery_data_hash));
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE,
sig_s));
+ GNUNET_free (sig_s);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
ANASTASIS_HTTP_HEADER_POLICY_VERSION,
version_s));
+ etag = GNUNET_STRINGS_data_to_string_alloc (&recovery_data_hash,
+ sizeof (recovery_data_hash));
+ GNUNET_asprintf (&etagq,
+ "\"%s\"",
+ etag);
+ GNUNET_free (etag);
GNUNET_break (MHD_YES ==
MHD_add_response_header (resp,
MHD_HTTP_HEADER_ETAG,
- etag));
- GNUNET_free (etag);
- GNUNET_free (sig_s);
+ etagq));
+ GNUNET_free (etagq);
}
{
MHD_RESULT ret;
@@ -170,7 +167,7 @@ AH_policy_get (struct MHD_Connection *connection,
enum ANASTASIS_DB_AccountStatus as;
MHD_RESULT ret;
uint32_t version;
- struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_TIME_Timestamp expiration;
as = db->lookup_account (db->cls,
account_pub,
@@ -211,13 +208,16 @@ AH_policy_get (struct MHD_Connection *connection,
inm = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_IF_NONE_MATCH);
- if (NULL != inm)
+ if ( (NULL != inm) &&
+ (2 < strlen (inm)) &&
+ ('"' == inm[0]) &&
+ ('"' == inm[strlen (inm) - 1]) )
{
struct GNUNET_HashCode inm_h;
if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (inm,
- strlen (inm),
+ GNUNET_STRINGS_string_to_data (inm + 1,
+ strlen (inm) - 2,
&inm_h,
sizeof (inm_h)))
{
diff --git a/src/backend/anastasis-httpd_policy.h b/src/backend/anastasis-httpd_policy.h
index 33d51cf..2735db6 100644
--- a/src/backend/anastasis-httpd_policy.h
+++ b/src/backend/anastasis-httpd_policy.h
@@ -1,6 +1,6 @@
/*
This file is part of Anastasis
- Copyright (C) 2014, 2015, 2016 Anastasis SARL
+ Copyright (C) 2019-2021 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
diff --git a/src/backend/anastasis-httpd_terms.c b/src/backend/anastasis-httpd_terms.c
index b4debe7..94a6380 100644
--- a/src/backend/anastasis-httpd_terms.c
+++ b/src/backend/anastasis-httpd_terms.c
@@ -3,7 +3,7 @@
Copyright (C) 2020 Anastasis SARL
Anastasis is free software; you can redistribute it and/or modify it under the
- terms of the GNU Lesser General Public License as published by the Free Software
+ 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
@@ -36,13 +36,6 @@ static struct TALER_MHD_Legal *tos;
static struct TALER_MHD_Legal *pp;
-/**
- * Manages a /terms call.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @return MHD result code
- */
MHD_RESULT
AH_handler_terms (struct AH_RequestHandler *rh,
struct MHD_Connection *connection)
@@ -53,15 +46,8 @@ AH_handler_terms (struct AH_RequestHandler *rh,
}
-/**
- * Handle a "/privacy" request.
- *
- * @param rh context of the handler
- * @param connection the MHD connection to handle
- * @return MHD result code
- */
MHD_RESULT
-AH_handler_privacy (const struct AH_RequestHandler *rh,
+AH_handler_privacy (struct AH_RequestHandler *rh,
struct MHD_Connection *connection)
{
(void) rh;
@@ -70,11 +56,6 @@ AH_handler_privacy (const struct AH_RequestHandler *rh,
}
-/**
- * Load our terms of service as per configuration.
- *
- * @param cfg configuration to process
- */
void
AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
{
diff --git a/src/backend/anastasis-httpd_terms.h b/src/backend/anastasis-httpd_terms.h
index e34c86e..5043c57 100644
--- a/src/backend/anastasis-httpd_terms.h
+++ b/src/backend/anastasis-httpd_terms.h
@@ -45,7 +45,7 @@ AH_handler_terms (struct AH_RequestHandler *rh,
* @return MHD result code
*/
MHD_RESULT
-AH_handler_privacy (const struct AH_RequestHandler *rh,
+AH_handler_privacy (struct AH_RequestHandler *rh,
struct MHD_Connection *connection);
/**
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth-challenge.c
index 4dd3ddc..a7d138f 100644
--- a/src/backend/anastasis-httpd_truth.c
+++ b/src/backend/anastasis-httpd_truth-challenge.c
@@ -1,6 +1,6 @@
/*
This file is part of Anastasis
- Copyright (C) 2019, 2021 Anastasis SARL
+ 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
@@ -14,8 +14,8 @@
Anastasis; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
- * @file anastasis-httpd_truth.c
- * @brief functions to handle incoming requests on /truth
+ * @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
@@ -29,6 +29,7 @@
#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
@@ -38,18 +39,17 @@
GNUNET_TIME_UNIT_SECONDS, 30)
/**
- * How long do we hold an HTTP client connection if
- * we are awaiting payment before giving up?
- */
-#define CHECK_PAYMENT_GENERIC_TIMEOUT GNUNET_TIME_relative_multiply ( \
- GNUNET_TIME_UNIT_MINUTES, 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?
@@ -57,7 +57,7 @@
#define INITIAL_RETRY_COUNTER 3
-struct GetContext
+struct ChallengeContext
{
/**
@@ -86,14 +86,24 @@ struct GetContext
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 GetContext *next;
+ struct ChallengeContext *next;
/**
* Kept in DLL for shutdown handling while suspended.
*/
- struct GetContext *prev;
+ struct ChallengeContext *prev;
/**
* Connection handle for closing or resuming
@@ -131,13 +141,7 @@ struct GetContext
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?
+ * When should this request time out?
*/
struct GNUNET_TIME_Absolute timeout;
@@ -152,9 +156,9 @@ struct GetContext
unsigned int response_code;
/**
- * true if client provided a payment secret / order ID?
+ * true if client did not provide a payment secret / order ID.
*/
- bool payment_identifier_provided;
+ bool no_payment_identifier_provided;
/**
* True if this entry is in the #gc_head DLL.
@@ -166,13 +170,9 @@ struct GetContext
*/
bool suspended;
- /**
- * Did the request include a response?
- */
- bool have_response;
-
};
+
/**
* Information we track for refunds.
*/
@@ -223,12 +223,12 @@ static struct RefundEntry *re_tail;
/**
* Head of linked list over all authorization processes
*/
-static struct GetContext *gc_head;
+static struct ChallengeContext *gc_head;
/**
* Tail of linked list over all authorization processes
*/
-static struct GetContext *gc_tail;
+static struct ChallengeContext *gc_tail;
/**
* Task running #do_timeout().
@@ -237,6 +237,27 @@ 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
@@ -244,7 +265,7 @@ static struct GNUNET_SCHEDULER_Task *to_task;
static void
do_timeout (void *cls)
{
- struct GetContext *gc;
+ struct ChallengeContext *gc;
(void) cls;
to_task = NULL;
@@ -276,9 +297,9 @@ do_timeout (void *cls)
void
-AH_truth_shutdown (void)
+AH_truth_challenge_shutdown (void)
{
- struct GetContext *gc;
+ struct ChallengeContext *gc;
struct RefundEntry *re;
while (NULL != (re = re_head))
@@ -339,22 +360,17 @@ AH_truth_shutdown (void)
* 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.
+ * @param rr response details
*/
static void
refund_cb (
void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
- const char *taler_refund_uri,
- const struct GNUNET_HashCode *h_contract)
+ const struct TALER_MERCHANT_RefundResponse *rr)
{
struct RefundEntry *re = cls;
re->ro = NULL;
- switch (hr->http_status)
+ switch (rr->hr.http_status)
{
case MHD_HTTP_OK:
{
@@ -386,9 +402,9 @@ refund_cb (
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);
+ rr->hr.http_status,
+ rr->hr.hint,
+ (unsigned int) rr->hr.ec);
break;
}
GNUNET_CONTAINER_DLL_remove (re_head,
@@ -405,7 +421,7 @@ refund_cb (
* @param gc request where we failed and should now grant a refund for
*/
static void
-begin_refund (const struct GetContext *gc)
+begin_refund (const struct ChallengeContext *gc)
{
struct RefundEntry *re;
@@ -447,7 +463,7 @@ begin_refund (const struct GetContext *gc)
static void
request_done (struct TM_HandlerContext *hc)
{
- struct GetContext *gc = hc->ctx;
+ struct ChallengeContext *gc = hc->ctx;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Request completed\n");
@@ -484,6 +500,12 @@ request_done (struct TM_HandlerContext *hc)
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;
}
@@ -495,7 +517,7 @@ request_done (struct TM_HandlerContext *hc)
* @param gc context to make payment request for
*/
static void
-make_payment_request (struct GetContext *gc)
+make_payment_request (struct ChallengeContext *gc)
{
struct MHD_Response *resp;
@@ -559,14 +581,14 @@ make_payment_request (struct GetContext *gc)
* Callbacks of this type are used to serve the result of submitting a
* /contract request to a merchant.
*
- * @param cls our `struct GetContext`
+ * @param cls our `struct ChallengeContext`
* @param por response details
*/
static void
proposal_cb (void *cls,
const struct TALER_MERCHANT_PostOrdersReply *por)
{
- struct GetContext *gc = cls;
+ struct ChallengeContext *gc = cls;
enum GNUNET_DB_QueryStatus qs;
gc->po = NULL;
@@ -622,17 +644,16 @@ proposal_cb (void *cls,
/**
* Callback to process a GET /check-payment request
*
- * @param cls our `struct GetContext`
- * @param hr HTTP response details
+ * @param cls our `struct ChallengeContext`
* @param osr order status
*/
static void
check_payment_cb (void *cls,
- const struct TALER_MERCHANT_HttpResponse *hr,
const struct TALER_MERCHANT_OrderStatusResponse *osr)
{
- struct GetContext *gc = cls;
+ struct ChallengeContext *gc = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
gc->cpo = NULL;
GNUNET_assert (gc->in_list);
@@ -686,7 +707,8 @@ check_payment_cb (void *cls,
}
}
- switch (osr->status)
+ GNUNET_assert (MHD_HTTP_OK == hr->http_status);
+ switch (osr->details.ok.status)
{
case TALER_MERCHANT_OSC_PAID:
{
@@ -729,7 +751,7 @@ check_payment_cb (void *cls,
* @return MHD status code
*/
static MHD_RESULT
-begin_payment (struct GetContext *gc)
+begin_payment (struct ChallengeContext *gc)
{
enum GNUNET_DB_QueryStatus qs;
char *order_id;
@@ -769,7 +791,6 @@ begin_payment (struct GetContext *gc)
AH_backend_url,
order_id,
NULL /* NOT session-bound */,
- false,
timeout,
&check_payment_cb,
gc);
@@ -777,8 +798,9 @@ begin_payment (struct GetContext *gc)
else
{
/* Create a fresh order */
+ static const char *no_uuids[1] = { NULL };
json_t *order;
- struct GNUNET_TIME_Absolute pay_deadline;
+ struct GNUNET_TIME_Timestamp pay_deadline;
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
&gc->payment_identifier,
@@ -789,9 +811,8 @@ begin_payment (struct GetContext *gc)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Creating fresh order `%s'\n",
order_id);
- pay_deadline = GNUNET_TIME_relative_to_absolute (
+ pay_deadline = GNUNET_TIME_relative_to_timestamp (
ANASTASIS_CHALLENGE_OFFER_LIFETIME);
- GNUNET_TIME_round_abs (&pay_deadline);
order = GNUNET_JSON_PACK (
TALER_JSON_pack_amount ("amount",
&gc->challenge_cost),
@@ -801,8 +822,8 @@ begin_payment (struct GetContext *gc)
order_id),
GNUNET_JSON_pack_time_rel ("auto_refund",
AUTO_REFUND_TIMEOUT),
- GNUNET_JSON_pack_time_abs ("pay_deadline",
- pay_deadline));
+ GNUNET_JSON_pack_timestamp ("pay_deadline",
+ pay_deadline));
gc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
AH_backend_url,
order,
@@ -811,7 +832,7 @@ begin_payment (struct GetContext *gc)
0,
NULL, /* no inventory products */
0,
- NULL, /* no uuids */
+ no_uuids, /* no uuids */
false, /* do NOT require claim token */
&proposal_cb,
gc);
@@ -824,59 +845,33 @@ begin_payment (struct GetContext *gc)
/**
- * Load encrypted keyshare from db and return it to the client.
+ * Mark @a gc as suspended and update the respective
+ * data structures and jobs.
*
- * @param truth_uuid UUID to the truth for the looup
- * @param connection the connection to respond upon
- * @return MHD status code
+ * @param[in,out] gc context of the suspended operation
*/
-static MHD_RESULT
-return_key_share (
- const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
- struct MHD_Connection *connection)
+static void
+gc_suspended (struct ChallengeContext *gc)
{
- struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
-
+ 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)
{
- 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_SCHEDULER_cancel (to_task);
+ to_task = NULL;
}
-
- 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;
+ struct ChallengeContext *rn;
+
+ rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
+ to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
+ &do_timeout,
+ NULL);
}
}
@@ -890,21 +885,30 @@ return_key_share (
*/
static MHD_RESULT
run_authorization_process (struct MHD_Connection *connection,
- struct GetContext *gc)
+ struct ChallengeContext *gc)
{
- enum ANASTASIS_AUTHORIZATION_Result ret;
+ enum ANASTASIS_AUTHORIZATION_ChallengeResult ret;
enum GNUNET_DB_QueryStatus qs;
GNUNET_assert (! gc->suspended);
- ret = gc->authorization->process (gc->as,
- gc->timeout,
- connection);
+ 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_RES_SUCCESS:
+ case ANASTASIS_AUTHORIZATION_CRES_SUCCESS:
/* Challenge sent successfully */
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Authorization request sent successfully\n");
+ "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,
@@ -913,38 +917,19 @@ run_authorization_process (struct MHD_Connection *connection,
gc->authorization->cleanup (gc->as);
gc->as = NULL;
return MHD_YES;
- case ANASTASIS_AUTHORIZATION_RES_FAILED:
- if (gc->payment_identifier_provided)
+ 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_RES_SUSPENDED:
+ case ANASTASIS_AUTHORIZATION_CRES_SUSPENDED:
/* connection was suspended */
- 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 GetContext *rn;
-
- rn = GNUNET_CONTAINER_heap_peek (AH_to_heap);
- to_task = GNUNET_SCHEDULER_add_at (rn->timeout,
- &do_timeout,
- NULL);
- }
+ gc_suspended (gc);
return MHD_YES;
- case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+ case ANASTASIS_AUTHORIZATION_CRES_SUCCESS_REPLY_FAILED:
/* Challenge sent successfully */
qs = db->mark_challenge_sent (db->cls,
&gc->payment_identifier,
@@ -954,24 +939,10 @@ run_authorization_process (struct MHD_Connection *connection,
gc->authorization->cleanup (gc->as);
gc->as = NULL;
return MHD_NO;
- case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+ case ANASTASIS_AUTHORIZATION_CRES_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;
@@ -979,141 +950,31 @@ run_authorization_process (struct MHD_Connection *connection,
MHD_RESULT
-AH_handler_truth_get (
+AH_handler_truth_challenge (
struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
- struct TM_HandlerContext *hc)
+ const char *upload_data,
+ size_t *upload_data_size)
{
- struct GetContext *gc = hc->ctx;
+ 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;
- bool is_question;
if (NULL == gc)
{
/* Fresh request, do initial setup */
- gc = GNUNET_new (struct GetContext);
+ 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);
- }
- }
-
- {
- /* check if header contains Truth-Decryption-Key */
- const char *tdk;
-
- tdk = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
- if (NULL == tdk)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MISSING,
- ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
- }
-
- if (GNUNET_OK !=
- GNUNET_STRINGS_string_to_data (
- tdk,
- strlen (tdk),
- &gc->truth_key,
- sizeof (struct ANASTASIS_CRYPTO_TruthKeyP)))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- ANASTASIS_HTTP_HEADER_TRUTH_DECRYPTION_KEY);
- }
- }
-
- {
- const char *challenge_response_s;
-
- challenge_response_s = MHD_lookup_connection_value (connection,
- MHD_GET_ARGUMENT_KIND,
- "response");
- if ( (NULL != challenge_response_s) &&
- (GNUNET_OK !=
- GNUNET_CRYPTO_hash_from_string (challenge_response_s,
- &gc->challenge_response)) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "response");
- }
- gc->have_response = (NULL != challenge_response_s);
- }
-
- {
- 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);
- }
- }
-
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ PAYMENT_TIMEOUT);
} /* end of first-time initialization (if NULL == gc) */
else
{
@@ -1149,6 +1010,61 @@ AH_handler_truth_get (
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;
@@ -1177,39 +1093,58 @@ AH_handler_truth_get (
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
break;
}
- is_question = (0 == strcmp ("question",
- method));
- if (! is_question)
+ if (0 == strcmp ("question",
+ method))
{
- gc->authorization
- = ANASTASIS_authorization_plugin_load (method,
- db,
- AH_cfg);
- if (NULL == gc->authorization)
- {
- MHD_RESULT ret;
+ 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");
+ }
- 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;
+ 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;
}
- else
+
+ if (gc->authorization->user_provided_code)
{
- gc->challenge_cost = AH_question_cost;
+ 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 ( (is_question) ||
- (! gc->authorization->payment_plugin_managed) )
+ if (! gc->authorization->payment_plugin_managed)
{
if (! TALER_amount_is_zero (&gc->challenge_cost))
{
@@ -1217,7 +1152,7 @@ AH_handler_truth_get (
enum GNUNET_DB_QueryStatus qs;
bool paid;
- if (! gc->payment_identifier_provided)
+ if (gc->no_payment_identifier_provided)
{
GNUNET_free (truth_mime);
GNUNET_free (encrypted_truth);
@@ -1282,246 +1217,86 @@ AH_handler_truth_get (
{
GNUNET_free (truth_mime);
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_EXPECTATION_FAILED,
+ MHD_HTTP_CONFLICT,
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)
+ /* 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");
{
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Handling security question challenge\n");
- if (! gc->have_response)
- {
- GNUNET_free (decrypted_truth);
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_ANASTASIS_TRUTH_CHALLENGE_RESPONSE_REQUIRED,
- NULL);
- }
+ 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)
{
- enum GNUNET_DB_QueryStatus qs;
- struct GNUNET_TIME_Absolute rt;
- uint64_t code;
- enum ANASTASIS_DB_CodeStatus cs;
- struct GNUNET_HashCode hc;
- bool satisfied;
- uint64_t dummy;
-
- rt = GNUNET_TIME_UNIT_FOREVER_ABS;
- 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);
- GNUNET_free (decrypted_truth);
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "create_challenge_code (for rate limiting)");
- }
- if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
- {
- GNUNET_free (decrypted_truth);
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL);
- }
- /* 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 */
- break;
- case ANASTASIS_DB_CODE_STATUS_HARD_ERROR:
- case ANASTASIS_DB_CODE_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,
- "verify_challenge_code");
- case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL);
- case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
- /* this should be impossible, we used code+1 */
- GNUNET_assert (0);
- }
- }
- 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));
+ case GNUNET_OK:
+ /* data valid, continued below */
+ break;
+ case GNUNET_NO:
+ /* data invalid, reply was queued */
GNUNET_free (decrypted_truth);
- GNUNET_free (truth_mime);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_FORBIDDEN,
- TALER_EC_ANASTASIS_TRUTH_CHALLENGE_FAILED,
- NULL);
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ /* data invalid, reply was NOT queued */
+ GNUNET_free (decrypted_truth);
+ return MHD_NO;
}
- GNUNET_free (decrypted_truth);
- GNUNET_free (truth_mime);
- return return_key_share (&gc->truth_uuid,
- connection);
}
- /* Not security question, check for answer in DB */
- if (gc->have_response)
+ /* Setup challenge and begin authorization process */
{
- enum ANASTASIS_DB_CodeStatus cs;
- bool satisfied;
- uint64_t code;
+ struct GNUNET_TIME_Timestamp transmission_date;
+ enum GNUNET_DB_QueryStatus qs;
- GNUNET_free (truth_mime);
- cs = db->verify_challenge_code (db->cls,
+ qs = db->create_challenge_code (db->cls,
&gc->truth_uuid,
- &gc->challenge_response,
- &code,
- &satisfied);
- switch (cs)
+ gc->authorization->code_rotation_period,
+ gc->authorization->code_validity_period,
+ gc->authorization->retry_counter,
+ &transmission_date,
+ &gc->code);
+ switch (qs)
{
- 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:
+ 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,
- "verify_challenge_code");
- case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
- GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Response code unknown (possibly expired). Testing if we may provide a new one.\n");
- gc->have_response = false;
- break;
- case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ "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,
- "Response code valid (%s)\n",
- satisfied ? "satisfied" : "unsatisfied");
- if (satisfied)
- {
- GNUNET_free (decrypted_truth);
- return return_key_share (&gc->truth_uuid,
- connection);
- }
- /* continue with authorization plugin below */
- gc->code = code;
+ "Created fresh challenge\n");
break;
- default:
- GNUNET_break (0);
- return MHD_NO;
}
- }
- if (! gc->have_response)
- {
- /* 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 */
+ if (GNUNET_TIME_relative_cmp (
+ GNUNET_TIME_absolute_get_duration (
+ transmission_date.abs_time),
+ <,
+ gc->authorization->code_retransmission_frequency) )
{
- struct GNUNET_TIME_Absolute 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 TALER_MHD_reply_with_error (connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL);
- 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_absolute_get_duration (transmission_date).rel_value_us <
- gc->authorization->code_retransmission_frequency.rel_value_us)
- {
- /* 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);
- }
+ /* 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"));
}
}
diff --git a/src/backend/anastasis-httpd_truth-solve.c b/src/backend/anastasis-httpd_truth-solve.c
new file mode 100644
index 0000000..eb09dc7
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth-solve.c
@@ -0,0 +1,1474 @@
+/*
+ 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 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;
+
+};
+
+
+/**
+ * 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)
+{
+ if (NULL != gc->authorization)
+ 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));
+ /* must be security question */
+ 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",
+ INITIAL_RETRY_COUNTER),
+ GNUNET_JSON_pack_time_rel ("request_frequency",
+ MAX_QUESTION_FREQ));
+}
+
+
+/**
+ * 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 osr order status
+ */
+static void
+check_payment_cb (void *cls,
+ const struct TALER_MERCHANT_OrderStatusResponse *osr)
+
+{
+ struct SolveContext *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 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 */,
+ timeout,
+ &check_payment_cb,
+ gc);
+ }
+ else
+ {
+ /* Create a fresh order */
+ static const char *no_uuids[1] = { NULL };
+ 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,
+ no_uuids, /* 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;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key share of %s\n",
+ TALER_B2S (truth_uuid));
+ {
+ 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:
+ /* this should be "impossible", after all the
+ client was able to solve the challenge!
+ (Exception: we deleted the truth via GC
+ just while the client was trying to recover.
+ Alas, highly unlikely...) */
+ GNUNET_break (0);
+ 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;
+ }
+ }
+
+ {
+ 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)
+{
+ GNUNET_assert (NULL == gc->hn);
+ GNUNET_assert (! gc->suspended);
+ 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_SolveResult ret;
+
+ GNUNET_assert (! gc->suspended);
+ if (NULL == gc->authorization->solve)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+ "solve method not implemented for authorization method");
+ }
+ ret = gc->authorization->solve (gc->as,
+ gc->timeout,
+ &gc->challenge_response,
+ connection);
+ switch (ret)
+ {
+ case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
+ /* connection was suspended */
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_SRES_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;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Using intentionally wrong answer to produce rate-limiting\n");
+ /* 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. Also ensures that the
+ * request is rate-limited.
+ *
+ * @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_SolveResult aar;
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = rate_limit (gc);
+ if (GNUNET_OK != ret)
+ return (GNUNET_NO == ret) ? 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);
+ }
+ if (NULL == gc->authorization->solve)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+ "solve method not implemented for authorization method");
+ }
+ aar = gc->authorization->solve (gc->as,
+ gc->timeout,
+ &gc->challenge_response,
+ gc->connection);
+ switch (aar)
+ {
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED:
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending request handling\n");
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_SRES_FINISHED:
+ return return_key_share (&gc->truth_uuid,
+ gc->connection);
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/**
+ * Handle special case of an answer being checked
+ * by the plugin asynchronously (IBAN) after we inverted
+ * the hash using the database.
+ *
+ * @param[in,out] gc request to handle
+ * @param code validation code provided by the client
+ * @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
+iban_validation (struct SolveContext *gc,
+ uint64_t code,
+ const void *decrypted_truth,
+ size_t decrypted_truth_size)
+{
+ enum ANASTASIS_AUTHORIZATION_SolveResult aar;
+
+ gc->as = gc->authorization->start (gc->authorization->cls,
+ &AH_trigger_daemon,
+ NULL,
+ &gc->truth_uuid,
+ code,
+ 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);
+ }
+ if (NULL == gc->authorization->solve)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+ "solve method not implemented for authorization method");
+ }
+ aar = gc->authorization->solve (gc->as,
+ gc->timeout,
+ &gc->challenge_response,
+ gc->connection);
+ switch (aar)
+ {
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED:
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_SUSPENDED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending request handling\n");
+ gc_suspended (gc);
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_SRES_FAILED_REPLY_FAILED:
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_SRES_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;
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ TALER_MHD_parse_request_timeout (connection,
+ &gc->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! */
+ }
+
+ 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_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; 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->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 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Timeout with user provided code\n");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_IBAN_MISSING_TRANSFER,
+ "timeout awaiting validation");
+ }
+ res = direct_validation (gc,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (decrypted_truth);
+ return res;
+ }
+
+ /* random code, check against database */
+ 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);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Specified challenge code %s was not issued\n",
+ GNUNET_h2s (&gc->challenge_response));
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_TRUTH_CHALLENGE_UNKNOWN,
+ "specific challenge code was not issued");
+ case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
+ if (! satisfied)
+ {
+ MHD_RESULT res;
+
+ res = iban_validation (gc,
+ code,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (decrypted_truth);
+ return res;
+ }
+ 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 d9e63c3..1c2a58d 100644
--- a/src/backend/anastasis-httpd_truth_upload.c
+++ b/src/backend/anastasis-httpd_truth-upload.c
@@ -287,15 +287,14 @@ proposal_cb (void *cls,
* Callback to process a GET /check-payment request
*
* @param cls our `struct PolicyUploadContext`
- * @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 TruthUploadContext *tuc = cls;
+ const struct TALER_MERCHANT_HttpResponse *hr = &osr->hr;
tuc->cpo = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
@@ -311,7 +310,7 @@ check_payment_cb (void *cls,
NULL);
break;
case MHD_HTTP_OK:
- switch (osr->status)
+ switch (osr->details.ok.status)
{
case TALER_MERCHANT_OSC_PAID:
{
@@ -321,13 +320,12 @@ check_payment_cb (void *cls,
const json_t *contract;
struct TALER_Amount amount;
struct GNUNET_JSON_Specification cspec[] = {
- TALER_JSON_spec_amount ("amount",
- AH_currency,
- &amount),
+ TALER_JSON_spec_amount_any ("amount",
+ &amount),
GNUNET_JSON_spec_end ()
};
- contract = osr->details.paid.contract_terms;
+ contract = osr->details.ok.details.paid.contract_terms;
if (GNUNET_OK !=
GNUNET_JSON_parse (contract,
cspec,
@@ -356,7 +354,7 @@ check_payment_cb (void *cls,
qs = db->record_truth_upload_payment (
db->cls,
&tuc->truth_uuid,
- &osr->details.paid.deposit_total,
+ &osr->details.ok.details.paid.deposit_total,
paid_until);
if (qs <= 0)
{
@@ -398,6 +396,7 @@ check_payment_cb (void *cls,
case MHD_HTTP_NOT_FOUND:
/* Setup fresh order */
{
+ static const char *no_uuids[1] = { NULL };
char *order_id;
json_t *order;
@@ -417,7 +416,6 @@ check_payment_cb (void *cls,
"description", "challenge storage fee",
"quantity", (json_int_t) tuc->years_to_pay,
"unit", "years",
-
"order_id",
order_id);
GNUNET_free (order_id);
@@ -429,7 +427,7 @@ check_payment_cb (void *cls,
0,
NULL, /* no inventory products */
0,
- NULL, /* no uuids */
+ no_uuids, /* no uuids */
false, /* do NOT require claim token */
&proposal_cb,
tuc);
@@ -487,7 +485,6 @@ begin_payment (struct TruthUploadContext *tuc)
AH_backend_url,
order_id,
NULL /* our payments are NOT session-bound */,
- false,
timeout,
&check_payment_cb,
tuc);
@@ -519,17 +516,18 @@ AH_handler_truth_post (
struct TruthUploadContext *tuc = hc->ctx;
MHD_RESULT ret;
int res;
- struct ANASTASIS_CRYPTO_EncryptedKeyShareP keyshare_data;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP key_share_data;
void *encrypted_truth;
size_t encrypted_truth_size;
const char *truth_mime = NULL;
const char *type;
enum GNUNET_DB_QueryStatus qs;
uint32_t storage_years;
- struct GNUNET_TIME_Absolute paid_until;
+ struct GNUNET_TIME_Timestamp paid_until
+ = GNUNET_TIME_UNIT_ZERO_TS;
struct GNUNET_JSON_Specification spec[] = {
- GNUNET_JSON_spec_fixed_auto ("keyshare_data",
- &keyshare_data),
+ GNUNET_JSON_spec_fixed_auto ("key_share_data",
+ &key_share_data),
GNUNET_JSON_spec_string ("type",
&type),
GNUNET_JSON_spec_varsize ("encrypted_truth",
@@ -537,7 +535,8 @@ AH_handler_truth_post (
&encrypted_truth_size),
GNUNET_JSON_spec_mark_optional (
GNUNET_JSON_spec_string ("truth_mime",
- &truth_mime)),
+ &truth_mime),
+ NULL),
GNUNET_JSON_spec_uint32 ("storage_duration_years",
&storage_years),
GNUNET_JSON_spec_end ()
@@ -550,78 +549,12 @@ AH_handler_truth_post (
tuc->truth_uuid = *truth_uuid;
hc->ctx = tuc;
hc->cc = &cleanup_truth_post;
-
- /* check for excessive upload */
- {
- const char *lens;
- unsigned long len;
- char dummy;
-
- lens = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONTENT_LENGTH);
- if ( (NULL == lens) ||
- (1 != sscanf (lens,
- "%lu%c",
- &len,
- &dummy)) )
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (
- connection,
- MHD_HTTP_BAD_REQUEST,
- (NULL == lens)
- ? TALER_EC_ANASTASIS_GENERIC_MISSING_CONTENT_LENGTH
- : TALER_EC_ANASTASIS_GENERIC_MALFORMED_CONTENT_LENGTH,
- NULL);
- }
- if (len / 1024 / 1024 >= AH_upload_limit_mb)
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_PAYLOAD_TOO_LARGE,
- TALER_EC_SYNC_MALFORMED_CONTENT_LENGTH,
- "Content-length value not acceptable");
- }
- }
-
- {
- 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)");
- }
- tuc->timeout
- = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
- GNUNET_TIME_UNIT_MILLISECONDS,
- timeout));
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Long polling for %u ms enabled\n",
- timeout);
- }
- else
- {
- tuc->timeout = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_UNIT_SECONDS);
- }
- }
-
+ TALER_MHD_check_content_length (connection,
+ AH_upload_limit_mb * 1024LLU * 1024LLU);
+ tuc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ TALER_MHD_parse_request_timeout (connection,
+ &tuc->timeout);
} /* end 'if (NULL == tuc)' */
if (NULL != tuc->resp)
@@ -695,95 +628,89 @@ AH_handler_truth_post (
if (0 == storage_years)
storage_years = 1;
+ if (! TALER_amount_is_zero (&AH_truth_upload_fee))
{
- struct TALER_Amount zero_amount;
-
- TALER_amount_set_zero (AH_currency,
- &zero_amount);
- if (0 != TALER_amount_cmp (&AH_truth_upload_fee,
- &zero_amount))
+ struct GNUNET_TIME_Timestamp desired_until;
+ enum GNUNET_DB_QueryStatus qs;
+
+ desired_until
+ = GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ storage_years));
+ qs = db->check_truth_upload_paid (db->cls,
+ truth_uuid,
+ &paid_until);
+ if (qs < 0)
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ if ( (0 == qs) ||
+ (GNUNET_TIME_timestamp_cmp (paid_until,
+ <,
+ desired_until) ) )
{
- struct GNUNET_TIME_Absolute desired_until;
- enum GNUNET_DB_QueryStatus qs;
-
- desired_until
- = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
- storage_years));
- qs = db->check_truth_upload_paid (db->cls,
- truth_uuid,
- &paid_until);
- if (qs < 0)
+ struct GNUNET_TIME_Relative rem;
+
+ if (GNUNET_TIME_absolute_is_past (paid_until.abs_time))
+ paid_until = GNUNET_TIME_timestamp_get ();
+ rem = GNUNET_TIME_absolute_get_difference (paid_until.abs_time,
+ desired_until.abs_time);
+ tuc->years_to_pay = rem.rel_value_us
+ / GNUNET_TIME_UNIT_YEARS.rel_value_us;
+ if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
+ tuc->years_to_pay++;
+ if (0 >
+ TALER_amount_multiply (&tuc->upload_fee,
+ &AH_truth_upload_fee,
+ tuc->years_to_pay))
+ {
+ GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- NULL);
- if ( (0 == qs) ||
- (paid_until.abs_value_us < desired_until.abs_value_us) )
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration_years");
+ }
+ if (! TALER_amount_is_zero (&tuc->upload_fee))
{
- struct GNUNET_TIME_Absolute now;
- struct GNUNET_TIME_Relative rem;
-
- now = GNUNET_TIME_absolute_get ();
- if (paid_until.abs_value_us < now.abs_value_us)
- paid_until = now;
- rem = GNUNET_TIME_absolute_get_difference (paid_until,
- desired_until);
- tuc->years_to_pay = rem.rel_value_us
- / GNUNET_TIME_UNIT_YEARS.rel_value_us;
- if (0 != (rem.rel_value_us % GNUNET_TIME_UNIT_YEARS.rel_value_us))
- tuc->years_to_pay++;
- if (0 >
- TALER_amount_multiply (&tuc->upload_fee,
- &AH_truth_upload_fee,
- tuc->years_to_pay))
- {
- GNUNET_break_op (0);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_BAD_REQUEST,
- TALER_EC_GENERIC_PARAMETER_MALFORMED,
- "storage_duration_years");
- }
- if ( (0 != tuc->upload_fee.fraction) ||
- (0 != tuc->upload_fee.value) )
- {
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "Truth upload payment required (%d)!\n",
- qs);
- return begin_payment (tuc);
- }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Truth upload payment required (%d)!\n",
+ qs);
+ return begin_payment (tuc);
}
- GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
- "TRUTH paid until %s (%d)!\n",
- GNUNET_STRINGS_relative_time_to_string (
- GNUNET_TIME_absolute_get_remaining (
- paid_until),
- GNUNET_YES),
- qs);
- }
- else
- {
- paid_until
- = GNUNET_TIME_relative_to_absolute (
- GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
- ANASTASIS_MAX_YEARS_STORAGE));
}
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH paid until %s (%d)!\n",
+ GNUNET_TIME_relative2s (
+ GNUNET_TIME_absolute_get_remaining (
+ paid_until.abs_time),
+ GNUNET_YES),
+ qs);
+ }
+ else
+ {
+ paid_until
+ = GNUNET_TIME_relative_to_timestamp (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ ANASTASIS_MAX_YEARS_STORAGE));
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
- "Storing truth until %s!\n",
- GNUNET_STRINGS_absolute_time_to_string (paid_until));
+ "Storing truth %s until %s!\n",
+ TALER_B2S (truth_uuid),
+ GNUNET_TIME_timestamp2s (paid_until));
qs = db->store_truth (db->cls,
truth_uuid,
- &keyshare_data,
+ &key_share_data,
(NULL == truth_mime)
? ""
: truth_mime,
encrypted_truth,
encrypted_truth_size,
type,
- GNUNET_TIME_absolute_get_remaining (paid_until));
+ GNUNET_TIME_absolute_get_remaining (
+ paid_until.abs_time));
switch (qs)
{
case GNUNET_DB_STATUS_HARD_ERROR:
@@ -813,8 +740,8 @@ AH_handler_truth_post (
ok = ( (xtruth_size == encrypted_truth_size) &&
(0 == strcmp (xmethod,
type)) &&
- (0 == strcmp (truth_mime,
- xtruth_mime)) &&
+ (0 == strcmp (((NULL == truth_mime) ? "" : truth_mime),
+ ((NULL == xtruth_mime) ? "" : xtruth_mime))) &&
(0 == memcmp (xtruth,
encrypted_truth,
xtruth_size)) );
diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h
index 87e570b..a436394 100644
--- a/src/backend/anastasis-httpd_truth.h
+++ b/src/backend/anastasis-httpd_truth.h
@@ -1,6 +1,6 @@
/*
This file is part of Anastasis
- Copyright (C) 2014, 2015, 2016, 2021 Anastasis SARL
+ Copyright (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
@@ -24,13 +24,18 @@
#define ANASTASIS_HTTPD_TRUTH_H
#include <microhttpd.h>
-
/**
- * Prepare all active GET truth requests for system shutdown.
+ * Prepare all active POST truth solve requests for system shutdown.
*/
void
-AH_truth_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.
@@ -40,36 +45,60 @@ AH_truth_upload_shutdown (void);
/**
- * Handle a GET to /truth/$UUID
+ * Handle a POST to /truth/$UUID.
*
- * @param connection the MHD connection to handle
+ * @param[in,out] connection the MHD connection to handle
+ * @param[in,out] hc connection context
* @param truth_uuid the truth UUID
- * @param hc connection context
+ * @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_get (
+AH_handler_truth_post (
struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
- struct TM_HandlerContext *hc);
+ const char *truth_data,
+ size_t *truth_data_size);
/**
- * Handle a POST to /truth/$UUID.
+ * Handle a POST to /truth/$UUID/solve.
*
- * @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
* @return MHD result code
*/
MHD_RESULT
-AH_handler_truth_post (
+AH_handler_truth_solve (
struct MHD_Connection *connection,
struct TM_HandlerContext *hc,
const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
- const char *truth_data,
- size_t *truth_data_size);
+ 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
diff --git a/src/backend/anastasis.conf b/src/backend/anastasis.conf
index 8d2f0f6..4721760 100644
--- a/src/backend/anastasis.conf
+++ b/src/backend/anastasis.conf
@@ -49,18 +49,18 @@ UPLOAD_LIMIT_MB = 16
FULFILLMENT_URL = taler://fulfillment-success
# Server salt 16 Byte
-# SERVER_SALT = gUfO1KGOKYIFlFQg
+# PROVIDER_SALT = gUfO1KGOKYIFlFQg
# Directory with our terms of service.
-TERMS_DIR = $DATADIR/tos/
+TERMS_DIR = ${DATADIR}tos/
# Etag / filename for the terms of service.
TERMS_ETAG = 0
# Directory with our privacy policy.
-PRIVACY_DIR = $DATADIR/pp/
+PRIVACY_DIR = ${DATADIR}pp/
# Etag / filename for the privacy policy.
PRIVACY_ETAG = 0