summaryrefslogtreecommitdiff
path: root/src/stasis/plugin_anastasis_postgres.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/stasis/plugin_anastasis_postgres.c')
-rw-r--r--src/stasis/plugin_anastasis_postgres.c2301
1 files changed, 2301 insertions, 0 deletions
diff --git a/src/stasis/plugin_anastasis_postgres.c b/src/stasis/plugin_anastasis_postgres.c
new file mode 100644
index 0000000..4aba97c
--- /dev/null
+++ b/src/stasis/plugin_anastasis_postgres.c
@@ -0,0 +1,2301 @@
+/*
+ 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 anastasis/plugin_anastasisdb_postgres.c
+ * @brief database helper functions for postgres used by the anastasis
+ * @author Sree Harsha Totakura <sreeharsha@totakura.in>
+ * @author Christian Grothoff
+ * @author Marcello Stanisci
+ */
+#include "platform.h"
+#include "anastasis_database_plugin.h"
+#include "anastasis_database_lib.h"
+#include <taler/taler_pq_lib.h>
+
+/**
+ * How long do we keep transient accounts open (those that have
+ * not been paid at all, but are awaiting payment). This puts
+ * a cap on how long users have to make a payment after a payment
+ * request was generated.
+ */
+#define TRANSIENT_LIFETIME GNUNET_TIME_UNIT_WEEKS
+
+/**
+ * How often do we re-try if we run into a DB serialization error?
+ */
+#define MAX_RETRIES 3
+
+
+/**
+ * Type of the "cls" argument given to each of the functions in
+ * our API.
+ */
+struct PostgresClosure
+{
+
+ /**
+ * Postgres connection handle.
+ */
+ struct GNUNET_PQ_Context *conn;
+
+ /**
+ * Underlying configuration.
+ */
+ const struct GNUNET_CONFIGURATION_Handle *cfg;
+
+ /**
+ * Name of the currently active transaction, NULL if none is active.
+ */
+ const char *transaction_name;
+
+ /**
+ * Currency we accept payments in.
+ */
+ char *currency;
+
+};
+
+
+/**
+ * Drop anastasis tables
+ *
+ * @param cls closure our `struct Plugin`
+ * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure
+ */
+static int
+postgres_drop_tables (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_Context *conn;
+
+ conn = GNUNET_PQ_connect_with_cfg (pg->cfg,
+ "stasis-postgres",
+ "drop",
+ NULL,
+ NULL);
+ if (NULL == conn)
+ return GNUNET_SYSERR;
+ GNUNET_PQ_disconnect (conn);
+ return GNUNET_OK;
+}
+
+
+/**
+ * Check that the database connection is still up.
+ *
+ * @param pg connection to check
+ */
+static void
+check_connection (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+
+ GNUNET_PQ_reconnect_if_down (pg->conn);
+}
+
+
+/**
+ * Do a pre-flight check that we are not in an uncommitted transaction.
+ * If we are, try to commit the previous transaction and output a warning.
+ * Does not return anything, as we will continue regardless of the outcome.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ */
+static void
+postgres_preflight (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("COMMIT"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (NULL == pg->transaction_name)
+ return; /* all good */
+ if (GNUNET_OK ==
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check committed transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ else
+ {
+ GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
+ "BUG: Preflight check failed to commit transaction `%s'!\n",
+ pg->transaction_name);
+ }
+ pg->transaction_name = NULL;
+}
+
+
+/**
+ * Start a transaction.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @param name unique name identifying the transaction (for debugging),
+ * must point to a constant
+ * @return #GNUNET_OK on success
+ */
+static int
+begin_transaction (void *cls,
+ const char *name)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("START TRANSACTION ISOLATION LEVEL SERIALIZABLE"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ pg->transaction_name = name;
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to start transaction\n");
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
+* Roll back the current transaction of a database connection.
+*
+* @param cls the `struct PostgresClosure` with the plugin-specific state
+* @return #GNUNET_OK on success
+*/
+static void
+rollback (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_ExecuteStatement es[] = {
+ GNUNET_PQ_make_execute ("ROLLBACK"),
+ GNUNET_PQ_EXECUTE_STATEMENT_END
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_exec_statements (pg->conn,
+ es))
+ {
+ TALER_LOG_ERROR ("Failed to rollback transaction\n");
+ GNUNET_break (0);
+ }
+ pg->transaction_name = NULL;
+}
+
+
+/**
+ * Commit the current transaction of a database connection.
+ *
+ * @param cls the `struct PostgresClosure` with the plugin-specific state
+ * @return transaction status code
+ */
+static enum GNUNET_DB_QueryStatus
+commit_transaction (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_QueryParam no_params[] = {
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "do_commit",
+ no_params);
+ pg->transaction_name = NULL;
+ return qs;
+}
+
+
+/**
+ * Function called to perform "garbage collection" on the
+ * database, expiring records we no longer require. Deletes
+ * all user records that are not paid up (and by cascade deletes
+ * the associated recovery documents). Also deletes expired
+ * truth and financial records older than @a fin_expire.
+ *
+ * @param cls closure
+ * @param expire_backups backups older than the given time stamp should be garbage collected
+ * @param expire_pending_payments payments still pending from since before
+ * this value should be garbage collected
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_gc (void *cls,
+ struct GNUNET_TIME_Absolute expire_backups,
+ struct GNUNET_TIME_Absolute expire_pending_payments)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expire_backups),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_QueryParam params2[] = {
+ GNUNET_PQ_query_param_absolute_time (&expire_pending_payments),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_accounts",
+ params);
+ if (qs < 0)
+ return qs;
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_recdoc_pending_payments",
+ params2);
+}
+
+
+/**
+ * Store encrypted recovery document.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature affirming storage request
+ * @param data_hash hash of @a data
+ * @param data contains encrypted_recovery_document
+ * @param data_size size of data blob
+ * @param payment_secret identifier for the payment, used to later charge on uploads
+ * @param[out] version set to the version assigned to the document by the database
+ * @return transaction status, 0 if upload could not be finished because @a payment_secret
+ * did not have enough upload left; HARD error if @a payment_secret is unknown, ...
+ */
+static enum ANASTASIS_DB_StoreStatus
+postgres_store_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_AccountSignatureP *account_sig,
+ const struct GNUNET_HashCode *recovery_data_hash,
+ const void *recovery_data,
+ size_t recovery_data_size,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ for (unsigned int retry = 0; retry<MAX_RETRIES; retry++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "store_recovery_document"))
+ {
+ GNUNET_break (0);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ }
+ /* get the current version and hash of the latest recovery document
+ for this account */
+ {
+ struct GNUNET_HashCode dh;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ &dh),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recovery_version_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ *version = 1;
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* had an existing recovery_data, is it identical? */
+ if (0 == GNUNET_memcmp (&dh,
+ recovery_data_hash))
+ {
+ /* Yes. Previous identical recovery data exists */
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_NO_RESULTS;
+ }
+ (*version)++;
+ break;
+ default:
+ rollback (pg);
+ return qs;
+ }
+ }
+
+ /* First, check if account exists */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_PAYMENT_REQUIRED;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handle interesting case below */
+ break;
+ }
+
+ {
+ uint32_t postcounter;
+
+ /* lookup if the user has enough uploads left and decrement */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("post_counter",
+ &postcounter),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "postcounter_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+
+ if (0 == postcounter)
+ {
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_STORE_LIMIT_EXCEEDED;
+ }
+ /* Decrement the postcounter by one */
+ postcounter--;
+
+ /* Update the postcounter in the Database */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint32 (&postcounter),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "postcounter_update",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ default:
+ rollback (pg);
+ return qs;
+ }
+ }
+ }
+
+ /* finally, actually insert the recovery document */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (version),
+ GNUNET_PQ_query_param_auto_from_type (account_sig),
+ GNUNET_PQ_query_param_auto_from_type (recovery_data_hash),
+ GNUNET_PQ_query_param_fixed_size (recovery_data,
+ recovery_data_size),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recovery_document_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return ANASTASIS_DB_STORE_STATUS_HARD_ERROR;
+ return ANASTASIS_DB_STORE_STATUS_SUCCESS;
+ }
+ }
+retry:
+ rollback (pg);
+ }
+ return ANASTASIS_DB_STORE_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Increment account lifetime.
+ *
+ * @param cls closure
+ * @param anastasis_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param lifetime for how long is the account now paid (increment)
+ * @param[out] paid_until set to the end of the lifetime after the operation
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_increment_lifetime (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Relative lifetime,
+ struct GNUNET_TIME_Absolute *paid_until)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "increment lifetime"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_done",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ *paid_until = GNUNET_TIME_UNIT_ZERO_ABS;
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* continued below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* continued below */
+ break;
+ }
+ }
+
+ {
+ enum GNUNET_DB_QueryStatus qs2;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs2 = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ switch (qs2)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs2;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* inconsistent, cannot have recdoc payment but no user!? */
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+ else
+ {
+ /* user does not exist, create new one */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_relative_to_absolute (lifetime);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ *paid_until = expiration;
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ {
+ /* existing rec doc payment, return expiration */
+ *paid_until = expiration;
+ rollback (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment existed, lifetime of account %s unchanged at %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (*paid_until));
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ else
+ {
+ /* user exists, update expiration_date */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_absolute_add (expiration,
+ lifetime);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ *paid_until = expiration;
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_update",
+ params);
+ }
+ break;
+ }
+ }
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Incremented lifetime of account %s to %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (*paid_until));
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Update account lifetime to the maximum of the current
+ * value and @a eol.
+ *
+ * @param cls closure
+ * @param account_pub which account received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @param eol for how long is the account now paid (absolute)
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_update_lifetime (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier,
+ struct GNUNET_TIME_Absolute eol)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "update lifetime"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_done",
+ params);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (0 >= qs)
+ {
+ /* same payment made before, or unknown, or error
+ => no further action! */
+ rollback (pg);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Payment existed, lifetime of account %s unchanged\n",
+ TALER_B2S (anastasis_pub));
+ return qs;
+ }
+ }
+
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ /* user does not exist, create new one */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&eol),
+ GNUNET_PQ_query_param_end
+ };
+
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ eol.abs_value_us);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created new account %s with expiration %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (eol));
+ }
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ {
+ /* user exists, update expiration_date */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+
+ expiration = GNUNET_TIME_absolute_max (expiration,
+ eol);
+ GNUNET_break (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us !=
+ expiration.abs_value_us);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_update",
+ params);
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Updated account %s to new expiration %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (expiration));
+ }
+ break;
+ }
+ }
+
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Store payment. Used to begin a payment, not indicative
+ * that the payment actually was made. (That is done
+ * when we increment the account's lifetime.)
+ *
+ * @param cls closure
+ * @param anastasis_pub anastasis's public key
+ * @param post_counter how many uploads does @a amount pay for
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_recdoc_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t post_counter,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute expiration;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (&post_counter),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+
+ /* because of constraint at user_id, first we have to verify
+ if user exists, and if not, create one */
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ &expiration),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ {
+ /* create new user with short lifetime */
+ struct GNUNET_TIME_Absolute exp
+ = GNUNET_TIME_relative_to_absolute (TRANSIENT_LIFETIME);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_absolute_time (&exp),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "user_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* successful, continue below */
+ GNUNET_log (GNUNET_ERROR_TYPE_INFO,
+ "Created new account %s with transient life until %s\n",
+ TALER_B2S (anastasis_pub),
+ GNUNET_STRINGS_absolute_time_to_string (exp));
+ break;
+ }
+ }
+ /* continue below */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* handle case below */
+ break;
+ }
+
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "recdoc_payment_insert",
+ params);
+}
+
+
+/**
+ * Record truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param amount the amount that was paid
+ * @param duration how long is the truth paid for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_truth_upload_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ const struct TALER_Amount *amount,
+ struct GNUNET_TIME_Relative duration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute exp = GNUNET_TIME_relative_to_absolute (duration);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_absolute_time (&exp),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "truth_payment_insert",
+ params);
+}
+
+
+/**
+ * Inquire whether truth upload payment was made.
+ *
+ * @param cls closure
+ * @param uuid the truth's UUID
+ * @param[out] paid_until set for how long this truth is paid for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_truth_upload_paid (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *uuid,
+ struct GNUNET_TIME_Absolute *paid_until)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (uuid),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration",
+ paid_until),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "truth_payment_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Store payment for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param amount how much we asked for
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct TALER_Amount *amount)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_amount (amount),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_payment_insert",
+ params);
+}
+
+
+/**
+ * Store refund granted for challenge.
+ *
+ * @param cls closure
+ * @param truth_key identifier of the challenge to pay
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_record_challenge_refund (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_secret)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_refund_update",
+ params);
+}
+
+
+/**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ bool *paid)
+{
+ struct PostgresClosure *pg = cls;
+ uint8_t paid8;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challenge_payment_select",
+ params,
+ rs);
+ *paid = (0 != paid8);
+ return qs;
+}
+
+
+/**
+ * Check payment identifier. Used to check if a payment identifier given by
+ * the user is valid (existing and paid).
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param[out] paid bool value to show if payment is paid
+ * @param[out] valid_counter bool value to show if post_counter is > 0
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_check_payment_identifier (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ bool *paid,
+ bool *valid_counter)
+{
+ struct PostgresClosure *pg = cls;
+ uint32_t counter;
+ uint8_t paid8;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("paid",
+ &paid8),
+ GNUNET_PQ_result_spec_uint32 ("post_counter",
+ &counter),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "recdoc_payment_select",
+ params,
+ rs);
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == qs)
+ {
+ if (counter > 0)
+ *valid_counter = true;
+ else
+ *valid_counter = false;
+ *paid = (0 != paid8);
+ }
+ return qs;
+}
+
+
+/**
+ * Upload Truth, which contains the Truth and the KeyShare.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param key_share_data contains information of an EncryptedKeyShare
+ * @param method name of method
+ * @param nonce nonce used to compute encryption key for encrypted_truth
+ * @param aes_gcm_tag authentication tag of encrypted_truth
+ * @param encrypted_truth contains the encrypted Truth which includes the ground truth i.e. H(challenge answer), phonenumber, SMS
+ * @param encrypted_truth_size the size of the Truth
+ * @param truth_expiration time till the according data will be stored
+ * @return transaction status
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_store_truth (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share_data,
+ const char *mime_type,
+ const void *encrypted_truth,
+ size_t encrypted_truth_size,
+ const char *method,
+ struct GNUNET_TIME_Relative truth_expiration)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute expiration = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_auto_from_type (key_share_data),
+ GNUNET_PQ_query_param_string (method),
+ GNUNET_PQ_query_param_fixed_size (encrypted_truth,
+ encrypted_truth_size),
+ GNUNET_PQ_query_param_string (mime_type),
+ TALER_PQ_query_param_absolute_time (&expiration),
+ GNUNET_PQ_query_param_end
+ };
+
+
+ expiration = GNUNET_TIME_absolute_add (expiration,
+ truth_expiration);
+ GNUNET_TIME_round_abs (&expiration);
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "truth_insert",
+ params);
+}
+
+
+/**
+ * Get the encrypted truth to validate the challenge response
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] truth contains the encrypted truth
+ * @param[out] truth_size size of the encrypted truth
+ * @param[out] truth_mime mime type of truth
+ * @param[out] method type of the challenge
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_escrow_challenge (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ void **truth,
+ size_t *truth_size,
+ char **truth_mime,
+ char **method)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_variable_size ("encrypted_truth",
+ truth,
+ truth_size),
+ GNUNET_PQ_result_spec_string ("truth_mime",
+ truth_mime),
+ GNUNET_PQ_result_spec_string ("method_name",
+ method),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "truth_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Lookup (encrypted) key share by @a truth_uuid.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the Truth
+ * @param[out] key_share contains the encrypted Keyshare
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_key_share (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_CRYPTO_EncryptedKeyShareP *key_share)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("key_share_data",
+ key_share),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "key_share_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Check if an account exists, and if so, return the
+ * current @a recovery_document_hash.
+ *
+ * @param cls closure
+ * @param anastasis_pub account identifier
+ * @param[out] paid_until until when is the account paid up?
+ * @param[out] recovery_data_hash set to hash of @a recovery document
+ * @param[out] version set to the recovery policy version
+ * @return transaction status
+ */
+enum ANASTASIS_DB_AccountStatus
+postgres_lookup_account (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct GNUNET_TIME_Absolute *paid_until,
+ struct GNUNET_HashCode *recovery_data_hash,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ paid_until),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recovery_version_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ break; /* handle interesting case below */
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ return ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED;
+ }
+
+ /* check if account exists */
+ {
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_absolute_time ("expiration_date",
+ paid_until),
+ GNUNET_PQ_result_spec_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "user_select",
+ params,
+ rs);
+ }
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* indicates: no account */
+ return ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ /* indicates: no backup */
+ *version = UINT32_MAX;
+ memset (recovery_data_hash,
+ 0,
+ sizeof (*recovery_data_hash));
+ return ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS;
+ default:
+ GNUNET_break (0);
+ return ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR;
+ }
+}
+
+
+/**
+ * Fetch latest recovery document for user.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param account_sig signature
+ * @param recovery_data_hash hash of the current recovery data
+ * @param data_size size of data blob
+ * @param data blob which contains the recovery document
+ * @param version[OUT] set to the version number of the policy being returned
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_latest_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data,
+ uint32_t *version)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint32 ("version",
+ version),
+ GNUNET_PQ_result_spec_auto_from_type ("account_sig",
+ account_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_variable_size ("recovery_data",
+ data,
+ data_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "latest_recoverydocument_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Fetch recovery document for user according given version.
+ *
+ * @param cls closure
+ * @param anastasis_pub public key of the user's account
+ * @param version the version number of the policy the user requests
+ * @param[out] account_sig signature
+ * @param[out] recovery_data_hash hash of the current recovery data
+ * @param[out] data_size size of data blob
+ * @param[out] data blob which contains the recovery document
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_get_recovery_document (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_AccountPublicKeyP *anastasis_pub,
+ uint32_t version,
+ struct ANASTASIS_AccountSignatureP *account_sig,
+ struct GNUNET_HashCode *recovery_data_hash,
+ size_t *data_size,
+ void **data)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (anastasis_pub),
+ GNUNET_PQ_query_param_uint32 (&version),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("account_sig",
+ account_sig),
+ GNUNET_PQ_result_spec_auto_from_type ("recovery_data_hash",
+ recovery_data_hash),
+ GNUNET_PQ_result_spec_variable_size ("recovery_data",
+ data,
+ data_size),
+ GNUNET_PQ_result_spec_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "recoverydocument_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Closure for check_valid_code().
+ */
+struct CheckValidityContext
+{
+ /**
+ * Code to check for.
+ */
+ const struct GNUNET_HashCode *hashed_code;
+
+ /**
+ * Truth we are processing.
+ */
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid;
+
+ /**
+ * Database context.
+ */
+ struct PostgresClosure *pg;
+
+ /**
+ * Set to true if a code matching @e hashed_code was found.
+ */
+ bool valid;
+
+ /**
+ * Set to true if we had a database failure.
+ */
+ bool db_failure;
+
+};
+
+
+/**
+ * Helper function for #postgres_verify_challenge_code().
+ * To be called with the results of a SELECT statement
+ * that has returned @a num_results results.
+ *
+ * @param cls closure of type `struct CheckValidityContext *`
+ * @param result the postgres result
+ * @param num_result the number of results in @a result
+ */
+static void
+check_valid_code (void *cls,
+ PGresult *result,
+ unsigned int num_results)
+{
+ struct CheckValidityContext *cvc = cls;
+ struct PostgresClosure *pg = cvc->pg;
+
+ for (unsigned int i = 0; i < num_results; i++)
+ {
+ uint64_t server_code;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("code",
+ &server_code),
+ GNUNET_PQ_result_spec_end
+ };
+
+ if (GNUNET_OK !=
+ GNUNET_PQ_extract_result (result,
+ rs,
+ i))
+ {
+ GNUNET_break (0);
+ cvc->db_failure = true;
+ return;
+ }
+ {
+ struct GNUNET_HashCode shashed_code;
+
+ ANASTASIS_hash_answer (server_code,
+ &shashed_code);
+ if (0 ==
+ GNUNET_memcmp (&shashed_code,
+ cvc->hashed_code))
+ {
+ cvc->valid = true;
+ }
+ else
+ {
+ /* count failures to prevent brute-force attacks */
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (cvc->truth_uuid),
+ GNUNET_PQ_query_param_uint64 (&server_code),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_update_retry",
+ params);
+ if (qs <= 0)
+ {
+ GNUNET_break (0);
+ cvc->db_failure = true;
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * Verify the provided code with the code on the server.
+ * If the code matches the function will return with success, if the code
+ * does not match, the retry counter will be decreased by one.
+ *
+ * @param cls closure
+ * @param truth_pub identification of the challenge which the code corresponds to
+ * @param hashed_code code which the user provided and wants to verify
+ * @return code validity status
+ */
+enum ANASTASIS_DB_CodeStatus
+postgres_verify_challenge_code (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct GNUNET_HashCode *hashed_code)
+{
+ struct PostgresClosure *pg = cls;
+ struct CheckValidityContext cvc = {
+ .truth_uuid = truth_uuid,
+ .hashed_code = hashed_code,
+ .pg = pg
+ };
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ GNUNET_TIME_round_abs (&now);
+ qs = GNUNET_PQ_eval_prepared_multi_select (pg->conn,
+ "challengecode_select",
+ params,
+ &check_valid_code,
+ &cvc);
+ if ( (qs < 0) ||
+ (cvc.db_failure) )
+ return ANASTASIS_DB_CODE_STATUS_HARD_ERROR;
+ if (cvc.valid)
+ return ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED;
+ if (0 == qs)
+ return ANASTASIS_DB_CODE_STATUS_NO_RESULTS;
+ return ANASTASIS_DB_CODE_STATUS_CHALLENGE_CODE_MISMATCH;
+}
+
+
+/**
+ * Lookup pending payment for a certain challenge.
+ *
+ * @param cls closure
+ * @param truth_uuid identification of the challenge
+ * @param[out] payment_secret set to the challenge payment secret
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_lookup_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct ANASTASIS_PaymentSecretP *payment_secret)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute recent
+ = GNUNET_TIME_absolute_subtract (now,
+ ANASTASIS_CHALLENGE_OFFER_LIFETIME);
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_absolute_time (&recent),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_auto_from_type ("payment_identifier",
+ payment_secret),
+ GNUNET_PQ_result_spec_end
+ };
+
+ return GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challenge_pending_payment_select",
+ params,
+ rs);
+}
+
+
+/**
+ * Update payment status of challenge
+ *
+ * @param cls closure
+ * @param truth_uuid which challenge received a payment
+ * @param payment_identifier proof of payment, must be unique and match pending payment
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_update_challenge_payment (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ const struct ANASTASIS_PaymentSecretP *payment_identifier)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (payment_identifier),
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challenge_payment_done",
+ params);
+}
+
+
+/**
+ * Create a new challenge code for a given challenge identified by the challenge
+ * public key. The function will first check if there is already a valid code
+ * for this challenge present and won't insert a new one in this case.
+ *
+ * @param cls closure
+ * @param truth_uuid the identifier for the challenge
+ * @param rotation_period for how long is the code available
+ * @param validity_period for how long is the code available
+ * @param retry_counter amount of retries allowed
+ * @param[out] retransmission_date when to next retransmit
+ * @param[out] code set to the code which will be checked for later
+ * @return transaction status,
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if we are out of valid tries,
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if @a code is now in the DB
+ */
+enum GNUNET_DB_QueryStatus
+postgres_create_challenge_code (
+ void *cls,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ struct GNUNET_TIME_Relative rotation_period,
+ struct GNUNET_TIME_Relative validity_period,
+ unsigned int retry_counter,
+ struct GNUNET_TIME_Absolute *retransmission_date,
+ uint64_t *code)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute expiration_date;
+ struct GNUNET_TIME_Absolute ex_rot;
+
+ check_connection (pg);
+ GNUNET_TIME_round_abs (&now);
+ expiration_date = GNUNET_TIME_absolute_add (now,
+ validity_period);
+ ex_rot = GNUNET_TIME_absolute_subtract (now,
+ rotation_period);
+ for (unsigned int retries = 0; retries<MAX_RETRIES; retries++)
+ {
+ if (GNUNET_OK !=
+ begin_transaction (pg,
+ "create_challenge_code"))
+ {
+ GNUNET_break (0);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ }
+
+ {
+ uint32_t old_retry_counter;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ TALER_PQ_query_param_absolute_time (&now),
+ TALER_PQ_query_param_absolute_time (&ex_rot),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("code",
+ code),
+ GNUNET_PQ_result_spec_uint32 ("retry_counter",
+ &old_retry_counter),
+ GNUNET_PQ_result_spec_absolute_time ("retransmission_date",
+ retransmission_date),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
+ "challengecode_select_meta",
+ params,
+ rs);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ GNUNET_break (0);
+ rollback (pg);
+ return qs;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ /* no active challenge, create fresh one (below) */
+ break;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ if (0 == old_retry_counter)
+ {
+ rollback (pg);
+ return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
+ }
+ rollback (pg);
+ return qs;
+ }
+ }
+
+ *code = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
+ INT64_MAX);
+ *retransmission_date = GNUNET_TIME_UNIT_ZERO_ABS;
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_uint64 (code),
+ TALER_PQ_query_param_absolute_time (&now),
+ TALER_PQ_query_param_absolute_time (&expiration_date),
+ GNUNET_PQ_query_param_uint32 (&retry_counter),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_insert",
+ params);
+ switch (qs)
+ {
+ case GNUNET_DB_STATUS_HARD_ERROR:
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SOFT_ERROR:
+ goto retry;
+ case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
+ GNUNET_break (0);
+ rollback (pg);
+ return GNUNET_DB_STATUS_HARD_ERROR;
+ case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
+ break;
+ }
+ }
+ qs = commit_transaction (pg);
+ if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
+ goto retry;
+ if (qs < 0)
+ return qs;
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
+retry:
+ rollback (pg);
+ }
+ return GNUNET_DB_STATUS_SOFT_ERROR;
+}
+
+
+/**
+ * Remember in the database that we successfully sent a challenge.
+ *
+ * @param cls closure
+ * @param payment_secret payment secret which the user must provide with every upload
+ * @param truth_uuid the identifier for the challenge
+ * @param code the challenge that was sent
+ */
+static enum GNUNET_DB_QueryStatus
+postgres_mark_challenge_sent (
+ void *cls,
+ const struct ANASTASIS_PaymentSecretP *payment_secret,
+ const struct ANASTASIS_CRYPTO_TruthUUIDP *truth_uuid,
+ uint64_t code)
+{
+ struct PostgresClosure *pg = cls;
+ enum GNUNET_DB_QueryStatus qs;
+
+ check_connection (pg);
+ {
+ struct GNUNET_TIME_Absolute now;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_uint64 (&code),
+ TALER_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_end
+ };
+
+ now = GNUNET_TIME_absolute_get ();
+ GNUNET_TIME_round_abs (&now);
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengecode_mark_sent",
+ params);
+ if (qs <= 0)
+ return qs;
+ }
+ {
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (truth_uuid),
+ GNUNET_PQ_query_param_auto_from_type (payment_secret),
+ GNUNET_PQ_query_param_end
+ };
+
+ qs = GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "challengepayment_dec_counter",
+ params);
+ if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
+ return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT; /* probably was free */
+ return qs;
+ }
+}
+
+
+/**
+ * Function called to remove all expired codes from the database.
+ * FIXME maybe implemented as part of postgres_gc() in the future.
+ *
+ * @return transaction status
+ */
+enum GNUNET_DB_QueryStatus
+postgres_challenge_gc (void *cls)
+{
+ struct PostgresClosure *pg = cls;
+ struct GNUNET_TIME_Absolute time_now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_absolute_time (&time_now),
+ GNUNET_PQ_query_param_end
+ };
+
+ check_connection (pg);
+ postgres_preflight (pg);
+ return GNUNET_PQ_eval_prepared_non_select (pg->conn,
+ "gc_challengecodes",
+ params);
+}
+
+
+/**
+ * Initialize Postgres database subsystem.
+ *
+ * @param cls a configuration instance
+ * @return NULL on error, otherwise a `struct TALER_ANASTASISDB_Plugin`
+ */
+void *
+libanastasis_plugin_db_postgres_init (void *cls)
+{
+ struct GNUNET_CONFIGURATION_Handle *cfg = cls;
+ struct PostgresClosure *pg;
+ struct ANASTASIS_DatabasePlugin *plugin;
+ struct GNUNET_PQ_PreparedStatement ps[] = {
+ GNUNET_PQ_make_prepare ("user_insert",
+ "INSERT INTO anastasis_user "
+ "(user_id"
+ ",expiration_date"
+ ") VALUES "
+ "($1, $2);",
+ 2),
+ GNUNET_PQ_make_prepare ("do_commit",
+ "COMMIT",
+ 0),
+ GNUNET_PQ_make_prepare ("user_select",
+ "SELECT"
+ " expiration_date "
+ "FROM anastasis_user"
+ " WHERE user_id=$1"
+ " FOR UPDATE;",
+ 1),
+ GNUNET_PQ_make_prepare ("user_update",
+ "UPDATE anastasis_user"
+ " SET "
+ " expiration_date=$1"
+ " WHERE user_id=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("recdoc_payment_insert",
+ "INSERT INTO anastasis_recdoc_payment "
+ "(user_id"
+ ",post_counter"
+ ",amount_val"
+ ",amount_frac"
+ ",payment_identifier"
+ ",creation_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);",
+ 6),
+ GNUNET_PQ_make_prepare ("challenge_payment_insert",
+ "INSERT INTO anastasis_challenge_payment "
+ "(truth_uuid"
+ ",amount_val"
+ ",amount_frac"
+ ",payment_identifier"
+ ",creation_date"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("truth_payment_insert",
+ "INSERT INTO anastasis_truth_payment "
+ "(truth_uuid"
+ ",amount_val"
+ ",amount_frac"
+ ",expiration"
+ ") VALUES "
+ "($1, $2, $3, $4);",
+ 4),
+ GNUNET_PQ_make_prepare ("recdoc_payment_done",
+ "UPDATE anastasis_recdoc_payment "
+ "SET"
+ " paid=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " user_id=$2"
+ " AND"
+ " paid=FALSE;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_refund_update",
+ "UPDATE anastasis_challenge_payment "
+ "SET"
+ " refunded=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " paid=TRUE"
+ " AND"
+ " truth_uuid=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_payment_done",
+ "UPDATE anastasis_challenge_payment "
+ "SET"
+ " paid=TRUE "
+ "WHERE"
+ " payment_identifier=$1"
+ " AND"
+ " refunded=FALSE"
+ " AND"
+ " truth_uuid=$2"
+ " AND"
+ " paid=FALSE;",
+ 2),
+ GNUNET_PQ_make_prepare ("recdoc_payment_select",
+ "SELECT"
+ " creation_date"
+ ",post_counter"
+ ",amount_val"
+ ",amount_frac"
+ ",paid"
+ " FROM anastasis_recdoc_payment"
+ " WHERE payment_identifier=$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("truth_payment_select",
+ "SELECT"
+ " expiration"
+ " FROM anastasis_truth_payment"
+ " WHERE truth_uuid=$1"
+ " AND expiration>$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("challenge_payment_select",
+ "SELECT"
+ " creation_date"
+ ",amount_val"
+ ",amount_frac"
+ ",paid"
+ " FROM anastasis_challenge_payment"
+ " WHERE payment_identifier=$1"
+ " AND truth_uuid=$2"
+ " AND refunded=FALSE"
+ " AND counter>0;",
+ 1),
+ GNUNET_PQ_make_prepare ("challenge_pending_payment_select",
+ "SELECT"
+ " creation_date"
+ ",payment_identifier"
+ ",amount_val"
+ ",amount_frac"
+ " FROM anastasis_challenge_payment"
+ " WHERE"
+ " paid=FALSE"
+ " AND"
+ " refunded=FALSE"
+ " AND"
+ " truth_uuid=$1"
+ " AND"
+ " creation_date > $2;",
+ 1),
+ GNUNET_PQ_make_prepare ("recdoc_payments_select",
+ "SELECT"
+ " user_id"
+ ",payment_identifier"
+ ",amount_val"
+ ",amount_frac"
+ " FROM anastasis_recdoc_payment"
+ " WHERE paid=FALSE;",
+ 0),
+ GNUNET_PQ_make_prepare ("gc_accounts",
+ "DELETE FROM anastasis_user "
+ "WHERE"
+ " expiration_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("gc_recdoc_pending_payments",
+ "DELETE FROM anastasis_recdoc_payment "
+ "WHERE"
+ " paid=FALSE"
+ " AND"
+ " creation_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("gc_challenge_pending_payments",
+ "DELETE FROM anastasis_challenge_payment "
+ "WHERE"
+ " (paid=FALSE"
+ " OR"
+ " refunded=TRUE)"
+ " AND"
+ " creation_date < $1;",
+ 1),
+ GNUNET_PQ_make_prepare ("truth_insert",
+ "INSERT INTO anastasis_truth "
+ "(truth_uuid"
+ ",key_share_data"
+ ",method_name"
+ ",encrypted_truth"
+ ",truth_mime"
+ ",expiration"
+ ") VALUES "
+ "($1, $2, $3, $4, $5, $6);",
+ 6),
+ GNUNET_PQ_make_prepare ("recovery_document_insert",
+ "INSERT INTO anastasis_recoverydocument "
+ "(user_id"
+ ",version"
+ ",account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("truth_select",
+ "SELECT "
+ " method_name"
+ ",encrypted_truth"
+ ",truth_mime"
+ " FROM anastasis_truth"
+ " WHERE truth_uuid =$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("latest_recoverydocument_select",
+ "SELECT "
+ " version"
+ ",account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ " FROM anastasis_recoverydocument"
+ " WHERE user_id =$1 "
+ " ORDER BY version DESC"
+ " LIMIT 1;",
+ 1),
+ GNUNET_PQ_make_prepare ("latest_recovery_version_select",
+ "SELECT"
+ " version"
+ ",recovery_data_hash"
+ ",expiration_date"
+ " FROM anastasis_recoverydocument"
+ " JOIN anastasis_user USING (user_id)"
+ " WHERE user_id=$1"
+ " ORDER BY version DESC"
+ " LIMIT 1;",
+ 1),
+ GNUNET_PQ_make_prepare ("recoverydocument_select",
+ "SELECT "
+ " account_sig"
+ ",recovery_data_hash"
+ ",recovery_data"
+ " FROM anastasis_recoverydocument"
+ " WHERE user_id=$1"
+ " AND version=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("postcounter_select",
+ "SELECT"
+ " post_counter"
+ " FROM anastasis_recdoc_payment"
+ " WHERE user_id=$1"
+ " AND payment_identifier=$2;",
+ 2),
+ GNUNET_PQ_make_prepare ("postcounter_update",
+ "UPDATE "
+ "anastasis_recdoc_payment "
+ "SET "
+ "post_counter=$1 "
+ "WHERE user_id =$2 "
+ "AND payment_identifier=$3;",
+ 3),
+ GNUNET_PQ_make_prepare ("key_share_select",
+ "SELECT "
+ "key_share_data "
+ "FROM "
+ "anastasis_truth "
+ "WHERE truth_uuid =$1;",
+ 1),
+ GNUNET_PQ_make_prepare ("challengecode_insert",
+ "INSERT INTO anastasis_challengecode "
+ "(truth_uuid"
+ ",code"
+ ",creation_date"
+ ",expiration_date"
+ ",retry_counter"
+ ") VALUES "
+ "($1, $2, $3, $4, $5);",
+ 5),
+ GNUNET_PQ_make_prepare ("challengecode_select",
+ "SELECT "
+ " code"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND expiration_date > $2"
+ " AND retry_counter > 0;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_select_meta",
+ "SELECT "
+ " code"
+ ",retry_counter"
+ ",retransmission_date"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND expiration_date > $2"
+ " AND creation_date > $3"
+ " ORDER BY creation_date DESC"
+ " LIMIT 1;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_update_retry",
+ "UPDATE anastasis_challengecode"
+ " SET retry_counter=retry_counter - 1"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " AND retry_counter > 0;",
+ 1),
+ GNUNET_PQ_make_prepare ("challengepayment_dec_counter",
+ "UPDATE anastasis_challenge_payment"
+ " SET counter=counter - 1"
+ " WHERE truth_uuid=$1"
+ " AND payment_identifier=$2"
+ " AND counter > 0;",
+ 2),
+ GNUNET_PQ_make_prepare ("challengecode_mark_sent",
+ "UPDATE anastasis_challengecode"
+ " SET retransmission_date=$3"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " AND creation_date IN"
+ " (SELECT creation_date"
+ " FROM anastasis_challengecode"
+ " WHERE truth_uuid=$1"
+ " AND code=$2"
+ " ORDER BY creation_date DESC"
+ " LIMIT 1);",
+ 3),
+ GNUNET_PQ_make_prepare ("gc_challengecodes",
+ "DELETE FROM anastasis_challengecode "
+ "WHERE "
+ "expiration_date < $1;",
+ 1),
+ GNUNET_PQ_PREPARED_STATEMENT_END
+ };
+
+ pg = GNUNET_new (struct PostgresClosure);
+ pg->cfg = cfg;
+ pg->conn = GNUNET_PQ_connect_with_cfg (cfg,
+ "stasis-postgres",
+ "stasis-",
+ NULL,
+ ps);
+ if (NULL == pg->conn)
+ {
+ GNUNET_free (pg);
+ return NULL;
+ }
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_string (cfg,
+ "taler",
+ "CURRENCY",
+ &pg->currency))
+ {
+ GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
+ "taler",
+ "CURRENCY");
+ GNUNET_PQ_disconnect (pg->conn);
+ GNUNET_free (pg);
+ return NULL;
+ }
+ plugin = GNUNET_new (struct ANASTASIS_DatabasePlugin);
+ plugin->cls = pg;
+ plugin->drop_tables = &postgres_drop_tables;
+ plugin->gc = &postgres_gc;
+ plugin->preflight = &postgres_preflight;
+ plugin->rollback = &rollback;
+ plugin->commit = &commit_transaction;
+ plugin->store_recovery_document = &postgres_store_recovery_document;
+ plugin->record_recdoc_payment = &postgres_record_recdoc_payment;
+ plugin->store_truth = &postgres_store_truth;
+ plugin->get_escrow_challenge = &postgres_get_escrow_challenge;
+ plugin->get_key_share = &postgres_get_key_share;
+ plugin->get_latest_recovery_document = &postgres_get_latest_recovery_document;
+ plugin->get_recovery_document = &postgres_get_recovery_document;
+ plugin->lookup_account = &postgres_lookup_account;
+ plugin->check_payment_identifier = &postgres_check_payment_identifier;
+ plugin->increment_lifetime = &postgres_increment_lifetime;
+ plugin->update_lifetime = &postgres_update_lifetime;
+ plugin->start = &begin_transaction;
+ plugin->check_connection = &check_connection;
+ plugin->verify_challenge_code = &postgres_verify_challenge_code;
+ plugin->create_challenge_code = &postgres_create_challenge_code;
+ plugin->mark_challenge_sent = &postgres_mark_challenge_sent;
+ plugin->challenge_gc = &postgres_challenge_gc;
+ plugin->record_truth_upload_payment = &postgres_record_truth_upload_payment;
+ plugin->check_truth_upload_paid = &postgres_check_truth_upload_paid;
+ plugin->record_challenge_payment = &postgres_record_challenge_payment;
+ plugin->record_challenge_refund = &postgres_record_challenge_refund;
+ plugin->check_challenge_payment = &postgres_check_challenge_payment;
+ plugin->lookup_challenge_payment = &postgres_lookup_challenge_payment;
+ plugin->update_challenge_payment = &postgres_update_challenge_payment;
+ return plugin;
+}
+
+
+/**
+ * Shutdown Postgres database subsystem.
+ *
+ * @param cls a `struct ANASTASIS_DB_STATUS_Plugin`
+ * @return NULL (always)
+ */
+void *
+libanastasis_plugin_db_postgres_done (void *cls)
+{
+ struct ANASTASIS_DatabasePlugin *plugin = cls;
+ struct PostgresClosure *pg = plugin->cls;
+
+ GNUNET_PQ_disconnect (pg->conn);
+ GNUNET_free (pg->currency);
+ GNUNET_free (pg);
+ GNUNET_free (plugin);
+ return NULL;
+}
+
+
+/* end of plugin_anastasisdb_postgres.c */