summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
committerChristian Grothoff <christian@grothoff.org>2021-07-30 10:38:27 +0200
commit7e669bcf6b6336ec429da949bcb4aa456971dba2 (patch)
treed19912f950d1cac1c38b857b7d5bdaba2289544e /src/backend
downloadanastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.gz
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.tar.bz2
anastasis-7e669bcf6b6336ec429da949bcb4aa456971dba2.zip
folding history in preparation of GNU Anastasis v0.0.0 release
Diffstat (limited to 'src/backend')
-rw-r--r--src/backend/Makefile.am45
-rw-r--r--src/backend/anastasis-httpd.c943
-rw-r--r--src/backend/anastasis-httpd.h225
-rw-r--r--src/backend/anastasis-httpd_config.c132
-rw-r--r--src/backend/anastasis-httpd_config.h41
-rw-r--r--src/backend/anastasis-httpd_mhd.c70
-rw-r--r--src/backend/anastasis-httpd_mhd.h61
-rw-r--r--src/backend/anastasis-httpd_policy.c252
-rw-r--r--src/backend/anastasis-httpd_policy.h66
-rw-r--r--src/backend/anastasis-httpd_policy_upload.c1211
-rw-r--r--src/backend/anastasis-httpd_terms.c98
-rw-r--r--src/backend/anastasis-httpd_terms.h62
-rw-r--r--src/backend/anastasis-httpd_truth.c1428
-rw-r--r--src/backend/anastasis-httpd_truth.h75
-rw-r--r--src/backend/anastasis-httpd_truth_upload.c855
-rw-r--r--src/backend/anastasis.conf77
16 files changed, 5641 insertions, 0 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am
new file mode 100644
index 0000000..1046810
--- /dev/null
+++ b/src/backend/Makefile.am
@@ -0,0 +1,45 @@
+# This Makefile.am is in the public domain
+AM_CPPFLAGS = -I$(top_srcdir)/src/include
+
+pkgcfgdir = $(prefix)/share/anastasis/config.d/
+
+if USE_COVERAGE
+ AM_CFLAGS = --coverage -O0
+ XLIB = -lgcov
+endif
+
+pkgcfg_DATA = \
+ anastasis.conf
+
+bin_PROGRAMS = \
+ anastasis-httpd
+
+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_terms.c anastasis-httpd_terms.h \
+ anastasis-httpd_config.c anastasis-httpd_config.h \
+ anastasis-httpd_truth_upload.c
+
+anastasis_httpd_LDADD = \
+ $(top_builddir)/src/util/libanastasisutil.la \
+ $(top_builddir)/src/stasis/libanastasisdb.la \
+ $(top_builddir)/src/authorization/libanastasisauthorization.la \
+ -ljansson \
+ -ltalermerchant \
+ -ltalermhd \
+ -ltalerjson \
+ -ltalerutil \
+ -lgnunetcurl \
+ -lgnunetrest \
+ -lgnunetjson \
+ -lgnunetutil \
+ -lmicrohttpd \
+ -luuid \
+ $(XLIB)
+
+EXTRA_DIST = \
+ $(pkgcfg_DATA)
diff --git a/src/backend/anastasis-httpd.c b/src/backend/anastasis-httpd.c
new file mode 100644
index 0000000..56bd7c9
--- /dev/null
+++ b/src/backend/anastasis-httpd.c
@@ -0,0 +1,943 @@
+/*
+ This file is part of TALER
+ (C) 2020 Taler Systems SA
+
+ TALER 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.
+
+ TALER 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backup/anastasis-httpd.c
+ * @brief HTTP serving layer intended to provide basic backup operations
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis_util_lib.h"
+#include "anastasis-httpd_mhd.h"
+#include "anastasis_database_lib.h"
+#include "anastasis-httpd_policy.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;
+
+/**
+ * Annual fee for the backup account.
+ */
+struct TALER_Amount AH_annual_fee;
+
+/**
+ * Fee for a truth upload.
+ */
+struct TALER_Amount AH_truth_upload_fee;
+
+/**
+ * Amount of insurance.
+ */
+struct TALER_Amount AH_insurance;
+
+/**
+ * Cost for secure question truth download.
+ */
+struct TALER_Amount AH_question_cost;
+
+/**
+ * Our configuration.
+ */
+const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
+
+/**
+ * Our Taler backend to process payments.
+ */
+char *AH_backend_url;
+
+/**
+ * Taler currency.
+ */
+char *AH_currency;
+
+/**
+ * Our fulfillment URL.
+ */
+char *AH_fulfillment_url;
+
+/**
+ * Our business name.
+ */
+char *AH_business_name;
+
+/**
+ * Our server salt.
+ */
+struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+
+/**
+ * Number of policy uploads permitted per annual fee payment.
+ */
+unsigned long long AH_post_counter = 64LLU;
+
+/**
+ * Our context for making HTTP requests.
+ */
+struct GNUNET_CURL_Context *AH_ctx;
+
+/**
+ * Should a "Connection: close" header be added to each HTTP response?
+ */
+static int AH_connection_close;
+
+/**
+ * Task running the HTTP server.
+ */
+static struct GNUNET_SCHEDULER_Task *mhd_task;
+
+/**
+ * Global return code
+ */
+static int global_result;
+
+/**
+ * The MHD Daemon
+ */
+static struct MHD_Daemon *mhd;
+
+/**
+ * Connection handle to the our database
+ */
+struct ANASTASIS_DatabasePlugin *db;
+
+/**
+ * Reschedule context for #SH_ctx.
+ */
+static struct GNUNET_CURL_RescheduleContext *rc;
+
+/**
+ * Set if we should immediately #MHD_run again.
+ */
+static int triggered;
+
+/**
+ * Username and password to use for client authentication
+ * (optional).
+ */
+static char *userpass;
+
+/**
+ * Type of the client's TLS certificate (optional).
+ */
+static char *certtype;
+
+/**
+ * File with the client's TLS certificate (optional).
+ */
+static char *certfile;
+
+/**
+ * File with the client's TLS private key (optional).
+ */
+static char *keyfile;
+
+/**
+ * This value goes in the Authorization:-header.
+ */
+static char *apikey;
+
+/**
+ * Passphrase to decrypt client's TLS private key file (optional).
+ */
+static char *keypass;
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void);
+
+
+/**
+ * Call MHD to process pending requests and then go back
+ * and schedule the next run.
+ *
+ * @param cls the `struct MHD_Daemon` of the HTTP server to run
+ */
+static void
+run_daemon (void *cls)
+{
+ (void) cls;
+ mhd_task = NULL;
+ do {
+ triggered = 0;
+ GNUNET_assert (MHD_YES == MHD_run (mhd));
+ } while (0 != triggered);
+ mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * Kick MHD to run now, to be called after MHD_resume_connection().
+ * Basically, we need to explicitly resume MHD's event loop whenever
+ * we made progress serving a request. This function re-schedules
+ * the task processing MHD's activities to run immediately.
+ *
+ * @param cls NULL
+ */
+void
+AH_trigger_daemon (void *cls)
+{
+ (void) cls;
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = GNUNET_SCHEDULER_add_now (&run_daemon,
+ NULL);
+ }
+ else
+ {
+ triggered = 1;
+ }
+}
+
+
+/**
+ * Kick GNUnet Curl scheduler to begin curl interactions.
+ */
+void
+AH_trigger_curl (void)
+{
+ GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
+}
+
+
+/**
+ * A client has requested the given url using the given method
+ * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
+ * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
+ * must call MHD callbacks to provide content to give back to the
+ * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
+ * #MHD_HTTP_NOT_FOUND, etc.).
+ *
+ * @param cls argument given together with the function
+ * pointer when the handler was registered with MHD
+ * @param url the requested url
+ * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
+ * #MHD_HTTP_METHOD_PUT, etc.)
+ * @param version the HTTP version string (i.e.
+ * #MHD_HTTP_VERSION_1_1)
+ * @param upload_data the data being uploaded (excluding HEADERS,
+ * for a POST that fits into memory and that is encoded
+ * with a supported encoding, the POST data will NOT be
+ * given in upload_data and is instead available as
+ * part of #MHD_get_connection_values; very large POST
+ * data *will* be made available incrementally in
+ * @a upload_data)
+ * @param upload_data_size set initially to the size of the
+ * @a upload_data provided; the method must update this
+ * value to the number of bytes NOT processed;
+ * @param con_cls pointer that the callback can set to some
+ * address and that will be preserved by MHD for future
+ * calls for this request; since the access handler may
+ * be called many times (i.e., for a PUT/POST operation
+ * with plenty of upload data) this allows the application
+ * to easily associate some request-specific state.
+ * If necessary, this state can be cleaned up in the
+ * global #MHD_RequestCompletedCallback (which
+ * can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
+ * Initially, `*con_cls` will be NULL.
+ * @return #MHD_YES if the connection was handled successfully,
+ * #MHD_NO if the socket must be closed due to a serious
+ * error while handling the request
+ */
+static MHD_RESULT
+url_handler (void *cls,
+ struct MHD_Connection *connection,
+ const char *url,
+ const char *method,
+ const char *version,
+ const char *upload_data,
+ size_t *upload_data_size,
+ void **con_cls)
+{
+ static struct AH_RequestHandler handlers[] = {
+ /* Landing page, tell humans to go away. */
+ { "/", MHD_HTTP_METHOD_GET, "text/plain",
+ "Hello, I'm Anastasis. This HTTP server is not for humans.\n", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_OK },
+ { "/agpl", MHD_HTTP_METHOD_GET, "text/plain",
+ NULL, 0,
+ &TMH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND },
+ { "/terms", MHD_HTTP_METHOD_GET, NULL,
+ NULL, 0,
+ &AH_handler_terms, MHD_HTTP_OK },
+ { "/privacy", MHD_HTTP_METHOD_GET, NULL,
+ NULL, 0,
+ &AH_handler_terms, MHD_HTTP_OK },
+ { "/config", MHD_HTTP_METHOD_GET, "text/json",
+ NULL, 0,
+ &AH_handler_config, MHD_HTTP_OK },
+ {NULL, NULL, NULL, NULL, 0, 0 }
+ };
+ static struct AH_RequestHandler h404 = {
+ "", NULL, "text/html",
+ "<html><title>404: not found</title></html>", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
+ };
+ static struct AH_RequestHandler h405 = {
+ "", NULL, "text/html",
+ "<html><title>405: method not allowed</title></html>", 0,
+ &TMH_MHD_handler_static_response, MHD_HTTP_METHOD_NOT_ALLOWED
+ };
+ struct TM_HandlerContext *hc = *con_cls;
+ const char *correlation_id = NULL;
+ bool path_matched;
+
+ if (NULL == hc)
+ {
+ struct GNUNET_AsyncScopeId aid;
+
+ GNUNET_async_scope_fresh (&aid);
+ /* We only read the correlation ID on the first callback for every client */
+ correlation_id = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ "Anastasis-Correlation-Id");
+ if ((NULL != correlation_id) &&
+ (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Invalid incoming correlation ID\n");
+ correlation_id = NULL;
+ }
+ hc = GNUNET_new (struct TM_HandlerContext);
+ *con_cls = hc;
+ hc->async_scope_id = aid;
+ }
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_HEAD))
+ method = MHD_HTTP_METHOD_GET; /* MHD will throw away the body */
+
+ GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
+ if (NULL != correlation_id)
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request for (%s) URL '%s', correlation_id=%s\n",
+ method,
+ url,
+ correlation_id);
+ else
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Handling request (%s) for URL '%s'\n",
+ method,
+ url);
+ if (0 == strncmp (url,
+ "/policy/",
+ strlen ("/policy/")))
+ {
+ const char *account = url + strlen ("/policy/");
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP account_pub;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ account,
+ strlen (account),
+ &account_pub,
+ sizeof (struct ANASTASIS_CRYPTO_AccountPublicKeyP)))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "account public key");
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ return AH_policy_get (connection,
+ &account_pub);
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ return AH_handler_policy_post (connection,
+ hc,
+ &account_pub,
+ upload_data,
+ upload_data_size);
+ }
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ }
+ if (0 == strncmp (url,
+ "/truth/",
+ strlen ("/truth/")))
+ {
+ struct ANASTASIS_CRYPTO_TruthUUIDP tu;
+ const char *pub_key_str;
+
+ pub_key_str = &url[strlen ("/truth/")];
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (
+ pub_key_str,
+ strlen (pub_key_str),
+ &tu,
+ sizeof(tu)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "truth UUID");
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_GET))
+ {
+ return AH_handler_truth_get (connection,
+ &tu,
+ hc);
+ }
+ if (0 == strcmp (method,
+ MHD_HTTP_METHOD_POST))
+ {
+ return AH_handler_truth_post (connection,
+ hc,
+ &tu,
+ upload_data,
+ upload_data_size);
+ }
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ }
+ path_matched = false;
+ for (unsigned int i = 0; NULL != handlers[i].url; i++)
+ {
+ struct AH_RequestHandler *rh = &handlers[i];
+
+ if (0 == strcmp (url,
+ rh->url))
+ {
+ path_matched = true;
+ if (0 == strcasecmp (method,
+ MHD_HTTP_METHOD_OPTIONS))
+ {
+ return TALER_MHD_reply_cors_preflight (connection);
+ }
+ if ( (NULL == rh->method) ||
+ (0 == strcasecmp (method,
+ rh->method)) )
+ {
+ return rh->handler (rh,
+ connection);
+ }
+ }
+ }
+ if (path_matched)
+ return TMH_MHD_handler_static_response (&h405,
+ connection);
+ return TMH_MHD_handler_static_response (&h404,
+ connection);
+}
+
+
+/**
+ * Shutdown task (magically invoked when the application is being
+ * quit)
+ *
+ * @param cls NULL
+ */
+static void
+do_shutdown (void *cls)
+{
+ (void) cls;
+ AH_resume_all_bc ();
+ AH_truth_shutdown ();
+ AH_truth_upload_shutdown ();
+ if (NULL != mhd_task)
+ {
+ GNUNET_SCHEDULER_cancel (mhd_task);
+ mhd_task = NULL;
+ }
+ if (NULL != AH_ctx)
+ {
+ GNUNET_CURL_fini (AH_ctx);
+ AH_ctx = NULL;
+ }
+ if (NULL != rc)
+ {
+ GNUNET_CURL_gnunet_rc_destroy (rc);
+ rc = NULL;
+ }
+ if (NULL != mhd)
+ {
+ MHD_stop_daemon (mhd);
+ mhd = NULL;
+ }
+ if (NULL != db)
+ {
+ ANASTASIS_DB_plugin_unload (db);
+ db = NULL;
+ }
+}
+
+
+/**
+ * Function called whenever MHD is done with a request. If the
+ * request was a POST, we may have stored a `struct Buffer *` in the
+ * @a con_cls that might still need to be cleaned up. Call the
+ * respective function to free the memory.
+ *
+ * @param cls client-defined closure
+ * @param connection connection handle
+ * @param con_cls value as set by the last call to
+ * the #MHD_AccessHandlerCallback
+ * @param toe reason for request termination
+ * @see #MHD_OPTION_NOTIFY_COMPLETED
+ * @ingroup request
+ */
+static void
+handle_mhd_completion_callback (void *cls,
+ struct MHD_Connection *connection,
+ void **con_cls,
+ enum MHD_RequestTerminationCode toe)
+{
+ struct TM_HandlerContext *hc = *con_cls;
+
+ (void) cls;
+ (void) connection;
+ if (NULL == hc)
+ return;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Finished handling request with status %d\n",
+ (int) toe);
+ if (NULL != hc->cc)
+ hc->cc (hc);
+ GNUNET_free (hc);
+ *con_cls = NULL;
+}
+
+
+/**
+ * Function that queries MHD's select sets and
+ * starts the task waiting for them.
+ *
+ * @param daemon_handle HTTP server to prepare to run
+ */
+static struct GNUNET_SCHEDULER_Task *
+prepare_daemon (void)
+{
+ struct GNUNET_SCHEDULER_Task *ret;
+ fd_set rs;
+ fd_set ws;
+ fd_set es;
+ struct GNUNET_NETWORK_FDSet *wrs;
+ struct GNUNET_NETWORK_FDSet *wws;
+ int max;
+ MHD_UNSIGNED_LONG_LONG timeout;
+ int haveto;
+ struct GNUNET_TIME_Relative tv;
+
+ FD_ZERO (&rs);
+ FD_ZERO (&ws);
+ FD_ZERO (&es);
+ wrs = GNUNET_NETWORK_fdset_create ();
+ wws = GNUNET_NETWORK_fdset_create ();
+ max = -1;
+ GNUNET_assert (MHD_YES ==
+ MHD_get_fdset (mhd,
+ &rs,
+ &ws,
+ &es,
+ &max));
+ haveto = MHD_get_timeout (mhd, &timeout);
+ if (haveto == MHD_YES)
+ tv = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout);
+ else
+ tv = GNUNET_TIME_UNIT_FOREVER_REL;
+ GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
+ GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Adding run_daemon select task\n");
+ ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
+ tv,
+ wrs,
+ wws,
+ &run_daemon,
+ NULL);
+ GNUNET_NETWORK_fdset_destroy (wrs);
+ GNUNET_NETWORK_fdset_destroy (wws);
+ return ret;
+}
+
+
+/**
+ * Main function that will be run by the scheduler.
+ *
+ * @param cls closure
+ * @param args remaining command-line arguments
+ * @param cfgfile name of the configuration file used (for saving, can be
+ * NULL!)
+ * @param config configuration
+ */
+static void
+run (void *cls,
+ char *const *args,
+ const char *cfgfile,
+ const struct GNUNET_CONFIGURATION_Handle *config)
+{
+ int fh;
+ uint16_t port;
+ enum TALER_MHD_GlobalOptions go;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Starting anastasis-httpd\n");
+ go = TALER_MHD_GO_NONE;
+ if (AH_connection_close)
+ go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
+ AH_load_terms (config);
+ TALER_MHD_setup (go);
+ AH_cfg = config;
+ global_result = GNUNET_SYSERR;
+ GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
+ NULL);
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (config,
+ "anastasis",
+ "UPLOAD_LIMIT_MB",
+ &AH_upload_limit_mb))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "UPLOAD_LIMIT_MB");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "INSURANCE",
+ &AH_insurance))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "INSURANCE");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "authorization-question",
+ "COST",
+ &AH_question_cost))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "authorization-question",
+ "COST");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "ANNUAL_FEE",
+ &AH_annual_fee))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "ANNUAL_FEE");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ TALER_config_get_amount (config,
+ "anastasis",
+ "TRUTH_UPLOAD_FEE",
+ &AH_truth_upload_fee))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "TRUTH_UPLOAD_FEE");
+ GNUNET_SCHEDULER_shutdown ();
+ 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",
+ "PAYMENT_BACKEND_URL",
+ &AH_backend_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if ( (0 != strncasecmp ("https://",
+ AH_backend_url,
+ strlen ("https://"))) &&
+ (0 != strncasecmp ("http://",
+ AH_backend_url,
+ strlen ("http://"))) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL",
+ "Must be HTTP(S) URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ if ( (0 == strcasecmp ("https://",
+ AH_backend_url)) ||
+ (0 == strcasecmp ("http://",
+ AH_backend_url)) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "PAYMENT_BACKEND_URL",
+ "Must have domain name");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "FULFILLMENT_URL",
+ &AH_fulfillment_url))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "FULFILLMENT_URL");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_number (config,
+ "anastasis",
+ "ANNUAL_POLICY_UPLOAD_LIMIT",
+ &AH_post_counter))
+ {
+ /* only warn, we will use the default */
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
+ "anastasis",
+ "ANNUAL_POLICY_UPLOAD_LIMIT");
+ }
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "BUSINESS_NAME",
+ &AH_business_name))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "BUSINESS_NAME");
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ {
+ char *server_salt;
+
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (config,
+ "anastasis",
+ "SERVER_SALT",
+ &server_salt))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "anastasis",
+ "SERVER_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),
+ NULL,
+ 0));
+ GNUNET_free (server_salt);
+ }
+
+ /* setup HTTP client event loop */
+ AH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
+ &rc);
+ rc = GNUNET_CURL_gnunet_rc_create (AH_ctx);
+ if (NULL != userpass)
+ GNUNET_CURL_set_userpass (AH_ctx,
+ userpass);
+ if (NULL != keyfile)
+ GNUNET_CURL_set_tlscert (AH_ctx,
+ certtype,
+ certfile,
+ keyfile,
+ keypass);
+ if (NULL != apikey)
+ {
+ char *auth_header;
+
+ GNUNET_asprintf (&auth_header,
+ "%s: %s",
+ MHD_HTTP_HEADER_AUTHORIZATION,
+ apikey);
+ if (GNUNET_OK !=
+ GNUNET_CURL_append_header (AH_ctx,
+ auth_header))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed so set %s header, trying without\n",
+ MHD_HTTP_HEADER_AUTHORIZATION);
+ }
+ GNUNET_free (auth_header);
+ }
+
+ if (NULL ==
+ (db = ANASTASIS_DB_plugin_load (config)))
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+
+ fh = TALER_MHD_bind (config,
+ "anastasis",
+ &port);
+ if ( (0 == port) &&
+ (-1 == fh) )
+ {
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
+ port,
+ NULL, NULL,
+ &url_handler, NULL,
+ MHD_OPTION_LISTEN_SOCKET, fh,
+ MHD_OPTION_NOTIFY_COMPLETED,
+ &handle_mhd_completion_callback, NULL,
+ MHD_OPTION_CONNECTION_TIMEOUT, (unsigned
+ int) 10 /* 10s */,
+ MHD_OPTION_END);
+ if (NULL == mhd)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to launch HTTP service (port %u in use?), exiting.\n",
+ port);
+ GNUNET_SCHEDULER_shutdown ();
+ return;
+ }
+ global_result = GNUNET_OK;
+ mhd_task = prepare_daemon ();
+}
+
+
+/**
+ * The main function of the serve tool
+ *
+ * @param argc number of arguments from the command line
+ * @param argv command line arguments
+ * @return 0 ok, 1 on error
+ */
+int
+main (int argc,
+ char *const *argv)
+{
+ enum GNUNET_GenericReturnValue res;
+ struct GNUNET_GETOPT_CommandLineOption options[] = {
+ GNUNET_GETOPT_option_string ('A',
+ "auth",
+ "USERNAME:PASSWORD",
+ "use the given USERNAME and PASSWORD for client authentication",
+ &userpass),
+ GNUNET_GETOPT_option_flag ('C',
+ "connection-close",
+ "force HTTP connections to be closed after each request",
+ &AH_connection_close),
+ GNUNET_GETOPT_option_string ('k',
+ "key",
+ "KEYFILE",
+ "file with the private TLS key for TLS client authentication",
+ &keyfile),
+ GNUNET_GETOPT_option_string ('p',
+ "pass",
+ "KEYFILEPASSPHRASE",
+ "passphrase needed to decrypt the TLS client private key file",
+ &keypass),
+ GNUNET_GETOPT_option_string ('K',
+ "apikey",
+ "APIKEY",
+ "API key to use in the HTTP request to the merchant backend",
+ &apikey),
+ GNUNET_GETOPT_option_string ('t',
+ "type",
+ "CERTTYPE",
+ "type of the TLS client certificate, defaults to PEM if not specified",
+ &certtype),
+
+ GNUNET_GETOPT_OPTION_END
+ };
+
+ /* FIRST get the libtalerutil initialization out
+ of the way. Then throw that one away, and force
+ the ANASTASIS defaults to be used! */
+ (void) TALER_project_data_default ();
+ GNUNET_OS_init (ANASTASIS_project_data_default ());
+ res = GNUNET_PROGRAM_run (argc, argv,
+ "anastasis-httpd",
+ "Anastasis HTTP interface",
+ options, &run, NULL);
+ if (GNUNET_SYSERR == res)
+ return 3;
+ if (GNUNET_NO == res)
+ return 0;
+ return (GNUNET_OK == global_result) ? 0 : 1;
+}
diff --git a/src/backend/anastasis-httpd.h b/src/backend/anastasis-httpd.h
new file mode 100644
index 0000000..6fe0023
--- /dev/null
+++ b/src/backend/anastasis-httpd.h
@@ -0,0 +1,225 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis/anastasis-httpd.h
+ * @brief HTTP serving layer
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_H
+#define ANASTASIS_HTTPD_H
+
+#include "platform.h"
+#include "anastasis_database_lib.h"
+#include <microhttpd.h>
+#include <taler/taler_mhd_lib.h>
+#include <gnunet/gnunet_mhd_compat.h>
+
+
+/**
+ * For how many years do we allow users to store truth at most? Also
+ * how long we store things if the cost is zero.
+ */
+#define ANASTASIS_MAX_YEARS_STORAGE 5
+
+
+/**
+ * @brief Struct describing an URL and the handler for it.
+ */
+struct AH_RequestHandler
+{
+
+ /**
+ * URL the handler is for.
+ */
+ const char *url;
+
+ /**
+ * Method the handler is for, NULL for "all".
+ */
+ const char *method;
+
+ /**
+ * Mime type to use in reply (hint, can be NULL).
+ */
+ const char *mime_type;
+
+ /**
+ * Raw data for the @e handler
+ */
+ const void *data;
+
+ /**
+ * Number of bytes in @e data, 0 for 0-terminated.
+ */
+ size_t data_size;
+
+
+ /**
+ * Function to call to handle the request.
+ *
+ * @param rh this struct
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+ MHD_RESULT (*handler)(struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+ /**
+ * Default response code.
+ */
+ unsigned int response_code;
+};
+
+
+/**
+ * Each MHD response handler that sets the "connection_cls" to a
+ * non-NULL value must use a struct that has this struct as its first
+ * member. This struct contains a single callback, which will be
+ * invoked to clean up the memory when the contection is completed.
+ */
+struct TM_HandlerContext;
+
+/**
+ * Signature of a function used to clean up the context
+ * we keep in the "connection_cls" of MHD when handling
+ * a request.
+ *
+ * @param hc header of the context to clean up.
+ */
+typedef void
+(*TM_ContextCleanup)(struct TM_HandlerContext *hc);
+
+
+/**
+ * Each MHD response handler that sets the "connection_cls" to a
+ * non-NULL value must use a struct that has this struct as its first
+ * member. This struct contains a single callback, which will be
+ * invoked to clean up the memory when the connection is completed.
+ */
+struct TM_HandlerContext
+{
+
+ /**
+ * Function to execute the handler-specific cleanup of the
+ * (typically larger) context.
+ */
+ TM_ContextCleanup cc;
+
+ /**
+ * Handler-specific context.
+ */
+ void *ctx;
+
+ /**
+ * Which request handler is handling this request?
+ */
+ const struct AH_RequestHandler *rh;
+
+ /**
+ * Asynchronous request context id.
+ */
+ struct GNUNET_AsyncScopeId async_scope_id;
+};
+
+/**
+ * Handle to the database backend.
+ */
+extern struct ANASTASIS_DatabasePlugin *db;
+
+/**
+ * Upload limit to the service, in megabytes.
+ */
+extern unsigned long long AH_upload_limit_mb;
+
+/**
+ * Annual fee for the backup account.
+ */
+extern struct TALER_Amount AH_annual_fee;
+
+/**
+ * Fee for a truth upload.
+ */
+extern struct TALER_Amount AH_truth_upload_fee;
+
+/**
+ * Amount of insurance.
+ */
+extern struct TALER_Amount AH_insurance;
+
+/**
+ * Cost for secure question truth download.
+ */
+extern struct TALER_Amount AH_question_cost;
+
+/**
+ * Our Taler backend to process payments.
+ */
+extern char *AH_backend_url;
+
+/**
+ * Taler currency.
+ */
+extern char *AH_currency;
+
+/**
+ * Our configuration.
+ */
+extern const struct GNUNET_CONFIGURATION_Handle *AH_cfg;
+
+/**
+ * Number of policy uploads permitted per annual fee payment.
+ */
+extern unsigned long long AH_post_counter;
+
+/**
+ * Our fulfillment URL
+ */
+extern char *AH_fulfillment_url;
+
+/**
+ * Our business name.
+ */
+extern char *AH_business_name;
+
+/**
+ * Our server salt.
+ */
+extern struct ANASTASIS_CRYPTO_ProviderSaltP AH_server_salt;
+
+/**
+ * Our context for making HTTP requests.
+ */
+extern struct GNUNET_CURL_Context *AH_ctx;
+
+
+/**
+ * Kick MHD to run now, to be called after MHD_resume_connection().
+ * Basically, we need to explicitly resume MHD's event loop whenever
+ * we made progress serving a request. This function re-schedules
+ * the task processing MHD's activities to run immediately.
+ *
+ * @param cls NULL
+ */
+void
+AH_trigger_daemon (void *cls);
+
+/**
+ * Kick GNUnet Curl scheduler to begin curl interactions.
+ */
+void
+AH_trigger_curl (void);
+
+#endif
diff --git a/src/backend/anastasis-httpd_config.c b/src/backend/anastasis-httpd_config.c
new file mode 100644
index 0000000..fff6bcb
--- /dev/null
+++ b/src/backend/anastasis-httpd_config.c
@@ -0,0 +1,132 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ 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
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_config.c
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "anastasis-httpd_config.h"
+#include "anastasis-httpd.h"
+#include <taler/taler_json_lib.h>
+#include "anastasis_authorization_lib.h"
+
+
+/**
+ * Add enabled methods and their fees to the ``/config`` response.
+ *
+ * @param[in,out] cls a `json_t` array to build
+ * @param section configuration section to inspect
+ */
+static void
+add_methods (void *cls,
+ const char *section)
+{
+ json_t *method_arr = cls;
+ struct ANASTASIS_AuthorizationPlugin *p;
+ struct TALER_Amount cost;
+ json_t *method;
+
+ if (0 != strncasecmp (section,
+ "authorization-",
+ strlen ("authorization-")))
+ return;
+ if (GNUNET_YES !=
+ GNUNET_CONFIGURATION_get_value_yesno (AH_cfg,
+ section,
+ "ENABLED"))
+ return;
+ section += strlen ("authorization-");
+ p = ANASTASIS_authorization_plugin_load (section,
+ AH_cfg,
+ &cost);
+ if (NULL == p)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Failed to load authorization plugin `%s'\n",
+ section);
+ return;
+ }
+ method = json_pack ("{s:s, s:o}",
+ "type",
+ section,
+ "cost",
+ TALER_JSON_from_amount (&cost));
+ GNUNET_assert (NULL != method);
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (method_arr,
+ method));
+}
+
+
+MHD_RESULT
+AH_handler_config (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ json_t *method_arr = json_array ();
+
+ GNUNET_assert (NULL != method_arr);
+ {
+ json_t *method;
+
+ method = json_pack ("{s:s, s:o}",
+ "type",
+ "question",
+ "cost",
+ TALER_JSON_from_amount (&AH_question_cost));
+ GNUNET_assert (
+ 0 ==
+ json_array_append_new (method_arr,
+ method));
+ }
+ GNUNET_CONFIGURATION_iterate_sections (AH_cfg,
+ &add_methods,
+ method_arr);
+ return TALER_MHD_reply_json_pack (connection,
+ MHD_HTTP_OK,
+ "{s:s, s:s, s:s, s:s, s:o, s:I,"
+ " s:o, s:o, s:o, s:o }",
+ "name",
+ "anastasis",
+ "version",
+ "0:0:0",
+ "business_name",
+ AH_business_name,
+ "currency",
+ (char *) AH_currency,
+ "methods",
+ method_arr,
+ "storage_limit_in_megabytes",
+ (json_int_t) AH_upload_limit_mb,
+ /* 6 */
+ "annual_fee",
+ TALER_JSON_from_amount (&AH_annual_fee),
+ "truth_upload_fee",
+ TALER_JSON_from_amount (
+ &AH_truth_upload_fee),
+ "liability_limit",
+ TALER_JSON_from_amount (&AH_insurance),
+ "server_salt",
+ GNUNET_JSON_from_data_auto (
+ &AH_server_salt));
+}
+
+
+/* end of anastasis-httpd_config.c */
diff --git a/src/backend/anastasis-httpd_config.h b/src/backend/anastasis-httpd_config.h
new file mode 100644
index 0000000..7d58792
--- /dev/null
+++ b/src/backend/anastasis-httpd_config.h
@@ -0,0 +1,41 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ 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
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_config.h
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_HTTPD_CONFIG_H
+#define ANASTASIS_HTTPD_CONFIG_H
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+/**
+ * Manages a /config call.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_config (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+#endif
+
+/* end of anastasis-httpd_config.h */
diff --git a/src/backend/anastasis-httpd_mhd.c b/src/backend/anastasis-httpd_mhd.c
new file mode 100644
index 0000000..c39a54c
--- /dev/null
+++ b/src/backend/anastasis-httpd_mhd.c
@@ -0,0 +1,70 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V. and INRIA
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_mhd.c
+ * @brief helpers for MHD interaction; these are TALER_EXCHANGE_handler_ functions
+ * that generate simple MHD replies that do not require any real operations
+ * to be performed (error handling, static pages, etc.)
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include <jansson.h>
+#include "anastasis-httpd_mhd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_static_response (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ if (0 == rh->data_size)
+ rh->data_size = strlen ((const char *) rh->data);
+ return TALER_MHD_reply_static (connection,
+ rh->response_code,
+ rh->mime_type,
+ (void *) rh->data,
+ rh->data_size);
+}
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_agpl_redirect (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection)
+{
+ (void) rh;
+ return TALER_MHD_reply_agpl (connection,
+ "http://www.git.taler.net/anastasis.git");
+}
+
+
+/* end of anastasis-httpd_mhd.c */
diff --git a/src/backend/anastasis-httpd_mhd.h b/src/backend/anastasis-httpd_mhd.h
new file mode 100644
index 0000000..628abfa
--- /dev/null
+++ b/src/backend/anastasis-httpd_mhd.h
@@ -0,0 +1,61 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015 GNUnet e.V. and INRIA
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+
+/**
+ * @file anastasis-httpd_mhd.h
+ * @brief helpers for MHD interaction, used to generate simple responses
+ * @author Florian Dold
+ * @author Benedikt Mueller
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_MHD_H
+#define ANASTASIS_HTTPD_MHD_H
+#include <gnunet/gnunet_util_lib.h>
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+
+/**
+ * Function to call to handle the request by sending
+ * back static data from the @a rh.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @param[in,out] connection_cls the connection's closure (can be updated)
+ * @param upload_data upload data
+ * @param[in,out] upload_data_size number of bytes (left) in @a upload_data
+ * @param mi merchant backend instance, NULL is allowed in this case!
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_static_response (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+/**
+ * Function to call to handle the request by sending
+ * back a redirect to the AGPL source code.
+ *
+ * @param rh context of the handler
+ * @param connection the MHD connection to handle
+ * @return MHD result code
+ */
+MHD_RESULT
+TMH_MHD_handler_agpl_redirect (struct AH_RequestHandler *rh,
+ struct MHD_Connection *connection);
+
+
+#endif
diff --git a/src/backend/anastasis-httpd_policy.c b/src/backend/anastasis-httpd_policy.c
new file mode 100644
index 0000000..2417e15
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy.c
@@ -0,0 +1,252 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.c
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis-httpd_policy.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>
+
+/**
+ * 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.
+ *
+ * @param connection MHD connection to use
+ * @param account account to query
+ * @return MHD result code
+ */
+static MHD_RESULT
+return_policy (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ struct MHD_Response *resp;
+ struct ANASTASIS_AccountSignatureP account_sig;
+ struct GNUNET_HashCode recovery_data_hash;
+ const char *version_s;
+ char version_b[14];
+ uint32_t version;
+ void *res_recovery_data;
+ size_t res_recovery_data_size;
+
+ version_s = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "version");
+ if (NULL != version_s)
+ {
+ char dummy;
+
+ if (1 != sscanf (version_s,
+ "%u%c",
+ &version,
+ &dummy))
+ {
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "version");
+ }
+ qs = db->get_recovery_document (db->cls,
+ account_pub,
+ version,
+ &account_sig,
+ &recovery_data_hash,
+ &res_recovery_data_size,
+ &res_recovery_data);
+ }
+ else
+ {
+ qs = db->get_latest_recovery_document (db->cls,
+ account_pub,
+ &account_sig,
+ &recovery_data_hash,
+ &res_recovery_data_size,
+ &res_recovery_data,
+ &version);
+ GNUNET_snprintf (version_b,
+ sizeof (version_b),
+ "%u",
+ (unsigned int) version);
+ version_s = version_b;
+ }
+ 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);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* interesting case below */
+ break;
+ }
+ resp = MHD_create_response_from_buffer (res_recovery_data_size,
+ res_recovery_data,
+ MHD_RESPMEM_MUST_FREE);
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *sig_s;
+ char *etag;
+
+ 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_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ MHD_HTTP_HEADER_ETAG,
+ etag));
+ GNUNET_free (etag);
+ GNUNET_free (sig_s);
+ }
+ {
+ MHD_RESULT ret;
+
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+MHD_RESULT
+AH_policy_get (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
+{
+ struct GNUNET_HashCode recovery_data_hash;
+ enum ANASTASIS_DB_AccountStatus as;
+ MHD_RESULT ret;
+ uint32_t version;
+ struct GNUNET_TIME_Absolute 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_SYNC_ACCOUNT_UNKNOWN,
+ NULL);
+ 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:
+ {
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ MHD_destroy_response (resp);
+ }
+ return ret;
+ case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
+ {
+ const char *inm;
+
+ inm = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if (NULL != inm)
+ {
+ struct GNUNET_HashCode inm_h;
+
+ if (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (inm,
+ strlen (inm),
+ &inm_h,
+ sizeof (inm_h)))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_BAD_IF_NONE_MATCH,
+ "Etag must be a base32-encoded SHA-512 hash");
+ }
+ if (0 == GNUNET_memcmp (&inm_h,
+ &recovery_data_hash))
+ {
+ struct MHD_Response *resp;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ }
+ }
+ /* We have a result, should fetch and return it! */
+ break;
+ }
+ return return_policy (connection,
+ account_pub);
+}
diff --git a/src/backend/anastasis-httpd_policy.h b/src/backend/anastasis-httpd_policy.h
new file mode 100644
index 0000000..9fb630d
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy.h
@@ -0,0 +1,66 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016 GNUnet e.V.
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.h
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_POLICY_H
+#define ANASTASIS_HTTPD_POLICY_H
+#include <microhttpd.h>
+
+
+/**
+ * Service is shutting down, resume all MHD connections NOW.
+ */
+void
+AH_resume_all_bc (void);
+
+
+/**
+ * Handle GET /policy/$ACCOUNT_PUB request.
+ *
+ * @param connection the MHD connection to handle
+ * @param account_pub public key of the account
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_policy_get (struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub);
+
+
+/**
+ * Handle POST /policy/$ACCOUNT_PUB request.
+ *
+ * @param connection the MHD connection to handle
+ * @param con_cls the connection's closure
+ * @param account_pub public key of the account
+ * @param upload_data upload data
+ * @param upload_data_size number of bytes (left) in @a upload_data
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_policy_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub,
+ const char *upload_data,
+ size_t *upload_data_size);
+
+
+#endif
diff --git a/src/backend/anastasis-httpd_policy_upload.c b/src/backend/anastasis-httpd_policy_upload.c
new file mode 100644
index 0000000..b8bd5ed
--- /dev/null
+++ b/src/backend/anastasis-httpd_policy_upload.c
@@ -0,0 +1,1211 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2021 Anastasis SARL
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_policy.c
+ * @brief functions to handle incoming requests on /policy/
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#include "platform.h"
+#include "anastasis-httpd.h"
+#include "anastasis-httpd_policy.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>
+
+/**
+ * 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)
+
+
+/**
+ * Context for an upload operation.
+ */
+struct PolicyUploadContext
+{
+
+ /**
+ * Signature of the account holder.
+ */
+ struct ANASTASIS_AccountSignatureP account_sig;
+
+ /**
+ * Public key of the account holder.
+ */
+ struct ANASTASIS_CRYPTO_AccountPublicKeyP account;
+
+ /**
+ * Hash of the upload we are receiving right now (as promised
+ * by the client, to be verified!).
+ */
+ struct GNUNET_HashCode new_policy_upload_hash;
+
+ /**
+ * Hash context for the upload.
+ */
+ struct GNUNET_HashContext *hash_ctx;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct PolicyUploadContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct PolicyUploadContext *prev;
+
+ /**
+ * Used while suspended for resumption.
+ */
+ struct MHD_Connection *con;
+
+ /**
+ * Upload, with as many bytes as we have received so far.
+ */
+ char *upload;
+
+ /**
+ * 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;
+
+ /**
+ * Order under which the client promised payment, or NULL.
+ */
+ const char *order_id;
+
+ /**
+ * Payment Identifier
+ */
+ struct ANASTASIS_PaymentSecretP payment_identifier;
+
+ /**
+ * Timestamp of the order in @e payment_identifier. Used to
+ * select the most recent unpaid offer.
+ */
+ struct GNUNET_TIME_Absolute existing_pi_timestamp;
+
+ /**
+ * When does the operation timeout?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * How long must the account be valid? Determines whether we should
+ * trigger payment, and if so how much.
+ */
+ struct GNUNET_TIME_Absolute end_date;
+
+ /**
+ * How long is the account already valid?
+ * Determines how much the user needs to pay.
+ */
+ struct GNUNET_TIME_Absolute paid_until;
+
+ /**
+ * Expected total upload size.
+ */
+ size_t upload_size;
+
+ /**
+ * Current offset for the upload.
+ */
+ size_t upload_off;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * For how many years does the client still have
+ * to pay?
+ */
+ unsigned int years_to_pay;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ bool payment_identifier_provided;
+
+};
+
+
+/**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+static struct PolicyUploadContext *puc_head;
+
+/**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+static struct PolicyUploadContext *puc_tail;
+
+
+/**
+ * Service is shutting down, resume all MHD connections NOW.
+ */
+void
+AH_resume_all_bc ()
+{
+ struct PolicyUploadContext *puc;
+
+ while (NULL != (puc = puc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ if (NULL != puc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (puc->po);
+ puc->po = NULL;
+ }
+ if (NULL != puc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (puc->cpo);
+ puc->cpo = NULL;
+ }
+ MHD_resume_connection (puc->con);
+ }
+}
+
+
+/**
+ * Function called to clean up a backup context.
+ *
+ * @param hc a `struct PolicyUploadContext`
+ */
+static void
+cleanup_ctx (struct TM_HandlerContext *hc)
+{
+ struct PolicyUploadContext *puc = hc->ctx;
+
+ if (NULL != puc->po)
+ TALER_MERCHANT_orders_post_cancel (puc->po);
+ if (NULL != puc->cpo)
+ TALER_MERCHANT_merchant_order_get_cancel (puc->cpo);
+ if (NULL != puc->hash_ctx)
+ GNUNET_CRYPTO_hash_context_abort (puc->hash_ctx);
+ if (NULL != puc->resp)
+ MHD_destroy_response (puc->resp);
+ GNUNET_free (puc->upload);
+ GNUNET_free (puc);
+}
+
+
+/**
+ * Transmit a payment request for @a order_id on @a connection
+ *
+ * @param connection MHD connection
+ * @param order_id our backend's order ID
+ * @return #GNUNET_OK on success
+ */
+static int
+make_payment_request (struct PolicyUploadContext *puc)
+{
+ struct MHD_Response *resp;
+
+ /* request payment via Taler */
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ if (NULL == resp)
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *hdr;
+ char *pfx;
+ 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
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (resp);
+ return GNUNET_SYSERR;
+ }
+ if (0 == strlen (hn))
+ {
+ GNUNET_break (0);
+ MHD_destroy_response (resp);
+ return GNUNET_SYSERR;
+ }
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof (puc->payment_identifier));
+ GNUNET_asprintf (&hdr,
+ "%spay/%s%s/",
+ pfx,
+ hn,
+ order_id);
+ GNUNET_free (order_id);
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_TALER,
+ hdr));
+ GNUNET_free (hdr);
+ }
+ puc->resp = resp;
+ puc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+ return GNUNET_OK;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /private/orders request to a merchant.
+ *
+ * @param cls our `struct PolicyUploadContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct PolicyUploadContext *puc = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ puc->po = NULL;
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ MHD_resume_connection (puc->con);
+ 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);
+ puc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_SYNC_PAYMENT_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != puc->resp);
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storing payment request for order `%s'\n",
+ por->details.ok.order_id);
+
+ qs = db->record_recdoc_payment (db->cls,
+ &puc->account,
+ (uint32_t) AH_post_counter,
+ &puc->payment_identifier,
+ &AH_annual_fee);
+ if (0 >= qs)
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "record recdoc payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ if (GNUNET_OK !=
+ make_payment_request (puc))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+}
+
+
+/**
+ * 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;
+
+ /* refunds are not supported, verify */
+ puc->cpo = NULL;
+ GNUNET_CONTAINER_DLL_remove (puc_head,
+ puc_tail,
+ puc);
+ MHD_resume_connection (puc->con);
+ AH_trigger_daemon (NULL);
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_assert (NULL != osr);
+ break; /* processed below */
+ case MHD_HTTP_UNAUTHORIZED:
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED,
+ NULL);
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ default:
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment status checked: %s\n",
+ osr->status ? "paid" : "unpaid");
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int years;
+ struct GNUNET_TIME_Relative paid_until;
+ const json_t *contract;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification cspec[] = {
+ TALER_JSON_spec_amount ("amount",
+ AH_currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ contract = osr->details.paid.contract_terms;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract,
+ cspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "no amount given");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ years = TALER_amount_divide2 (&amount,
+ &AH_annual_fee);
+ paid_until = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years);
+ /* add 1 week grace period, otherwise if a user
+ wants to pay for 1 year, the first seconds
+ would have passed between making the payment
+ and our subsequent check if +1 year was
+ paid... So we actually say 1 year = 52 weeks
+ on the server, while the client calculates
+ with 365 days. */
+ paid_until = GNUNET_TIME_relative_add (paid_until,
+ GNUNET_TIME_UNIT_WEEKS);
+
+ qs = db->increment_lifetime (db->cls,
+ &puc->account,
+ &puc->payment_identifier,
+ paid_until,
+ &puc->paid_until);
+ if (0 <= qs)
+ return; /* continue as planned */
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "increment lifetime");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ case TALER_MERCHANT_OSC_UNPAID:
+ case TALER_MERCHANT_OSC_CLAIMED:
+ break;
+ }
+ if (0 != puc->existing_pi_timestamp.abs_value_us)
+ {
+ /* repeat payment request */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Repeating payment request\n");
+ if (GNUNET_OK !=
+ make_payment_request (puc))
+ {
+ GNUNET_break (0);
+ puc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "failed to initiate payment");
+ puc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ return;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Timeout waiting for payment\n");
+ puc->resp = TALER_MHD_make_error (TALER_EC_SYNC_PAYMENT_GENERIC_TIMEOUT,
+ "Timeout awaiting promised payment");
+ GNUNET_assert (NULL != puc->resp);
+ puc->response_code = MHD_HTTP_REQUEST_TIMEOUT;
+}
+
+
+/**
+ * Helper function used to ask our backend to await
+ * a payment for the user's account.
+ *
+ * @param puc context to begin payment for.
+ * @param timeout when to give up trying
+ */
+static void
+await_payment (struct PolicyUploadContext *puc)
+{
+ struct GNUNET_TIME_Relative timeout
+ = GNUNET_TIME_absolute_get_remaining (puc->timeout);
+
+ GNUNET_CONTAINER_DLL_insert (puc_head,
+ puc_tail,
+ puc);
+ MHD_suspend_connection (puc->con);
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof(struct ANASTASIS_PaymentSecretP));
+ puc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* our payments are NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ puc);
+ GNUNET_free (order_id);
+ }
+ AH_trigger_curl ();
+}
+
+
+/**
+ * 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 puc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct PolicyUploadContext *puc)
+{
+ json_t *order;
+
+ GNUNET_CONTAINER_DLL_insert (puc_head,
+ puc_tail,
+ puc);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Suspending connection while creating order at `%s'\n",
+ AH_backend_url);
+ {
+ char *order_id;
+ struct TALER_Amount upload_fee;
+
+ if (0 >
+ TALER_amount_multiply (&upload_fee,
+ &AH_annual_fee,
+ puc->years_to_pay))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration_years");
+ }
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &puc->payment_identifier,
+ sizeof(struct ANASTASIS_PaymentSecretP));
+ 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",
+ "products",
+ "description", "policy storage fee",
+ "quantity", (json_int_t) puc->years_to_pay,
+ "unit", "years",
+ "order_id", order_id);
+ GNUNET_free (order_id);
+ }
+ MHD_suspend_connection (puc->con);
+ puc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ GNUNET_TIME_UNIT_ZERO,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ puc);
+ AH_trigger_curl ();
+ json_decref (order);
+ return MHD_YES;
+}
+
+
+/**
+ * Prepare to receive a payment, possibly requesting it, or just waiting
+ * for it to be completed by the client.
+ *
+ * @param puc context to prepare payment for
+ * @return MHD status
+ */
+static MHD_RESULT
+prepare_payment (struct PolicyUploadContext *puc)
+{
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (
+ GNUNET_CRYPTO_QUALITY_NONCE,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No payment identifier, initiating payment\n");
+ return begin_payment (puc);
+ }
+ await_payment (puc);
+ return MHD_YES;
+}
+
+
+MHD_RESULT
+AH_handler_policy_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub,
+ const char *recovery_data,
+ size_t *recovery_data_size)
+{
+ struct PolicyUploadContext *puc = hc->ctx;
+
+ if (NULL == puc)
+ {
+ /* first call, setup internals */
+ puc = GNUNET_new (struct PolicyUploadContext);
+ hc->ctx = puc;
+ hc->cc = &cleanup_ctx;
+ puc->con = connection;
+
+ {
+ 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),
+ &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);
+ }
+ }
+ puc->account = *account_pub;
+ /* now setup 'puc' */
+ {
+ const char *lens;
+ unsigned long len;
+
+ lens = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_CONTENT_LENGTH);
+ if ( (NULL == lens) ||
+ (1 != sscanf (lens,
+ "%lu",
+ &len)) )
+ {
+ 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");
+ }
+ puc->upload = GNUNET_malloc_large (len);
+ if (NULL == puc->upload)
+ {
+ GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
+ "malloc");
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_PAYLOAD_TOO_LARGE,
+ TALER_EC_ANASTASIS_POLICY_OUT_OF_MEMORY_ON_CONTENT_LENGTH,
+ NULL);
+ }
+ 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");
+ }
+ }
+ {
+ /* Check if header contains an ETAG */
+ const char *etag;
+
+ etag = MHD_lookup_connection_value (connection,
+ MHD_HEADER_KIND,
+ MHD_HTTP_HEADER_IF_NONE_MATCH);
+ if ( (NULL == etag) ||
+ (GNUNET_OK !=
+ GNUNET_STRINGS_string_to_data (etag,
+ strlen (etag),
+ &puc->new_policy_upload_hash,
+ sizeof (puc->new_policy_upload_hash))) )
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_BAD_IF_MATCH,
+ MHD_HTTP_HEADER_IF_NONE_MATCH
+ " header must include a base32-encoded SHA-512 hash");
+ }
+ }
+ /* validate signature */
+ {
+ struct ANASTASIS_UploadSignaturePS usp = {
+ .purpose.size = htonl (sizeof (usp)),
+ .purpose.purpose = htonl (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD),
+ .new_recovery_data_hash = puc->new_policy_upload_hash
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_ANASTASIS_POLICY_UPLOAD,
+ &usp,
+ &puc->account_sig.eddsa_sig,
+ &account_pub->pub))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_FORBIDDEN,
+ TALER_EC_ANASTASIS_POLICY_BAD_SIGNATURE,
+ ANASTASIS_HTTP_HEADER_POLICY_SIGNATURE);
+ }
+ }
+
+ {
+ 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;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ 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);
+ }
+ }
+
+ /* check if the client insists on paying */
+ {
+ const char *req;
+ unsigned int years;
+
+ req = MHD_lookup_connection_value (connection,
+ MHD_GET_ARGUMENT_KIND,
+ "storage_duration");
+ if (NULL != req)
+ {
+ char dummy;
+
+ if (1 != sscanf (req,
+ "%u%c",
+ &years,
+ &dummy))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "storage_duration (must be non-negative number)");
+ }
+ }
+ else
+ {
+ years = 0;
+ }
+ puc->end_date = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years));
+ }
+
+ /* get ready to hash (done here as we may go async for payments next) */
+ puc->hash_ctx = GNUNET_CRYPTO_hash_context_start ();
+
+ /* Check database to see if the transaction is permissible */
+ {
+ struct GNUNET_TIME_Relative rem;
+
+ rem = GNUNET_TIME_absolute_get_remaining (puc->end_date);
+ 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++;
+
+ if (puc->payment_identifier_provided)
+ {
+ /* check if payment identifier is valid (existing and paid) */
+ bool paid;
+ bool valid_counter;
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->check_payment_identifier (db->cls,
+ &puc->payment_identifier,
+ &paid,
+ &valid_counter);
+ if (qs < 0)
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+
+ if ( (! paid) ||
+ (! valid_counter) )
+ {
+ if (! valid_counter)
+ {
+ puc->payment_identifier_provided = false;
+ if (0 == puc->years_to_pay)
+ puc->years_to_pay = 1;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Too many uploads with this payment identifier, initiating fresh payment\n");
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Given payment identifier not known to be paid, initiating payment\n");
+ }
+ return prepare_payment (puc);
+ }
+ }
+
+ 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))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "No payment identifier, requesting payment\n");
+ return begin_payment (puc);
+ }
+ /* Cost is zero, fake "zero" payment having happened */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy upload is free, allowing upload without payment\n");
+ qs = db->record_recdoc_payment (db->cls,
+ account_pub,
+ AH_post_counter,
+ &puc->payment_identifier,
+ &AH_annual_fee);
+ if (qs <= 0)
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ rel = GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_YEARS,
+ 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),
+ ANASTASIS_MAX_YEARS_STORAGE);
+ puc->paid_until = GNUNET_TIME_relative_to_absolute (rel);
+ qs = db->update_lifetime (db->cls,
+ account_pub,
+ &puc->payment_identifier,
+ puc->paid_until);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ }
+ }
+ }
+
+ /* Check if existing policy matches upload (and if, skip it) */
+ {
+ struct GNUNET_HashCode hc;
+ enum ANASTASIS_DB_AccountStatus as;
+ uint32_t version;
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_TIME_Relative rem;
+
+ as = db->lookup_account (db->cls,
+ account_pub,
+ &puc->paid_until,
+ &hc,
+ &version);
+ now = GNUNET_TIME_absolute_get ();
+ if (puc->paid_until.abs_value_us < now.abs_value_us)
+ puc->paid_until = now;
+ rem = GNUNET_TIME_absolute_get_difference (puc->paid_until,
+ puc->end_date);
+ 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++;
+
+ if ( (ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED == as) &&
+ (0 != puc->years_to_pay) )
+ {
+ /* user requested extension, force payment */
+ as = ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED;
+ }
+ switch (as)
+ {
+ case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Expiration too low, initiating payment\n");
+ return prepare_payment (puc);
+ case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR:
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS:
+ /* continue below */
+ break;
+ case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
+ if (0 == GNUNET_memcmp (&hc,
+ &puc->new_policy_upload_hash))
+ {
+ /* Refuse upload: we already have that backup! */
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+ char version_s[14];
+
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ (unsigned int) version);
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ break;
+ }
+ }
+ /* ready to begin! */
+ return MHD_YES;
+ }
+
+ if (NULL != puc->resp)
+ {
+ MHD_RESULT ret;
+
+ /* We generated a response asynchronously, queue that */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning asynchronously generated response with HTTP status %u\n",
+ puc->response_code);
+ ret = MHD_queue_response (connection,
+ puc->response_code,
+ puc->resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (puc->resp);
+ puc->resp = NULL;
+ return ret;
+ }
+
+ /* handle upload */
+ if (0 != *recovery_data_size)
+ {
+ /* check MHD invariant */
+ GNUNET_assert (puc->upload_off + *recovery_data_size <= puc->upload_size);
+ memcpy (&puc->upload[puc->upload_off],
+ recovery_data,
+ *recovery_data_size);
+ puc->upload_off += *recovery_data_size;
+ GNUNET_CRYPTO_hash_context_read (puc->hash_ctx,
+ recovery_data,
+ *recovery_data_size);
+ *recovery_data_size = 0;
+ return MHD_YES;
+ }
+
+ if ( (0 == puc->upload_off) &&
+ (0 != puc->upload_size) &&
+ (NULL == puc->resp) )
+ {
+ /* wait for upload */
+ return MHD_YES;
+ }
+
+ /* finished with upload, check hash */
+ if (NULL != puc->hash_ctx)
+ {
+ struct GNUNET_HashCode our_hash;
+
+ GNUNET_CRYPTO_hash_context_finish (puc->hash_ctx,
+ &our_hash);
+ puc->hash_ctx = NULL;
+ if (0 != GNUNET_memcmp (&our_hash,
+ &puc->new_policy_upload_hash))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_POLICY_INVALID_UPLOAD,
+ "Data uploaded does not match Etag promise");
+ }
+ }
+
+ /* store backup to database */
+ {
+ enum ANASTASIS_DB_StoreStatus ss;
+ uint32_t version = UINT32_MAX;
+ char version_s[14];
+ char expir_s[32];
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Uploading recovery document\n");
+ ss = db->store_recovery_document (db->cls,
+ &puc->account,
+ &puc->account_sig,
+ &puc->new_policy_upload_hash,
+ puc->upload,
+ puc->upload_size,
+ &puc->payment_identifier,
+ &version);
+ GNUNET_snprintf (version_s,
+ sizeof (version_s),
+ "%u",
+ (unsigned int) version);
+ GNUNET_snprintf (expir_s,
+ sizeof (expir_s),
+ "%llu",
+ (unsigned long long)
+ (puc->paid_until.abs_value_us
+ / GNUNET_TIME_UNIT_SECONDS.rel_value_us));
+ switch (ss)
+ {
+ case ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Storage request limit exceeded, requesting payment\n");
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Also no payment identifier, requesting payment\n");
+ }
+ return begin_payment (puc);
+ case ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED:
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Policy store operation requires payment\n");
+ if (! puc->payment_identifier_provided)
+ {
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_STRONG,
+ &puc->payment_identifier,
+ sizeof (struct ANASTASIS_PaymentSecretP));
+ puc->payment_identifier_provided = true;
+ }
+ return begin_payment (puc);
+ case ANASTASIS_DB_STORE_STATUS_HARD_ERROR:
+ case ANASTASIS_DB_STORE_STATUS_SOFT_ERROR:
+ return TALER_MHD_reply_with_error (puc->con,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ NULL);
+ case ANASTASIS_DB_STORE_STATUS_NO_RESULTS:
+ {
+ /* database says nothing actually changed, 304 (could
+ theoretically happen if another equivalent upload succeeded
+ since we last checked!) */
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ "Anastasis-Version",
+ version_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NOT_MODIFIED,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ case ANASTASIS_DB_STORE_STATUS_SUCCESS:
+ /* generate main (204) standard success reply */
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_VERSION,
+ version_s));
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_POLICY_EXPIRATION,
+ expir_s));
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+ }
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
diff --git a/src/backend/anastasis-httpd_terms.c b/src/backend/anastasis-httpd_terms.c
new file mode 100644
index 0000000..6be5690
--- /dev/null
+++ b/src/backend/anastasis-httpd_terms.c
@@ -0,0 +1,98 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020 Taler Systems SA
+
+ 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
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_terms.c
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#include "platform.h"
+#include "anastasis-httpd_terms.h"
+#include <taler/taler_json_lib.h>
+
+/**
+ * Our terms of service.
+ */
+static struct TALER_MHD_Legal *tos;
+
+
+/**
+ * Our privacy policy.
+ */
+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)
+{
+ (void) rh;
+ return TALER_MHD_reply_legal (connection,
+ tos);
+}
+
+
+/**
+ * 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,
+ struct MHD_Connection *connection)
+{
+ (void) rh;
+ return TALER_MHD_reply_legal (connection,
+ pp);
+}
+
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg)
+{
+ tos = TALER_MHD_legal_load (cfg,
+ "anastasis",
+ "TERMS_DIR",
+ "TERMS_ETAG");
+ if (NULL == tos)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Terms of service not configured\n");
+ pp = TALER_MHD_legal_load (cfg,
+ "anastasis",
+ "PRIVACY_DIR",
+ "PRIVACY_ETAG");
+ if (NULL == pp)
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Privacy policy not configured\n");
+}
+
+
+/* end of anastasis-httpd_terms.c */
diff --git a/src/backend/anastasis-httpd_terms.h b/src/backend/anastasis-httpd_terms.h
new file mode 100644
index 0000000..dc59d41
--- /dev/null
+++ b/src/backend/anastasis-httpd_terms.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of Anastasis
+ Copyright (C) 2020, 2021 Taler Systems SA
+
+ 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
+ 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ Anastasis; see the file COPYING.GPL. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file backend/anastasis-httpd_terms.h
+ * @brief headers for /terms handler
+ * @author Christian Grothoff
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ */
+#ifndef ANASTASIS_HTTPD_TERMS_H
+#define ANASTASIS_HTTPD_TERMS_H
+#include <microhttpd.h>
+#include "anastasis-httpd.h"
+
+/**
+ * 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);
+
+
+/**
+ * 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,
+ struct MHD_Connection *connection);
+
+/**
+ * Load our terms of service as per configuration.
+ *
+ * @param cfg configuration to process
+ */
+void
+AH_load_terms (const struct GNUNET_CONFIGURATION_Handle *cfg);
+
+
+#endif
+
+/* end of anastasis-httpd_terms.h */
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c
new file mode 100644
index 0000000..164c33a
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth.c
@@ -0,0 +1,1428 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER 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.
+
+ TALER 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
+ TALER; 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
+ * @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>
+
+/**
+ * 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 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 many retries do we allow per code?
+ */
+#define INITIAL_RETRY_COUNTER 3
+
+struct GetContext
+{
+
+ /**
+ * 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;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ struct TALER_Amount challenge_cost;
+
+ /**
+ * Our handler context.
+ */
+ struct TM_HandlerContext *hc;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct GetContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct GetContext *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;
+
+ /**
+ * How long do we wait at most for payment?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Random authorization code we are using.
+ */
+ uint64_t code;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * true if client provided a payment secret / order ID?
+ */
+ bool payment_identifier_provided;
+
+ /**
+ * True if this entry is in the #gc_head DLL.
+ */
+ bool in_list;
+
+ /**
+ * True if this entry is currently suspended.
+ */
+ bool suspended;
+
+ /**
+ * Did the request include a response?
+ */
+ bool have_response;
+
+};
+
+/**
+ * Information we track for refunds.
+ */
+struct RefundEntry
+{
+ /**
+ * Kept in a DLL.
+ */
+ struct RefundEntry *next;
+
+ /**
+ * Kept in a DLL.
+ */
+ struct RefundEntry *prev;
+
+ /**
+ * Operation handle.
+ */
+ struct TALER_MERCHANT_OrderRefundHandle *ro;
+
+ /**
+ * Which order is being refunded.
+ */
+ char *order_id;
+
+ /**
+ * Payment Identifier
+ */
+ struct ANASTASIS_PaymentSecretP payment_identifier;
+
+ /**
+ * Public key of the challenge which is solved.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+};
+
+
+/**
+ * Head of linked list of active refund operations.
+ */
+static struct RefundEntry *re_head;
+
+/**
+ * Tail of linked list of active refund operations.
+ */
+static struct RefundEntry *re_tail;
+
+/**
+ * Head of linked list over all authorization processes
+ */
+static struct GetContext *gc_head;
+
+/**
+ * Tail of linked list over all authorization processes
+ */
+static struct GetContext *gc_tail;
+
+
+void
+AH_truth_shutdown (void)
+{
+ struct GetContext *gc;
+ struct RefundEntry *re;
+
+ while (NULL != (re = re_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (re_head,
+ re_tail,
+ re);
+ if (NULL != re->ro)
+ {
+ TALER_MERCHANT_post_order_refund_cancel (re->ro);
+ re->ro = NULL;
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refund `%s' failed due to shutdown\n",
+ re->order_id);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+ }
+
+ while (NULL != (gc = gc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ if (NULL != gc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (gc->cpo);
+ gc->cpo = NULL;
+ }
+ if (NULL != gc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (gc->po);
+ gc->po = NULL;
+ }
+ if (gc->suspended)
+ {
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ }
+ if (NULL != gc->as)
+ {
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ gc->authorization = NULL;
+ }
+ }
+ ANASTASIS_authorization_plugin_shutdown ();
+}
+
+
+/**
+ * Callback to process a POST /orders/ID/refund request
+ *
+ * @param cls closure
+ * @param http_status HTTP status code for this request
+ * @param ec taler-specific error code
+ * @param taler_refund_uri the refund uri offered to the wallet
+ * @param h_contract hash of the contract a Browser may need to authorize
+ * obtaining the HTTP response.
+ */
+static void
+refund_cb (
+ void *cls,
+ const struct TALER_MERCHANT_HttpResponse *hr,
+ const char *taler_refund_uri,
+ const struct GNUNET_HashCode *h_contract)
+{
+ struct RefundEntry *re = cls;
+
+ re->ro = NULL;
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Refund `%s' succeeded\n",
+ re->order_id);
+ qs = db->record_challenge_refund (db->cls,
+ &re->truth_uuid,
+ &re->payment_identifier);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ break;
+ default:
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "Refund `%s' failed with HTTP status %u: %s (#%u)\n",
+ re->order_id,
+ hr->http_status,
+ hr->hint,
+ (unsigned int) hr->ec);
+ break;
+ }
+ GNUNET_CONTAINER_DLL_remove (re_head,
+ re_tail,
+ re);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+}
+
+
+/**
+ * Start to give a refund for the challenge created by @a gc.
+ *
+ * @param gc request where we failed and should now grant a refund for
+ */
+static void
+begin_refund (const struct GetContext *gc)
+{
+ struct RefundEntry *re;
+
+ re = GNUNET_new (struct RefundEntry);
+ re->order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Challenge execution failed, triggering refund for order `%s'\n",
+ re->order_id);
+ re->payment_identifier = gc->payment_identifier;
+ re->truth_uuid = gc->truth_uuid;
+ re->ro = TALER_MERCHANT_post_order_refund (AH_ctx,
+ AH_backend_url,
+ re->order_id,
+ &gc->challenge_cost,
+ "failed to issue challenge",
+ &refund_cb,
+ re);
+ if (NULL == re->ro)
+ {
+ GNUNET_break (0);
+ GNUNET_free (re->order_id);
+ GNUNET_free (re);
+ return;
+ }
+ GNUNET_CONTAINER_DLL_insert (re_head,
+ re_tail,
+ re);
+}
+
+
+/**
+ * Callback used to notify the application about completed requests.
+ * Cleans up the requests data structures.
+ *
+ * @param hc
+ */
+static void
+request_done (struct TM_HandlerContext *hc)
+{
+ struct GetContext *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->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;
+ }
+ 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 GetContext *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 GetContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct GetContext *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);
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ 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 (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_TRUTH_PAYMENT_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != gc->resp);
+ 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 GetContext`
+ * @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 GetContext *gc = cls;
+
+ gc->cpo = NULL;
+ GNUNET_assert (gc->in_list);
+ GNUNET_CONTAINER_DLL_remove (gc_head,
+ gc_tail,
+ gc);
+ gc->in_list = false;
+ GNUNET_assert (gc->suspended);
+ MHD_resume_connection (gc->connection);
+ gc->suspended = false;
+ AH_trigger_daemon (NULL);
+
+ switch (hr->http_status)
+ {
+ case MHD_HTTP_OK:
+ GNUNET_assert (NULL != osr);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* We created this order before, how can it be not found now? */
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_TRUTH_ORDER_DISAPPEARED,
+ NULL);
+ gc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ case MHD_HTTP_BAD_GATEWAY:
+ gc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_TRUTH_BACKEND_EXCHANGE_BAD,
+ NULL);
+ gc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ case MHD_HTTP_GATEWAY_TIMEOUT:
+ gc->resp = TALER_MHD_make_error (TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+ "Timeout check payment status");
+ GNUNET_assert (NULL != gc->resp);
+ gc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+ return;
+ default:
+ {
+ char status[14];
+
+ GNUNET_snprintf (status,
+ sizeof (status),
+ "%u",
+ hr->http_status);
+ gc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_TRUTH_UNEXPECTED_PAYMENT_STATUS,
+ status);
+ GNUNET_assert (NULL != gc->resp);
+ gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return;
+ }
+ }
+
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->update_challenge_payment (db->cls,
+ &gc->truth_uuid,
+ &gc->payment_identifier);
+ if (0 <= qs)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order has been paid, continuing with request processing\n");
+ return; /* continue as planned */
+ }
+ GNUNET_break (0);
+ gc->resp = TALER_MHD_make_error (TALER_EC_GENERIC_DB_STORE_FAILED,
+ "update challenge payment");
+ gc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ return; /* continue as planned */
+ }
+ case TALER_MERCHANT_OSC_CLAIMED:
+ case TALER_MERCHANT_OSC_UNPAID:
+ /* repeat payment request */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order remains unpaid, sending payment request again\n");
+ make_payment_request (gc);
+ return;
+ }
+ /* should never get here */
+ GNUNET_break (0);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the user's account. May perform asynchronous
+ * operations by suspending the connection if required.
+ *
+ * @param gc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct GetContext *gc)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ char *order_id;
+
+ qs = db->lookup_challenge_payment (db->cls,
+ &gc->truth_uuid,
+ &gc->payment_identifier);
+ if (qs < 0)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "lookup challenge payment");
+ }
+ GNUNET_assert (! gc->in_list);
+ gc->in_list = true;
+ GNUNET_CONTAINER_DLL_insert (gc_tail,
+ gc_head,
+ gc);
+ GNUNET_assert (! gc->suspended);
+ gc->suspended = true;
+ MHD_suspend_connection (gc->connection);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ /* We already created the order, check if it was paid */
+ struct GNUNET_TIME_Relative timeout;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &gc->payment_identifier,
+ sizeof (gc->payment_identifier));
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Order exists, checking payment status for order `%s'\n",
+ order_id);
+ timeout = GNUNET_TIME_absolute_get_remaining (gc->timeout);
+ gc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ gc);
+ }
+ else
+ {
+ /* Create a fresh order */
+ json_t *order;
+ struct GNUNET_TIME_Absolute 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_absolute (
+ ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+ GNUNET_TIME_round_abs (&pay_deadline);
+ order = json_pack ("{s:o, s:s, s:s, s:o, s:o}",
+ "amount", TALER_JSON_from_amount (&gc->challenge_cost),
+ "summary", "challenge fee for anastasis service",
+ "order_id", order_id,
+ "auto_refund", GNUNET_JSON_from_time_rel (
+ AUTO_REFUND_TIMEOUT),
+ "pay_deadline", GNUNET_JSON_from_time_abs (
+ pay_deadline));
+ gc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ AUTO_REFUND_TIMEOUT,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ gc);
+ json_decref (order);
+ }
+ GNUNET_free (order_id);
+ AH_trigger_curl ();
+ return MHD_YES;
+}
+
+
+/**
+ * Load encrypted keyshare from db and return it to the client.
+ *
+ * @param truth_uuid UUID to the truth for the looup
+ * @param connection the connection to respond upon
+ * @return MHD status code
+ */
+static MHD_RESULT
+return_key_share (
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct MHD_Connection *connection)
+{
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP encrypted_keyshare;
+
+ {
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = db->get_key_share (db->cls,
+ truth_uuid,
+ &encrypted_keyshare);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get key share");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_KEY_SHARE_GONE,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Returning key share\n");
+ {
+ struct MHD_Response *resp;
+ MHD_RESULT ret;
+
+ resp = MHD_create_response_from_buffer (sizeof (encrypted_keyshare),
+ &encrypted_keyshare,
+ MHD_RESPMEM_MUST_COPY);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_OK,
+ resp);
+ MHD_destroy_response (resp);
+ return ret;
+ }
+}
+
+
+/**
+ * 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 GetContext *gc)
+{
+ enum ANASTASIS_AUTHORIZATION_Result ret;
+ enum GNUNET_DB_QueryStatus qs;
+
+ ret = gc->authorization->process (gc->as,
+ connection);
+ switch (ret)
+ {
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS:
+ /* Challenge sent successfully */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Authorization request sent successfully\n");
+ qs = db->mark_challenge_sent (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ gc->code);
+ GNUNET_break (0 < qs);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED:
+ if (gc->payment_identifier_provided)
+ {
+ begin_refund (gc);
+ }
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUSPENDED:
+ /* connection was suspended again, odd that this happens */
+ return MHD_YES;
+ case ANASTASIS_AUTHORIZATION_RES_SUCCESS_REPLY_FAILED:
+ /* Challenge sent successfully */
+ qs = db->mark_challenge_sent (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ gc->code);
+ GNUNET_break (0 < qs);
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ case ANASTASIS_AUTHORIZATION_RES_FAILED_REPLY_FAILED:
+ gc->authorization->cleanup (gc->as);
+ gc->as = NULL;
+ return MHD_NO;
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+}
+
+
+/**
+ * @param connection the MHD connection to handle
+ * @param url handles a URL of the format "/truth/$UUID[&response=$RESPONSE]"
+ * @param hc
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct TM_HandlerContext *hc)
+{
+ struct GetContext *gc = hc->ctx;
+ struct GNUNET_HashCode challenge_response;
+ 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->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,
+ &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;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ GNUNET_break_op (0);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_GENERIC_PARAMETER_MALFORMED,
+ "timeout_ms (must be non-negative number)");
+ }
+ gc->timeout
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_relative_multiply (
+ GNUNET_TIME_UNIT_MILLISECONDS,
+ timeout));
+ }
+ else
+ {
+ gc->timeout = GNUNET_TIME_relative_to_absolute (
+ GNUNET_TIME_UNIT_SECONDS);
+ }
+ }
+
+ } /* end of first-time initialization (if NULL == gc) */
+ else
+ {
+ 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");
+ return run_authorization_process (connection,
+ gc);
+
+ }
+ /* We get here if the async check for payment said this request
+ was indeed paid! */
+ }
+
+ {
+ /* load encrypted truth from DB */
+ enum GNUNET_DB_QueryStatus qs;
+ char *method;
+
+ qs = db->get_escrow_challenge (db->cls,
+ &gc->truth_uuid,
+ &encrypted_truth,
+ &encrypted_truth_size,
+ &truth_mime,
+ &method);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "get escrow challenge");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_NOT_FOUND,
+ TALER_EC_ANASTASIS_TRUTH_UNKNOWN,
+ NULL);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ is_question = (0 == strcmp ("question",
+ method));
+ if (! is_question)
+ {
+ gc->authorization
+ = ANASTASIS_authorization_plugin_load (method,
+ AH_cfg,
+ &gc->challenge_cost);
+ 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
+ {
+ gc->challenge_cost = AH_question_cost;
+ }
+ GNUNET_free (method);
+ }
+
+ {
+ struct TALER_Amount zero_amount;
+
+ TALER_amount_set_zero (AH_currency,
+ &zero_amount);
+ if (0 != TALER_amount_cmp (&gc->challenge_cost,
+ &zero_amount))
+ {
+ /* Check database to see if the transaction is paid for */
+ enum GNUNET_DB_QueryStatus qs;
+ bool paid;
+
+ if (! gc->payment_identifier_provided)
+ {
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning payment, client did not provide payment identifier\n");
+ return begin_payment (gc);
+ }
+ qs = db->check_challenge_payment (db->cls,
+ &gc->payment_identifier,
+ &gc->truth_uuid,
+ &paid);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "check challenge payment");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* Create fresh payment identifier (cannot trust client) */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Client-provided payment identifier is unknown.\n");
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return begin_payment (gc);
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (! paid)
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment identifier known. Checking payment with client's payment identifier\n");
+ GNUNET_free (truth_mime);
+ GNUNET_free (encrypted_truth);
+ return begin_payment (gc);
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment confirmed\n");
+ break;
+ }
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Request is free of charge\n");
+ }
+ }
+
+ /* We've been paid, now validate response */
+ {
+ /* decrypt encrypted_truth */
+ ANASTASIS_CRYPTO_truth_decrypt (&gc->truth_key,
+ encrypted_truth,
+ encrypted_truth_size,
+ &decrypted_truth,
+ &decrypted_truth_size);
+ GNUNET_free (encrypted_truth);
+ }
+ if (NULL == decrypted_truth)
+ {
+ GNUNET_free (truth_mime);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_EXPECTATION_FAILED,
+ TALER_EC_ANASTASIS_TRUTH_DECRYPTION_FAILED,
+ NULL);
+ }
+
+ /* Special case for secure question: we do not generate a numeric challenge,
+ but check that the hash matches */
+ if (is_question)
+ {
+ 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_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute rt;
+ uint64_t code;
+ enum ANASTASIS_DB_CodeStatus cs;
+ struct GNUNET_HashCode hc;
+
+ 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);
+ 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 (challenge_response)) ||
+ (0 != memcmp (&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 (challenge_response));
+ 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);
+ }
+ 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)
+ {
+ enum ANASTASIS_DB_CodeStatus cs;
+
+ GNUNET_free (decrypted_truth);
+ GNUNET_free (truth_mime);
+ cs = db->verify_challenge_code (db->cls,
+ &gc->truth_uuid,
+ &challenge_response);
+ 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");
+ 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);
+ 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_DEBUG,
+ "No challenge known (challenge is invalidated after %u requests)\n",
+ INITIAL_RETRY_COUNTER);
+ 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:
+ return return_key_share (&gc->truth_uuid,
+ connection);
+ }
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+
+ /* Not security question and no answer: use plugin to check if
+ decrypted truth is a valid challenge! */
+ {
+ enum GNUNET_GenericReturnValue ret;
+
+ ret = gc->authorization->validate (gc->authorization->cls,
+ connection,
+ truth_mime,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (truth_mime);
+ switch (ret)
+ {
+ case GNUNET_OK:
+ /* data valid, continued below */
+ break;
+ case GNUNET_NO:
+ /* data invalid, reply was queued */
+ GNUNET_free (decrypted_truth);
+ return MHD_YES;
+ case GNUNET_SYSERR:
+ /* data invalid, reply was NOT queued */
+ GNUNET_free (decrypted_truth);
+ return MHD_NO;
+ }
+ }
+
+ /* Setup challenge and begin authorization process */
+ {
+ struct GNUNET_TIME_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,
+ INITIAL_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,
+ "store_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);
+ }
+ }
+
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Beginning authorization process\n");
+ gc->as = gc->authorization->start (gc->authorization->cls,
+ &AH_trigger_daemon,
+ NULL,
+ &gc->truth_uuid,
+ gc->code,
+ decrypted_truth,
+ decrypted_truth_size);
+ GNUNET_free (decrypted_truth);
+ if (NULL == gc->as)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (gc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_TRUTH_AUTHORIZATION_START_FAILED,
+ NULL);
+ }
+ GNUNET_assert (! gc->in_list);
+ gc->in_list = true;
+ GNUNET_CONTAINER_DLL_insert (gc_head,
+ gc_tail,
+ gc);
+ return run_authorization_process (connection,
+ gc);
+}
diff --git a/src/backend/anastasis-httpd_truth.h b/src/backend/anastasis-httpd_truth.h
new file mode 100644
index 0000000..7a1b95f
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth.h
@@ -0,0 +1,75 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2014, 2015, 2016, 2021 Taler Systems SA
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth.h
+ * @brief functions to handle incoming requests on /truth
+ * @author Dennis Neufeld
+ * @author Dominik Meister
+ * @author Christian Grothoff
+ */
+#ifndef ANASTASIS_HTTPD_TRUTH_H
+#define ANASTASIS_HTTPD_TRUTH_H
+#include <microhttpd.h>
+
+
+/**
+ * Prepare all active GET truth requests for system shutdown.
+ */
+void
+AH_truth_shutdown (void);
+
+
+/**
+ * Prepare all active POST truth requests for system shutdown.
+ */
+void
+AH_truth_upload_shutdown (void);
+
+
+/**
+ * Handle a GET to /truth/$UUID
+ *
+ * @param connection the MHD connection to handle
+ * @param truth_uuid the truth UUID
+ * @param con_cls
+ * @return MHD result code
+ */
+MHD_RESULT
+AH_handler_truth_get (
+ struct MHD_Connection *connection,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct TM_HandlerContext *hc);
+
+
+/**
+ * Handle a POST to /truth/$UUID.
+ *
+ * @param connection the MHD connection to handle
+ * @param con_cls the connection's closure
+ * @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
+ */
+int
+AH_handler_truth_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const char *truth_data,
+ size_t *truth_data_size);
+
+#endif
diff --git a/src/backend/anastasis-httpd_truth_upload.c b/src/backend/anastasis-httpd_truth_upload.c
new file mode 100644
index 0000000..9767087
--- /dev/null
+++ b/src/backend/anastasis-httpd_truth_upload.c
@@ -0,0 +1,855 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2019, 2021 Taler Systems SA
+
+ TALER 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.
+
+ TALER 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
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file anastasis-httpd_truth_upload.c
+ * @brief functions to handle incoming POST request on /truth
+ * @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 <taler/taler_json_lib.h>
+#include <taler/taler_merchant_service.h>
+#include <taler/taler_signatures.h>
+#include "anastasis_authorization_lib.h"
+
+
+/**
+ * Information we track per truth upload.
+ */
+struct TruthUploadContext
+{
+
+ /**
+ * UUID of the truth object we are processing.
+ */
+ struct ANASTASIS_CRYPTO_TruthUUIDP truth_uuid;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct TruthUploadContext *next;
+
+ /**
+ * Kept in DLL for shutdown handling while suspended.
+ */
+ struct TruthUploadContext *prev;
+
+ /**
+ * Used while we are awaiting proposal creation.
+ */
+ struct TALER_MERCHANT_PostOrdersHandle *po;
+
+ /**
+ * Used while we are waiting payment.
+ */
+ struct TALER_MERCHANT_OrderMerchantGetHandle *cpo;
+
+ /**
+ * Post parser context.
+ */
+ void *post_ctx;
+
+ /**
+ * Handle to the client request.
+ */
+ struct MHD_Connection *connection;
+
+ /**
+ * Incoming JSON, NULL if not yet available.
+ */
+ json_t *json;
+
+ /**
+ * HTTP response code to use on resume, if non-NULL.
+ */
+ struct MHD_Response *resp;
+
+ /**
+ * When should this request time out?
+ */
+ struct GNUNET_TIME_Absolute timeout;
+
+ /**
+ * Fee that is to be paid for this upload.
+ */
+ struct TALER_Amount upload_fee;
+
+ /**
+ * HTTP response code to use on resume, if resp is set.
+ */
+ unsigned int response_code;
+
+ /**
+ * For how many years must the customer still pay?
+ */
+ unsigned int years_to_pay;
+
+};
+
+
+/**
+ * Head of linked list over all truth upload processes
+ */
+static struct TruthUploadContext *tuc_head;
+
+/**
+ * Tail of linked list over all truth upload processes
+ */
+static struct TruthUploadContext *tuc_tail;
+
+
+void
+AH_truth_upload_shutdown (void)
+{
+ struct TruthUploadContext *tuc;
+
+ while (NULL != (tuc = tuc_head))
+ {
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ if (NULL != tuc->cpo)
+ {
+ TALER_MERCHANT_merchant_order_get_cancel (tuc->cpo);
+ tuc->cpo = NULL;
+ }
+ if (NULL != tuc->po)
+ {
+ TALER_MERCHANT_orders_post_cancel (tuc->po);
+ tuc->po = NULL;
+ }
+ MHD_resume_connection (tuc->connection);
+ }
+}
+
+
+/**
+ * Function called to clean up a `struct TruthUploadContext`.
+ *
+ * @param hc general handler context
+ */
+static void
+cleanup_truth_post (struct TM_HandlerContext *hc)
+{
+ struct TruthUploadContext *tuc = hc->ctx;
+
+ TALER_MHD_parse_post_cleanup_callback (tuc->post_ctx);
+ if (NULL != tuc->po)
+ TALER_MERCHANT_orders_post_cancel (tuc->po);
+ if (NULL != tuc->cpo)
+ TALER_MERCHANT_merchant_order_get_cancel (tuc->cpo);
+ if (NULL != tuc->resp)
+ MHD_destroy_response (tuc->resp);
+ if (NULL != tuc->json)
+ json_decref (tuc->json);
+ GNUNET_free (tuc);
+}
+
+
+/**
+ * Transmit a payment request for @a tuc.
+ *
+ * @param tuc upload context to generate payment request for
+ */
+static void
+make_payment_request (struct TruthUploadContext *tuc)
+{
+ struct MHD_Response *resp;
+
+ /* request payment via Taler */
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ GNUNET_assert (NULL != resp);
+ TALER_MHD_add_global_headers (resp);
+ {
+ char *hdr;
+ 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));
+ {
+ char *order_id;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof (tuc->truth_uuid));
+ GNUNET_asprintf (&hdr,
+ "%spay/%s%s/",
+ pfx,
+ hn,
+ order_id);
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Returning %u %s\n",
+ MHD_HTTP_PAYMENT_REQUIRED,
+ order_id);
+ GNUNET_free (order_id);
+ }
+ GNUNET_break (MHD_YES ==
+ MHD_add_response_header (resp,
+ ANASTASIS_HTTP_HEADER_TALER,
+ hdr));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "TRUTH payment request made: %s\n",
+ hdr);
+ GNUNET_free (hdr);
+ }
+ tuc->resp = resp;
+ tuc->response_code = MHD_HTTP_PAYMENT_REQUIRED;
+}
+
+
+/**
+ * Callbacks of this type are used to serve the result of submitting a
+ * POST /private/orders request to a merchant.
+ *
+ * @param cls our `struct TruthUploadContext`
+ * @param por response details
+ */
+static void
+proposal_cb (void *cls,
+ const struct TALER_MERCHANT_PostOrdersReply *por)
+{
+ struct TruthUploadContext *tuc = cls;
+
+ tuc->po = NULL;
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_resume_connection (tuc->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);
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_ORDER_CREATE_BACKEND_ERROR,
+ "hint",
+ "Failed to setup order with merchant backend",
+ "backend-ec",
+ (json_int_t) por->hr.ec,
+ "backend-http-status",
+ (json_int_t) por->hr.http_status,
+ "backend-reply",
+ por->hr.reply);
+ GNUNET_assert (NULL != tuc->resp);
+ tuc->response_code = MHD_HTTP_BAD_GATEWAY;
+ return;
+ }
+ make_payment_request (tuc);
+}
+
+
+/**
+ * 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;
+
+ tuc->cpo = NULL;
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking backend order status returned %u\n",
+ hr->http_status);
+ switch (hr->http_status)
+ {
+ case 0:
+ /* Likely timeout, complain! */
+ tuc->response_code = MHD_HTTP_GATEWAY_TIMEOUT;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_ANASTASIS_GENERIC_BACKEND_TIMEOUT,
+ NULL);
+ break;
+ case MHD_HTTP_OK:
+ switch (osr->status)
+ {
+ case TALER_MERCHANT_OSC_PAID:
+ {
+ enum GNUNET_DB_QueryStatus qs;
+ unsigned int years;
+ struct GNUNET_TIME_Relative paid_until;
+ const json_t *contract;
+ struct TALER_Amount amount;
+ struct GNUNET_JSON_Specification cspec[] = {
+ TALER_JSON_spec_amount ("amount",
+ AH_currency,
+ &amount),
+ GNUNET_JSON_spec_end ()
+ };
+
+ contract = osr->details.paid.contract_terms;
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (contract,
+ cspec,
+ NULL, NULL))
+ {
+ GNUNET_break (0);
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_MERCHANT_GENERIC_DB_CONTRACT_CONTENT_INVALID,
+ "contract terms in database are malformed");
+ break;
+ }
+ years = TALER_amount_divide2 (&amount,
+ &AH_truth_upload_fee);
+ paid_until = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_YEARS,
+ years);
+ /* add 1 week grace period, otherwise if a user
+ wants to pay for 1 year, the first seconds
+ would have passed between making the payment
+ and our subsequent check if +1 year was
+ paid... So we actually say 1 year = 52 weeks
+ on the server, while the client calculates
+ with 365 days. */
+ paid_until = GNUNET_TIME_relative_add (paid_until,
+ GNUNET_TIME_UNIT_WEEKS);
+ qs = db->record_truth_upload_payment (
+ db->cls,
+ &tuc->truth_uuid,
+ &osr->details.paid.deposit_total,
+ paid_until);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_error (
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "record_truth_upload_payment");
+ break;
+ }
+ }
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Payment confirmed, resuming upload\n");
+ break;
+ case TALER_MERCHANT_OSC_UNPAID:
+ case TALER_MERCHANT_OSC_CLAIMED:
+ make_payment_request (tuc);
+ break;
+ }
+ break;
+ case MHD_HTTP_UNAUTHORIZED:
+ /* Configuration issue, complain! */
+ tuc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED,
+ "hint",
+ TALER_ErrorCode_get_hint (
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_UNAUTHORIZED),
+ "backend-ec",
+ (json_int_t) hr->ec,
+ "backend-http-status",
+ (json_int_t) hr->http_status,
+ "backend-reply",
+ hr->reply);
+ GNUNET_assert (NULL != tuc->resp);
+ break;
+ case MHD_HTTP_NOT_FOUND:
+ /* Setup fresh order */
+ {
+ char *order_id;
+ json_t *order;
+
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof(tuc->truth_uuid));
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "%u, setting up fresh order %s\n",
+ MHD_HTTP_NOT_FOUND,
+ order_id);
+ order = json_pack ("{s:o, s:s, s:[{s:s,s:I,s:s}], s:s}",
+ "amount",
+ TALER_JSON_from_amount (&tuc->upload_fee),
+ "summary",
+ "Anastasis challenge storage fee",
+ "products",
+ "description", "challenge storage fee",
+ "quantity", (json_int_t) tuc->years_to_pay,
+ "unit", "years",
+
+ "order_id",
+ order_id);
+ GNUNET_free (order_id);
+ tuc->po = TALER_MERCHANT_orders_post2 (AH_ctx,
+ AH_backend_url,
+ order,
+ GNUNET_TIME_UNIT_ZERO,
+ NULL, /* no payment target */
+ 0,
+ NULL, /* no inventory products */
+ 0,
+ NULL, /* no uuids */
+ false, /* do NOT require claim token */
+ &proposal_cb,
+ tuc);
+ AH_trigger_curl ();
+ json_decref (order);
+ return;
+ }
+ default:
+ /* Unexpected backend response */
+ tuc->response_code = MHD_HTTP_BAD_GATEWAY;
+ tuc->resp = TALER_MHD_make_json_pack (
+ "{s:I, s:s, s:I, s:I, s:O?}",
+ "code",
+ (json_int_t) TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR,
+ "hint",
+ TALER_ErrorCode_get_hint (TALER_EC_ANASTASIS_GENERIC_BACKEND_ERROR),
+ "backend-ec",
+ (json_int_t) hr->ec,
+ "backend-http-status",
+ (json_int_t) hr->http_status,
+ "backend-reply",
+ hr->reply);
+ break;
+ }
+ GNUNET_CONTAINER_DLL_remove (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_resume_connection (tuc->connection);
+ AH_trigger_daemon (NULL);
+}
+
+
+/**
+ * Helper function used to ask our backend to begin processing a
+ * payment for the truth upload. May perform asynchronous operations
+ * by suspending the connection if required.
+ *
+ * @param tuc context to begin payment for.
+ * @return MHD status code
+ */
+static MHD_RESULT
+begin_payment (struct TruthUploadContext *tuc)
+{
+ char *order_id;
+ struct GNUNET_TIME_Relative timeout;
+
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Checking backend order status...\n");
+ timeout = GNUNET_TIME_absolute_get_remaining (tuc->timeout);
+ order_id = GNUNET_STRINGS_data_to_string_alloc (
+ &tuc->truth_uuid,
+ sizeof (tuc->truth_uuid));
+ tuc->cpo = TALER_MERCHANT_merchant_order_get (AH_ctx,
+ AH_backend_url,
+ order_id,
+ NULL /* our payments are NOT session-bound */,
+ false,
+ timeout,
+ &check_payment_cb,
+ tuc);
+ GNUNET_free (order_id);
+ if (NULL == tuc->cpo)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (tuc->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_ANASTASIS_GENERIC_PAYMENT_CHECK_START_FAILED,
+ "Could not check order status");
+ }
+ GNUNET_CONTAINER_DLL_insert (tuc_head,
+ tuc_tail,
+ tuc);
+ MHD_suspend_connection (tuc->connection);
+ return MHD_YES;
+}
+
+
+int
+AH_handler_truth_post (
+ struct MHD_Connection *connection,
+ struct TM_HandlerContext *hc,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const char *truth_data,
+ size_t *truth_data_size)
+{
+ struct TruthUploadContext *tuc = hc->ctx;
+ MHD_RESULT ret;
+ int res;
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP keyshare_data;
+ void *encrypted_truth;
+ size_t encrypted_truth_size;
+ const char *truth_mime;
+ const char *type;
+ enum GNUNET_DB_QueryStatus qs;
+ uint32_t storage_years;
+ struct GNUNET_TIME_Absolute paid_until;
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_fixed_auto ("keyshare_data",
+ &keyshare_data),
+ GNUNET_JSON_spec_string ("type",
+ &type),
+ GNUNET_JSON_spec_varsize ("encrypted_truth",
+ &encrypted_truth,
+ &encrypted_truth_size),
+ GNUNET_JSON_spec_string ("truth_mime",
+ &truth_mime),
+ GNUNET_JSON_spec_uint32 ("storage_duration_years",
+ &storage_years),
+ GNUNET_JSON_spec_end ()
+ };
+
+ if (NULL == tuc)
+ {
+ tuc = GNUNET_new (struct TruthUploadContext);
+ tuc->connection = connection;
+ 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;
+
+ if (1 != sscanf (long_poll_timeout_ms,
+ "%u",
+ &timeout))
+ {
+ 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);
+ }
+ }
+
+ } /* end 'if (NULL == tuc)' */
+
+ if (NULL != tuc->resp)
+ {
+ /* We generated a response asynchronously, queue that */
+ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
+ "Returning asynchronously generated response with HTTP status %u\n",
+ tuc->response_code);
+ ret = MHD_queue_response (connection,
+ tuc->response_code,
+ tuc->resp);
+ GNUNET_break (MHD_YES == ret);
+ MHD_destroy_response (tuc->resp);
+ tuc->resp = NULL;
+ return ret;
+ }
+
+ if (NULL == tuc->json)
+ {
+ res = TALER_MHD_parse_post_json (connection,
+ &tuc->post_ctx,
+ truth_data,
+ truth_data_size,
+ &tuc->json);
+ if (GNUNET_SYSERR == res)
+ {
+ GNUNET_break (0);
+ return MHD_NO;
+ }
+ if ( (GNUNET_NO == res) ||
+ (NULL == tuc->json) )
+ return MHD_YES;
+ }
+ res = TALER_MHD_parse_json_data (connection,
+ tuc->json,
+ 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 */
+ }
+
+ /* check method is supported */
+ {
+ struct TALER_Amount dummy;
+
+ if ( (0 != strcmp ("question",
+ type)) &&
+ (NULL ==
+ ANASTASIS_authorization_plugin_load (type,
+ AH_cfg,
+ &dummy)) )
+ {
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_BAD_REQUEST,
+ TALER_EC_ANASTASIS_TRUTH_UPLOAD_METHOD_NOT_SUPPORTED,
+ type);
+ }
+ }
+
+ if (storage_years > ANASTASIS_MAX_YEARS_STORAGE)
+ {
+ 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 == storage_years)
+ storage_years = 1;
+
+ {
+ 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_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)
+ 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) )
+ {
+ 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 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_INFO,
+ "Storing truth until %s!\n",
+ GNUNET_STRINGS_absolute_time_to_string (paid_until));
+ qs = db->store_truth (db->cls,
+ truth_uuid,
+ &keyshare_data,
+ truth_mime,
+ encrypted_truth,
+ encrypted_truth_size,
+ type,
+ GNUNET_TIME_absolute_get_remaining (paid_until));
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ GNUNET_JSON_parse_free (spec);
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
+ "store_truth");
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ void *xtruth;
+ size_t xtruth_size;
+ char *xtruth_mime;
+ char *xmethod;
+ bool ok = false;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
+ db->get_escrow_challenge (db->cls,
+ truth_uuid,
+ &xtruth,
+ &xtruth_size,
+ &xtruth_mime,
+ &xmethod))
+ {
+ ok = ( (xtruth_size == encrypted_truth_size) &&
+ (0 == strcmp (xmethod,
+ type)) &&
+ (0 == strcmp (truth_mime,
+ xtruth_mime)) &&
+ (0 == memcmp (xtruth,
+ encrypted_truth,
+ xtruth_size)) );
+ GNUNET_free (encrypted_truth);
+ GNUNET_free (xtruth_mime);
+ GNUNET_free (xmethod);
+ }
+ if (! ok)
+ {
+ GNUNET_JSON_parse_free (spec);
+
+ return TALER_MHD_reply_with_error (connection,
+ MHD_HTTP_CONFLICT,
+ TALER_EC_ANASTASIS_TRUTH_UPLOAD_UUID_EXISTS,
+ NULL);
+ }
+ /* idempotency detected, intentional fall through! */
+ }
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ struct MHD_Response *resp;
+
+ GNUNET_JSON_parse_free (spec);
+ resp = MHD_create_response_from_buffer (0,
+ NULL,
+ MHD_RESPMEM_PERSISTENT);
+ TALER_MHD_add_global_headers (resp);
+ ret = MHD_queue_response (connection,
+ MHD_HTTP_NO_CONTENT,
+ resp);
+ MHD_destroy_response (resp);
+ GNUNET_break (MHD_YES == ret);
+ return ret;
+ }
+ }
+ GNUNET_JSON_parse_free (spec);
+ GNUNET_break (0);
+ return MHD_NO;
+}
diff --git a/src/backend/anastasis.conf b/src/backend/anastasis.conf
new file mode 100644
index 0000000..ddc1a65
--- /dev/null
+++ b/src/backend/anastasis.conf
@@ -0,0 +1,77 @@
+# This file is in the public domain.
+
+# These are default/sample settings for a merchant backend.
+
+
+# General settings for the backend.
+[anastasis]
+
+# Use TCP or UNIX domain sockets?
+SERVE = tcp
+
+# Which HTTP port does the backend listen on? Only used if "SERVE" is 'tcp'.
+PORT = 9977
+
+# Which IP address should we bind to? i.e. 127.0.0.1 or ::1 for loopback.
+# Can also be given as a hostname. We will bind to the wildcard (dual-stack)
+# if left empty. Only used if "SERVE" is 'tcp'.
+# BIND_TO =
+
+
+# Which unix domain path should we bind to? Only used if "SERVE" is 'unix'.
+UNIXPATH = ${ANASTASIS_RUNTIME_DIR}/backend.http
+# What should be the file access permissions (see chmod) for "UNIXPATH"?
+UNIXPATH_MODE = 660
+
+# Which database backend do we use?
+DB = postgres
+
+# Annual fee for an account
+# ANNUAL_FEE = TESTKUDOS:0.1
+
+# Number of policy uploads included in one annual fee payment
+ANNUAL_POLICY_UPLĂ„OAD_LIMIT = 64
+
+# Insurance
+# INSURANCE = TESTKUDOS:1.0
+
+# Upload limit per backup, in megabytes
+UPLOAD_LIMIT_MB = 16
+
+# Authentication costs
+
+# Cost of authentication by question
+#QUESTION_COST = EUR:0
+
+# Cost of authentication by file (only for testing purposes)
+#FILE_COST = EUR:1
+
+# Cost of authentication by E-Mail
+#EMAIL_COST = EUR:0
+
+# Cost of authentication by SMS
+#SMS_COST = EUR:0
+
+# Cost of authentication by postal
+#POSTAL_COST = EUR:0
+
+# Cost of authentication by video
+#VIDEO_COST = EUR:0
+
+#SMS authentication command which is executed
+#SMSAUTH_COMMAND = some_sms_script.sh
+
+#E-Mail authentication command which is executed
+#EMAILAUTH_COMMAND = some_email_script.sh
+
+# Fulfillment URL of the ANASTASIS service itself.
+FULFILLMENT_URL = taler://fulfillment-success
+
+# Base URL of our payment backend
+# PAYMENT_BACKEND_URL = http://localhost:9976/
+
+# Server salt 16 Byte
+# SERVER_SALT = gUfO1KGOKYIFlFQg
+
+# Supported methods
+SUPPORTED_METHODS = question