exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit c626ccac51dc968725b2ead1ecb0cebedd9987fc
parent eae1896a4bfc02b5d7586f81bbedfea69b29acc8
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sat, 28 Mar 2015 11:06:00 +0100

second round of renamefest

Diffstat:
Mconfigure.ac | 4+++-
Msrc/Makefile.am | 2+-
Msrc/include/Makefile.am | 1+
Asrc/include/taler_mintdb_lib.h | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/include/taler_mintdb_plugin.h | 5+++--
Rsrc/lib/Makefile.am -> src/mint-lib/Makefile.am | 0
Rsrc/lib/mint_api.c -> src/mint-lib/mint_api.c | 0
Rsrc/lib/test_mint_api.c -> src/mint-lib/test_mint_api.c | 0
Asrc/mint-tools/Makefile.am | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mint-tools/taler-mint-dbinit.c | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mint-tools/taler-mint-keycheck.c | 227+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mint-tools/taler-mint-keyup.c | 954+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mint-tools/taler-mint-reservemod.c | 305++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/mint/Makefile.am | 128++-----------------------------------------------------------------------------
Dsrc/mint/key_io.c | 347-------------------------------------------------------------------------------
Dsrc/mint/key_io.h | 187-------------------------------------------------------------------------------
Dsrc/mint/plugin.c | 147-------------------------------------------------------------------------------
Dsrc/mint/plugin.h | 50--------------------------------------------------
Dsrc/mint/plugin_mintdb_postgres.c | 2357-------------------------------------------------------------------------------
Dsrc/mint/taler-mint-dbinit.c | 101-------------------------------------------------------------------------------
Msrc/mint/taler-mint-httpd.c | 12++++++++----
Msrc/mint/taler-mint-httpd.h | 5+++++
Msrc/mint/taler-mint-httpd_db.c | 128++++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/mint/taler-mint-httpd_db.h | 1-
Msrc/mint/taler-mint-httpd_deposit.c | 1-
Msrc/mint/taler-mint-httpd_keystate.h | 2+-
Msrc/mint/taler-mint-httpd_mhd.c | 1-
Dsrc/mint/taler-mint-keycheck.c | 227-------------------------------------------------------------------------------
Dsrc/mint/taler-mint-keyup.c | 954-------------------------------------------------------------------------------
Dsrc/mint/taler-mint-reservemod.c | 305------------------------------------------------------------------------------
Asrc/mintdb/Makefile.am | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mintdb/mintdb_keyio.c | 347+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/mintdb/mintdb_plugin.c | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/mint/plugin_mintdb_common.c -> src/mintdb/plugin_mintdb_common.c | 0
Asrc/mintdb/plugin_mintdb_postgres.c | 2356+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rsrc/mint/test_mint_db.c -> src/mintdb/test_mintdb.c | 0
Rsrc/mint/test_mint_deposits.c -> src/mintdb/test_mintdb_deposits.c | 0
Rsrc/mint/test_mint_common.c -> src/mintdb/test_mintdb_keyio.c | 0
38 files changed, 4871 insertions(+), 4876 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -174,7 +174,9 @@ AC_CONFIG_FILES([Makefile src/include/Makefile src/util/Makefile src/pq/Makefile + src/mintdb/Makefile src/mint/Makefile - src/lib/Makefile + src/mint-tools/Makefile + src/mint-lib/Makefile ]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am @@ -1,3 +1,3 @@ # This Makefile.am is in the public domain AM_CPPFLAGS = -I$(top_srcdir)/src/include -SUBDIRS = include util pq mint lib +SUBDIRS = include util pq mintdb mint mint-tools mint-lib diff --git a/src/include/Makefile.am b/src/include/Makefile.am @@ -8,6 +8,7 @@ talerinclude_HEADERS = \ taler_json_lib.h \ taler_util.h \ taler_mint_service.h \ + taler_mintdb_lib.h \ taler_mintdb_plugin.h \ taler_pq_lib.h \ taler_signatures.h diff --git a/src/include/taler_mintdb_lib.h b/src/include/taler_mintdb_lib.h @@ -0,0 +1,207 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_mintdb_lib.h + * @brief IO operations for the mint's private keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINTDB_LIB_H +#define TALER_MINTDB_LIB_H + +#include <gnunet/gnunet_util_lib.h> +#include "taler_signatures.h" + +/** + * Subdirectroy under the mint's base directory which contains + * the mint's signing keys. + */ +#define DIR_SIGNKEYS "signkeys" + +/** + * Subdirectory under the mint's base directory which contains + * the mint's denomination keys. + */ +#define DIR_DENOMKEYS "denomkeys" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * On disk format used for a mint signing key. Signing keys are used + * by the mint to affirm its messages, but not to create coins. + * Includes the private key followed by the public information about + * the signing key. + */ +struct TALER_MintSigningKeyValidityPSPriv +{ + /** + * Private key part of the mint's signing key. + */ + struct TALER_MintPrivateKeyP signkey_priv; + + /** + * Public information about a mint signing key. + */ + struct TALER_MintSigningKeyValidityPS issue; +}; + + +GNUNET_NETWORK_STRUCT_END + + +/** + * All information about a denomination key (which is used to + * sign coins into existence). + */ +struct TALER_DenominationKeyIssueInformation +{ + /** + * The private key of the denomination. Will be NULL if the private + * key is not available (this is the case after the key has expired + * for signing coins, but is still valid for depositing coins). + */ + struct TALER_DenominationPrivateKey denom_priv; + + /** + * Decoded denomination public key (the hash of it is in + * @e issue, but we sometimes need the full public key as well). + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Signed public information about a denomination key. + */ + struct TALER_DenominationKeyValidityPS issue; +}; + + +/** + * Iterator over signing keys. + * + * @param cls closure + * @param filename name of the file the key came from + * @param ski the sign key + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int +(*TALER_MINT_SignkeyIterator)(void *cls, + const char *filename, + const struct TALER_MintSigningKeyValidityPSPriv *ski); + + +/** + * Iterator over denomination keys. + * + * @param cls closure + * @param dki the denomination key + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +typedef int +(*TALER_MINT_DenomkeyIterator)(void *cls, + const char *alias, + const struct TALER_DenominationKeyIssueInformation *dki); + + + +/** + * Call @a it for each signing key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_SIGNKEYS + * subdirectory + * @param it function to call on each signing key + * @param it_cls closure for @a it + * @return number of files found (may not match + * number of keys given to @a it as malformed + * files are simply skipped), -1 on error + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, + void *it_cls); + + +/** + * Call @a it for each denomination key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_DENOMKEYS + * subdirectory + * @param it function to call on each denomination key found + * @param it_cls closure for @a it + * @return -1 on error, 0 if no files were found, otherwise + * a positive number (however, even with a positive + * number it is possible that @a it was never called + * as maybe none of the files were well-formed) + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, + void *it_cls); + + +/** + * Exports a denomination key to the given file. + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_DenominationKeyIssueInformation *dki); + + +/** + * Import a denomination key from the given file. + * + * @param filename the file to import the key from + * @param[OUT] dki set to the imported denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_DenominationKeyIssueInformation *dki); + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return NULL on failure + */ +struct TALER_MINTDB_Plugin * +TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Shutdown the plugin. + * + * @param plugin plugin to unload + */ +void +TALER_MINT_plugin_unload (struct TALER_MINTDB_Plugin *plugin); + + + +#endif diff --git a/src/include/taler_mintdb_plugin.h b/src/include/taler_mintdb_plugin.h @@ -479,9 +479,10 @@ struct TALER_MINTDB_Plugin void *cls; /** - * Name of the library which generated this plugin + * Name of the library which generated this plugin. Set by the + * plugin loader. */ - const char *library_name; + char *library_name; /** * Get the thread-local database-handle. diff --git a/src/lib/Makefile.am b/src/mint-lib/Makefile.am diff --git a/src/lib/mint_api.c b/src/mint-lib/mint_api.c diff --git a/src/lib/test_mint_api.c b/src/mint-lib/test_mint_api.c diff --git a/src/mint-tools/Makefile.am b/src/mint-tools/Makefile.am @@ -0,0 +1,63 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +bin_PROGRAMS = \ + taler-mint-keyup \ + taler-mint-keycheck \ + taler-mint-reservemod \ + taler-mint-dbinit + +taler_mint_keyup_SOURCES = \ + taler-mint-keyup.c + +taler_mint_keyup_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/mint/libtalermint_common.la \ + -lgnunetutil +taler_mint_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) + + +taler_mint_keycheck_SOURCES = \ + taler-mint-keycheck.c + +taler_mint_keycheck_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/mint/libtalermint_common.la \ + -lgnunetutil +taler_mint_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS) + +taler_mint_reservemod_SOURCES = \ + taler-mint-reservemod.c +taler_mint_reservemod_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/mint/libtalermint_common.la \ + -lpq \ + -lgnunetutil +taler_mint_reservemod_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) +taler_mint_reservemod_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/pq/ \ + $(POSTGRESQL_CPPFLAGS) + +taler_mint_dbinit_SOURCES = \ + taler-mint-dbinit.c +taler_mint_dbinit_LDADD = \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + $(top_builddir)/src/pq/libtalerpq.la \ + $(top_builddir)/src/mint/libtalermint_common.la \ + -lpq \ + -lgnunetutil +taler_mint_dbinit_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) +taler_mint_dbinit_CPPFLAGS = \ + -I$(top_srcdir)/src/include \ + -I$(top_srcdir)/src/pq/ \ + $(POSTGRESQL_CPPFLAGS) diff --git a/src/mint-tools/taler-mint-dbinit.c b/src/mint-tools/taler-mint-dbinit.c @@ -0,0 +1,106 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mint/taler-mint-dbinit.c + * @brief Create tables for the mint database. + * @author Florian Dold + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_mintdb_plugin.h" +#include "taler_mintdb_lib.h" + +/** + * Mint directory with the keys. + */ +static char *mint_base_dir; + +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +static struct TALER_MINTDB_Plugin *plugin; + + +/** + * The main function of the database initialization tool. + * Used to initialize the Taler Mint's database. + * + * @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) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-dbinit OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory", 1, + &GNUNET_GETOPT_set_filename, &mint_base_dir}, + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_GETOPT_run ("taler-mint-dbinit", + options, + argc, argv) < 0) + return 1; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-dbinit", + "INFO", + NULL)); + if (NULL == mint_base_dir) + { + fprintf (stderr, + "Mint base directory not given.\n"); + return 1; + } + cfg = TALER_config_load (mint_base_dir); + if (NULL == cfg) + { + fprintf (stderr, + "Failed to load mint configuration.\n"); + return 1; + } + if (NULL == + (plugin = TALER_MINT_plugin_load (cfg))) + { + fprintf (stderr, + "Failed to initialize database plugin.\n"); + return 1; + } + if (GNUNET_OK != + plugin->create_tables (plugin->cls, + GNUNET_NO)) + { + fprintf (stderr, + "Failed to initialize database.\n"); + TALER_MINT_plugin_unload (plugin); + return 1; + } + TALER_MINT_plugin_unload (plugin); + return 0; +} + +/* end of taler-mint-dbinit.c */ diff --git a/src/mint-tools/taler-mint-keycheck.c b/src/mint-tools/taler-mint-keycheck.c @@ -0,0 +1,227 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-keycheck.c + * @brief Check mint keys for validity. Reads the signing and denomination + * keys from the mint directory and checks to make sure they are + * well-formed. This is purely a diagnostic tool. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_mintdb_lib.h" + +/** + * Mint directory with the keys. + */ +static char *mint_directory; + +/** + * Our configuration. + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + + +/** + * Function called on each signing key. + * + * @param cls closure (NULL) + * @param filename name of the file the key came from + * @param ski the sign key + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +signkeys_iter (void *cls, + const char *filename, + const struct TALER_MintSigningKeyValidityPSPriv *ski) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Iterating over key `%s' for start time %s\n", + filename, + GNUNET_STRINGS_absolute_time_to_string + (GNUNET_TIME_absolute_ntoh (ski->issue.start))); + + if (ntohl (ski->issue.purpose.size) != + (sizeof (struct TALER_MintSigningKeyValidityPS) - + offsetof (struct TALER_MintSigningKeyValidityPS, purpose))) + { + fprintf (stderr, + "Signing key `%s' has invalid purpose size\n", + filename); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_SIGNING_KEY_VALIDITY, + &ski->issue.purpose, + &ski->issue.signature.eddsa_signature, + &ski->issue.master_public_key.eddsa_pub)) + { + fprintf (stderr, + "Signing key `%s' has invalid signature\n", + filename); + return GNUNET_SYSERR; + } + printf ("Signing key `%s' valid\n", + filename); + return GNUNET_OK; +} + + +/** + * Check signing keys. + * + * @return #GNUNET_OK if the keys are OK + * #GNUNET_NO if not + */ +static int +mint_signkeys_check () +{ + if (0 > TALER_MINT_signkeys_iterate (mint_directory, + &signkeys_iter, + NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +/** + * Function called on each denomination key. + * + * @param cls closure (NULL) + * @param dki the denomination key + * @param alias coin alias + * @return #GNUNET_OK to continue to iterate, + * #GNUNET_NO to stop iteration with no error, + * #GNUNET_SYSERR to abort iteration with error! + */ +static int +denomkeys_iter (void *cls, + const char *alias, + const struct TALER_DenominationKeyIssueInformation *dki) +{ + struct GNUNET_HashCode hc; + + if (ntohl (dki->issue.purpose.size) != + sizeof (struct TALER_DenominationKeyValidityPS)) + { + fprintf (stderr, + "Denomination key for `%s' has invalid purpose size\n", + alias); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_DENOMINATION_KEY_VALIDITY, + &dki->issue.purpose, + &dki->issue.signature.eddsa_signature, + &dki->issue.master.eddsa_pub)) + { + fprintf (stderr, + "Denomination key for `%s' has invalid signature\n", + alias); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, + &hc); + if (0 != memcmp (&hc, + &dki->issue.denom_hash, + sizeof (struct GNUNET_HashCode))) + { + fprintf (stderr, + "Public key for `%s' does not match signature\n", + alias); + return GNUNET_SYSERR; + } + printf ("Denomination key `%s' is valid\n", + alias); + + return GNUNET_OK; +} + + +/** + * Check denomination keys. + * + * @return #GNUNET_OK if the keys are OK + * #GNUNET_NO if not + */ +static int +mint_denomkeys_check () +{ + if (0 > TALER_MINT_denomkeys_iterate (mint_directory, + &denomkeys_iter, + NULL)) + return GNUNET_NO; + return GNUNET_OK; +} + + +/** + * The main function of the keyup 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) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keycheck OPTIONS"), + {'d', "directory", "DIRECTORY", + "mint directory with keys to check", 1, + &GNUNET_GETOPT_set_filename, &mint_directory}, + GNUNET_GETOPT_OPTION_END + }; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-keycheck", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keycheck", + options, + argc, argv) < 0) + return 1; + if (NULL == mint_directory) + { + fprintf (stderr, + "Mint directory not given\n"); + return 1; + } + + kcfg = TALER_config_load (mint_directory); + if (NULL == kcfg) + { + fprintf (stderr, + "Failed to load mint configuration\n"); + return 1; + } + if ( (GNUNET_OK != mint_signkeys_check ()) || + (GNUNET_OK != mint_denomkeys_check ()) ) + { + GNUNET_CONFIGURATION_destroy (kcfg); + return 1; + } + GNUNET_CONFIGURATION_destroy (kcfg); + return 0; +} + +/* end of taler-mint-keycheck.c */ diff --git a/src/mint-tools/taler-mint-keyup.c b/src/mint-tools/taler-mint-keyup.c @@ -0,0 +1,954 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-keyup.c + * @brief Update the mint's keys for coins and signatures, + * using the mint's offline master key. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include <platform.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_util.h" +#include "taler_mintdb_lib.h" + +/** + * When generating filenames from a cryptographic hash, we do not use + * all 512 bits but cut off after this number of characters (in + * base32-encoding). Base32 is 5 bit per character, and given that we + * have very few coin types we hash, at 100 bits the chance of + * collision (by accident over tiny set -- birthday paradox does not + * apply here!) is negligible. + */ +#define HASH_CUTOFF 20 + +/** + * Macro to round microseconds to seconds in GNUNET_TIME_* structs. + * + * @param name value to round + * @param field rel_value_us or abs_value_us + */ +#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Struct with all of the key information for a kind of coin. Hashed + * to generate a unique directory name per coin type. + */ +struct CoinTypeNBOP +{ + /** + * How long can the coin be spend? + */ + struct GNUNET_TIME_RelativeNBO duration_spend; + + /** + * How long can the coin be withdrawn (generated)? + */ + struct GNUNET_TIME_RelativeNBO duration_withdraw; + + /** + * What is the value of the coin? + */ + struct TALER_AmountNBO value; + + /** + * What is the fee charged for withdrawl? + */ + struct TALER_AmountNBO fee_withdraw; + + /** + * What is the fee charged for deposits? + */ + struct TALER_AmountNBO fee_deposit; + + /** + * What is the fee charged for melting? + */ + struct TALER_AmountNBO fee_refresh; + + /** + * Key size in NBO. + */ + uint32_t rsa_keysize; +}; + +GNUNET_NETWORK_STRUCT_END + +/** + * Set of all of the parameters that chracterize a coin. + */ +struct CoinTypeParams +{ + + /** + * How long can the coin be spend? Should be significantly + * larger than @e duration_withdraw (i.e. years). + */ + struct GNUNET_TIME_Relative duration_spend; + + /** + * How long can the coin be withdrawn (generated)? Should be small + * enough to limit how many coins will be signed into existence with + * the same key, but large enough to still provide a reasonable + * anonymity set. + */ + struct GNUNET_TIME_Relative duration_withdraw; + + /** + * How much should coin creation (@e duration_withdraw) duration + * overlap with the next coin? Basically, the starting time of two + * coins is always @e duration_withdraw - @e duration_overlap apart. + */ + struct GNUNET_TIME_Relative duration_overlap; + + /** + * What is the value of the coin? + */ + struct TALER_Amount value; + + /** + * What is the fee charged for withdrawl? + */ + struct TALER_Amount fee_withdraw; + + /** + * What is the fee charged for deposits? + */ + struct TALER_Amount fee_deposit; + + /** + * What is the fee charged for melting? + */ + struct TALER_Amount fee_refresh; + + /** + * Time at which this coin is supposed to become valid. + */ + struct GNUNET_TIME_Absolute anchor; + + /** + * Length of the RSA key in bits. + */ + uint32_t rsa_keysize; +}; + + +/** + * Filename of the master private key. + */ +static char *masterkeyfile; + +/** + * Director of the mint, containing the keys. + */ +static char *mint_directory; + +/** + * Time to pretend when the key update is executed. + */ +static char *pretend_time_str; + +/** + * Handle to the mint's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *kcfg; + +/** + * Time when the key update is executed. Either the actual current time, or a + * pretended time. + */ +static struct GNUNET_TIME_Absolute now; + +/** + * Master private key of the mint. + */ +static struct TALER_MasterPrivateKeyP master_priv; + +/** + * Master public key of the mint. + */ +static struct TALER_MasterPublicKeyP master_public_key; + +/** + * Until what time do we provide keys? + */ +static struct GNUNET_TIME_Absolute lookahead_sign_stamp; + + +/** + * Obtain the name of the directory we use to store signing + * keys created at time @a start. + * + * @param start time at which we create the signing key + * @return name of the directory we should use, basically "$MINTDIR/$TIME/"; + * (valid until next call to this function) + */ +static const char * +get_signkey_file (struct GNUNET_TIME_Absolute start) +{ + static char dir[4096]; + + GNUNET_snprintf (dir, + sizeof (dir), + "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu", + mint_directory, + (unsigned long long) start.abs_value_us); + return dir; +} + + +/** + * Hash the data defining the coin type. Exclude information that may + * not be the same for all instances of the coin type (i.e. the + * anchor, overlap). + * + * @param p coin parameters to convert to a hash + * @param hash[OUT] set to the hash matching @a p + */ +static void +hash_coin_type (const struct CoinTypeParams *p, + struct GNUNET_HashCode *hash) +{ + struct CoinTypeNBOP p_nbo; + + memset (&p_nbo, + 0, + sizeof (struct CoinTypeNBOP)); + p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); + p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); + TALER_amount_hton (&p_nbo.value, + &p->value); + TALER_amount_hton (&p_nbo.fee_withdraw, + &p->fee_withdraw); + TALER_amount_hton (&p_nbo.fee_deposit, + &p->fee_deposit); + TALER_amount_hton (&p_nbo.fee_refresh, + &p->fee_refresh); + p_nbo.rsa_keysize = htonl (p->rsa_keysize); + GNUNET_CRYPTO_hash (&p_nbo, + sizeof (struct CoinTypeNBOP), + hash); +} + + +/** + * Obtain the name of the directory we should use to store coins of + * the given type. The directory name has the format + * "$MINTDIR/$VALUE/$HASH/" where "$VALUE" represents the value of the + * coin and "$HASH" encodes all of the coin's parameters, generating a + * unique string for each type of coin. Note that the "$HASH" + * includes neither the absolute creation time nor the key of the + * coin, thus the files in the subdirectory really just refer to the + * same type of coins, not the same coin. + * + * @param p coin parameters to convert to a directory name + * @return directory name (valid until next call to this function) + */ +static const char * +get_cointype_dir (const struct CoinTypeParams *p) +{ + static char dir[4096]; + struct GNUNET_HashCode hash; + char *hash_str; + char *val_str; + size_t i; + + hash_coin_type (p, &hash); + hash_str = GNUNET_STRINGS_data_to_string_alloc (&hash, + sizeof (struct GNUNET_HashCode)); + GNUNET_assert (NULL != hash_str); + GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); + hash_str[HASH_CUTOFF] = 0; + + val_str = TALER_amount_to_string (&p->value); + for (i = 0; i < strlen (val_str); i++) + if ( (':' == val_str[i]) || + ('.' == val_str[i]) ) + val_str[i] = '_'; + + GNUNET_snprintf (dir, + sizeof (dir), + "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s", + mint_directory, + val_str, + hash_str); + GNUNET_free (hash_str); + GNUNET_free (val_str); + return dir; +} + + +/** + * Obtain the name of the file we would use to store the key + * information for a coin of the given type @a p and validity + * start time @a start + * + * @param p parameters for the coin + * @param start when would the coin begin to be issued + * @return name of the file to use for this coin + * (valid until next call to this function) + */ +static const char * +get_cointype_file (const struct CoinTypeParams *p, + struct GNUNET_TIME_Absolute start) +{ + static char filename[4096]; + const char *dir; + + dir = get_cointype_dir (p); + GNUNET_snprintf (filename, + sizeof (filename), + "%s" DIR_SEPARATOR_STR "%llu", + dir, + (unsigned long long) start.abs_value_us); + return filename; +} + + +/** + * Get the latest key file from a past run of the key generation + * tool. Used to calculate the starting time for the keys we + * generate during this invocation. This function is used to + * handle both signing keys and coin keys, as in both cases + * the filenames correspond to the timestamps we need. + * + * @param cls closure, a `struct GNUNET_TIME_Absolute *`, updated + * to contain the highest timestamp (below #now) + * that was found + * @param filename complete filename (absolute path) + * @return #GNUNET_OK (to continue to iterate) + */ +static int +get_anchor_iter (void *cls, + const char *filename) +{ + struct GNUNET_TIME_Absolute *anchor = cls; + struct GNUNET_TIME_Absolute stamp; + const char *base; + char *end = NULL; + + base = GNUNET_STRINGS_get_short_name (filename); + stamp.abs_value_us = strtol (base, + &end, + 10); + if ((NULL == end) || (0 != *end)) + { + fprintf(stderr, + "Ignoring unexpected file `%s'.\n", + filename); + return GNUNET_OK; + } + if (stamp.abs_value_us <= now.abs_value_us) + *anchor = GNUNET_TIME_absolute_max (stamp, + *anchor); + return GNUNET_OK; +} + + +/** + * Get the timestamp where the first new key should be generated. + * Relies on correctly named key files (as we do not parse them, + * but just look at the filenames to "guess" at their contents). + * + * @param dir directory that should contain the existing keys + * @param duration how long is one key valid (for signing)? + * @param overlap what's the overlap between the keys validity period? + * @param[out] anchor the timestamp where the first new key should be generated + */ +static void +get_anchor (const char *dir, + struct GNUNET_TIME_Relative duration, + struct GNUNET_TIME_Relative overlap, + struct GNUNET_TIME_Absolute *anchor) +{ + GNUNET_assert (0 == duration.rel_value_us % 1000000); + GNUNET_assert (0 == overlap.rel_value_us % 1000000); + if (GNUNET_YES != + GNUNET_DISK_directory_test (dir, + GNUNET_YES)) + { + *anchor = now; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No existing keys found, starting with fresh key set.\n"); + return; + } + *anchor = GNUNET_TIME_UNIT_ZERO_ABS; + if (-1 == + GNUNET_DISK_directory_scan (dir, + &get_anchor_iter, + anchor)) + { + *anchor = now; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "No existing keys found, starting with fresh key set.\n"); + return; + } + + /* FIXME: this check is a bit dubious, as 'now' + may be way into the future if we want to generate + many keys... #3727*/ + if ((GNUNET_TIME_absolute_add (*anchor, + duration)).abs_value_us < now.abs_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Existing keys are way too old, starting with fresh key set.\n"); + *anchor = now; + } + else if (anchor->abs_value_us != now.abs_value_us) // Also odd... + { + /* Real starting time is the last start time + duration - overlap */ + *anchor = GNUNET_TIME_absolute_add (*anchor, + duration); + *anchor = GNUNET_TIME_absolute_subtract (*anchor, + overlap); + } + /* anchor is now the stamp where we need to create a new key */ +} + + +/** + * Create a mint signing key (for signing mint messages, not for coins) + * and assert its correctness by signing it with the master key. + * + * @param start start time of the validity period for the key + * @param duration how long should the key be valid + * @param pi[OUT] set to the signing key information + */ +static void +create_signkey_issue_priv (struct GNUNET_TIME_Absolute start, + struct GNUNET_TIME_Relative duration, + struct TALER_MintSigningKeyValidityPSPriv *pi) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + struct TALER_MintSigningKeyValidityPS *issue = &pi->issue; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + pi->signkey_priv.eddsa_priv = *priv; + GNUNET_free (priv); + issue->master_public_key = master_public_key; + issue->start = GNUNET_TIME_absolute_hton (start); + issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, + duration)); + GNUNET_CRYPTO_eddsa_key_get_public (&pi->signkey_priv.eddsa_priv, + &issue->signkey_pub.eddsa_pub); + issue->purpose.purpose = htonl (TALER_SIGNATURE_MINT_SIGNING_KEY_VALIDITY); + issue->purpose.size = htonl (sizeof (struct TALER_MintSigningKeyValidityPS) - + offsetof (struct TALER_MintSigningKeyValidityPS, + purpose)); + + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, + &issue->purpose, + &issue->signature.eddsa_signature)); +} + + +/** + * Generate signing keys starting from the last key found to + * the lookahead time. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +mint_keys_update_signkeys () +{ + struct GNUNET_TIME_Relative signkey_duration; + struct GNUNET_TIME_Absolute anchor; + char *signkey_dir; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "mint_keys", + "signkey_duration", + &signkey_duration)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint_keys", + "signkey_duration"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (signkey_duration, + rel_value_us); + GNUNET_asprintf (&signkey_dir, + "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, + mint_directory); + /* make sure the directory exists */ + if (GNUNET_OK != + GNUNET_DISK_directory_create (signkey_dir)) + { + fprintf (stderr, + "Failed to create signing key directory\n"); + return GNUNET_SYSERR; + } + + get_anchor (signkey_dir, + signkey_duration, + GNUNET_TIME_UNIT_ZERO /* no overlap for signing keys */, + &anchor); + + while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) + { + const char *skf; + struct TALER_MintSigningKeyValidityPSPriv signkey_issue; + ssize_t nwrite; + + skf = get_signkey_file (anchor); + GNUNET_break (GNUNET_YES != + GNUNET_DISK_file_test (skf)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Generating signing key for %s.\n", + GNUNET_STRINGS_absolute_time_to_string (anchor)); + create_signkey_issue_priv (anchor, + signkey_duration, + &signkey_issue); + nwrite = GNUNET_DISK_fn_write (skf, + &signkey_issue, + sizeof (struct TALER_MintSigningKeyValidityPS), + GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); + if (nwrite != sizeof (struct TALER_MintSigningKeyValidityPS)) + { + fprintf (stderr, + "Failed to write to file `%s': %s\n", + skf, + STRERROR (errno)); + return GNUNET_SYSERR; + } + anchor = GNUNET_TIME_absolute_add (anchor, + signkey_duration); + } + return GNUNET_OK; +} + + +/** + * Parse configuration for coin type parameters. Also determines + * our anchor by looking at the existing coins of the same type. + * + * @param ct section in the configuration file giving the coin type parameters + * @param params[OUT] set to the coin parameters from the configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid + */ +static int +get_cointype_params (const char *ct, + struct CoinTypeParams *params) +{ + const char *dir; + unsigned long long rsa_keysize; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_withdraw", + &params->duration_withdraw)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "duration_withdraw"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_withdraw, + rel_value_us); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_spend", + &params->duration_spend)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "duration_spend"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_spend, + rel_value_us); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + ct, + "duration_overlap", + &params->duration_overlap)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "mint_denom_duration_overlap"); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (params->duration_overlap, + rel_value_us); + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (kcfg, + ct, + "rsa_keysize", + &rsa_keysize)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "rsa_keysize"); + return GNUNET_SYSERR; + } + if ( (rsa_keysize > 4 * 2048) || + (rsa_keysize < 1024) ) + { + fprintf (stderr, + "Given RSA keysize %llu outside of permitted range\n", + rsa_keysize); + return GNUNET_SYSERR; + } + params->rsa_keysize = (unsigned int) rsa_keysize; + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "value", + &params->value)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "value"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_withdraw", + &params->fee_withdraw)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_withdraw"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_deposit", + &params->fee_deposit)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_deposit"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + TALER_config_get_denom (kcfg, + ct, + "fee_refresh", + &params->fee_refresh)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + ct, + "fee_refresh"); + return GNUNET_SYSERR; + } + + dir = get_cointype_dir (params); + get_anchor (dir, + params->duration_spend, + params->duration_overlap, + &params->anchor); + return GNUNET_OK; +} + + +/** + * Initialize the private and public key information structure for + * signing coins into existence. Generates the private signing key + * and signes it together with the coin's meta data using the master + * signing key. + * + * @param params parameters used to initialize the @a dki + * @param dki[OUT] initialized according to @a params + */ +static void +create_denomkey_issue (const struct CoinTypeParams *params, + struct TALER_DenominationKeyIssueInformation *dki) +{ + dki->denom_priv.rsa_private_key + = GNUNET_CRYPTO_rsa_private_key_create (params->rsa_keysize); + GNUNET_assert (NULL != dki->denom_priv.rsa_private_key); + dki->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_private_key_get_public (dki->denom_priv.rsa_private_key); + GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, + &dki->issue.denom_hash); + dki->issue.master = master_public_key; + dki->issue.start = GNUNET_TIME_absolute_hton (params->anchor); + dki->issue.expire_withdraw = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_withdraw)); + dki->issue.expire_spend = + GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, + params->duration_spend)); + TALER_amount_hton (&dki->issue.value, + &params->value); + TALER_amount_hton (&dki->issue.fee_withdraw, + &params->fee_withdraw); + TALER_amount_hton (&dki->issue.fee_deposit, + &params->fee_deposit); + TALER_amount_hton (&dki->issue.fee_refresh, + &params->fee_refresh); + dki->issue.purpose.purpose = htonl (TALER_SIGNATURE_MINT_DENOMINATION_KEY_VALIDITY); + dki->issue.purpose.size = htonl (sizeof (struct TALER_DenominationKeyIssueInformation) - + offsetof (struct TALER_DenominationKeyIssueInformation, + issue.purpose)); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, + &dki->issue.purpose, + &dki->issue.signature.eddsa_signature)); +} + + +/** + * Generate new coin signing keys for the coin type of the given @a + * coin_alias. + * + * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure + * @param coin_alias name of the coin's section in the configuration + */ +static void +mint_keys_update_cointype (void *cls, + const char *coin_alias) +{ + int *ret = cls; + struct CoinTypeParams p; + const char *dkf; + struct TALER_DenominationKeyIssueInformation denomkey_issue; + + if (0 != strncasecmp (coin_alias, + "coin_", + strlen ("coin_"))) + return; /* not a coin definition */ + if (GNUNET_OK != + get_cointype_params (coin_alias, + &p)) + { + *ret = GNUNET_SYSERR; + return; + } + if (GNUNET_OK != + GNUNET_DISK_directory_create (get_cointype_dir (&p))) + { + *ret = GNUNET_SYSERR; + return; + } + + while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) + { + dkf = get_cointype_file (&p, + p.anchor); + GNUNET_break (GNUNET_YES != GNUNET_DISK_file_test (dkf)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Generating denomination key for type `%s', start %s at %s\n", + coin_alias, + GNUNET_STRINGS_absolute_time_to_string (p.anchor), + dkf); + create_denomkey_issue (&p, + &denomkey_issue); + if (GNUNET_OK != + TALER_MINT_write_denom_key (dkf, + &denomkey_issue)) + { + fprintf (stderr, + "Failed to write denomination key information to file `%s'.\n", + dkf); + *ret = GNUNET_SYSERR; + GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); + return; + } + GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); + p.anchor = GNUNET_TIME_absolute_add (p.anchor, + p.duration_spend); + p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, + p.duration_overlap); + } +} + + +/** + * Update all of the denomination keys of the mint. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +mint_keys_update_denomkeys () +{ + int ok; + + ok = GNUNET_OK; + GNUNET_CONFIGURATION_iterate_sections (kcfg, + &mint_keys_update_cointype, + &ok); + return ok; +} + + +/** + * The main function of the taler-mint-keyup tool. This tool is used + * to create the signing and denomination keys for the mint. It uses + * the long-term offline private key and writes the (additional) key + * files to the respective mint directory (from where they can then be + * copied to the online server). Note that we need (at least) the + * most recent generated previous keys so as to align the validity + * periods. + * + * @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) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), + {'m', "master-key", "FILE", + "master key file (private key)", 1, + &GNUNET_GETOPT_set_filename, &masterkeyfile}, + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mint_directory}, + {'t', "time", "TIMESTAMP", + "pretend it is a different time for the update", 0, + &GNUNET_GETOPT_set_string, &pretend_time_str}, + GNUNET_GETOPT_OPTION_END + }; + struct GNUNET_TIME_Relative lookahead_sign; + struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-keyup", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-keyup", + options, + argc, argv) < 0) + return 1; + if (NULL == mint_directory) + { + fprintf (stderr, + "Mint directory not given\n"); + return 1; + } + if (NULL != pretend_time_str) + { + if (GNUNET_OK != + GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, + &now)) + { + fprintf (stderr, + "timestamp `%s' invalid\n", + pretend_time_str); + return 1; + } + } + else + { + now = GNUNET_TIME_absolute_get (); + } + ROUND_TO_SECS (now, abs_value_us); + + kcfg = TALER_config_load (mint_directory); + if (NULL == kcfg) + { + fprintf (stderr, + "Failed to load mint configuration\n"); + return 1; + } + if (NULL == masterkeyfile) + { + fprintf (stderr, + "Master key file not given\n"); + return 1; + } + eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); + if (NULL == eddsa_priv) + { + fprintf (stderr, + "Failed to initialize master key from file `%s'\n", + masterkeyfile); + return 1; + } + master_priv.eddsa_priv = *eddsa_priv; + GNUNET_free (eddsa_priv); + GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv, + &master_public_key.eddsa_pub); + + /* check if key from file matches the one from the configuration */ + { + struct GNUNET_CRYPTO_EddsaPublicKey master_public_key_from_cfg; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_data (kcfg, + "mint", + "master_public_key", + &master_public_key_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "master_public_key"); + return 1; + } + if (0 != + memcmp (&master_public_key, + &master_public_key_from_cfg, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "mint", + "master_public_key", + _("does not match with private key")); + return 1; + } + } + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_time (kcfg, + "mint_keys", + "lookahead_sign", + &lookahead_sign)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint_keys", + "lookahead_sign"); + return GNUNET_SYSERR; + } + if (0 == lookahead_sign.rel_value_us) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "mint_keys", + "lookahead_sign", + _("must not be zero")); + return GNUNET_SYSERR; + } + ROUND_TO_SECS (lookahead_sign, + rel_value_us); + lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, + lookahead_sign); + + + /* finally, do actual work */ + if (GNUNET_OK != mint_keys_update_signkeys ()) + return 1; + + if (GNUNET_OK != mint_keys_update_denomkeys ()) + return 1; + return 0; +} + +/* end of taler-mint-keyup.c */ diff --git a/src/mint-tools/taler-mint-reservemod.c b/src/mint-tools/taler-mint-reservemod.c @@ -0,0 +1,305 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-reservemod.c + * @brief Modify reserves. Allows manipulation of reserve balances. + * @author Florian Dold + * @author Benedikt Mueller + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <libpq-fe.h> +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_mintdb_plugin.h" +#include "taler_mintdb_lib.h" + + +/** + * Director of the mint, containing the keys. + */ +static char *mint_directory; + +/** + * Public key of the reserve to manipulate. + */ +static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub; + +/** + * Handle to the mint's configuration + */ +static struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Database connection handle. + */ +static PGconn *db_conn; + + +/** + * Create a new or add to existing reserve. Fails if currencies do + * not match. + * + * @param denom denomination to add + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on error + */ +// FIXME: this should use the DB abstraction layer. (#3717) +// FIXME: this should be done by adding an inbound transaction +// to the table with the transactions for this reserve, +// not by modifying some 'total' value for the reserve! +// (we should in fact probably never modify, always just append!) (#3633) +static int +reservemod_add (struct TALER_Amount denom) +{ + PGresult *result; + const void *param_values[] = { + reserve_pub + }; + int param_lengths[] = { + sizeof(struct GNUNET_CRYPTO_EddsaPublicKey) + }; + int param_formats[] = { + 1 + }; + struct TALER_Amount old_denom; + struct TALER_Amount new_denom; + struct TALER_AmountNBO new_denom_nbo; + + result = PQexecParams (db_conn, + "SELECT balance_value, balance_fraction, balance_currency" + " FROM reserves" + " WHERE reserve_pub=$1" + " LIMIT 1;", + 1, + NULL, + (const char * const *) param_values, + param_lengths, + param_formats, + 1); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + fprintf (stderr, + "Select failed: %s\n", + PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + struct GNUNET_TIME_AbsoluteNBO exnbo; + uint32_t value = htonl (denom.value); + uint32_t fraction = htonl (denom.fraction); + const void *param_values[] = { + reserve_pub, + &value, + &fraction, + denom.currency, + &exnbo + }; + int param_lengths[] = { + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), + sizeof (uint32_t), + sizeof (uint32_t), + strlen (denom.currency), + sizeof (struct GNUNET_TIME_AbsoluteNBO) + }; + int param_formats[] = { + 1, 1, 1, 1, 1 + }; + + exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS)); + result = PQexecParams (db_conn, + "INSERT INTO reserves (reserve_pub, balance_value, balance_fraction, balance_currency, expiration_date)" + " VALUES ($1,$2,$3,$4,$5);", + 5, + NULL, + (const char **) param_values, + param_lengths, + param_formats, + 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, + "Insert failed: %s\n", + PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + } + else + { + const void *param_values[] = { + &new_denom_nbo.value, + &new_denom_nbo.fraction, + reserve_pub + }; + int param_lengths[] = { + sizeof (new_denom_nbo.value), + sizeof (new_denom_nbo.fraction), + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey) + }; + int param_formats[] = { + 1, 1, 1 + }; + + GNUNET_assert (GNUNET_OK == + TALER_PQ_extract_amount (result, 0, + "balance_value", + "balance_fraction", + "balance_currency", + &old_denom)); + TALER_amount_add (&new_denom, + &old_denom, + &denom); + TALER_amount_hton (&new_denom_nbo, + &new_denom); + result = PQexecParams (db_conn, + "UPDATE reserves" + " SET balance_value = $1, balance_fraction = $2, status_sig = NULL, status_sign_pub = NULL" + " WHERE reserve_pub = $3;", + 3, + NULL, + (const char **) param_values, + param_lengths, + param_formats, + 1); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + fprintf (stderr, + "Update failed: %s\n", + PQresultErrorMessage (result)); + return GNUNET_SYSERR; + } + /* Yes, for historic reasons libpq returns a 'const char *'... */ + if (0 != strcmp ("1", + PQcmdTuples (result))) + { + fprintf (stderr, + "Update failed (updated `%s' tupes instead of '1')\n", + PQcmdTuples (result)); + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * The main function of the reservemod 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) +{ + static char *reserve_pub_str; + static char *add_str; + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-reservemod OPTIONS"), + {'d', "mint-dir", "DIR", + "mint directory with keys to update", 1, + &GNUNET_GETOPT_set_filename, &mint_directory}, + {'R', "reserve", "KEY", + "reserve (public key) to modify", 1, + &GNUNET_GETOPT_set_string, &reserve_pub_str}, + {'a', "add", "DENOM", + "value to add", 1, + &GNUNET_GETOPT_set_string, &add_str}, + GNUNET_GETOPT_OPTION_END + }; + char *connection_cfg_str; + + GNUNET_assert (GNUNET_OK == + GNUNET_log_setup ("taler-mint-reservemod", + "WARNING", + NULL)); + + if (GNUNET_GETOPT_run ("taler-mint-reservemod", + options, + argc, argv) < 0) + return 1; + if (NULL == mint_directory) + { + fprintf (stderr, + "Mint directory not given\n"); + return 1; + } + + reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); + if ((NULL == reserve_pub_str) || + (GNUNET_OK != + GNUNET_STRINGS_string_to_data (reserve_pub_str, + strlen (reserve_pub_str), + reserve_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))) + { + fprintf (stderr, + "Parsing reserve key invalid\n"); + return 1; + } + cfg = TALER_config_load (mint_directory); + if (NULL == cfg) + { + fprintf (stderr, + "Failed to load mint configuration\n"); + return 1; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "db", + &connection_cfg_str)) + { + fprintf (stderr, + "Database configuration string not found\n"); + return 1; + } + db_conn = PQconnectdb (connection_cfg_str); + if (CONNECTION_OK != PQstatus (db_conn)) + { + fprintf (stderr, + "Database connection failed: %s\n", + PQerrorMessage (db_conn)); + return 1; + } + if (NULL != add_str) + { + struct TALER_Amount add_value; + + if (GNUNET_OK != + TALER_string_to_amount (add_str, + &add_value)) + { + fprintf (stderr, + "Failed to parse currency amount `%s'\n", + add_str); + return 1; + } + if (GNUNET_OK != + reservemod_add (add_value)) + { + fprintf (stderr, + "Failed to update reserve.\n"); + return 1; + } + } + return 0; +} + +/* end taler-mint-reservemod.c */ diff --git a/src/mint/Makefile.am b/src/mint/Makefile.am @@ -1,82 +1,8 @@ # This Makefile.am is in the public domain -AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) - -plugindir = $(libdir)/taler - -plugin_LTLIBRARIES = \ - libtaler_plugin_mintdb_postgres.la - -EXTRA_DIST = plugin_mintdb_common.c - -libtaler_plugin_mintdb_postgres_la_SOURCES = \ - plugin_mintdb_postgres.c -libtaler_plugin_mintdb_postgres_la_LIBADD = \ - $(LTLIBINTL) -libtaler_plugin_mintdb_postgres_la_LDFLAGS = \ - $(TALER_PLUGIN_LDFLAGS) \ - -lpq \ - -lgnunetutil - -lib_LTLIBRARIES = \ - libtalermint_common.la - -libtalermint_common_la_SOURCES = \ - key_io.c key_io.h \ - plugin.c plugin.h - -libtalermint_common_la_LIBADD = \ - $(top_builddir)/src/util/libtalerutil.la \ - -lgnunetutil - -libtalermint_common_la_LDFLAGS = \ - $(POSTGRESQL_LDFLAGS) \ - -version-info 0:0:0 \ - -no-undefined - +AM_CPPFLAGS = -I$(top_srcdir)/src/include bin_PROGRAMS = \ - taler-mint-keyup \ - taler-mint-keycheck \ - taler-mint-reservemod \ - taler-mint-httpd \ - taler-mint-dbinit - -taler_mint_keyup_SOURCES = \ - taler-mint-keyup.c - -taler_mint_keyup_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/pq/libtalerpq.la \ - $(top_builddir)/src/mint/libtalermint_common.la \ - -lpq \ - -lgnunetutil -taler_mint_keyup_LDFLAGS = $(POSTGRESQL_LDFLAGS) - - -taler_mint_keycheck_SOURCES = \ - taler-mint-keycheck.c - -taler_mint_keycheck_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/pq/libtalerpq.la \ - $(top_builddir)/src/mint/libtalermint_common.la \ - -lgnunetutil \ - -lpq -taler_mint_keycheck_LDFLAGS = $(POSTGRESQL_LDFLAGS) - -taler_mint_reservemod_SOURCES = \ - taler-mint-reservemod.c -taler_mint_reservemod_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/pq/libtalerpq.la \ - $(top_builddir)/src/mint/libtalermint_common.la \ - -lpq \ - -lgnunetutil -taler_mint_reservemod_LDFLAGS = \ - $(POSTGRESQL_LDFLAGS) + taler-mint-httpd taler_mint_httpd_SOURCES = \ taler-mint-httpd.c \ @@ -92,56 +18,8 @@ taler_mint_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ $(top_builddir)/src/util/libtalerutil.la \ $(top_builddir)/src/pq/libtalerpq.la \ - $(top_builddir)/src/mint/libtalermint_common.la \ - -lpq \ + $(top_builddir)/src/mintdb/libtalermintdb.la \ -lmicrohttpd \ -ljansson \ -lgnunetutil \ -lpthread -taler_mint_httpd_LDFLAGS = \ - $(POSTGRESQL_LDFLAGS) - - -taler_mint_dbinit_SOURCES = \ - taler-mint-dbinit.c -taler_mint_dbinit_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/pq/libtalerpq.la \ - $(top_builddir)/src/mint/libtalermint_common.la \ - -lpq \ - -lgnunetutil -taler_mint_dbinit_LDFLAGS = $(POSTGRESQL_LDFLAGS) - -check_PROGRAMS = \ - test-mint-deposits \ - test-mint-common \ - test-mint-db-postgres - -test_mint_deposits_SOURCES = \ - test_mint_deposits.c -test_mint_deposits_LDADD = \ - libtalermint_common.la \ - $(top_srcdir)/src/util/libtalerutil.la \ - $(top_srcdir)/src/pq/libtalerpq.la \ - -lgnunetutil \ - -ljansson \ - -lpq - -test_mint_common_SOURCES = \ - test_mint_common.c -test_mint_common_LDADD = \ - libtalermint_common.la \ - $(top_srcdir)/src/util/libtalerutil.la \ - $(top_srcdir)/src/pq/libtalerpq.la \ - -lgnunetutil - -test_mint_db_postgres_SOURCES = \ - test_mint_db.c -test_mint_db_postgres_LDADD = \ - libtalermint_common.la \ - $(top_srcdir)/src/util/libtalerutil.la \ - $(top_srcdir)/src/pq/libtalerpq.la \ - -lgnunetutil -ljansson -EXTRA_test_mint_db_postgres_DEPENDENCIES = \ - libtaler_plugin_mintdb_postgres.la diff --git a/src/mint/key_io.c b/src/mint/key_io.c @@ -1,347 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mint/key_io.c - * @brief I/O operations for the Mint's private keys - * @author Florian Dold - * @author Benedikt Mueller - * @author Sree Harsha Totakura - * @author Christian Grothoff - */ -#include "platform.h" -#include "key_io.h" - - -/** - * Closure for the #signkeys_iterate_dir_iter(). - */ -struct SignkeysIterateContext -{ - - /** - * Function to call on each signing key. - */ - TALER_MINT_SignkeyIterator it; - - /** - * Closure for @e it. - */ - void *it_cls; -}; - - -/** - * Function called on each file in the directory with - * our signing keys. Parses the file and calls the - * iterator from @a cls. - * - * @param cls the `struct SignkeysIterateContext *` - * @param filename name of the file to parse - * @return #GNUNET_OK to continue, - * #GNUNET_NO to stop iteration without error, - * #GNUNET_SYSERR to stop iteration with error - */ -static int -signkeys_iterate_dir_iter (void *cls, - const char *filename) -{ - struct SignkeysIterateContext *skc = cls; - ssize_t nread; - struct TALER_MintSigningKeyValidityPSPriv issue; - - nread = GNUNET_DISK_fn_read (filename, - &issue, - sizeof (struct TALER_MintSigningKeyValidityPSPriv)); - if (nread != sizeof (struct TALER_MintSigningKeyValidityPSPriv)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid signkey file `%s': wrong size\n", - filename); - return GNUNET_OK; - } - return skc->it (skc->it_cls, - filename, - &issue); -} - - -/** - * Call @a it for each signing key found in the @a mint_base_dir. - * - * @param mint_base_dir base directory for the mint, - * the signing keys must be in the #DIR_SIGNKEYS - * subdirectory - * @param it function to call on each signing key - * @param it_cls closure for @a it - * @return number of files found (may not match - * number of keys given to @a it as malformed - * files are simply skipped), -1 on error - */ -int -TALER_MINT_signkeys_iterate (const char *mint_base_dir, - TALER_MINT_SignkeyIterator it, - void *it_cls) -{ - char *signkey_dir; - struct SignkeysIterateContext skc; - int ret; - - GNUNET_asprintf (&signkey_dir, - "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, - mint_base_dir); - skc.it = it; - skc.it_cls = it_cls; - ret = GNUNET_DISK_directory_scan (signkey_dir, - &signkeys_iterate_dir_iter, - &skc); - GNUNET_free (signkey_dir); - return ret; -} - - -/** - * Import a denomination key from the given file. - * - * @param filename the file to import the key from - * @param[OUT] dki set to the imported denomination key - * @return #GNUNET_OK upon success; - * #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_read_denom_key (const char *filename, - struct TALER_DenominationKeyIssueInformation *dki) -{ - uint64_t size; - size_t offset; - void *data; - struct GNUNET_CRYPTO_rsa_PrivateKey *priv; - - if (GNUNET_OK != GNUNET_DISK_file_size (filename, - &size, - GNUNET_YES, - GNUNET_YES)) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Skipping inaccessable denomination key file `%s'\n", - filename); - return GNUNET_SYSERR; - } - offset = sizeof (struct TALER_DenominationKeyValidityPS); - if (size <= offset) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - data = GNUNET_malloc (size); - if (size != - GNUNET_DISK_fn_read (filename, - data, - size)) - { - GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, - "read", - filename); - GNUNET_free (data); - return GNUNET_SYSERR; - } - if (NULL == - (priv = GNUNET_CRYPTO_rsa_private_key_decode (data + offset, - size - offset))) - { - GNUNET_free (data); - return GNUNET_SYSERR; - } - dki->denom_priv.rsa_private_key = priv; - dki->denom_pub.rsa_public_key - = GNUNET_CRYPTO_rsa_private_key_get_public (priv); - memcpy (&dki->issue, - data, - offset); - GNUNET_free (data); - return GNUNET_OK; -} - - -/** - * Exports a denomination key to the given file. - * - * @param filename the file where to write the denomination key - * @param dki the denomination key - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. - */ -int -TALER_MINT_write_denom_key (const char *filename, - const struct TALER_DenominationKeyIssueInformation *dki) -{ - char *priv_enc; - size_t priv_enc_size; - struct GNUNET_DISK_FileHandle *fh; - ssize_t wrote; - size_t wsize; - int ret; - - fh = NULL; - priv_enc_size - = GNUNET_CRYPTO_rsa_private_key_encode (dki->denom_priv.rsa_private_key, - &priv_enc); - ret = GNUNET_SYSERR; - if (NULL == (fh = GNUNET_DISK_file_open - (filename, - GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, - GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) - goto cleanup; - wsize = sizeof (struct TALER_DenominationKeyValidityPS); - if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, - &dki->issue.signature, - wsize))) - goto cleanup; - if (wrote != wsize) - goto cleanup; - if (GNUNET_SYSERR == - (wrote = GNUNET_DISK_file_write (fh, - priv_enc, - priv_enc_size))) - goto cleanup; - if (wrote != priv_enc_size) - goto cleanup; - ret = GNUNET_OK; - cleanup: - GNUNET_free_non_null (priv_enc); - if (NULL != fh) - (void) GNUNET_DISK_file_close (fh); - return ret; -} - - -/** - * Closure for #denomkeys_iterate_keydir_iter() and - * #denomkeys_iterate_topdir_iter(). - */ -struct DenomkeysIterateContext -{ - - /** - * Set to the name of the directory below the top-level directory - * during the call to #denomkeys_iterate_keydir_iter(). - */ - const char *alias; - - /** - * Function to call on each denomination key. - */ - TALER_MINT_DenomkeyIterator it; - - /** - * Closure for @e it. - */ - void *it_cls; -}; - - -/** - * Decode the denomination key in the given file @a filename and call - * the callback in @a cls with the information. - * - * @param cls the `struct DenomkeysIterateContext *` - * @param filename name of a file that should contain - * a denomination key - * @return #GNUNET_OK to continue to iterate - * #GNUNET_NO to abort iteration with success - * #GNUNET_SYSERR to abort iteration with failure - */ -static int -denomkeys_iterate_keydir_iter (void *cls, - const char *filename) -{ - struct DenomkeysIterateContext *dic = cls; - struct TALER_DenominationKeyIssueInformation issue; - - if (GNUNET_OK != - TALER_MINT_read_denom_key (filename, - &issue)) - { - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Invalid denomkey file: '%s'\n", - filename); - return GNUNET_OK; - } - return dic->it (dic->it_cls, - dic->alias, - &issue); -} - - -/** - * Function called on each subdirectory in the #DIR_DENOMKEYS. Will - * call the #denomkeys_iterate_keydir_iter() on each file in the - * subdirectory. - * - * @param cls the `struct DenomkeysIterateContext *` - * @param filename name of the subdirectory to scan - * @return #GNUNET_OK on success, - * #GNUNET_SYSERR if we need to abort - */ -static int -denomkeys_iterate_topdir_iter (void *cls, - const char *filename) -{ - struct DenomkeysIterateContext *dic = cls; - - dic->alias = GNUNET_STRINGS_get_short_name (filename); - if (0 > GNUNET_DISK_directory_scan (filename, - &denomkeys_iterate_keydir_iter, - dic)) - return GNUNET_SYSERR; - return GNUNET_OK; -} - - -/** - * Call @a it for each denomination key found in the @a mint_base_dir. - * - * @param mint_base_dir base directory for the mint, - * the signing keys must be in the #DIR_DENOMKEYS - * subdirectory - * @param it function to call on each denomination key found - * @param it_cls closure for @a it - * @return -1 on error, 0 if no files were found, otherwise - * a positive number (however, even with a positive - * number it is possible that @a it was never called - * as maybe none of the files were well-formed) - */ -int -TALER_MINT_denomkeys_iterate (const char *mint_base_dir, - TALER_MINT_DenomkeyIterator it, - void *it_cls) -{ - char *dir; - struct DenomkeysIterateContext dic; - int ret; - - GNUNET_asprintf (&dir, - "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS, - mint_base_dir); - dic.it = it; - dic.it_cls = it_cls; - ret = GNUNET_DISK_directory_scan (dir, - &denomkeys_iterate_topdir_iter, - &dic); - GNUNET_free (dir); - return ret; -} - - -/* end of key_io.c */ diff --git a/src/mint/key_io.h b/src/mint/key_io.h @@ -1,187 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mint/key_io.h - * @brief IO operations for the mint's private keys - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#ifndef KEY_IO_H -#define KEY_IO_H - -#include <gnunet/gnunet_util_lib.h> -#include "taler_signatures.h" - -/** - * Subdirectroy under the mint's base directory which contains - * the mint's signing keys. - */ -#define DIR_SIGNKEYS "signkeys" - -/** - * Subdirectory under the mint's base directory which contains - * the mint's denomination keys. - */ -#define DIR_DENOMKEYS "denomkeys" - - -GNUNET_NETWORK_STRUCT_BEGIN - -/** - * On disk format used for a mint signing key. Signing keys are used - * by the mint to affirm its messages, but not to create coins. - * Includes the private key followed by the public information about - * the signing key. - */ -struct TALER_MintSigningKeyValidityPSPriv -{ - /** - * Private key part of the mint's signing key. - */ - struct TALER_MintPrivateKeyP signkey_priv; - - /** - * Public information about a mint signing key. - */ - struct TALER_MintSigningKeyValidityPS issue; -}; - - -GNUNET_NETWORK_STRUCT_END - - -/** - * All information about a denomination key (which is used to - * sign coins into existence). - */ -struct TALER_DenominationKeyIssueInformation -{ - /** - * The private key of the denomination. Will be NULL if the private - * key is not available (this is the case after the key has expired - * for signing coins, but is still valid for depositing coins). - */ - struct TALER_DenominationPrivateKey denom_priv; - - /** - * Decoded denomination public key (the hash of it is in - * @e issue, but we sometimes need the full public key as well). - */ - struct TALER_DenominationPublicKey denom_pub; - - /** - * Signed public information about a denomination key. - */ - struct TALER_DenominationKeyValidityPS issue; -}; - - -/** - * Iterator over signing keys. - * - * @param cls closure - * @param filename name of the file the key came from - * @param ski the sign key - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -typedef int -(*TALER_MINT_SignkeyIterator)(void *cls, - const char *filename, - const struct TALER_MintSigningKeyValidityPSPriv *ski); - - -/** - * Iterator over denomination keys. - * - * @param cls closure - * @param dki the denomination key - * @param alias coin alias - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -typedef int -(*TALER_MINT_DenomkeyIterator)(void *cls, - const char *alias, - const struct TALER_DenominationKeyIssueInformation *dki); - - - -/** - * Call @a it for each signing key found in the @a mint_base_dir. - * - * @param mint_base_dir base directory for the mint, - * the signing keys must be in the #DIR_SIGNKEYS - * subdirectory - * @param it function to call on each signing key - * @param it_cls closure for @a it - * @return number of files found (may not match - * number of keys given to @a it as malformed - * files are simply skipped), -1 on error - */ -int -TALER_MINT_signkeys_iterate (const char *mint_base_dir, - TALER_MINT_SignkeyIterator it, - void *it_cls); - - -/** - * Call @a it for each denomination key found in the @a mint_base_dir. - * - * @param mint_base_dir base directory for the mint, - * the signing keys must be in the #DIR_DENOMKEYS - * subdirectory - * @param it function to call on each denomination key found - * @param it_cls closure for @a it - * @return -1 on error, 0 if no files were found, otherwise - * a positive number (however, even with a positive - * number it is possible that @a it was never called - * as maybe none of the files were well-formed) - */ -int -TALER_MINT_denomkeys_iterate (const char *mint_base_dir, - TALER_MINT_DenomkeyIterator it, - void *it_cls); - - -/** - * Exports a denomination key to the given file. - * - * @param filename the file where to write the denomination key - * @param dki the denomination key - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. - */ -int -TALER_MINT_write_denom_key (const char *filename, - const struct TALER_DenominationKeyIssueInformation *dki); - - -/** - * Import a denomination key from the given file. - * - * @param filename the file to import the key from - * @param[OUT] dki set to the imported denomination key - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -int -TALER_MINT_read_denom_key (const char *filename, - struct TALER_DenominationKeyIssueInformation *dki); - - -#endif diff --git a/src/mint/plugin.c b/src/mint/plugin.c @@ -1,147 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mint/plugin.c - * @brief Logic to load database plugin - * @author Christian Grothoff - */ -#include "platform.h" -#include "plugin.h" -#include <ltdl.h> - - -/** - * Global variable with the plugin (once loaded). - */ -struct TALER_MINTDB_Plugin *plugin; - -/** - * Libtool search path before we started. - */ -static char *old_dlsearchpath; - - -/** - * Initialize the plugin. - * - * @param cfg configuration to use - * @return #GNUNET_OK on success - */ -int -TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - char *plugin_name; - char *lib_name; - struct GNUNET_CONFIGURATION_Handle *cfg_dup; - - if (NULL != plugin) - return GNUNET_OK; - if (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", - "db", - &plugin_name)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "db"); - return GNUNET_SYSERR; - } - (void) GNUNET_asprintf (&lib_name, - "libtaler_plugin_mintdb_%s", - plugin_name); - GNUNET_free (plugin_name); - cfg_dup = GNUNET_CONFIGURATION_dup (cfg); - plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); - GNUNET_CONFIGURATION_destroy (cfg_dup); - GNUNET_free (lib_name); - if (NULL == plugin) - return GNUNET_SYSERR; - return GNUNET_OK; -} - - -/** - * Shutdown the plugin. - */ -void -TALER_MINT_plugin_unload () -{ - if (NULL == plugin) - return; - GNUNET_assert (NULL == GNUNET_PLUGIN_unload (plugin->library_name, - plugin)); -} - - -/** - * Setup libtool paths. - */ -void __attribute__ ((constructor)) -plugin_init () -{ - int err; - const char *opath; - char *path; - char *cpath; - - err = lt_dlinit (); - if (err > 0) - { - FPRINTF (stderr, - _("Initialization of plugin mechanism failed: %s!\n"), - lt_dlerror ()); - return; - } - opath = lt_dlgetsearchpath (); - if (NULL != opath) - old_dlsearchpath = GNUNET_strdup (opath); - path = TALER_os_installation_get_path (GNUNET_OS_IPK_LIBDIR); - if (NULL != path) - { - if (NULL != opath) - { - GNUNET_asprintf (&cpath, "%s:%s", opath, path); - lt_dlsetsearchpath (cpath); - GNUNET_free (path); - GNUNET_free (cpath); - } - else - { - lt_dlsetsearchpath (path); - GNUNET_free (path); - } - } -} - - -/** - * Shutdown libtool. - */ -void __attribute__ ((destructor)) -plugin_fini () -{ - lt_dlsetsearchpath (old_dlsearchpath); - if (NULL != old_dlsearchpath) - { - GNUNET_free (old_dlsearchpath); - old_dlsearchpath = NULL; - } - lt_dlexit (); -} - - -/* end of plugin.c */ diff --git a/src/mint/plugin.h b/src/mint/plugin.h @@ -1,50 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mint/plugin.h - * @brief Logic to load database plugins - * @author Christian Grothoff - */ -#ifndef PLUGIN_H -#define PLUGIN_H - -#include <gnunet/gnunet_util_lib.h> -#include "taler_mintdb_plugin.h" - -/** - * Global variable with the plugin (once loaded). - */ -extern struct TALER_MINTDB_Plugin *plugin; - - -/** - * Initialize the plugin. - * - * @param cfg configuration to use - * @return #GNUNET_OK on success - */ -int -TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); - - -/** - * Shutdown the plugin. - */ -void -TALER_MINT_plugin_unload (void); - - -#endif diff --git a/src/mint/plugin_mintdb_postgres.c b/src/mint/plugin_mintdb_postgres.c @@ -1,2357 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ - -/** - * @file plugin_mintdb_postgres.c - * @brief Low-level (statement-level) Postgres database access for the mint - * @author Florian Dold - * @author Christian Grothoff - * @author Sree Harsha Totakura - */ -#include "platform.h" -#include "taler_pq_lib.h" -#include "taler_signatures.h" -#include "taler_mintdb_plugin.h" -#include <pthread.h> -#include <libpq-fe.h> - -#include "plugin_mintdb_common.c" - -#define TALER_TEMP_SCHEMA_NAME "taler_temporary" - -#define QUERY_ERR(result) \ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) - - -#define BREAK_DB_ERR(result) do { \ - GNUNET_break(0); \ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ - } while (0) - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -#define SQLEXEC_(conn, sql, result) \ - do { \ - result = PQexec (conn, sql); \ - if (PGRES_COMMAND_OK != PQresultStatus (result)) \ - { \ - BREAK_DB_ERR (result); \ - PQclear (result); result = NULL; \ - goto SQLEXEC_fail; \ - } \ - PQclear (result); result = NULL; \ - } while (0) - -/** - * This the length of the currency strings (without 0-termination) we use. Note - * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the - * DB only needs to store 3 bytes instead of 8 bytes. - */ -#define TALER_PQ_CURRENCY_LEN 3 - - -/** - * Handle for a database session (per-thread, for transactions). - */ -struct TALER_MINTDB_Session -{ - /** - * Postgres connection handle. - */ - PGconn *conn; -}; - - -/** - * Type of the "cls" argument given to each of the functions in - * our API. - */ -struct PostgresClosure -{ - - /** - * Thread-local database connection. - * Contains a pointer to PGconn or NULL. - */ - pthread_key_t db_conn_threadlocal; - - /** - * Database connection string, as read from - * the configuration. - */ - char *connection_cfg_str; -}; - - - -/** - * Set the given connection to use a temporary schema - * - * @param db the database connection - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error - */ -static int -set_temporary_schema (PGconn *db) -{ - PGresult *result; - - SQLEXEC_(db, - "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" - "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", - result); - return GNUNET_OK; - SQLEXEC_fail: - return GNUNET_SYSERR; -} - - -/** - * Drop the temporary taler schema. This is only useful for testcases - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -static int -postgres_drop_temporary (void *cls, - struct TALER_MINTDB_Session *session) -{ - PGresult *result; - - SQLEXEC_ (session->conn, - "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", - result); - return GNUNET_OK; - SQLEXEC_fail: - return GNUNET_SYSERR; -} - - -/** - * Create the necessary tables if they are not present - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param temporary should we use a temporary schema - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -static int -postgres_create_tables (void *cls, - int temporary) -{ - struct PostgresClosure *pc = cls; - PGresult *result; - PGconn *conn; - - result = NULL; - conn = PQconnectdb (pc->connection_cfg_str); - if (CONNECTION_OK != PQstatus (conn)) - { - TALER_LOG_ERROR ("Database connection failed: %s\n", - PQerrorMessage (conn)); - GNUNET_break (0); - return GNUNET_SYSERR; - } - if ( (GNUNET_YES == temporary) && - (GNUNET_SYSERR == set_temporary_schema (conn))) - { - PQfinish (conn); - return GNUNET_SYSERR; - } -#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); - /* reserves table is for summarization of a reserve. It is updated when new - funds are added and existing funds are withdrawn */ - SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" - "(" - " reserve_pub BYTEA PRIMARY KEY" - ",current_balance_value INT8 NOT NULL" - ",current_balance_fraction INT4 NOT NULL" - ",balance_currency VARCHAR(4) NOT NULL" - ",expiration_date INT8 NOT NULL" - ")"); - /* reserves_in table collects the transactions which transfer funds into the - reserve. The amount and expiration date for the corresponding reserve are - updated when new transfer funds are added. The rows of this table - correspond to each incoming transaction. */ - SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" - "(" - " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" - ",balance_value INT8 NOT NULL" - ",balance_fraction INT4 NOT NULL" - ",balance_currency VARCHAR(4) NOT NULL" - ",expiration_date INT8 NOT NULL" - ");"); - /* Create an index on the foreign key as it is not created automatically by PSQL */ - SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" - " ON reserves_in (reserve_pub);"); - SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" - "(" - "blind_ev BYTEA PRIMARY KEY" - ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ - ",denom_sig BYTEA NOT NULL" - ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" - ",reserve_sig BYTEA NOT NULL" - ");"); - SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" - " collectable_blindcoins (reserve_pub)"); - SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " - "(" - " coin_pub BYTEA NOT NULL PRIMARY KEY" - ",denom_pub BYTEA NOT NULL" - ",denom_sig BYTEA NOT NULL" - ",expended_value INT8 NOT NULL" - ",expended_fraction INT4 NOT NULL" - ",expended_currency VARCHAR(4) NOT NULL" - ",refresh_session_hash BYTEA" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " - "(" - " session_hash BYTEA PRIMARY KEY CHECK (length(session_hash) = 32)" - ",session_melt_sig BYTEA" - ",session_commit_sig BYTEA" - ",noreveal_index INT2 NOT NULL" - // non-zero if all reveals were ok - // and the new coin signatures are ready - ",reveal_ok BOOLEAN NOT NULL DEFAULT false" - ") "); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " - "( " - " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" - ",newcoin_index INT2 NOT NULL " - ",denom_pub BYTEA NOT NULL " - ",PRIMARY KEY (session_hash, newcoin_index)" - ") "); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" - "(" - " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" - ",transfer_pub BYTEA NOT NULL" - ",link_secret_enc BYTEA NOT NULL" - // index of the old coin in the customer's request - ",oldcoin_index INT2 NOT NULL" - // index for cut and choose, - // ranges from 0 to #TALER_CNC_KAPPA-1 - ",cnc_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" - "(" - " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " - ",link_vector_enc BYTEA NOT NULL" - // index of the new coin in the customer's request - ",newcoin_index INT2 NOT NULL" - // index for cut and choose, - ",cnc_index INT2 NOT NULL" - ",coin_ev BYTEA NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" - "(" - " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " - ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " - ",denom_pub BYTEA NOT NULL " - ",oldcoin_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" - "(" - " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " - ",ev_sig BYTEA NOT NULL" - ",newcoin_index INT2 NOT NULL" - ")"); - SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " - "( " - " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" - ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ - ",denom_sig BYTEA NOT NULL" - ",transaction_id INT8 NOT NULL" - ",amount_currency VARCHAR(4) NOT NULL" - ",amount_value INT8 NOT NULL" - ",amount_fraction INT4 NOT NULL" - ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" - ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" - ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" - ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" - ",wire TEXT NOT NULL" - ")"); -#undef SQLEXEC - - PQfinish (conn); - return GNUNET_OK; - - SQLEXEC_fail: - PQfinish (conn); - return GNUNET_SYSERR; -} - - -/** - * Setup prepared statements. - * - * @param db_conn connection handle to initialize - * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure - */ -static int -postgres_prepare (PGconn *db_conn) -{ - PGresult *result; - -#define PREPARE(name, sql, ...) \ - do { \ - result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ - if (PGRES_COMMAND_OK != PQresultStatus (result)) \ - { \ - BREAK_DB_ERR (result); \ - PQclear (result); result = NULL; \ - return GNUNET_SYSERR; \ - } \ - PQclear (result); result = NULL; \ - } while (0); - - PREPARE ("get_reserve", - "SELECT " - "current_balance_value" - ",current_balance_fraction" - ",balance_currency " - ",expiration_date " - "FROM reserves " - "WHERE reserve_pub=$1 " - "LIMIT 1; ", - 1, NULL); - PREPARE ("create_reserve", - "INSERT INTO reserves (" - " reserve_pub," - " current_balance_value," - " current_balance_fraction," - " balance_currency," - " expiration_date) VALUES (" - "$1, $2, $3, $4, $5);", - 5, NULL); - PREPARE ("update_reserve", - "UPDATE reserves " - "SET" - " current_balance_value=$2 " - ",current_balance_fraction=$3 " - ",expiration_date=$4 " - "WHERE reserve_pub=$1 ", - 4, NULL); - PREPARE ("create_reserves_in_transaction", - "INSERT INTO reserves_in (" - " reserve_pub," - " balance_value," - " balance_fraction," - " balance_currency," - " expiration_date) VALUES (" - " $1, $2, $3, $4, $5);", - 5, NULL); - PREPARE ("get_reserves_in_transactions", - "SELECT" - " balance_value" - ",balance_fraction" - ",balance_currency" - ",expiration_date" - " FROM reserves_in WHERE reserve_pub=$1", - 1, NULL); - PREPARE ("insert_collectable_blindcoin", - "INSERT INTO collectable_blindcoins ( " - " blind_ev" - ",denom_pub, denom_sig" - ",reserve_pub, reserve_sig) " - "VALUES ($1, $2, $3, $4, $5)", - 5, NULL); - PREPARE ("get_collectable_blindcoin", - "SELECT " - " denom_pub, denom_sig" - ",reserve_sig, reserve_pub " - "FROM collectable_blindcoins " - "WHERE blind_ev = $1", - 1, NULL); - PREPARE ("get_reserves_blindcoins", - "select" - " blind_ev" - ",denom_pub, denom_sig" - ",reserve_sig" - " FROM collectable_blindcoins" - " WHERE reserve_pub=$1;", - 1, NULL); - - /* FIXME: does it make sense to store these computed values in the DB? */ -#if 0 - PREPARE ("get_refresh_session", - "SELECT " - " (SELECT count(*) FROM refresh_melt WHERE session_hash = $1)::INT2 as num_oldcoins " - ",(SELECT count(*) FROM refresh_blind_session_keys " - " WHERE session_hash = $1 and cnc_index = 0)::INT2 as num_newcoins " - ",(SELECT count(*) FROM refresh_blind_session_keys " - " WHERE session_hash = $1 and newcoin_index = 0)::INT2 as kappa " - ",noreveal_index" - ",session_commit_sig " - ",reveal_ok " - "FROM refresh_sessions " - "WHERE session_hash = $1", - 1, NULL); -#endif - - PREPARE ("get_known_coin", - "SELECT " - " coin_pub, denom_pub, denom_sig " - ",expended_value, expended_fraction, expended_currency " - ",refresh_session_hash " - "FROM known_coins " - "WHERE coin_pub = $1", - 1, NULL); - PREPARE ("update_known_coin", - "UPDATE known_coins " - "SET " - " denom_pub = $2 " - ",denom_sig = $3 " - ",expended_value = $4 " - ",expended_fraction = $5 " - ",expended_currency = $6 " - ",refresh_session_hash = $7 " - "WHERE " - " coin_pub = $1 ", - 7, NULL); - PREPARE ("insert_known_coin", - "INSERT INTO known_coins (" - " coin_pub" - ",denom_pub" - ",denom_sig" - ",expended_value" - ",expended_fraction" - ",expended_currency" - ",refresh_session_hash" - ")" - "VALUES ($1,$2,$3,$4,$5,$6,$7)", - 7, NULL); - PREPARE ("get_refresh_commit_link", - "SELECT " - " transfer_pub " - ",link_secret_enc " - "FROM refresh_commit_link " - "WHERE session_hash = $1 AND cnc_index = $2 AND oldcoin_index = $3", - 3, NULL); - PREPARE ("get_refresh_commit_coin", - "SELECT " - " link_vector_enc " - ",coin_ev " - "FROM refresh_commit_coin " - "WHERE session_hash = $1 AND cnc_index = $2 AND newcoin_index = $3", - 3, NULL); - PREPARE ("insert_refresh_order", - "INSERT INTO refresh_order ( " - " newcoin_index " - ",session_hash " - ",denom_pub " - ") " - "VALUES ($1, $2, $3) ", - 3, NULL); - PREPARE ("insert_refresh_melt", - "INSERT INTO refresh_melt ( " - " session_hash " - ",oldcoin_index " - ",coin_pub " - ",denom_pub " - ") " - "VALUES ($1, $2, $3, $4) ", - 3, NULL); - PREPARE ("get_refresh_order", - "SELECT denom_pub " - "FROM refresh_order " - "WHERE session_hash = $1 AND newcoin_index = $2", - 2, NULL); - PREPARE ("get_refresh_collectable", - "SELECT ev_sig " - "FROM refresh_collectable " - "WHERE session_hash = $1 AND newcoin_index = $2", - 2, NULL); - PREPARE ("get_refresh_melt", - "SELECT coin_pub " - "FROM refresh_melt " - "WHERE session_hash = $1 AND oldcoin_index = $2", - 2, NULL); - PREPARE ("insert_refresh_session", - "INSERT INTO refresh_sessions ( " - " session_hash " - ",noreveal_index " - ") " - "VALUES ($1, $2) ", - 2, NULL); - PREPARE ("insert_refresh_commit_link", - "INSERT INTO refresh_commit_link ( " - " session_hash " - ",transfer_pub " - ",cnc_index " - ",oldcoin_index " - ",link_secret_enc " - ") " - "VALUES ($1, $2, $3, $4, $5) ", - 5, NULL); - PREPARE ("insert_refresh_commit_coin", - "INSERT INTO refresh_commit_coin ( " - " session_hash " - ",coin_ev " - ",cnc_index " - ",newcoin_index " - ",link_vector_enc " - ") " - "VALUES ($1, $2, $3, $4, $5) ", - 5, NULL); - PREPARE ("insert_refresh_collectable", - "INSERT INTO refresh_collectable ( " - " session_hash " - ",newcoin_index " - ",ev_sig " - ") " - "VALUES ($1, $2, $3) ", - 3, NULL); - PREPARE ("set_reveal_ok", - "UPDATE refresh_sessions " - "SET reveal_ok = TRUE " - "WHERE session_hash = $1 ", - 1, NULL); - PREPARE ("get_link", - "SELECT link_vector_enc, ro.denom_pub, ev_sig " - "FROM refresh_melt rm " - " JOIN refresh_order ro USING (session_hash) " - " JOIN refresh_commit_coin rcc USING (session_hash) " - " JOIN refresh_sessions rs USING (session_hash) " - " JOIN refresh_collectable rc USING (session_hash) " - "WHERE rm.coin_pub = $1 " - "AND ro.newcoin_index = rcc.newcoin_index " - "AND ro.newcoin_index = rc.newcoin_index " - "AND rcc.cnc_index = rs.noreveal_index % ( " - " SELECT count(*) FROM refresh_commit_coin rcc2 " - " WHERE rcc2.newcoin_index = 0 AND rcc2.session_hash = rs.session_hash " - " ) ", - 1, NULL); - PREPARE ("get_transfer", - "SELECT transfer_pub, link_secret_enc " - "FROM refresh_melt rm " - " JOIN refresh_commit_link rcl USING (session_hash) " - " JOIN refresh_sessions rs USING (session_hash) " - "WHERE rm.coin_pub = $1 " - "AND rm.oldcoin_index = rcl.oldcoin_index " - "AND rcl.cnc_index = rs.noreveal_index % ( " - " SELECT count(*) FROM refresh_commit_coin rcc2 " - " WHERE newcoin_index = 0 AND rcc2.session_hash = rm.session_hash " - " ) ", - 1, NULL); - PREPARE ("insert_deposit", - "INSERT INTO deposits (" - "coin_pub," - "denom_pub," - "denom_sig," - "transaction_id," - "amount_value," - "amount_fraction," - "amount_currency," - "merchant_pub," - "h_contract," - "h_wire," - "coin_sig," - "wire" - ") VALUES (" - "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" - ")", - 12, NULL); - PREPARE ("get_deposit", - "SELECT " - "coin_pub," - "denom_pub," - "transaction_id," - "amount_value," - "amount_fraction," - "amount_currency," - "merchant_pub," - "h_contract," - "h_wire," - "coin_sig" - " FROM deposits WHERE (" - "(coin_pub = $1) AND" - "(transaction_id = $2) AND" - "(merchant_pub = $3)" - ")", - 3, NULL); - return GNUNET_OK; -#undef PREPARE -} - - -/** - * Close thread-local database connection when a thread is destroyed. - * - * @param closure we get from pthreads (the db handle) - */ -static void -db_conn_destroy (void *cls) -{ - PGconn *db_conn = cls; - - if (NULL != db_conn) - PQfinish (db_conn); -} - - -/** - * Get the thread-local database-handle. - * Connect to the db if the connection does not exist yet. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the - * database default one - * @return the database connection, or NULL on error - */ -static struct TALER_MINTDB_Session * -postgres_get_session (void *cls, - int temporary) -{ - struct PostgresClosure *pc = cls; - PGconn *db_conn; - struct TALER_MINTDB_Session *session; - - if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) - return session; - db_conn = PQconnectdb (pc->connection_cfg_str); - if (CONNECTION_OK != - PQstatus (db_conn)) - { - TALER_LOG_ERROR ("Database connection failed: %s\n", - PQerrorMessage (db_conn)); - GNUNET_break (0); - return NULL; - } - if ((GNUNET_YES == temporary) - && (GNUNET_SYSERR == set_temporary_schema(db_conn))) - { - GNUNET_break (0); - return NULL; - } - if (GNUNET_OK != - postgres_prepare (db_conn)) - { - GNUNET_break (0); - return NULL; - } - session = GNUNET_new (struct TALER_MINTDB_Session); - session->conn = db_conn; - if (0 != pthread_setspecific (pc->db_conn_threadlocal, - session)) - { - GNUNET_break (0); - // FIXME: close db_conn! - GNUNET_free (session); - return NULL; - } - return session; -} - - -/** - * Start a transaction. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection - * @return #GNUNET_OK on success - */ -static int -postgres_start (void *cls, - struct TALER_MINTDB_Session *session) -{ - PGresult *result; - - result = PQexec (session->conn, - "BEGIN"); - if (PGRES_COMMAND_OK != - PQresultStatus (result)) - { - TALER_LOG_ERROR ("Failed to start transaction: %s\n", - PQresultErrorMessage (result)); - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Roll back the current transaction of a database connection. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection - * @return #GNUNET_OK on success - */ -static void -postgres_rollback (void *cls, - struct TALER_MINTDB_Session *session) -{ - PGresult *result; - - result = PQexec (session->conn, - "ROLLBACK"); - GNUNET_break (PGRES_COMMAND_OK == - PQresultStatus (result)); - PQclear (result); -} - - -/** - * Commit the current transaction of a database connection. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection - * @return #GNUNET_OK on success - */ -static int -postgres_commit (void *cls, - struct TALER_MINTDB_Session *session) -{ - PGresult *result; - - result = PQexec (session->conn, - "COMMIT"); - if (PGRES_COMMAND_OK != - PQresultStatus (result)) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Get the summary of a reserve. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection handle - * @param reserve the reserve data. The public key of the reserve should be set - * in this structure; it is used to query the database. The balance - * and expiration are then filled accordingly. - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure - */ -static int -postgres_reserve_get (void *cls, - struct TALER_MINTDB_Session *session, - struct Reserve *reserve) -{ - PGresult *result; - uint64_t expiration_date_nbo; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(&reserve->pub), - TALER_PQ_QUERY_PARAM_END - }; - - result = TALER_PQ_exec_prepared (session->conn, - "get_reserve", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC("expiration_date", &expiration_date_nbo), - TALER_PQ_RESULT_SPEC_END - }; - EXITIF (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)); - EXITIF (GNUNET_OK != - TALER_PQ_extract_amount (result, 0, - "current_balance_value", - "current_balance_fraction", - "balance_currency", - &reserve->balance)); - reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); - PQclear (result); - return GNUNET_OK; - - EXITIF_exit: - PQclear (result); - return GNUNET_SYSERR; -} - - -/** - * Updates a reserve with the data from the given reserve structure. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection - * @param reserve the reserve structure whose data will be used to update the - * corresponding record in the database. - * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error - */ -static int -postgres_reserves_update (void *cls, - struct TALER_MINTDB_Session *session, - struct Reserve *reserve) -{ - PGresult *result; - struct TALER_AmountNBO balance_nbo; - struct GNUNET_TIME_AbsoluteNBO expiry_nbo; - int ret; - - if (NULL == reserve) - return GNUNET_SYSERR; - ret = GNUNET_OK; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), - TALER_PQ_QUERY_PARAM_END - }; - TALER_amount_hton (&balance_nbo, - &reserve->balance); - expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); - result = TALER_PQ_exec_prepared (session->conn, - "update_reserve", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - ret = GNUNET_SYSERR; - } - PQclear (result); - return ret; -} - - -/** - * Insert a incoming transaction into reserves. New reserves are also created - * through this function. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session the database connection handle - * @param reserve the reserve structure. The public key of the reserve should - * be set here. Upon successful execution of this function, the - * balance and expiration of the reserve will be updated. - * @param balance the amount that has to be added to the reserve - * @param expiry the new expiration time for the reserve - * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures - */ -static int -postgres_reserves_in_insert (void *cls, - struct TALER_MINTDB_Session *session, - struct Reserve *reserve, - const struct TALER_Amount *balance, - const struct GNUNET_TIME_Absolute expiry) -{ - struct TALER_AmountNBO balance_nbo; - struct GNUNET_TIME_AbsoluteNBO expiry_nbo; - PGresult *result; - int reserve_exists; - - result = NULL; - if (NULL == reserve) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - if (GNUNET_OK != postgres_start (cls, - session)) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - reserve_exists = postgres_reserve_get (cls, - session, - reserve); - if (GNUNET_SYSERR == reserve_exists) - { - postgres_rollback (cls, - session); - return GNUNET_SYSERR; - } - TALER_amount_hton (&balance_nbo, - balance); - expiry_nbo = GNUNET_TIME_absolute_hton (expiry); - if (GNUNET_NO == reserve_exists) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Reserve does not exist; creating a new one\n"); - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_PQ_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, - TALER_PQ_CURRENCY_LEN), - TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "create_reserve", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - goto rollback; - } - } - if (NULL != result) - PQclear (result); - result = NULL; - /* create new incoming transaction */ - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), - TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), - TALER_PQ_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, - TALER_PQ_CURRENCY_LEN), - TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "create_reserves_in_transaction", - params); - if (PGRES_COMMAND_OK != PQresultStatus(result)) - { - QUERY_ERR (result); - goto rollback; - } - PQclear (result); - result = NULL; - if (GNUNET_NO == reserve_exists) - { - if (GNUNET_OK != postgres_commit (cls, - session)) - return GNUNET_SYSERR; - reserve->balance = *balance; - reserve->expiry = expiry; - return GNUNET_OK; - } - /* Update reserve */ - struct Reserve updated_reserve; - updated_reserve.pub = reserve->pub; - - if (GNUNET_OK != - TALER_amount_add (&updated_reserve.balance, - &reserve->balance, - balance)) - { - return GNUNET_SYSERR; - } - updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); - if (GNUNET_OK != postgres_reserves_update (cls, - session, - &updated_reserve)) - goto rollback; - if (GNUNET_OK != postgres_commit (cls, - session)) - return GNUNET_SYSERR; - reserve->balance = updated_reserve.balance; - reserve->expiry = updated_reserve.expiry; - return GNUNET_OK; - - rollback: - PQclear (result); - postgres_rollback (cls, - session); - return GNUNET_SYSERR; -} - - -/** - * Locate the response for a /withdraw request under the - * key of the hash of the blinded message. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param h_blind hash of the blinded message - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -static int -postgres_get_collectable_blindcoin (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *h_blind, - struct CollectableBlindcoin *collectable) -{ - PGresult *result; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (h_blind), - TALER_PQ_QUERY_PARAM_END - }; - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - struct GNUNET_CRYPTO_rsa_Signature *denom_sig; - char *denom_pub_enc; - char *denom_sig_enc; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub = NULL; - denom_pub_enc = NULL; - denom_sig_enc = NULL; - result = TALER_PQ_exec_prepared (session->conn, - "get_collectable_blindcoin", - params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == PQntuples (result)) - { - ret = GNUNET_NO; - goto cleanup; - } - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), - TALER_PQ_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), - TALER_PQ_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), - TALER_PQ_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), - TALER_PQ_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) - { - GNUNET_break (0); - goto cleanup; - } - denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, - denom_pub_enc_size); - denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, - denom_sig_enc_size); - if ((NULL == denom_pub) || (NULL == denom_sig)) - { - GNUNET_break (0); - goto cleanup; - } - collectable->denom_pub.rsa_public_key = denom_pub; - collectable->sig.rsa_signature = denom_sig; - ret = GNUNET_YES; - - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - if (GNUNET_YES != ret) - { if (NULL != denom_pub) - GNUNET_CRYPTO_rsa_public_key_free (denom_pub); - if (NULL != denom_sig) - GNUNET_CRYPTO_rsa_signature_free (denom_sig); - } - return ret; -} - - -/** - * Store collectable bit coin under the corresponding - * hash of the blinded message. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param h_blind hash of the blinded message - * @param withdraw amount by which the reserve will be withdrawn with this - * transaction - * @param collectable corresponding collectable coin (blind signature) - * if a coin is found - * @return #GNUNET_SYSERR on internal error - * #GNUNET_NO if the collectable was not found - * #GNUNET_YES on success - */ -static int -postgres_insert_collectable_blindcoin (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *h_blind, - struct TALER_Amount withdraw, - const struct CollectableBlindcoin *collectable) -{ - PGresult *result; - struct Reserve reserve; - char *denom_pub_enc = NULL; - char *denom_sig_enc = NULL; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub_enc_size = - GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub.rsa_public_key, - &denom_pub_enc); - denom_sig_enc_size = - GNUNET_CRYPTO_rsa_signature_encode (collectable->sig.rsa_signature, - &denom_sig_enc); - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (h_blind), - TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), - TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ - TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_pub), - TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_sig), - TALER_PQ_QUERY_PARAM_END - }; - if (GNUNET_OK != postgres_start (cls, - session)) - goto cleanup; - result = TALER_PQ_exec_prepared (session->conn, - "insert_collectable_blindcoin", - params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto rollback; - } - reserve.pub = collectable->reserve_pub; - if (GNUNET_OK != postgres_reserve_get (cls, - session, - &reserve)) - goto rollback; - if (GNUNET_SYSERR == - TALER_amount_subtract (&reserve.balance, - &reserve.balance, - &withdraw)) - goto rollback; - if (GNUNET_OK != postgres_reserves_update (cls, - session, - &reserve)) - goto rollback; - if (GNUNET_OK == postgres_commit (cls, - session)) - { - ret = GNUNET_OK; - goto cleanup; - } - - rollback: - postgres_rollback (cls, - session); - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - return ret; -} - - -/** - * Get all of the transaction history associated with the specified - * reserve. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session connection to use - * @param reserve_pub public key of the reserve - * @return known transaction history (NULL if reserve is unknown) - */ -static struct ReserveHistory * -postgres_get_reserve_history (void *cls, - struct TALER_MINTDB_Session *session, - const struct TALER_ReservePublicKeyP *reserve_pub) -{ - PGresult *result; - struct ReserveHistory *rh; - struct ReserveHistory *rh_head; - int rows; - int ret; - - result = NULL; - rh = NULL; - rh_head = NULL; - ret = GNUNET_SYSERR; - { - struct BankTransfer *bt; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (reserve_pub), - TALER_PQ_QUERY_PARAM_END - }; - - result = TALER_PQ_exec_prepared (session->conn, - "get_reserves_in_transactions", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == (rows = PQntuples (result))) - { - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Asked to fetch history for an unknown reserve.\n"); - goto cleanup; - } - while (0 < rows) - { - bt = GNUNET_new (struct BankTransfer); - if (GNUNET_OK != TALER_PQ_extract_amount (result, - --rows, - "balance_value", - "balance_fraction", - "balance_currency", - &bt->amount)) - { - GNUNET_free (bt); - GNUNET_break (0); - goto cleanup; - } - bt->reserve_pub = *reserve_pub; - if (NULL != rh_head) - { - rh_head->next = GNUNET_new (struct ReserveHistory); - rh_head = rh_head->next; - } - else - { - rh_head = GNUNET_new (struct ReserveHistory); - rh = rh_head; - } - rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; - rh_head->details.bank = bt; - } - } - PQclear (result); - result = NULL; - { - struct GNUNET_HashCode blind_ev; - struct TALER_ReserveSignatureP reserve_sig; - struct CollectableBlindcoin *cbc; - char *denom_pub_enc; - char *denom_sig_enc; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (reserve_pub), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "get_reserves_blindcoins", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - QUERY_ERR (result); - goto cleanup; - } - if (0 == (rows = PQntuples (result))) - { - ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */ - goto cleanup; - } - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC ("blind_ev", &blind_ev), - TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), - TALER_PQ_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), - TALER_PQ_RESULT_SPEC ("reserve_sig", &reserve_sig), - TALER_PQ_RESULT_SPEC_END - }; - GNUNET_assert (NULL != rh); - GNUNET_assert (NULL != rh_head); - GNUNET_assert (NULL == rh_head->next); - while (0 < rows) - { - if (GNUNET_YES != TALER_PQ_extract_result (result, rs, --rows)) - { - GNUNET_break (0); - goto cleanup; - } - cbc = GNUNET_new (struct CollectableBlindcoin); - cbc->sig.rsa_signature - = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, - denom_sig_enc_size); - GNUNET_free (denom_sig_enc); - denom_sig_enc = NULL; - cbc->denom_pub.rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, - denom_pub_enc_size); - GNUNET_free (denom_pub_enc); - denom_pub_enc = NULL; - if ( (NULL == cbc->sig.rsa_signature) || - (NULL == cbc->denom_pub.rsa_public_key) ) - { - if (NULL != cbc->sig.rsa_signature) - GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); - if (NULL != cbc->denom_pub.rsa_public_key) - GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); - GNUNET_free (cbc); - GNUNET_break (0); - goto cleanup; - } - (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); - (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); - (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); - rh_head->next = GNUNET_new (struct ReserveHistory); - rh_head = rh_head->next; - rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; - rh_head->details.withdraw = cbc; - } - } - ret = GNUNET_OK; - - cleanup: - if (NULL != result) - PQclear (result); - if (GNUNET_SYSERR == ret) - { - common_free_reserve_history (cls, - rh); - rh = NULL; - } - return rh; -} - - -/** - * Check if we have the specified deposit already in the database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param deposit deposit to search for - * @return #GNUNET_YES if we know this operation, - * #GNUNET_NO if this deposit is unknown to us - */ -static int -postgres_have_deposit (void *cls, - struct TALER_MINTDB_Session *session, - const struct Deposit *deposit) -{ - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), - TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), - TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), - TALER_PQ_QUERY_PARAM_END - }; - PGresult *result; - int ret; - - ret = GNUNET_SYSERR; - result = TALER_PQ_exec_prepared (session->conn, - "get_deposit", - params); - if (PGRES_TUPLES_OK != - PQresultStatus (result)) - { - BREAK_DB_ERR (result); - goto cleanup; - } - - if (0 == PQntuples (result)) - { - ret = GNUNET_NO; - goto cleanup; - } - ret = GNUNET_YES; - - cleanup: - PQclear (result); - return ret; -} - - -/** - * Insert information about deposited coin into the - * database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session connection to the database - * @param deposit deposit information to store - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -postgres_insert_deposit (void *cls, - struct TALER_MINTDB_Session *session, - const struct Deposit *deposit) -{ - char *denom_pub_enc; - char *denom_sig_enc; - char *json_wire_enc; - PGresult *result; - struct TALER_AmountNBO amount_nbo; - size_t denom_pub_enc_size; - size_t denom_sig_enc_size; - int ret; - - ret = GNUNET_SYSERR; - denom_pub_enc_size = - GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub.rsa_public_key, - &denom_pub_enc); - denom_sig_enc_size = - GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig.rsa_signature, - &denom_sig_enc); - json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); - TALER_amount_hton (&amount_nbo, - &deposit->amount_with_fee); - struct TALER_PQ_QueryParam params[]= { - TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), - TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), - TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), - TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), - TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.value), - TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.fraction), - TALER_PQ_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, - TALER_CURRENCY_LEN - 1), - TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), - TALER_PQ_QUERY_PARAM_PTR (&deposit->h_contract), - TALER_PQ_QUERY_PARAM_PTR (&deposit->h_wire), - TALER_PQ_QUERY_PARAM_PTR (&deposit->csig), - TALER_PQ_QUERY_PARAM_PTR_SIZED (json_wire_enc, - strlen (json_wire_enc)), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, "insert_deposit", params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - goto cleanup; - } - ret = GNUNET_OK; - - cleanup: - PQclear (result); - GNUNET_free_non_null (denom_pub_enc); - GNUNET_free_non_null (denom_sig_enc); - GNUNET_free_non_null (json_wire_enc); - return ret; -} - - -/** - * Lookup refresh session data under the given @a session_hash. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database handle to use - * @param session_hash hash over the melt to use to locate the session - * @param refresh_session[OUT] where to store the result - * @return #GNUNET_YES on success, - * #GNUNET_NO if not found, - * #GNUNET_SYSERR on DB failure - */ -static int -postgres_get_refresh_session (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - struct RefreshSession *refresh_session) -{ - // FIXME: check logic! - int res; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "get_refresh_session", - params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Query failed: %s\n", - PQresultErrorMessage (result)); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - return GNUNET_NO; - - GNUNET_assert (1 == PQntuples (result)); - - /* We're done if the caller is only interested in - * whether the session exists or not */ - - if (NULL == refresh_session) - return GNUNET_YES; - - memset (session, 0, sizeof (struct RefreshSession)); - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins), - TALER_PQ_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins), - TALER_PQ_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index), - TALER_PQ_RESULT_SPEC_END - }; - - res = TALER_PQ_extract_result (result, rs, 0); - - if (GNUNET_OK != res) - { - GNUNET_break (0); - PQclear (result); - return GNUNET_SYSERR; - } - - refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins); - refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins); - refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index); - - PQclear (result); - return GNUNET_YES; -} - - -/** - * Store new refresh session data under the given @a session_hash. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database handle to use - * @param session_hash hash over the melt to use to locate the session - * @param refresh_session session data to store - * @return #GNUNET_YES on success, - * #GNUNET_SYSERR on DB failure - */ -static int -postgres_create_refresh_session (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - const struct RefreshSession *refresh_session) -{ - // FIXME: actually store session data! - uint16_t noreveal_index; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&noreveal_index), - TALER_PQ_QUERY_PARAM_END - }; - - noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); - noreveal_index = htonl (noreveal_index); - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_session", - params); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Store the given /refresh/melt request in the database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param oldcoin_index index of the coin to store - * @param melt melt operation details to store; includes - * the session hash of the melt - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -static int -postgres_insert_refresh_melt (void *cls, - struct TALER_MINTDB_Session *session, - uint16_t oldcoin_index, - const struct RefreshMelt *melt) -{ - // FIXME: check logic! - uint16_t oldcoin_index_nbo = htons (oldcoin_index); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub.rsa_public_key, - &buf); - { - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(&melt->session_hash), - TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&melt->coin.coin_pub), - TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_melt", - params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Get information about melted coin details from the database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param refresh_session session key of the melt operation - * @param oldcoin_index index of the coin to retrieve - * @param melt melt data to fill in - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -static int -postgres_get_refresh_melt (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - uint16_t oldcoin_index, - struct RefreshMelt *melt) -{ - // FIXME: check logic! - GNUNET_break (0); - return GNUNET_SYSERR; -} - - -/** - * Store in the database which coin(s) we want to create - * in a given refresh operation. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param session_hash hash to identify refresh session - * @param num_newcoins number of coins to generate, size of the @a denom_pubs array - * @param denom_pubs array denominations of the coins to create - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -static int -postgres_insert_refresh_order (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - uint16_t num_newcoins, - const struct TALER_DenominationPublicKey *denom_pubs) -{ - // FIXME: check logic: was written for just one COIN! - uint16_t newcoin_index_nbo = htons (num_newcoins); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs->rsa_public_key, - &buf); - - { - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR (&newcoin_index_nbo), - TALER_PQ_QUERY_PARAM_PTR (session_hash), - TALER_PQ_QUERY_PARAM_PTR_SIZED (buf, buf_size), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_order", - params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Lookup in the database the coins that we want to - * create in the given refresh operation. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param session_hash hash to identify refresh session - * @param newcoin_index array of the @a denom_pubs array - * @param denom_pubs where to store the deomination keys - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on internal error - */ -static int -postgres_get_refresh_order (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - uint16_t num_newcoins, - struct TALER_DenominationPublicKey *denom_pubs) -{ - // FIXME: check logic -- was written for just one coin! - char *buf; - size_t buf_size; - uint16_t newcoin_index_nbo = htons (num_newcoins); - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "get_refresh_order", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - /* FIXME: may want to distinguish between different error cases! */ - return GNUNET_SYSERR; - } - GNUNET_assert (1 == PQntuples (result)); - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), - TALER_PQ_RESULT_SPEC_END - }; - if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) - { - PQclear (result); - GNUNET_break (0); - return GNUNET_SYSERR; - } - PQclear (result); - denom_pubs->rsa_public_key - = GNUNET_CRYPTO_rsa_public_key_decode (buf, - buf_size); - GNUNET_free (buf); - return GNUNET_OK; -} - - - -/** - * Store information about the commitment of the - * given coin for the given refresh session in the database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param session_hash hash to identify refresh session - * @param i set index (1st dimension) - * @param num_newcoins coin index size of the @a commit_coins array - * @param commit_coins array of coin commitments to store - * @return #GNUNET_OK on success - * #GNUNET_SYSERR on error - */ -static int -postgres_insert_refresh_commit_coins (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - unsigned int i, - unsigned int num_newcoins, - const struct RefreshCommitCoin *commit_coins) -{ - // FIXME: check logic! -- was written for single commit_coin! - uint16_t cnc_index_nbo = htons (i); - uint16_t newcoin_index_nbo = htons (num_newcoins); - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR_SIZED(commit_coins->coin_ev, commit_coins->coin_ev_size), - TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_PQ_QUERY_PARAM_PTR_SIZED (commit_coins->refresh_link->coin_priv_enc, - commit_coins->refresh_link->blinding_key_enc_size + - sizeof (union TALER_CoinSpendPrivateKeyP)), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_commit_coin", - params); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain information about the commitment of the - * given coin of the given refresh session from the database. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param session_hash hash to identify refresh session - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to refreshed (new) coins - * @param commit_coin[OUT] coin commitment to return - * @return #GNUNET_OK on success - * #GNUNET_NO if not found - * #GNUNET_SYSERR on error - */ -static int -postgres_get_refresh_commit_coins (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - unsigned int cnc_index, - unsigned int newcoin_index, - struct RefreshCommitCoin *cc) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (cnc_index); - uint16_t newcoin_index_nbo = htons (newcoin_index); - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_PQ_QUERY_PARAM_END - }; - char *c_buf; - size_t c_buf_size; - char *rl_buf; - size_t rl_buf_size; - struct TALER_RefreshLinkEncrypted *rl; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "get_refresh_commit_coin", - params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), - TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), - TALER_PQ_RESULT_SPEC_END - }; - if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) - { - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - if (rl_buf_size < sizeof (union TALER_CoinSpendPrivateKeyP)) - { - GNUNET_free (c_buf); - GNUNET_free (rl_buf); - return GNUNET_SYSERR; - } - rl = TALER_refresh_link_encrypted_decode (rl_buf, - rl_buf_size); - GNUNET_free (rl_buf); - cc->refresh_link = rl; - cc->coin_ev = c_buf; - cc->coin_ev_size = c_buf_size; - return GNUNET_YES; -} - - -/** - * Store the commitment to the given (encrypted) refresh link data - * for the given refresh session. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param session_hash hash to identify refresh session - * @param i set index (1st dimension) - * @param j coin index (2nd dimension), corresponds to melted (old) coins - * @param commit_link link information to store - * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success - */ -static int -postgres_insert_refresh_commit_links (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - unsigned int i, - unsigned int j, - const struct RefreshCommitLink *commit_link) -{ - // FIXME: check logic! - uint16_t cnc_index_nbo = htons (i); - uint16_t oldcoin_index_nbo = htons (j); - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&commit_link->transfer_pub), - TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_commit_link", - params); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 != strcmp ("1", PQcmdTuples (result))) - { - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain the commited (encrypted) refresh link data - * for the given refresh session. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection to use - * @param session_hash hash to identify refresh session - * @param i set index (1st dimension) - * @param num_links size of the @a commit_link array - * @param links[OUT] array of link information to return - * @return #GNUNET_SYSERR on internal error, - * #GNUNET_NO if commitment was not found - * #GNUNET_OK on success - */ -static int -postgres_get_refresh_commit_links (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - unsigned int i, - unsigned int num_links, - struct RefreshCommitLink *links) -{ - // FIXME: check logic: was written for a single link! - uint16_t cnc_index_nbo = htons (i); - uint16_t oldcoin_index_nbo = htons (num_links); - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), - TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, - "get_refresh_commit_link", - params); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC("transfer_pub", &links->transfer_pub), - TALER_PQ_RESULT_SPEC("link_secret_enc", &links->shared_secret_enc), - TALER_PQ_RESULT_SPEC_END - }; - - if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) - { - PQclear (result); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Insert signature of a new coin generated during refresh into - * the database indexed by the refresh session and the index - * of the coin. This data is later used should an old coin - * be used to try to obtain the private keys during "/refresh/link". - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param session_hash hash to identify refresh session - * @param newcoin_index coin index - * @param ev_sig coin signature - * @return #GNUNET_OK on success - */ -static int -postgres_insert_refresh_collectable (void *cls, - struct TALER_MINTDB_Session *session, - const struct GNUNET_HashCode *session_hash, - uint16_t newcoin_index, - const struct TALER_DenominationSignature *ev_sig) -{ - // FIXME: check logic! - uint16_t newcoin_index_nbo = htons (newcoin_index); - char *buf; - size_t buf_size; - PGresult *result; - - buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig->rsa_signature, - &buf); - { - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(session_hash), - TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), - TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), - TALER_PQ_QUERY_PARAM_END - }; - result = TALER_PQ_exec_prepared (session->conn, - "insert_refresh_collectable", - params); - } - GNUNET_free (buf); - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - PQclear (result); - return GNUNET_OK; -} - - -/** - * Obtain the link data of a coin, that is the encrypted link - * information, the denomination keys and the signatures. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param coin_pub public key to use to retrieve linkage data - * @return all known link data for the coin - */ -static struct LinkDataList * -postgres_get_link_data_list (void *cls, - struct TALER_MINTDB_Session *session, - const union TALER_CoinSpendPublicKeyP *coin_pub) -{ - // FIXME: check logic! - struct LinkDataList *ldl; - struct LinkDataList *pos; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(coin_pub), - TALER_PQ_QUERY_PARAM_END - }; - PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_link", params); - - ldl = NULL; - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return NULL; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return NULL; - } - - - int i = 0; - - for (i = 0; i < PQntuples (result); i++) - { - struct TALER_RefreshLinkEncrypted *link_enc; - struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; - struct GNUNET_CRYPTO_rsa_Signature *sig; - char *ld_buf; - size_t ld_buf_size; - char *pk_buf; - size_t pk_buf_size; - char *sig_buf; - size_t sig_buf_size; - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), - TALER_PQ_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), - TALER_PQ_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), - TALER_PQ_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_PQ_extract_result (result, rs, i)) - { - PQclear (result); - GNUNET_break (0); - common_free_link_data_list (cls, - ldl); - return NULL; - } - if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) - { - PQclear (result); - GNUNET_free (pk_buf); - GNUNET_free (sig_buf); - GNUNET_free (ld_buf); - common_free_link_data_list (cls, - ldl); - return NULL; - } - // FIXME: use util API for this! - link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + - ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); - link_enc->blinding_key_enc = (const char *) &link_enc[1]; - link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); - memcpy (link_enc->coin_priv_enc, - ld_buf, - ld_buf_size); - - sig - = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, - sig_buf_size); - denom_pub - = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, - pk_buf_size); - GNUNET_free (pk_buf); - GNUNET_free (sig_buf); - GNUNET_free (ld_buf); - if ( (NULL == sig) || - (NULL == denom_pub) ) - { - if (NULL != denom_pub) - GNUNET_CRYPTO_rsa_public_key_free (denom_pub); - if (NULL != sig) - GNUNET_CRYPTO_rsa_signature_free (sig); - GNUNET_free (link_enc); - GNUNET_break (0); - PQclear (result); - common_free_link_data_list (cls, - ldl); - return NULL; - } - pos = GNUNET_new (struct LinkDataList); - pos->next = ldl; - pos->link_data_enc = link_enc; - pos->denom_pub.rsa_public_key = denom_pub; - pos->ev_sig.rsa_signature = sig; - ldl = pos; - } - return ldl; -} - - -/** - * Obtain shared secret and transfer public key from the public key of - * the coin. This information and the link information returned by - * #postgres_get_link_data_list() enable the owner of an old coin to - * determine the private keys of the new coins after the melt. - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param coin_pub public key of the coin - * @param transfer_pub[OUT] public transfer key - * @param shared_secret_enc[OUT] set to shared secret - * @return #GNUNET_OK on success, - * #GNUNET_NO on failure (not found) - * #GNUNET_SYSERR on internal failure (database issue) - */ -static int -postgres_get_transfer (void *cls, - struct TALER_MINTDB_Session *session, - const union TALER_CoinSpendPublicKeyP *coin_pub, - struct TALER_TransferPublicKeyP *transfer_pub, - struct TALER_EncryptedLinkSecretP *shared_secret_enc) -{ - // FIXME: check logic! - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_QUERY_PARAM_PTR(coin_pub), - TALER_PQ_QUERY_PARAM_END - }; - - PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_transfer", params); - - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - BREAK_DB_ERR (result); - PQclear (result); - return GNUNET_SYSERR; - } - - if (0 == PQntuples (result)) - { - PQclear (result); - return GNUNET_NO; - } - - if (1 != PQntuples (result)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "got %d tuples for get_transfer\n", - PQntuples (result)); - GNUNET_break (0); - return GNUNET_SYSERR; - } - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_RESULT_SPEC("transfer_pub", transfer_pub), - TALER_PQ_RESULT_SPEC("link_secret_enc", shared_secret_enc), - TALER_PQ_RESULT_SPEC_END - }; - - if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) - { - PQclear (result); - GNUNET_break (0); - return GNUNET_SYSERR; - } - - PQclear (result); - return GNUNET_OK; -} - - -/** - * Compile a list of all (historic) transactions performed - * with the given coin (/refresh/melt and /deposit operations). - * - * @param cls the `struct PostgresClosure` with the plugin-specific state - * @param session database connection - * @param coin_pub coin to investigate - * @return list of transactions, NULL if coin is fresh - */ -static struct TALER_MINT_DB_TransactionList * -postgres_get_coin_transactions (void *cls, - struct TALER_MINTDB_Session *session, - const union TALER_CoinSpendPublicKeyP *coin_pub) -{ - // FIXME: check logic! - GNUNET_break (0); // FIXME: implement! - return NULL; -} - - - -/** - * Initialize Postgres database subsystem. - * - * @param cls a configuration instance - * @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin` - */ -void * -libtaler_plugin_mintdb_postgres_init (void *cls) -{ - struct GNUNET_CONFIGURATION_Handle *cfg = cls; - struct PostgresClosure *pg; - struct TALER_MINTDB_Plugin *plugin; - - pg = GNUNET_new (struct PostgresClosure); - - if (0 != pthread_key_create (&pg->db_conn_threadlocal, - &db_conn_destroy)) - { - TALER_LOG_ERROR ("Cannnot create pthread key.\n"); - return NULL; - } - /* FIXME: use configuration section with "postgres" in its name... */ - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", "db_conn_str", - &pg->connection_cfg_str)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "db_conn_str"); - return NULL; - } - plugin = GNUNET_new (struct TALER_MINTDB_Plugin); - plugin->cls = pg; - plugin->library_name = "libtaler_plugin_mintdb_postgres"; - plugin->get_session = &postgres_get_session; - plugin->drop_temporary = &postgres_drop_temporary; - plugin->create_tables = &postgres_create_tables; - plugin->start = &postgres_start; - plugin->commit = &postgres_commit; - plugin->rollback = &postgres_rollback; - plugin->reserve_get = &postgres_reserve_get; - plugin->reserves_in_insert = &postgres_reserves_in_insert; - plugin->get_collectable_blindcoin = &postgres_get_collectable_blindcoin; - plugin->insert_collectable_blindcoin = &postgres_insert_collectable_blindcoin; - plugin->get_reserve_history = &postgres_get_reserve_history; - plugin->free_reserve_history = &common_free_reserve_history; - plugin->have_deposit = &postgres_have_deposit; - plugin->insert_deposit = &postgres_insert_deposit; - plugin->get_refresh_session = &postgres_get_refresh_session; - plugin->create_refresh_session = &postgres_create_refresh_session; - plugin->insert_refresh_melt = &postgres_insert_refresh_melt; - plugin->get_refresh_melt = &postgres_get_refresh_melt; - plugin->insert_refresh_order = &postgres_insert_refresh_order; - plugin->get_refresh_order = &postgres_get_refresh_order; - plugin->insert_refresh_commit_coins = &postgres_insert_refresh_commit_coins; - plugin->get_refresh_commit_coins = &postgres_get_refresh_commit_coins; - plugin->insert_refresh_commit_links = &postgres_insert_refresh_commit_links; - plugin->get_refresh_commit_links = &postgres_get_refresh_commit_links; - plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable; - plugin->get_link_data_list = &postgres_get_link_data_list; - plugin->free_link_data_list = &common_free_link_data_list; - plugin->get_transfer = &postgres_get_transfer; - // plugin->have_lock = &postgres_have_lock; - // plugin->insert_lock = &postgres_insert_lock; - plugin->get_coin_transactions = &postgres_get_coin_transactions; - plugin->free_coin_transaction_list = &common_free_coin_transaction_list; - return plugin; -} - - -/** - * Shutdown Postgres database subsystem. - * - * @param cls a `struct TALER_MINTDB_Plugin` - * @return NULL (always) - */ -void * -libtaler_plugin_mintdb_postgres_done (void *cls) -{ - struct TALER_MINTDB_Plugin *plugin = cls; - struct PostgresClosure *pg = plugin->cls; - - GNUNET_free (pg->connection_cfg_str); - GNUNET_free (pg); - GNUNET_free (plugin); - return NULL; -} - -/* end of plugin_mintdb_postgres.c */ diff --git a/src/mint/taler-mint-dbinit.c b/src/mint/taler-mint-dbinit.c @@ -1,101 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file mint/taler-mint-dbinit.c - * @brief Create tables for the mint database. - * @author Florian Dold - */ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <libpq-fe.h> -#include "taler_util.h" -#include "taler_mintdb_plugin.h" -#include "plugin.h" - -/** - * Mint directory with the keys. - */ -static char *mint_base_dir; - -/** - * Our configuration. - */ -static struct GNUNET_CONFIGURATION_Handle *cfg; - - -/** - * The main function of the database initialization tool. - * Used to initialize the Taler Mint's database. - * - * @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) -{ - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-dbinit OPTIONS"), - {'d', "mint-dir", "DIR", - "mint directory", 1, - &GNUNET_GETOPT_set_filename, &mint_base_dir}, - GNUNET_GETOPT_OPTION_END - }; - - if (GNUNET_GETOPT_run ("taler-mint-dbinit", - options, - argc, argv) < 0) - return 1; - - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-mint-dbinit", - "INFO", - NULL)); - if (NULL == mint_base_dir) - { - fprintf (stderr, - "Mint base directory not given.\n"); - return 1; - } - cfg = TALER_config_load (mint_base_dir); - if (NULL == cfg) - { - fprintf (stderr, - "Failed to load mint configuration.\n"); - return 1; - } - if (GNUNET_OK != - TALER_MINT_plugin_load (cfg)) - { - fprintf (stderr, - "Failed to initialize database plugin.\n"); - return 1; - } - if (GNUNET_OK != - plugin->create_tables (plugin->cls, - GNUNET_NO)) - { - fprintf (stderr, - "Failed to initialize database.\n"); - TALER_MINT_plugin_unload (); - return 1; - } - TALER_MINT_plugin_unload (); - return 0; -} - -/* end of taler-mint-dbinit.c */ diff --git a/src/mint/taler-mint-httpd.c b/src/mint/taler-mint-httpd.c @@ -25,7 +25,6 @@ #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> -#include <libpq-fe.h> #include <pthread.h> #include "taler_signatures.h" #include "taler_util.h" @@ -36,7 +35,6 @@ #include "taler-mint-httpd_refresh.h" #include "taler-mint-httpd_keystate.h" #include "taler_mintdb_plugin.h" -#include "plugin.h" /** @@ -61,6 +59,11 @@ struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; char *TMH_expected_wire_format = "sepa"; /** + * Our DB plugin. + */ +struct TALER_MINTDB_Plugin *TMH_plugin; + +/** * The HTTP Daemon. */ static struct MHD_Daemon *mydaemon; @@ -260,8 +263,8 @@ mint_serve_process_config (const char *mint_directory) } GNUNET_free (TMH_master_public_key_str); - if (GNUNET_OK != - TALER_MINT_plugin_load (cfg)) + if (NULL == + (TMH_plugin = TALER_MINT_plugin_load (cfg))) { fprintf (stderr, "failed to initialize DB subsystem\n"); @@ -355,5 +358,6 @@ main (int argc, char *const *argv) ret = TMH_KS_loop (); MHD_stop_daemon (mydaemon); + TALER_MINT_plugin_unload (TMH_plugin); return (GNUNET_OK == ret) ? 0 : 1; } diff --git a/src/mint/taler-mint-httpd.h b/src/mint/taler-mint-httpd.h @@ -59,6 +59,11 @@ extern struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; */ extern struct GNUNET_CRYPTO_EddsaPrivateKey TMH_mint_private_signing_key; +/** + * Our DB plugin. + */ +extern struct TALER_MINTDB_Plugin *TMH_plugin; + /** * Struct describing an URL and the handler for it. diff --git a/src/mint/taler-mint-httpd_db.c b/src/mint/taler-mint-httpd_db.c @@ -26,7 +26,7 @@ #include "taler-mint-httpd_responses.h" #include "taler_util.h" #include "taler-mint-httpd_keystate.h" -#include "plugin.h" +#include "taler_mintdb_lib.h" /** @@ -109,14 +109,14 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, struct TALER_DenominationKeyIssueInformation *dki; int ret; - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } if (GNUNET_YES == - plugin->have_deposit (plugin->cls, + TMH_plugin->have_deposit (TMH_plugin->cls, session, deposit)) { @@ -136,7 +136,7 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, TMH_KS_release (mks); if (GNUNET_OK != - plugin->start (plugin->cls, + TMH_plugin->start (TMH_plugin->cls, session)) { GNUNET_break (0); @@ -145,7 +145,7 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, /* fee for THIS transaction */ spent = deposit->amount_with_fee; /* add cost of all previous transactions */ - tl = plugin->get_coin_transactions (plugin->cls, + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, session, &deposit->coin.coin_pub); if (GNUNET_OK != @@ -153,7 +153,7 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, &spent, &spent)) { - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -162,30 +162,30 @@ TMH_DB_execute_deposit (struct MHD_Connection *connection, if (0 < TALER_amount_cmp (&spent, &value)) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); ret = TMH_RESPONSE_reply_deposit_insufficient_funds (connection, tl); - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); return ret; } - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); if (GNUNET_OK != - plugin->insert_deposit (plugin->cls, + TMH_plugin->insert_deposit (TMH_plugin->cls, session, deposit)) { TALER_LOG_WARNING ("Failed to store /deposit information in database\n"); - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } if (GNUNET_OK != - plugin->commit (plugin->cls, + TMH_plugin->commit (TMH_plugin->cls, session)) { TALER_LOG_WARNING ("/deposit transaction commit failed\n"); @@ -217,13 +217,13 @@ TMH_DB_execute_withdraw_status (struct MHD_Connection *connection, struct ReserveHistory *rh; int res; - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - rh = plugin->get_reserve_history (plugin->cls, + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, session, reserve_pub); if (NULL == rh) @@ -233,7 +233,7 @@ TMH_DB_execute_withdraw_status (struct MHD_Connection *connection, "error", "Reserve not found"); res = TMH_RESPONSE_reply_withdraw_status_success (connection, rh); - plugin->free_reserve_history (plugin->cls, + TMH_plugin->free_reserve_history (TMH_plugin->cls, rh); return res; } @@ -282,13 +282,13 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, blinded_msg_len, &h_blind); - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - res = plugin->get_collectable_blindcoin (plugin->cls, + res = TMH_plugin->get_collectable_blindcoin (TMH_plugin->cls, session, &h_blind, &collectable); @@ -323,7 +323,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, "Denomination not found"); } if (GNUNET_OK != - plugin->start (plugin->cls, + TMH_plugin->start (TMH_plugin->cls, session)) { GNUNET_break (0); @@ -331,12 +331,12 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, return TMH_RESPONSE_reply_internal_db_error (connection); } - rh = plugin->get_reserve_history (plugin->cls, + rh = TMH_plugin->get_reserve_history (TMH_plugin->cls, session, reserve); if (NULL == rh) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); TMH_KS_release (key_state); return TMH_RESPONSE_reply_json_pack (connection, @@ -357,7 +357,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, &value, &fee_withdraw)) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); @@ -378,7 +378,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, &deposit_total, &pos->details.bank->amount)) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); @@ -398,7 +398,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, &withdraw_total, &value)) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); TMH_KS_release (key_state); return TMH_RESPONSE_reply_internal_db_error (connection); @@ -416,15 +416,15 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, &balance)) { TMH_KS_release (key_state); - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); res = TMH_RESPONSE_reply_withdraw_sign_insufficient_funds (connection, rh); - plugin->free_reserve_history (plugin->cls, + TMH_plugin->free_reserve_history (TMH_plugin->cls, rh); return res; } - plugin->free_reserve_history (plugin->cls, + TMH_plugin->free_reserve_history (TMH_plugin->cls, rh); /* Balance is good, sign the coin! */ @@ -435,7 +435,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, if (NULL == sig) { GNUNET_break (0); - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_error (connection, "Internal error"); @@ -448,7 +448,7 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, &collectable.h_coin_envelope); collectable.reserve_sig = *signature; if (GNUNET_OK != - plugin->insert_collectable_blindcoin (plugin->cls, + TMH_plugin->insert_collectable_blindcoin (TMH_plugin->cls, session, &h_blind, amount_required, @@ -456,12 +456,12 @@ TMH_DB_execute_withdraw_sign (struct MHD_Connection *connection, { GNUNET_break (0); GNUNET_CRYPTO_rsa_signature_free (sig); - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } if (GNUNET_OK != - plugin->commit (plugin->cls, + TMH_plugin->commit (TMH_plugin->cls, session)) { TALER_LOG_WARNING ("/withdraw/sign transaction commit failed\n"); @@ -523,7 +523,7 @@ refresh_accept_melts (struct MHD_Connection *connection, /* fee for THIS transaction; the melt amount includes the fee! */ spent = coin_details->melt_amount_with_fee; /* add historic transaction costs of this coin */ - tl = plugin->get_coin_transactions (plugin->cls, + tl = TMH_plugin->get_coin_transactions (TMH_plugin->cls, session, &coin_public_info->coin_pub); if (GNUNET_OK != @@ -532,7 +532,7 @@ refresh_accept_melts (struct MHD_Connection *connection, &spent)) { GNUNET_break (0); - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -553,11 +553,11 @@ refresh_accept_melts (struct MHD_Connection *connection, coin_details->melt_amount_with_fee, coin_residual)) ? GNUNET_NO : GNUNET_SYSERR; - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); return res; } - plugin->free_coin_transaction_list (plugin->cls, + TMH_plugin->free_coin_transaction_list (TMH_plugin->cls, tl); melt.coin = *coin_public_info; @@ -565,7 +565,7 @@ refresh_accept_melts (struct MHD_Connection *connection, melt.session_hash = *session_hash; melt.amount_with_fee = coin_details->melt_amount_with_fee; if (GNUNET_OK != - plugin->insert_refresh_melt (plugin->cls, + TMH_plugin->insert_refresh_melt (TMH_plugin->cls, session, oldcoin_index, &melt)) @@ -617,26 +617,26 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, int res; unsigned int i; - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } if (GNUNET_OK != - plugin->start (plugin->cls, + TMH_plugin->start (TMH_plugin->cls, session)) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - res = plugin->get_refresh_session (plugin->cls, + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, session, session_hash, &refresh_session); if (GNUNET_YES == res) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); res = TMH_RESPONSE_reply_refresh_melt_success (connection, session_hash, @@ -645,7 +645,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, } if (GNUNET_SYSERR == res) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -664,7 +664,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, i))) { TMH_KS_release (key_state); - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return (GNUNET_SYSERR == res) ? MHD_NO : MHD_YES; } @@ -673,13 +673,13 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, /* store requested new denominations */ if (GNUNET_OK != - plugin->insert_refresh_order (plugin->cls, + TMH_plugin->insert_refresh_order (TMH_plugin->cls, session, session_hash, num_new_denoms, denom_pubs)) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -687,14 +687,14 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, for (i = 0; i < TALER_CNC_KAPPA; i++) { if (GNUNET_OK != - plugin->insert_refresh_commit_coins (plugin->cls, + TMH_plugin->insert_refresh_commit_coins (TMH_plugin->cls, session, session_hash, i, num_new_denoms, commit_coin[i])) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -702,14 +702,14 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, for (i = 0; i < TALER_CNC_KAPPA; i++) { if (GNUNET_OK != - plugin->insert_refresh_commit_links (plugin->cls, + TMH_plugin->insert_refresh_commit_links (TMH_plugin->cls, session, session_hash, i, coin_count, commit_link[i])) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -723,12 +723,12 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_STRONG, TALER_CNC_KAPPA); if (GNUNET_OK != - (res = plugin->create_refresh_session (plugin->cls, + (res = TMH_plugin->create_refresh_session (TMH_plugin->cls, session, session_hash, &refresh_session))) { - plugin->rollback (plugin->cls, + TMH_plugin->rollback (TMH_plugin->cls, session); return TMH_RESPONSE_reply_internal_db_error (connection); } @@ -736,7 +736,7 @@ TMH_DB_execute_refresh_melt (struct MHD_Connection *connection, if (GNUNET_OK != - plugin->commit (plugin->cls, + TMH_plugin->commit (TMH_plugin->cls, session)) { TALER_LOG_WARNING ("/refresh/melt transaction commit failed\n"); @@ -789,7 +789,7 @@ check_commitment (struct MHD_Connection *connection, commit_links = GNUNET_malloc (num_oldcoins * sizeof (struct RefreshCommitLink)); if (GNUNET_OK != - plugin->get_refresh_commit_links (plugin->cls, + TMH_plugin->get_refresh_commit_links (TMH_plugin->cls, session, session_hash, off, @@ -888,7 +888,7 @@ check_commitment (struct MHD_Connection *connection, sizeof (struct RefreshCommitCoin)); if (GNUNET_OK != - plugin->get_refresh_commit_coins (plugin->cls, + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, session, session_hash, off, @@ -1008,7 +1008,7 @@ refresh_mint_coin (struct MHD_Connection *connection, return ev_sig; } if (GNUNET_OK != - plugin->insert_refresh_collectable (plugin->cls, + TMH_plugin->insert_refresh_collectable (TMH_plugin->cls, session, session_hash, coin_off, @@ -1054,14 +1054,14 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, unsigned int j; unsigned int off; - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - res = plugin->get_refresh_session (plugin->cls, + res = TMH_plugin->get_refresh_session (TMH_plugin->cls, session, session_hash, &refresh_session); @@ -1081,7 +1081,7 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, for (j=0;j<refresh_session.num_oldcoins;j++) { if (GNUNET_OK != - plugin->get_refresh_melt (plugin->cls, + TMH_plugin->get_refresh_melt (TMH_plugin->cls, session, session_hash, j, @@ -1095,7 +1095,7 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, denom_pubs = GNUNET_malloc (refresh_session.num_newcoins * sizeof (struct TALER_DenominationPublicKey)); if (GNUNET_OK != - plugin->get_refresh_order (plugin->cls, + TMH_plugin->get_refresh_order (TMH_plugin->cls, session, session_hash, refresh_session.num_newcoins, @@ -1136,7 +1136,7 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, /* Client request OK, start transaction */ if (GNUNET_OK != - plugin->start (plugin->cls, + TMH_plugin->start (TMH_plugin->cls, session)) { GNUNET_break (0); @@ -1149,7 +1149,7 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, commit_coins = GNUNET_malloc (refresh_session.num_newcoins * sizeof (struct RefreshCommitCoin)); if (GNUNET_OK != - plugin->get_refresh_commit_coins (plugin->cls, + TMH_plugin->get_refresh_commit_coins (TMH_plugin->cls, session, session_hash, refresh_session.noreveal_index, @@ -1195,7 +1195,7 @@ TMH_DB_execute_refresh_reveal (struct MHD_Connection *connection, GNUNET_free (commit_coins); if (GNUNET_OK != - plugin->commit (plugin->cls, + TMH_plugin->commit (TMH_plugin->cls, session)) { TALER_LOG_WARNING ("/refresh/reveal transaction commit failed\n"); @@ -1234,13 +1234,13 @@ TMH_DB_execute_refresh_link (struct MHD_Connection *connection, struct TALER_EncryptedLinkSecretP shared_secret_enc; struct LinkDataList *ldl; - if (NULL == (session = plugin->get_session (plugin->cls, + if (NULL == (session = TMH_plugin->get_session (TMH_plugin->cls, GNUNET_NO))) { GNUNET_break (0); return TMH_RESPONSE_reply_internal_db_error (connection); } - res = plugin->get_transfer (plugin->cls, + res = TMH_plugin->get_transfer (TMH_plugin->cls, session, coin_pub, &transfer_pub, @@ -1260,7 +1260,7 @@ TMH_DB_execute_refresh_link (struct MHD_Connection *connection, } GNUNET_assert (GNUNET_OK == res); - ldl = plugin->get_link_data_list (plugin->cls, + ldl = TMH_plugin->get_link_data_list (TMH_plugin->cls, session, coin_pub); if (NULL == ldl) @@ -1275,7 +1275,7 @@ TMH_DB_execute_refresh_link (struct MHD_Connection *connection, &transfer_pub, &shared_secret_enc, ldl); - plugin->free_link_data_list (plugin->cls, + TMH_plugin->free_link_data_list (TMH_plugin->cls, ldl); return res; } diff --git a/src/mint/taler-mint-httpd_db.h b/src/mint/taler-mint-httpd_db.h @@ -21,7 +21,6 @@ #ifndef TALER_MINT_HTTPD_DB_H #define TALER_MINT_HTTPD_DB_H -#include <libpq-fe.h> #include <microhttpd.h> #include <gnunet/gnunet_util_lib.h> #include "taler_util.h" diff --git a/src/mint/taler-mint-httpd_deposit.c b/src/mint/taler-mint-httpd_deposit.c @@ -30,7 +30,6 @@ #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> -#include <libpq-fe.h> #include <pthread.h> #include "taler_mintdb_plugin.h" #include "taler_signatures.h" diff --git a/src/mint/taler-mint-httpd_keystate.h b/src/mint/taler-mint-httpd_keystate.h @@ -26,7 +26,7 @@ #include <gnunet/gnunet_util_lib.h> #include <microhttpd.h> #include "taler-mint-httpd.h" -#include "key_io.h" +#include "taler_mintdb_lib.h" /** diff --git a/src/mint/taler-mint-httpd_mhd.c b/src/mint/taler-mint-httpd_mhd.c @@ -27,7 +27,6 @@ #include <gnunet/gnunet_util_lib.h> #include <jansson.h> #include <microhttpd.h> -#include <libpq-fe.h> #include <pthread.h> #include "taler-mint-httpd_responses.h" #include "taler-mint-httpd.h" diff --git a/src/mint/taler-mint-keycheck.c b/src/mint/taler-mint-keycheck.c @@ -1,227 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-mint-keycheck.c - * @brief Check mint keys for validity. Reads the signing and denomination - * keys from the mint directory and checks to make sure they are - * well-formed. This is purely a diagnostic tool. - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include <platform.h> -#include <gnunet/gnunet_util_lib.h> -#include "key_io.h" - -/** - * Mint directory with the keys. - */ -static char *mint_directory; - -/** - * Our configuration. - */ -static struct GNUNET_CONFIGURATION_Handle *kcfg; - - -/** - * Function called on each signing key. - * - * @param cls closure (NULL) - * @param filename name of the file the key came from - * @param ski the sign key - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -static int -signkeys_iter (void *cls, - const char *filename, - const struct TALER_MintSigningKeyValidityPSPriv *ski) -{ - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Iterating over key `%s' for start time %s\n", - filename, - GNUNET_STRINGS_absolute_time_to_string - (GNUNET_TIME_absolute_ntoh (ski->issue.start))); - - if (ntohl (ski->issue.purpose.size) != - (sizeof (struct TALER_MintSigningKeyValidityPS) - - offsetof (struct TALER_MintSigningKeyValidityPS, purpose))) - { - fprintf (stderr, - "Signing key `%s' has invalid purpose size\n", - filename); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_SIGNING_KEY_VALIDITY, - &ski->issue.purpose, - &ski->issue.signature.eddsa_signature, - &ski->issue.master_public_key.eddsa_pub)) - { - fprintf (stderr, - "Signing key `%s' has invalid signature\n", - filename); - return GNUNET_SYSERR; - } - printf ("Signing key `%s' valid\n", - filename); - return GNUNET_OK; -} - - -/** - * Check signing keys. - * - * @return #GNUNET_OK if the keys are OK - * #GNUNET_NO if not - */ -static int -mint_signkeys_check () -{ - if (0 > TALER_MINT_signkeys_iterate (mint_directory, - &signkeys_iter, - NULL)) - return GNUNET_NO; - return GNUNET_OK; -} - - -/** - * Function called on each denomination key. - * - * @param cls closure (NULL) - * @param dki the denomination key - * @param alias coin alias - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -static int -denomkeys_iter (void *cls, - const char *alias, - const struct TALER_DenominationKeyIssueInformation *dki) -{ - struct GNUNET_HashCode hc; - - if (ntohl (dki->issue.purpose.size) != - sizeof (struct TALER_DenominationKeyValidityPS)) - { - fprintf (stderr, - "Denomination key for `%s' has invalid purpose size\n", - alias); - return GNUNET_SYSERR; - } - - if (GNUNET_OK != - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_DENOMINATION_KEY_VALIDITY, - &dki->issue.purpose, - &dki->issue.signature.eddsa_signature, - &dki->issue.master.eddsa_pub)) - { - fprintf (stderr, - "Denomination key for `%s' has invalid signature\n", - alias); - return GNUNET_SYSERR; - } - GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, - &hc); - if (0 != memcmp (&hc, - &dki->issue.denom_hash, - sizeof (struct GNUNET_HashCode))) - { - fprintf (stderr, - "Public key for `%s' does not match signature\n", - alias); - return GNUNET_SYSERR; - } - printf ("Denomination key `%s' is valid\n", - alias); - - return GNUNET_OK; -} - - -/** - * Check denomination keys. - * - * @return #GNUNET_OK if the keys are OK - * #GNUNET_NO if not - */ -static int -mint_denomkeys_check () -{ - if (0 > TALER_MINT_denomkeys_iterate (mint_directory, - &denomkeys_iter, - NULL)) - return GNUNET_NO; - return GNUNET_OK; -} - - -/** - * The main function of the keyup 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) -{ - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keycheck OPTIONS"), - {'d', "directory", "DIRECTORY", - "mint directory with keys to check", 1, - &GNUNET_GETOPT_set_filename, &mint_directory}, - GNUNET_GETOPT_OPTION_END - }; - - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-mint-keycheck", - "WARNING", - NULL)); - - if (GNUNET_GETOPT_run ("taler-mint-keycheck", - options, - argc, argv) < 0) - return 1; - if (NULL == mint_directory) - { - fprintf (stderr, - "Mint directory not given\n"); - return 1; - } - - kcfg = TALER_config_load (mint_directory); - if (NULL == kcfg) - { - fprintf (stderr, - "Failed to load mint configuration\n"); - return 1; - } - if ( (GNUNET_OK != mint_signkeys_check ()) || - (GNUNET_OK != mint_denomkeys_check ()) ) - { - GNUNET_CONFIGURATION_destroy (kcfg); - return 1; - } - GNUNET_CONFIGURATION_destroy (kcfg); - return 0; -} - -/* end of taler-mint-keycheck.c */ diff --git a/src/mint/taler-mint-keyup.c b/src/mint/taler-mint-keyup.c @@ -1,954 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-mint-keyup.c - * @brief Update the mint's keys for coins and signatures, - * using the mint's offline master key. - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - */ -#include <platform.h> -#include <gnunet/gnunet_util_lib.h> -#include "taler_util.h" -#include "key_io.h" - -/** - * When generating filenames from a cryptographic hash, we do not use - * all 512 bits but cut off after this number of characters (in - * base32-encoding). Base32 is 5 bit per character, and given that we - * have very few coin types we hash, at 100 bits the chance of - * collision (by accident over tiny set -- birthday paradox does not - * apply here!) is negligible. - */ -#define HASH_CUTOFF 20 - -/** - * Macro to round microseconds to seconds in GNUNET_TIME_* structs. - * - * @param name value to round - * @param field rel_value_us or abs_value_us - */ -#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); - - -GNUNET_NETWORK_STRUCT_BEGIN - -/** - * Struct with all of the key information for a kind of coin. Hashed - * to generate a unique directory name per coin type. - */ -struct CoinTypeNBOP -{ - /** - * How long can the coin be spend? - */ - struct GNUNET_TIME_RelativeNBO duration_spend; - - /** - * How long can the coin be withdrawn (generated)? - */ - struct GNUNET_TIME_RelativeNBO duration_withdraw; - - /** - * What is the value of the coin? - */ - struct TALER_AmountNBO value; - - /** - * What is the fee charged for withdrawl? - */ - struct TALER_AmountNBO fee_withdraw; - - /** - * What is the fee charged for deposits? - */ - struct TALER_AmountNBO fee_deposit; - - /** - * What is the fee charged for melting? - */ - struct TALER_AmountNBO fee_refresh; - - /** - * Key size in NBO. - */ - uint32_t rsa_keysize; -}; - -GNUNET_NETWORK_STRUCT_END - -/** - * Set of all of the parameters that chracterize a coin. - */ -struct CoinTypeParams -{ - - /** - * How long can the coin be spend? Should be significantly - * larger than @e duration_withdraw (i.e. years). - */ - struct GNUNET_TIME_Relative duration_spend; - - /** - * How long can the coin be withdrawn (generated)? Should be small - * enough to limit how many coins will be signed into existence with - * the same key, but large enough to still provide a reasonable - * anonymity set. - */ - struct GNUNET_TIME_Relative duration_withdraw; - - /** - * How much should coin creation (@e duration_withdraw) duration - * overlap with the next coin? Basically, the starting time of two - * coins is always @e duration_withdraw - @e duration_overlap apart. - */ - struct GNUNET_TIME_Relative duration_overlap; - - /** - * What is the value of the coin? - */ - struct TALER_Amount value; - - /** - * What is the fee charged for withdrawl? - */ - struct TALER_Amount fee_withdraw; - - /** - * What is the fee charged for deposits? - */ - struct TALER_Amount fee_deposit; - - /** - * What is the fee charged for melting? - */ - struct TALER_Amount fee_refresh; - - /** - * Time at which this coin is supposed to become valid. - */ - struct GNUNET_TIME_Absolute anchor; - - /** - * Length of the RSA key in bits. - */ - uint32_t rsa_keysize; -}; - - -/** - * Filename of the master private key. - */ -static char *masterkeyfile; - -/** - * Director of the mint, containing the keys. - */ -static char *mint_directory; - -/** - * Time to pretend when the key update is executed. - */ -static char *pretend_time_str; - -/** - * Handle to the mint's configuration - */ -static struct GNUNET_CONFIGURATION_Handle *kcfg; - -/** - * Time when the key update is executed. Either the actual current time, or a - * pretended time. - */ -static struct GNUNET_TIME_Absolute now; - -/** - * Master private key of the mint. - */ -static struct TALER_MasterPrivateKeyP master_priv; - -/** - * Master public key of the mint. - */ -static struct TALER_MasterPublicKeyP master_public_key; - -/** - * Until what time do we provide keys? - */ -static struct GNUNET_TIME_Absolute lookahead_sign_stamp; - - -/** - * Obtain the name of the directory we use to store signing - * keys created at time @a start. - * - * @param start time at which we create the signing key - * @return name of the directory we should use, basically "$MINTDIR/$TIME/"; - * (valid until next call to this function) - */ -static const char * -get_signkey_file (struct GNUNET_TIME_Absolute start) -{ - static char dir[4096]; - - GNUNET_snprintf (dir, - sizeof (dir), - "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS DIR_SEPARATOR_STR "%llu", - mint_directory, - (unsigned long long) start.abs_value_us); - return dir; -} - - -/** - * Hash the data defining the coin type. Exclude information that may - * not be the same for all instances of the coin type (i.e. the - * anchor, overlap). - * - * @param p coin parameters to convert to a hash - * @param hash[OUT] set to the hash matching @a p - */ -static void -hash_coin_type (const struct CoinTypeParams *p, - struct GNUNET_HashCode *hash) -{ - struct CoinTypeNBOP p_nbo; - - memset (&p_nbo, - 0, - sizeof (struct CoinTypeNBOP)); - p_nbo.duration_spend = GNUNET_TIME_relative_hton (p->duration_spend); - p_nbo.duration_withdraw = GNUNET_TIME_relative_hton (p->duration_withdraw); - TALER_amount_hton (&p_nbo.value, - &p->value); - TALER_amount_hton (&p_nbo.fee_withdraw, - &p->fee_withdraw); - TALER_amount_hton (&p_nbo.fee_deposit, - &p->fee_deposit); - TALER_amount_hton (&p_nbo.fee_refresh, - &p->fee_refresh); - p_nbo.rsa_keysize = htonl (p->rsa_keysize); - GNUNET_CRYPTO_hash (&p_nbo, - sizeof (struct CoinTypeNBOP), - hash); -} - - -/** - * Obtain the name of the directory we should use to store coins of - * the given type. The directory name has the format - * "$MINTDIR/$VALUE/$HASH/" where "$VALUE" represents the value of the - * coin and "$HASH" encodes all of the coin's parameters, generating a - * unique string for each type of coin. Note that the "$HASH" - * includes neither the absolute creation time nor the key of the - * coin, thus the files in the subdirectory really just refer to the - * same type of coins, not the same coin. - * - * @param p coin parameters to convert to a directory name - * @return directory name (valid until next call to this function) - */ -static const char * -get_cointype_dir (const struct CoinTypeParams *p) -{ - static char dir[4096]; - struct GNUNET_HashCode hash; - char *hash_str; - char *val_str; - size_t i; - - hash_coin_type (p, &hash); - hash_str = GNUNET_STRINGS_data_to_string_alloc (&hash, - sizeof (struct GNUNET_HashCode)); - GNUNET_assert (NULL != hash_str); - GNUNET_assert (HASH_CUTOFF <= strlen (hash_str) + 1); - hash_str[HASH_CUTOFF] = 0; - - val_str = TALER_amount_to_string (&p->value); - for (i = 0; i < strlen (val_str); i++) - if ( (':' == val_str[i]) || - ('.' == val_str[i]) ) - val_str[i] = '_'; - - GNUNET_snprintf (dir, - sizeof (dir), - "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS DIR_SEPARATOR_STR "%s-%s", - mint_directory, - val_str, - hash_str); - GNUNET_free (hash_str); - GNUNET_free (val_str); - return dir; -} - - -/** - * Obtain the name of the file we would use to store the key - * information for a coin of the given type @a p and validity - * start time @a start - * - * @param p parameters for the coin - * @param start when would the coin begin to be issued - * @return name of the file to use for this coin - * (valid until next call to this function) - */ -static const char * -get_cointype_file (const struct CoinTypeParams *p, - struct GNUNET_TIME_Absolute start) -{ - static char filename[4096]; - const char *dir; - - dir = get_cointype_dir (p); - GNUNET_snprintf (filename, - sizeof (filename), - "%s" DIR_SEPARATOR_STR "%llu", - dir, - (unsigned long long) start.abs_value_us); - return filename; -} - - -/** - * Get the latest key file from a past run of the key generation - * tool. Used to calculate the starting time for the keys we - * generate during this invocation. This function is used to - * handle both signing keys and coin keys, as in both cases - * the filenames correspond to the timestamps we need. - * - * @param cls closure, a `struct GNUNET_TIME_Absolute *`, updated - * to contain the highest timestamp (below #now) - * that was found - * @param filename complete filename (absolute path) - * @return #GNUNET_OK (to continue to iterate) - */ -static int -get_anchor_iter (void *cls, - const char *filename) -{ - struct GNUNET_TIME_Absolute *anchor = cls; - struct GNUNET_TIME_Absolute stamp; - const char *base; - char *end = NULL; - - base = GNUNET_STRINGS_get_short_name (filename); - stamp.abs_value_us = strtol (base, - &end, - 10); - if ((NULL == end) || (0 != *end)) - { - fprintf(stderr, - "Ignoring unexpected file `%s'.\n", - filename); - return GNUNET_OK; - } - if (stamp.abs_value_us <= now.abs_value_us) - *anchor = GNUNET_TIME_absolute_max (stamp, - *anchor); - return GNUNET_OK; -} - - -/** - * Get the timestamp where the first new key should be generated. - * Relies on correctly named key files (as we do not parse them, - * but just look at the filenames to "guess" at their contents). - * - * @param dir directory that should contain the existing keys - * @param duration how long is one key valid (for signing)? - * @param overlap what's the overlap between the keys validity period? - * @param[out] anchor the timestamp where the first new key should be generated - */ -static void -get_anchor (const char *dir, - struct GNUNET_TIME_Relative duration, - struct GNUNET_TIME_Relative overlap, - struct GNUNET_TIME_Absolute *anchor) -{ - GNUNET_assert (0 == duration.rel_value_us % 1000000); - GNUNET_assert (0 == overlap.rel_value_us % 1000000); - if (GNUNET_YES != - GNUNET_DISK_directory_test (dir, - GNUNET_YES)) - { - *anchor = now; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No existing keys found, starting with fresh key set.\n"); - return; - } - *anchor = GNUNET_TIME_UNIT_ZERO_ABS; - if (-1 == - GNUNET_DISK_directory_scan (dir, - &get_anchor_iter, - anchor)) - { - *anchor = now; - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "No existing keys found, starting with fresh key set.\n"); - return; - } - - /* FIXME: this check is a bit dubious, as 'now' - may be way into the future if we want to generate - many keys... #3727*/ - if ((GNUNET_TIME_absolute_add (*anchor, - duration)).abs_value_us < now.abs_value_us) - { - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - "Existing keys are way too old, starting with fresh key set.\n"); - *anchor = now; - } - else if (anchor->abs_value_us != now.abs_value_us) // Also odd... - { - /* Real starting time is the last start time + duration - overlap */ - *anchor = GNUNET_TIME_absolute_add (*anchor, - duration); - *anchor = GNUNET_TIME_absolute_subtract (*anchor, - overlap); - } - /* anchor is now the stamp where we need to create a new key */ -} - - -/** - * Create a mint signing key (for signing mint messages, not for coins) - * and assert its correctness by signing it with the master key. - * - * @param start start time of the validity period for the key - * @param duration how long should the key be valid - * @param pi[OUT] set to the signing key information - */ -static void -create_signkey_issue_priv (struct GNUNET_TIME_Absolute start, - struct GNUNET_TIME_Relative duration, - struct TALER_MintSigningKeyValidityPSPriv *pi) -{ - struct GNUNET_CRYPTO_EddsaPrivateKey *priv; - struct TALER_MintSigningKeyValidityPS *issue = &pi->issue; - - priv = GNUNET_CRYPTO_eddsa_key_create (); - pi->signkey_priv.eddsa_priv = *priv; - GNUNET_free (priv); - issue->master_public_key = master_public_key; - issue->start = GNUNET_TIME_absolute_hton (start); - issue->expire = GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (start, - duration)); - GNUNET_CRYPTO_eddsa_key_get_public (&pi->signkey_priv.eddsa_priv, - &issue->signkey_pub.eddsa_pub); - issue->purpose.purpose = htonl (TALER_SIGNATURE_MINT_SIGNING_KEY_VALIDITY); - issue->purpose.size = htonl (sizeof (struct TALER_MintSigningKeyValidityPS) - - offsetof (struct TALER_MintSigningKeyValidityPS, - purpose)); - - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, - &issue->purpose, - &issue->signature.eddsa_signature)); -} - - -/** - * Generate signing keys starting from the last key found to - * the lookahead time. - * - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -mint_keys_update_signkeys () -{ - struct GNUNET_TIME_Relative signkey_duration; - struct GNUNET_TIME_Absolute anchor; - char *signkey_dir; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - "mint_keys", - "signkey_duration", - &signkey_duration)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint_keys", - "signkey_duration"); - return GNUNET_SYSERR; - } - ROUND_TO_SECS (signkey_duration, - rel_value_us); - GNUNET_asprintf (&signkey_dir, - "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, - mint_directory); - /* make sure the directory exists */ - if (GNUNET_OK != - GNUNET_DISK_directory_create (signkey_dir)) - { - fprintf (stderr, - "Failed to create signing key directory\n"); - return GNUNET_SYSERR; - } - - get_anchor (signkey_dir, - signkey_duration, - GNUNET_TIME_UNIT_ZERO /* no overlap for signing keys */, - &anchor); - - while (anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) - { - const char *skf; - struct TALER_MintSigningKeyValidityPSPriv signkey_issue; - ssize_t nwrite; - - skf = get_signkey_file (anchor); - GNUNET_break (GNUNET_YES != - GNUNET_DISK_file_test (skf)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Generating signing key for %s.\n", - GNUNET_STRINGS_absolute_time_to_string (anchor)); - create_signkey_issue_priv (anchor, - signkey_duration, - &signkey_issue); - nwrite = GNUNET_DISK_fn_write (skf, - &signkey_issue, - sizeof (struct TALER_MintSigningKeyValidityPS), - GNUNET_DISK_PERM_USER_WRITE | GNUNET_DISK_PERM_USER_READ); - if (nwrite != sizeof (struct TALER_MintSigningKeyValidityPS)) - { - fprintf (stderr, - "Failed to write to file `%s': %s\n", - skf, - STRERROR (errno)); - return GNUNET_SYSERR; - } - anchor = GNUNET_TIME_absolute_add (anchor, - signkey_duration); - } - return GNUNET_OK; -} - - -/** - * Parse configuration for coin type parameters. Also determines - * our anchor by looking at the existing coins of the same type. - * - * @param ct section in the configuration file giving the coin type parameters - * @param params[OUT] set to the coin parameters from the configuration - * @return #GNUNET_OK on success, #GNUNET_SYSERR if the configuration is invalid - */ -static int -get_cointype_params (const char *ct, - struct CoinTypeParams *params) -{ - const char *dir; - unsigned long long rsa_keysize; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - ct, - "duration_withdraw", - &params->duration_withdraw)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "duration_withdraw"); - return GNUNET_SYSERR; - } - ROUND_TO_SECS (params->duration_withdraw, - rel_value_us); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - ct, - "duration_spend", - &params->duration_spend)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "duration_spend"); - return GNUNET_SYSERR; - } - ROUND_TO_SECS (params->duration_spend, - rel_value_us); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - ct, - "duration_overlap", - &params->duration_overlap)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "mint_denom_duration_overlap"); - return GNUNET_SYSERR; - } - ROUND_TO_SECS (params->duration_overlap, - rel_value_us); - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (kcfg, - ct, - "rsa_keysize", - &rsa_keysize)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "rsa_keysize"); - return GNUNET_SYSERR; - } - if ( (rsa_keysize > 4 * 2048) || - (rsa_keysize < 1024) ) - { - fprintf (stderr, - "Given RSA keysize %llu outside of permitted range\n", - rsa_keysize); - return GNUNET_SYSERR; - } - params->rsa_keysize = (unsigned int) rsa_keysize; - if (GNUNET_OK != - TALER_config_get_denom (kcfg, - ct, - "value", - &params->value)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "value"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_denom (kcfg, - ct, - "fee_withdraw", - &params->fee_withdraw)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "fee_withdraw"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_denom (kcfg, - ct, - "fee_deposit", - &params->fee_deposit)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "fee_deposit"); - return GNUNET_SYSERR; - } - if (GNUNET_OK != - TALER_config_get_denom (kcfg, - ct, - "fee_refresh", - &params->fee_refresh)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - ct, - "fee_refresh"); - return GNUNET_SYSERR; - } - - dir = get_cointype_dir (params); - get_anchor (dir, - params->duration_spend, - params->duration_overlap, - &params->anchor); - return GNUNET_OK; -} - - -/** - * Initialize the private and public key information structure for - * signing coins into existence. Generates the private signing key - * and signes it together with the coin's meta data using the master - * signing key. - * - * @param params parameters used to initialize the @a dki - * @param dki[OUT] initialized according to @a params - */ -static void -create_denomkey_issue (const struct CoinTypeParams *params, - struct TALER_DenominationKeyIssueInformation *dki) -{ - dki->denom_priv.rsa_private_key - = GNUNET_CRYPTO_rsa_private_key_create (params->rsa_keysize); - GNUNET_assert (NULL != dki->denom_priv.rsa_private_key); - dki->denom_pub.rsa_public_key - = GNUNET_CRYPTO_rsa_private_key_get_public (dki->denom_priv.rsa_private_key); - GNUNET_CRYPTO_rsa_public_key_hash (dki->denom_pub.rsa_public_key, - &dki->issue.denom_hash); - dki->issue.master = master_public_key; - dki->issue.start = GNUNET_TIME_absolute_hton (params->anchor); - dki->issue.expire_withdraw = - GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, - params->duration_withdraw)); - dki->issue.expire_spend = - GNUNET_TIME_absolute_hton (GNUNET_TIME_absolute_add (params->anchor, - params->duration_spend)); - TALER_amount_hton (&dki->issue.value, - &params->value); - TALER_amount_hton (&dki->issue.fee_withdraw, - &params->fee_withdraw); - TALER_amount_hton (&dki->issue.fee_deposit, - &params->fee_deposit); - TALER_amount_hton (&dki->issue.fee_refresh, - &params->fee_refresh); - dki->issue.purpose.purpose = htonl (TALER_SIGNATURE_MINT_DENOMINATION_KEY_VALIDITY); - dki->issue.purpose.size = htonl (sizeof (struct TALER_DenominationKeyIssueInformation) - - offsetof (struct TALER_DenominationKeyIssueInformation, - issue.purpose)); - GNUNET_assert (GNUNET_OK == - GNUNET_CRYPTO_eddsa_sign (&master_priv.eddsa_priv, - &dki->issue.purpose, - &dki->issue.signature.eddsa_signature)); -} - - -/** - * Generate new coin signing keys for the coin type of the given @a - * coin_alias. - * - * @param cls a `int *`, to be set to #GNUNET_SYSERR on failure - * @param coin_alias name of the coin's section in the configuration - */ -static void -mint_keys_update_cointype (void *cls, - const char *coin_alias) -{ - int *ret = cls; - struct CoinTypeParams p; - const char *dkf; - struct TALER_DenominationKeyIssueInformation denomkey_issue; - - if (0 != strncasecmp (coin_alias, - "coin_", - strlen ("coin_"))) - return; /* not a coin definition */ - if (GNUNET_OK != - get_cointype_params (coin_alias, - &p)) - { - *ret = GNUNET_SYSERR; - return; - } - if (GNUNET_OK != - GNUNET_DISK_directory_create (get_cointype_dir (&p))) - { - *ret = GNUNET_SYSERR; - return; - } - - while (p.anchor.abs_value_us < lookahead_sign_stamp.abs_value_us) - { - dkf = get_cointype_file (&p, - p.anchor); - GNUNET_break (GNUNET_YES != GNUNET_DISK_file_test (dkf)); - GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Generating denomination key for type `%s', start %s at %s\n", - coin_alias, - GNUNET_STRINGS_absolute_time_to_string (p.anchor), - dkf); - create_denomkey_issue (&p, - &denomkey_issue); - if (GNUNET_OK != - TALER_MINT_write_denom_key (dkf, - &denomkey_issue)) - { - fprintf (stderr, - "Failed to write denomination key information to file `%s'.\n", - dkf); - *ret = GNUNET_SYSERR; - GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); - return; - } - GNUNET_CRYPTO_rsa_private_key_free (denomkey_issue.denom_priv.rsa_private_key); - p.anchor = GNUNET_TIME_absolute_add (p.anchor, - p.duration_spend); - p.anchor = GNUNET_TIME_absolute_subtract (p.anchor, - p.duration_overlap); - } -} - - -/** - * Update all of the denomination keys of the mint. - * - * @return #GNUNET_OK on success, #GNUNET_SYSERR on error - */ -static int -mint_keys_update_denomkeys () -{ - int ok; - - ok = GNUNET_OK; - GNUNET_CONFIGURATION_iterate_sections (kcfg, - &mint_keys_update_cointype, - &ok); - return ok; -} - - -/** - * The main function of the taler-mint-keyup tool. This tool is used - * to create the signing and denomination keys for the mint. It uses - * the long-term offline private key and writes the (additional) key - * files to the respective mint directory (from where they can then be - * copied to the online server). Note that we need (at least) the - * most recent generated previous keys so as to align the validity - * periods. - * - * @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) -{ - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-keyup OPTIONS"), - {'m', "master-key", "FILE", - "master key file (private key)", 1, - &GNUNET_GETOPT_set_filename, &masterkeyfile}, - {'d', "mint-dir", "DIR", - "mint directory with keys to update", 1, - &GNUNET_GETOPT_set_filename, &mint_directory}, - {'t', "time", "TIMESTAMP", - "pretend it is a different time for the update", 0, - &GNUNET_GETOPT_set_string, &pretend_time_str}, - GNUNET_GETOPT_OPTION_END - }; - struct GNUNET_TIME_Relative lookahead_sign; - struct GNUNET_CRYPTO_EddsaPrivateKey *eddsa_priv; - - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-mint-keyup", - "WARNING", - NULL)); - - if (GNUNET_GETOPT_run ("taler-mint-keyup", - options, - argc, argv) < 0) - return 1; - if (NULL == mint_directory) - { - fprintf (stderr, - "Mint directory not given\n"); - return 1; - } - if (NULL != pretend_time_str) - { - if (GNUNET_OK != - GNUNET_STRINGS_fancy_time_to_absolute (pretend_time_str, - &now)) - { - fprintf (stderr, - "timestamp `%s' invalid\n", - pretend_time_str); - return 1; - } - } - else - { - now = GNUNET_TIME_absolute_get (); - } - ROUND_TO_SECS (now, abs_value_us); - - kcfg = TALER_config_load (mint_directory); - if (NULL == kcfg) - { - fprintf (stderr, - "Failed to load mint configuration\n"); - return 1; - } - if (NULL == masterkeyfile) - { - fprintf (stderr, - "Master key file not given\n"); - return 1; - } - eddsa_priv = GNUNET_CRYPTO_eddsa_key_create_from_file (masterkeyfile); - if (NULL == eddsa_priv) - { - fprintf (stderr, - "Failed to initialize master key from file `%s'\n", - masterkeyfile); - return 1; - } - master_priv.eddsa_priv = *eddsa_priv; - GNUNET_free (eddsa_priv); - GNUNET_CRYPTO_eddsa_key_get_public (&master_priv.eddsa_priv, - &master_public_key.eddsa_pub); - - /* check if key from file matches the one from the configuration */ - { - struct GNUNET_CRYPTO_EddsaPublicKey master_public_key_from_cfg; - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_data (kcfg, - "mint", - "master_public_key", - &master_public_key_from_cfg, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint", - "master_public_key"); - return 1; - } - if (0 != - memcmp (&master_public_key, - &master_public_key_from_cfg, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey))) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "mint", - "master_public_key", - _("does not match with private key")); - return 1; - } - } - - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_time (kcfg, - "mint_keys", - "lookahead_sign", - &lookahead_sign)) - { - GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, - "mint_keys", - "lookahead_sign"); - return GNUNET_SYSERR; - } - if (0 == lookahead_sign.rel_value_us) - { - GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, - "mint_keys", - "lookahead_sign", - _("must not be zero")); - return GNUNET_SYSERR; - } - ROUND_TO_SECS (lookahead_sign, - rel_value_us); - lookahead_sign_stamp = GNUNET_TIME_absolute_add (now, - lookahead_sign); - - - /* finally, do actual work */ - if (GNUNET_OK != mint_keys_update_signkeys ()) - return 1; - - if (GNUNET_OK != mint_keys_update_denomkeys ()) - return 1; - return 0; -} - -/* end of taler-mint-keyup.c */ diff --git a/src/mint/taler-mint-reservemod.c b/src/mint/taler-mint-reservemod.c @@ -1,305 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) - - 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, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-mint-reservemod.c - * @brief Modify reserves. Allows manipulation of reserve balances. - * @author Florian Dold - * @author Benedikt Mueller - */ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <libpq-fe.h> -#include "taler_util.h" -#include "taler_signatures.h" -#include "taler_mintdb_plugin.h" -#include "taler_pq_lib.h" - - -/** - * Director of the mint, containing the keys. - */ -static char *mint_directory; - -/** - * Public key of the reserve to manipulate. - */ -static struct GNUNET_CRYPTO_EddsaPublicKey *reserve_pub; - -/** - * Handle to the mint's configuration - */ -static struct GNUNET_CONFIGURATION_Handle *cfg; - -/** - * Database connection handle. - */ -static PGconn *db_conn; - - -/** - * Create a new or add to existing reserve. Fails if currencies do - * not match. - * - * @param denom denomination to add - * @return #GNUNET_OK on success, - * #GNUNET_SYSERR on error - */ -// FIXME: this should use the DB abstraction layer. (#3717) -// FIXME: this should be done by adding an inbound transaction -// to the table with the transactions for this reserve, -// not by modifying some 'total' value for the reserve! -// (we should in fact probably never modify, always just append!) (#3633) -static int -reservemod_add (struct TALER_Amount denom) -{ - PGresult *result; - const void *param_values[] = { - reserve_pub - }; - int param_lengths[] = { - sizeof(struct GNUNET_CRYPTO_EddsaPublicKey) - }; - int param_formats[] = { - 1 - }; - struct TALER_Amount old_denom; - struct TALER_Amount new_denom; - struct TALER_AmountNBO new_denom_nbo; - - result = PQexecParams (db_conn, - "SELECT balance_value, balance_fraction, balance_currency" - " FROM reserves" - " WHERE reserve_pub=$1" - " LIMIT 1;", - 1, - NULL, - (const char * const *) param_values, - param_lengths, - param_formats, - 1); - if (PGRES_TUPLES_OK != PQresultStatus (result)) - { - fprintf (stderr, - "Select failed: %s\n", - PQresultErrorMessage (result)); - return GNUNET_SYSERR; - } - if (0 == PQntuples (result)) - { - struct GNUNET_TIME_AbsoluteNBO exnbo; - uint32_t value = htonl (denom.value); - uint32_t fraction = htonl (denom.fraction); - const void *param_values[] = { - reserve_pub, - &value, - &fraction, - denom.currency, - &exnbo - }; - int param_lengths[] = { - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey), - sizeof (uint32_t), - sizeof (uint32_t), - strlen (denom.currency), - sizeof (struct GNUNET_TIME_AbsoluteNBO) - }; - int param_formats[] = { - 1, 1, 1, 1, 1 - }; - - exnbo = GNUNET_TIME_absolute_hton (GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_YEARS)); - result = PQexecParams (db_conn, - "INSERT INTO reserves (reserve_pub, balance_value, balance_fraction, balance_currency, expiration_date)" - " VALUES ($1,$2,$3,$4,$5);", - 5, - NULL, - (const char **) param_values, - param_lengths, - param_formats, - 1); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - fprintf (stderr, - "Insert failed: %s\n", - PQresultErrorMessage (result)); - return GNUNET_SYSERR; - } - } - else - { - const void *param_values[] = { - &new_denom_nbo.value, - &new_denom_nbo.fraction, - reserve_pub - }; - int param_lengths[] = { - sizeof (new_denom_nbo.value), - sizeof (new_denom_nbo.fraction), - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey) - }; - int param_formats[] = { - 1, 1, 1 - }; - - GNUNET_assert (GNUNET_OK == - TALER_PQ_extract_amount (result, 0, - "balance_value", - "balance_fraction", - "balance_currency", - &old_denom)); - TALER_amount_add (&new_denom, - &old_denom, - &denom); - TALER_amount_hton (&new_denom_nbo, - &new_denom); - result = PQexecParams (db_conn, - "UPDATE reserves" - " SET balance_value = $1, balance_fraction = $2, status_sig = NULL, status_sign_pub = NULL" - " WHERE reserve_pub = $3;", - 3, - NULL, - (const char **) param_values, - param_lengths, - param_formats, - 1); - - if (PGRES_COMMAND_OK != PQresultStatus (result)) - { - fprintf (stderr, - "Update failed: %s\n", - PQresultErrorMessage (result)); - return GNUNET_SYSERR; - } - /* Yes, for historic reasons libpq returns a 'const char *'... */ - if (0 != strcmp ("1", - PQcmdTuples (result))) - { - fprintf (stderr, - "Update failed (updated `%s' tupes instead of '1')\n", - PQcmdTuples (result)); - return GNUNET_SYSERR; - } - } - return GNUNET_OK; -} - - -/** - * The main function of the reservemod 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) -{ - static char *reserve_pub_str; - static char *add_str; - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_HELP ("gnunet-mint-reservemod OPTIONS"), - {'d', "mint-dir", "DIR", - "mint directory with keys to update", 1, - &GNUNET_GETOPT_set_filename, &mint_directory}, - {'R', "reserve", "KEY", - "reserve (public key) to modify", 1, - &GNUNET_GETOPT_set_string, &reserve_pub_str}, - {'a', "add", "DENOM", - "value to add", 1, - &GNUNET_GETOPT_set_string, &add_str}, - GNUNET_GETOPT_OPTION_END - }; - char *connection_cfg_str; - - GNUNET_assert (GNUNET_OK == - GNUNET_log_setup ("taler-mint-reservemod", - "WARNING", - NULL)); - - if (GNUNET_GETOPT_run ("taler-mint-reservemod", - options, - argc, argv) < 0) - return 1; - if (NULL == mint_directory) - { - fprintf (stderr, - "Mint directory not given\n"); - return 1; - } - - reserve_pub = GNUNET_new (struct GNUNET_CRYPTO_EddsaPublicKey); - if ((NULL == reserve_pub_str) || - (GNUNET_OK != - GNUNET_STRINGS_string_to_data (reserve_pub_str, - strlen (reserve_pub_str), - reserve_pub, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)))) - { - fprintf (stderr, - "Parsing reserve key invalid\n"); - return 1; - } - cfg = TALER_config_load (mint_directory); - if (NULL == cfg) - { - fprintf (stderr, - "Failed to load mint configuration\n"); - return 1; - } - if (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "mint", - "db", - &connection_cfg_str)) - { - fprintf (stderr, - "Database configuration string not found\n"); - return 1; - } - db_conn = PQconnectdb (connection_cfg_str); - if (CONNECTION_OK != PQstatus (db_conn)) - { - fprintf (stderr, - "Database connection failed: %s\n", - PQerrorMessage (db_conn)); - return 1; - } - if (NULL != add_str) - { - struct TALER_Amount add_value; - - if (GNUNET_OK != - TALER_string_to_amount (add_str, - &add_value)) - { - fprintf (stderr, - "Failed to parse currency amount `%s'\n", - add_str); - return 1; - } - if (GNUNET_OK != - reservemod_add (add_value)) - { - fprintf (stderr, - "Failed to update reserve.\n"); - return 1; - } - } - return 0; -} - -/* end taler-mint-reservemod.c */ diff --git a/src/mintdb/Makefile.am b/src/mintdb/Makefile.am @@ -0,0 +1,68 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include -I$(top_srcdir)/src/pq/ $(POSTGRESQL_CPPFLAGS) + +plugindir = $(libdir)/taler + +plugin_LTLIBRARIES = \ + libtaler_plugin_mintdb_postgres.la + +EXTRA_DIST = plugin_mintdb_common.c + +libtaler_plugin_mintdb_postgres_la_SOURCES = \ + plugin_mintdb_postgres.c +libtaler_plugin_mintdb_postgres_la_LIBADD = \ + $(LTLIBINTL) +libtaler_plugin_mintdb_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + -lpq \ + -lgnunetutil + +lib_LTLIBRARIES = \ + libtalermintdb.la + +libtalermintdb_la_SOURCES = \ + mintdb_keyio.c \ + mintdb_plugin.c + +libtalermintdb_la_LIBADD = \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil + +libtalermintdb_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + + +check_PROGRAMS = \ + test-mintdb-deposits \ + test-mintdb-keyio \ + test-mintdb-postgres + +test_mintdb_deposits_SOURCES = \ + test_mintdb_deposits.c +test_mintdb_deposits_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil \ + -ljansson \ + -lpq + +test_mintdb_keyio_SOURCES = \ + test_mintdb_keyio.c +test_mintdb_keyio_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil + +test_mintdb_postgres_SOURCES = \ + test_mintdb.c +test_mintdb_postgres_LDADD = \ + libtalermintdb.la \ + $(top_srcdir)/src/util/libtalerutil.la \ + $(top_srcdir)/src/pq/libtalerpq.la \ + -lgnunetutil -ljansson +EXTRA_test_mintdb_postgres_DEPENDENCIES = \ + libtaler_plugin_mintdb_postgres.la diff --git a/src/mintdb/mintdb_keyio.c b/src/mintdb/mintdb_keyio.c @@ -0,0 +1,347 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_keyio.c + * @brief I/O operations for the Mint's private keys + * @author Florian Dold + * @author Benedikt Mueller + * @author Sree Harsha Totakura + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" + + +/** + * Closure for the #signkeys_iterate_dir_iter(). + */ +struct SignkeysIterateContext +{ + + /** + * Function to call on each signing key. + */ + TALER_MINT_SignkeyIterator it; + + /** + * Closure for @e it. + */ + void *it_cls; +}; + + +/** + * Function called on each file in the directory with + * our signing keys. Parses the file and calls the + * iterator from @a cls. + * + * @param cls the `struct SignkeysIterateContext *` + * @param filename name of the file to parse + * @return #GNUNET_OK to continue, + * #GNUNET_NO to stop iteration without error, + * #GNUNET_SYSERR to stop iteration with error + */ +static int +signkeys_iterate_dir_iter (void *cls, + const char *filename) +{ + struct SignkeysIterateContext *skc = cls; + ssize_t nread; + struct TALER_MintSigningKeyValidityPSPriv issue; + + nread = GNUNET_DISK_fn_read (filename, + &issue, + sizeof (struct TALER_MintSigningKeyValidityPSPriv)); + if (nread != sizeof (struct TALER_MintSigningKeyValidityPSPriv)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid signkey file `%s': wrong size\n", + filename); + return GNUNET_OK; + } + return skc->it (skc->it_cls, + filename, + &issue); +} + + +/** + * Call @a it for each signing key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_SIGNKEYS + * subdirectory + * @param it function to call on each signing key + * @param it_cls closure for @a it + * @return number of files found (may not match + * number of keys given to @a it as malformed + * files are simply skipped), -1 on error + */ +int +TALER_MINT_signkeys_iterate (const char *mint_base_dir, + TALER_MINT_SignkeyIterator it, + void *it_cls) +{ + char *signkey_dir; + struct SignkeysIterateContext skc; + int ret; + + GNUNET_asprintf (&signkey_dir, + "%s" DIR_SEPARATOR_STR DIR_SIGNKEYS, + mint_base_dir); + skc.it = it; + skc.it_cls = it_cls; + ret = GNUNET_DISK_directory_scan (signkey_dir, + &signkeys_iterate_dir_iter, + &skc); + GNUNET_free (signkey_dir); + return ret; +} + + +/** + * Import a denomination key from the given file. + * + * @param filename the file to import the key from + * @param[OUT] dki set to the imported denomination key + * @return #GNUNET_OK upon success; + * #GNUNET_SYSERR upon failure + */ +int +TALER_MINT_read_denom_key (const char *filename, + struct TALER_DenominationKeyIssueInformation *dki) +{ + uint64_t size; + size_t offset; + void *data; + struct GNUNET_CRYPTO_rsa_PrivateKey *priv; + + if (GNUNET_OK != GNUNET_DISK_file_size (filename, + &size, + GNUNET_YES, + GNUNET_YES)) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Skipping inaccessable denomination key file `%s'\n", + filename); + return GNUNET_SYSERR; + } + offset = sizeof (struct TALER_DenominationKeyValidityPS); + if (size <= offset) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + data = GNUNET_malloc (size); + if (size != + GNUNET_DISK_fn_read (filename, + data, + size)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_WARNING, + "read", + filename); + GNUNET_free (data); + return GNUNET_SYSERR; + } + if (NULL == + (priv = GNUNET_CRYPTO_rsa_private_key_decode (data + offset, + size - offset))) + { + GNUNET_free (data); + return GNUNET_SYSERR; + } + dki->denom_priv.rsa_private_key = priv; + dki->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_private_key_get_public (priv); + memcpy (&dki->issue, + data, + offset); + GNUNET_free (data); + return GNUNET_OK; +} + + +/** + * Exports a denomination key to the given file. + * + * @param filename the file where to write the denomination key + * @param dki the denomination key + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure. + */ +int +TALER_MINT_write_denom_key (const char *filename, + const struct TALER_DenominationKeyIssueInformation *dki) +{ + char *priv_enc; + size_t priv_enc_size; + struct GNUNET_DISK_FileHandle *fh; + ssize_t wrote; + size_t wsize; + int ret; + + fh = NULL; + priv_enc_size + = GNUNET_CRYPTO_rsa_private_key_encode (dki->denom_priv.rsa_private_key, + &priv_enc); + ret = GNUNET_SYSERR; + if (NULL == (fh = GNUNET_DISK_file_open + (filename, + GNUNET_DISK_OPEN_WRITE | GNUNET_DISK_OPEN_CREATE | GNUNET_DISK_OPEN_TRUNCATE, + GNUNET_DISK_PERM_USER_READ | GNUNET_DISK_PERM_USER_WRITE))) + goto cleanup; + wsize = sizeof (struct TALER_DenominationKeyValidityPS); + if (GNUNET_SYSERR == (wrote = GNUNET_DISK_file_write (fh, + &dki->issue.signature, + wsize))) + goto cleanup; + if (wrote != wsize) + goto cleanup; + if (GNUNET_SYSERR == + (wrote = GNUNET_DISK_file_write (fh, + priv_enc, + priv_enc_size))) + goto cleanup; + if (wrote != priv_enc_size) + goto cleanup; + ret = GNUNET_OK; + cleanup: + GNUNET_free_non_null (priv_enc); + if (NULL != fh) + (void) GNUNET_DISK_file_close (fh); + return ret; +} + + +/** + * Closure for #denomkeys_iterate_keydir_iter() and + * #denomkeys_iterate_topdir_iter(). + */ +struct DenomkeysIterateContext +{ + + /** + * Set to the name of the directory below the top-level directory + * during the call to #denomkeys_iterate_keydir_iter(). + */ + const char *alias; + + /** + * Function to call on each denomination key. + */ + TALER_MINT_DenomkeyIterator it; + + /** + * Closure for @e it. + */ + void *it_cls; +}; + + +/** + * Decode the denomination key in the given file @a filename and call + * the callback in @a cls with the information. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of a file that should contain + * a denomination key + * @return #GNUNET_OK to continue to iterate + * #GNUNET_NO to abort iteration with success + * #GNUNET_SYSERR to abort iteration with failure + */ +static int +denomkeys_iterate_keydir_iter (void *cls, + const char *filename) +{ + struct DenomkeysIterateContext *dic = cls; + struct TALER_DenominationKeyIssueInformation issue; + + if (GNUNET_OK != + TALER_MINT_read_denom_key (filename, + &issue)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid denomkey file: '%s'\n", + filename); + return GNUNET_OK; + } + return dic->it (dic->it_cls, + dic->alias, + &issue); +} + + +/** + * Function called on each subdirectory in the #DIR_DENOMKEYS. Will + * call the #denomkeys_iterate_keydir_iter() on each file in the + * subdirectory. + * + * @param cls the `struct DenomkeysIterateContext *` + * @param filename name of the subdirectory to scan + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if we need to abort + */ +static int +denomkeys_iterate_topdir_iter (void *cls, + const char *filename) +{ + struct DenomkeysIterateContext *dic = cls; + + dic->alias = GNUNET_STRINGS_get_short_name (filename); + if (0 > GNUNET_DISK_directory_scan (filename, + &denomkeys_iterate_keydir_iter, + dic)) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Call @a it for each denomination key found in the @a mint_base_dir. + * + * @param mint_base_dir base directory for the mint, + * the signing keys must be in the #DIR_DENOMKEYS + * subdirectory + * @param it function to call on each denomination key found + * @param it_cls closure for @a it + * @return -1 on error, 0 if no files were found, otherwise + * a positive number (however, even with a positive + * number it is possible that @a it was never called + * as maybe none of the files were well-formed) + */ +int +TALER_MINT_denomkeys_iterate (const char *mint_base_dir, + TALER_MINT_DenomkeyIterator it, + void *it_cls) +{ + char *dir; + struct DenomkeysIterateContext dic; + int ret; + + GNUNET_asprintf (&dir, + "%s" DIR_SEPARATOR_STR DIR_DENOMKEYS, + mint_base_dir); + dic.it = it; + dic.it_cls = it_cls; + ret = GNUNET_DISK_directory_scan (dir, + &denomkeys_iterate_topdir_iter, + &dic); + GNUNET_free (dir); + return ret; +} + + +/* end of key_io.c */ diff --git a/src/mintdb/mintdb_plugin.c b/src/mintdb/mintdb_plugin.c @@ -0,0 +1,149 @@ +/* + This file is part of TALER + Copyright (C) 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file mintdb/mintdb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_mintdb_lib.h" +#include "taler_mintdb_plugin.h" +#include <ltdl.h> + + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_MINTDB_Plugin * +TALER_MINT_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct TALER_MINTDB_Plugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_mintdb_%s", + plugin_name); + GNUNET_free (plugin_name); + cfg_dup = GNUNET_CONFIGURATION_dup (cfg); + plugin = GNUNET_PLUGIN_load (lib_name, cfg_dup); + if (NULL != plugin) + plugin->library_name = lib_name; + else + GNUNET_free (lib_name); + GNUNET_CONFIGURATION_destroy (cfg_dup); + return plugin; +} + + +/** + * Shutdown the plugin. + * + * @param plugin the plugin to unload + */ +void +TALER_MINT_plugin_unload (struct TALER_MINTDB_Plugin *plugin) +{ + char *lib_name; + + if (NULL == plugin) + return; + lib_name = plugin->library_name; + GNUNET_assert (NULL == GNUNET_PLUGIN_unload (lib_name, + plugin)); + GNUNET_free (lib_name); +} + + +/** + * Setup libtool paths. + */ +void __attribute__ ((constructor)) +plugin_init () +{ + int err; + const char *opath; + char *path; + char *cpath; + + err = lt_dlinit (); + if (err > 0) + { + FPRINTF (stderr, + _("Initialization of plugin mechanism failed: %s!\n"), + lt_dlerror ()); + return; + } + opath = lt_dlgetsearchpath (); + if (NULL != opath) + old_dlsearchpath = GNUNET_strdup (opath); + path = TALER_os_installation_get_path (GNUNET_OS_IPK_LIBDIR); + if (NULL != path) + { + if (NULL != opath) + { + GNUNET_asprintf (&cpath, "%s:%s", opath, path); + lt_dlsetsearchpath (cpath); + GNUNET_free (path); + GNUNET_free (cpath); + } + else + { + lt_dlsetsearchpath (path); + GNUNET_free (path); + } + } +} + + +/** + * Shutdown libtool. + */ +void __attribute__ ((destructor)) +plugin_fini () +{ + lt_dlsetsearchpath (old_dlsearchpath); + if (NULL != old_dlsearchpath) + { + GNUNET_free (old_dlsearchpath); + old_dlsearchpath = NULL; + } + lt_dlexit (); +} + + +/* end of plugin.c */ diff --git a/src/mint/plugin_mintdb_common.c b/src/mintdb/plugin_mintdb_common.c diff --git a/src/mintdb/plugin_mintdb_postgres.c b/src/mintdb/plugin_mintdb_postgres.c @@ -0,0 +1,2356 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + 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, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file plugin_mintdb_postgres.c + * @brief Low-level (statement-level) Postgres database access for the mint + * @author Florian Dold + * @author Christian Grothoff + * @author Sree Harsha Totakura + */ +#include "platform.h" +#include "taler_pq_lib.h" +#include "taler_signatures.h" +#include "taler_mintdb_plugin.h" +#include <pthread.h> +#include <libpq-fe.h> + +#include "plugin_mintdb_common.c" + +#define TALER_TEMP_SCHEMA_NAME "taler_temporary" + +#define QUERY_ERR(result) \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Query failed at %s:%u: %s\n", __FILE__, __LINE__, PQresultErrorMessage (result)) + + +#define BREAK_DB_ERR(result) do { \ + GNUNET_break(0); \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Database failure: %s\n", PQresultErrorMessage (result)); \ + } while (0) + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +#define SQLEXEC_(conn, sql, result) \ + do { \ + result = PQexec (conn, sql); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + goto SQLEXEC_fail; \ + } \ + PQclear (result); result = NULL; \ + } while (0) + +/** + * This the length of the currency strings (without 0-termination) we use. Note + * that we need to use this at the DB layer instead of TALER_CURRENCY_LEN as the + * DB only needs to store 3 bytes instead of 8 bytes. + */ +#define TALER_PQ_CURRENCY_LEN 3 + + +/** + * Handle for a database session (per-thread, for transactions). + */ +struct TALER_MINTDB_Session +{ + /** + * Postgres connection handle. + */ + PGconn *conn; +}; + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Thread-local database connection. + * Contains a pointer to PGconn or NULL. + */ + pthread_key_t db_conn_threadlocal; + + /** + * Database connection string, as read from + * the configuration. + */ + char *connection_cfg_str; +}; + + + +/** + * Set the given connection to use a temporary schema + * + * @param db the database connection + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon error + */ +static int +set_temporary_schema (PGconn *db) +{ + PGresult *result; + + SQLEXEC_(db, + "CREATE SCHEMA IF NOT EXISTS " TALER_TEMP_SCHEMA_NAME ";" + "SET search_path to " TALER_TEMP_SCHEMA_NAME ";", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Drop the temporary taler schema. This is only useful for testcases + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_drop_temporary (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + SQLEXEC_ (session->conn, + "DROP SCHEMA " TALER_TEMP_SCHEMA_NAME " CASCADE;", + result); + return GNUNET_OK; + SQLEXEC_fail: + return GNUNET_SYSERR; +} + + +/** + * Create the necessary tables if they are not present + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary should we use a temporary schema + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_create_tables (void *cls, + int temporary) +{ + struct PostgresClosure *pc = cls; + PGresult *result; + PGconn *conn; + + result = NULL; + conn = PQconnectdb (pc->connection_cfg_str); + if (CONNECTION_OK != PQstatus (conn)) + { + TALER_LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (conn)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + if ( (GNUNET_YES == temporary) && + (GNUNET_SYSERR == set_temporary_schema (conn))) + { + PQfinish (conn); + return GNUNET_SYSERR; + } +#define SQLEXEC(sql) SQLEXEC_(conn, sql, result); + /* reserves table is for summarization of a reserve. It is updated when new + funds are added and existing funds are withdrawn */ + SQLEXEC ("CREATE TABLE IF NOT EXISTS reserves" + "(" + " reserve_pub BYTEA PRIMARY KEY" + ",current_balance_value INT8 NOT NULL" + ",current_balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ")"); + /* reserves_in table collects the transactions which transfer funds into the + reserve. The amount and expiration date for the corresponding reserve are + updated when new transfer funds are added. The rows of this table + correspond to each incoming transaction. */ + SQLEXEC("CREATE TABLE IF NOT EXISTS reserves_in" + "(" + " reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",balance_value INT8 NOT NULL" + ",balance_fraction INT4 NOT NULL" + ",balance_currency VARCHAR(4) NOT NULL" + ",expiration_date INT8 NOT NULL" + ");"); + /* Create an index on the foreign key as it is not created automatically by PSQL */ + SQLEXEC ("CREATE INDEX reserves_in_reserve_pub_index" + " ON reserves_in (reserve_pub);"); + SQLEXEC ("CREATE TABLE IF NOT EXISTS collectable_blindcoins" + "(" + "blind_ev BYTEA PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" /* FIXME: Make this a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",reserve_pub BYTEA REFERENCES reserves (reserve_pub) ON DELETE CASCADE" + ",reserve_sig BYTEA NOT NULL" + ");"); + SQLEXEC ("CREATE INDEX collectable_blindcoins_reserve_pub_index ON" + " collectable_blindcoins (reserve_pub)"); + SQLEXEC("CREATE TABLE IF NOT EXISTS known_coins " + "(" + " coin_pub BYTEA NOT NULL PRIMARY KEY" + ",denom_pub BYTEA NOT NULL" + ",denom_sig BYTEA NOT NULL" + ",expended_value INT8 NOT NULL" + ",expended_fraction INT4 NOT NULL" + ",expended_currency VARCHAR(4) NOT NULL" + ",refresh_session_hash BYTEA" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_sessions " + "(" + " session_hash BYTEA PRIMARY KEY CHECK (length(session_hash) = 32)" + ",session_melt_sig BYTEA" + ",session_commit_sig BYTEA" + ",noreveal_index INT2 NOT NULL" + // non-zero if all reveals were ok + // and the new coin signatures are ready + ",reveal_ok BOOLEAN NOT NULL DEFAULT false" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_order " + "( " + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" + ",newcoin_index INT2 NOT NULL " + ",denom_pub BYTEA NOT NULL " + ",PRIMARY KEY (session_hash, newcoin_index)" + ") "); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_link" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash)" + ",transfer_pub BYTEA NOT NULL" + ",link_secret_enc BYTEA NOT NULL" + // index of the old coin in the customer's request + ",oldcoin_index INT2 NOT NULL" + // index for cut and choose, + // ranges from 0 to #TALER_CNC_KAPPA-1 + ",cnc_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_commit_coin" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",link_vector_enc BYTEA NOT NULL" + // index of the new coin in the customer's request + ",newcoin_index INT2 NOT NULL" + // index for cut and choose, + ",cnc_index INT2 NOT NULL" + ",coin_ev BYTEA NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_melt" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",coin_pub BYTEA NOT NULL REFERENCES known_coins (coin_pub) " + ",denom_pub BYTEA NOT NULL " + ",oldcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS refresh_collectable" + "(" + " session_hash BYTEA NOT NULL REFERENCES refresh_sessions (session_hash) " + ",ev_sig BYTEA NOT NULL" + ",newcoin_index INT2 NOT NULL" + ")"); + SQLEXEC("CREATE TABLE IF NOT EXISTS deposits " + "( " + " coin_pub BYTEA NOT NULL PRIMARY KEY CHECK (length(coin_pub)=32)" + ",denom_pub BYTEA NOT NULL" /* FIXME: Link this as a foreign key? */ + ",denom_sig BYTEA NOT NULL" + ",transaction_id INT8 NOT NULL" + ",amount_currency VARCHAR(4) NOT NULL" + ",amount_value INT8 NOT NULL" + ",amount_fraction INT4 NOT NULL" + ",merchant_pub BYTEA NOT NULL CHECK (length(merchant_pub)=32)" + ",h_contract BYTEA NOT NULL CHECK (length(h_contract)=64)" + ",h_wire BYTEA NOT NULL CHECK (length(h_wire)=64)" + ",coin_sig BYTEA NOT NULL CHECK (length(coin_sig)=64)" + ",wire TEXT NOT NULL" + ")"); +#undef SQLEXEC + + PQfinish (conn); + return GNUNET_OK; + + SQLEXEC_fail: + PQfinish (conn); + return GNUNET_SYSERR; +} + + +/** + * Setup prepared statements. + * + * @param db_conn connection handle to initialize + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +postgres_prepare (PGconn *db_conn) +{ + PGresult *result; + +#define PREPARE(name, sql, ...) \ + do { \ + result = PQprepare (db_conn, name, sql, __VA_ARGS__); \ + if (PGRES_COMMAND_OK != PQresultStatus (result)) \ + { \ + BREAK_DB_ERR (result); \ + PQclear (result); result = NULL; \ + return GNUNET_SYSERR; \ + } \ + PQclear (result); result = NULL; \ + } while (0); + + PREPARE ("get_reserve", + "SELECT " + "current_balance_value" + ",current_balance_fraction" + ",balance_currency " + ",expiration_date " + "FROM reserves " + "WHERE reserve_pub=$1 " + "LIMIT 1; ", + 1, NULL); + PREPARE ("create_reserve", + "INSERT INTO reserves (" + " reserve_pub," + " current_balance_value," + " current_balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + "$1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("update_reserve", + "UPDATE reserves " + "SET" + " current_balance_value=$2 " + ",current_balance_fraction=$3 " + ",expiration_date=$4 " + "WHERE reserve_pub=$1 ", + 4, NULL); + PREPARE ("create_reserves_in_transaction", + "INSERT INTO reserves_in (" + " reserve_pub," + " balance_value," + " balance_fraction," + " balance_currency," + " expiration_date) VALUES (" + " $1, $2, $3, $4, $5);", + 5, NULL); + PREPARE ("get_reserves_in_transactions", + "SELECT" + " balance_value" + ",balance_fraction" + ",balance_currency" + ",expiration_date" + " FROM reserves_in WHERE reserve_pub=$1", + 1, NULL); + PREPARE ("insert_collectable_blindcoin", + "INSERT INTO collectable_blindcoins ( " + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_pub, reserve_sig) " + "VALUES ($1, $2, $3, $4, $5)", + 5, NULL); + PREPARE ("get_collectable_blindcoin", + "SELECT " + " denom_pub, denom_sig" + ",reserve_sig, reserve_pub " + "FROM collectable_blindcoins " + "WHERE blind_ev = $1", + 1, NULL); + PREPARE ("get_reserves_blindcoins", + "select" + " blind_ev" + ",denom_pub, denom_sig" + ",reserve_sig" + " FROM collectable_blindcoins" + " WHERE reserve_pub=$1;", + 1, NULL); + + /* FIXME: does it make sense to store these computed values in the DB? */ +#if 0 + PREPARE ("get_refresh_session", + "SELECT " + " (SELECT count(*) FROM refresh_melt WHERE session_hash = $1)::INT2 as num_oldcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_hash = $1 and cnc_index = 0)::INT2 as num_newcoins " + ",(SELECT count(*) FROM refresh_blind_session_keys " + " WHERE session_hash = $1 and newcoin_index = 0)::INT2 as kappa " + ",noreveal_index" + ",session_commit_sig " + ",reveal_ok " + "FROM refresh_sessions " + "WHERE session_hash = $1", + 1, NULL); +#endif + + PREPARE ("get_known_coin", + "SELECT " + " coin_pub, denom_pub, denom_sig " + ",expended_value, expended_fraction, expended_currency " + ",refresh_session_hash " + "FROM known_coins " + "WHERE coin_pub = $1", + 1, NULL); + PREPARE ("update_known_coin", + "UPDATE known_coins " + "SET " + " denom_pub = $2 " + ",denom_sig = $3 " + ",expended_value = $4 " + ",expended_fraction = $5 " + ",expended_currency = $6 " + ",refresh_session_hash = $7 " + "WHERE " + " coin_pub = $1 ", + 7, NULL); + PREPARE ("insert_known_coin", + "INSERT INTO known_coins (" + " coin_pub" + ",denom_pub" + ",denom_sig" + ",expended_value" + ",expended_fraction" + ",expended_currency" + ",refresh_session_hash" + ")" + "VALUES ($1,$2,$3,$4,$5,$6,$7)", + 7, NULL); + PREPARE ("get_refresh_commit_link", + "SELECT " + " transfer_pub " + ",link_secret_enc " + "FROM refresh_commit_link " + "WHERE session_hash = $1 AND cnc_index = $2 AND oldcoin_index = $3", + 3, NULL); + PREPARE ("get_refresh_commit_coin", + "SELECT " + " link_vector_enc " + ",coin_ev " + "FROM refresh_commit_coin " + "WHERE session_hash = $1 AND cnc_index = $2 AND newcoin_index = $3", + 3, NULL); + PREPARE ("insert_refresh_order", + "INSERT INTO refresh_order ( " + " newcoin_index " + ",session_hash " + ",denom_pub " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("insert_refresh_melt", + "INSERT INTO refresh_melt ( " + " session_hash " + ",oldcoin_index " + ",coin_pub " + ",denom_pub " + ") " + "VALUES ($1, $2, $3, $4) ", + 3, NULL); + PREPARE ("get_refresh_order", + "SELECT denom_pub " + "FROM refresh_order " + "WHERE session_hash = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_collectable", + "SELECT ev_sig " + "FROM refresh_collectable " + "WHERE session_hash = $1 AND newcoin_index = $2", + 2, NULL); + PREPARE ("get_refresh_melt", + "SELECT coin_pub " + "FROM refresh_melt " + "WHERE session_hash = $1 AND oldcoin_index = $2", + 2, NULL); + PREPARE ("insert_refresh_session", + "INSERT INTO refresh_sessions ( " + " session_hash " + ",noreveal_index " + ") " + "VALUES ($1, $2) ", + 2, NULL); + PREPARE ("insert_refresh_commit_link", + "INSERT INTO refresh_commit_link ( " + " session_hash " + ",transfer_pub " + ",cnc_index " + ",oldcoin_index " + ",link_secret_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_commit_coin", + "INSERT INTO refresh_commit_coin ( " + " session_hash " + ",coin_ev " + ",cnc_index " + ",newcoin_index " + ",link_vector_enc " + ") " + "VALUES ($1, $2, $3, $4, $5) ", + 5, NULL); + PREPARE ("insert_refresh_collectable", + "INSERT INTO refresh_collectable ( " + " session_hash " + ",newcoin_index " + ",ev_sig " + ") " + "VALUES ($1, $2, $3) ", + 3, NULL); + PREPARE ("set_reveal_ok", + "UPDATE refresh_sessions " + "SET reveal_ok = TRUE " + "WHERE session_hash = $1 ", + 1, NULL); + PREPARE ("get_link", + "SELECT link_vector_enc, ro.denom_pub, ev_sig " + "FROM refresh_melt rm " + " JOIN refresh_order ro USING (session_hash) " + " JOIN refresh_commit_coin rcc USING (session_hash) " + " JOIN refresh_sessions rs USING (session_hash) " + " JOIN refresh_collectable rc USING (session_hash) " + "WHERE rm.coin_pub = $1 " + "AND ro.newcoin_index = rcc.newcoin_index " + "AND ro.newcoin_index = rc.newcoin_index " + "AND rcc.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE rcc2.newcoin_index = 0 AND rcc2.session_hash = rs.session_hash " + " ) ", + 1, NULL); + PREPARE ("get_transfer", + "SELECT transfer_pub, link_secret_enc " + "FROM refresh_melt rm " + " JOIN refresh_commit_link rcl USING (session_hash) " + " JOIN refresh_sessions rs USING (session_hash) " + "WHERE rm.coin_pub = $1 " + "AND rm.oldcoin_index = rcl.oldcoin_index " + "AND rcl.cnc_index = rs.noreveal_index % ( " + " SELECT count(*) FROM refresh_commit_coin rcc2 " + " WHERE newcoin_index = 0 AND rcc2.session_hash = rm.session_hash " + " ) ", + 1, NULL); + PREPARE ("insert_deposit", + "INSERT INTO deposits (" + "coin_pub," + "denom_pub," + "denom_sig," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig," + "wire" + ") VALUES (" + "$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12" + ")", + 12, NULL); + PREPARE ("get_deposit", + "SELECT " + "coin_pub," + "denom_pub," + "transaction_id," + "amount_value," + "amount_fraction," + "amount_currency," + "merchant_pub," + "h_contract," + "h_wire," + "coin_sig" + " FROM deposits WHERE (" + "(coin_pub = $1) AND" + "(transaction_id = $2) AND" + "(merchant_pub = $3)" + ")", + 3, NULL); + return GNUNET_OK; +#undef PREPARE +} + + +/** + * Close thread-local database connection when a thread is destroyed. + * + * @param closure we get from pthreads (the db handle) + */ +static void +db_conn_destroy (void *cls) +{ + PGconn *db_conn = cls; + + if (NULL != db_conn) + PQfinish (db_conn); +} + + +/** + * Get the thread-local database-handle. + * Connect to the db if the connection does not exist yet. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param temporary #GNUNET_YES to use a temporary schema; #GNUNET_NO to use the + * database default one + * @return the database connection, or NULL on error + */ +static struct TALER_MINTDB_Session * +postgres_get_session (void *cls, + int temporary) +{ + struct PostgresClosure *pc = cls; + PGconn *db_conn; + struct TALER_MINTDB_Session *session; + + if (NULL != (session = pthread_getspecific (pc->db_conn_threadlocal))) + return session; + db_conn = PQconnectdb (pc->connection_cfg_str); + if (CONNECTION_OK != + PQstatus (db_conn)) + { + TALER_LOG_ERROR ("Database connection failed: %s\n", + PQerrorMessage (db_conn)); + GNUNET_break (0); + return NULL; + } + if ((GNUNET_YES == temporary) + && (GNUNET_SYSERR == set_temporary_schema(db_conn))) + { + GNUNET_break (0); + return NULL; + } + if (GNUNET_OK != + postgres_prepare (db_conn)) + { + GNUNET_break (0); + return NULL; + } + session = GNUNET_new (struct TALER_MINTDB_Session); + session->conn = db_conn; + if (0 != pthread_setspecific (pc->db_conn_threadlocal, + session)) + { + GNUNET_break (0); + // FIXME: close db_conn! + GNUNET_free (session); + return NULL; + } + return session; +} + + +/** + * Start a transaction. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_start (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "BEGIN"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + TALER_LOG_ERROR ("Failed to start transaction: %s\n", + PQresultErrorMessage (result)); + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Roll back the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static void +postgres_rollback (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "ROLLBACK"); + GNUNET_break (PGRES_COMMAND_OK == + PQresultStatus (result)); + PQclear (result); +} + + +/** + * Commit the current transaction of a database connection. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @return #GNUNET_OK on success + */ +static int +postgres_commit (void *cls, + struct TALER_MINTDB_Session *session) +{ + PGresult *result; + + result = PQexec (session->conn, + "COMMIT"); + if (PGRES_COMMAND_OK != + PQresultStatus (result)) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get the summary of a reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve data. The public key of the reserve should be set + * in this structure; it is used to query the database. The balance + * and expiration are then filled accordingly. + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_reserve_get (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + uint64_t expiration_date_nbo; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(&reserve->pub), + TALER_PQ_QUERY_PARAM_END + }; + + result = TALER_PQ_exec_prepared (session->conn, + "get_reserve", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("expiration_date", &expiration_date_nbo), + TALER_PQ_RESULT_SPEC_END + }; + EXITIF (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)); + EXITIF (GNUNET_OK != + TALER_PQ_extract_amount (result, 0, + "current_balance_value", + "current_balance_fraction", + "balance_currency", + &reserve->balance)); + reserve->expiry.abs_value_us = GNUNET_ntohll (expiration_date_nbo); + PQclear (result); + return GNUNET_OK; + + EXITIF_exit: + PQclear (result); + return GNUNET_SYSERR; +} + + +/** + * Updates a reserve with the data from the given reserve structure. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection + * @param reserve the reserve structure whose data will be used to update the + * corresponding record in the database. + * @return #GNUNET_OK upon successful update; #GNUNET_SYSERR upon any error + */ +static int +postgres_reserves_update (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve) +{ + PGresult *result; + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + int ret; + + if (NULL == reserve) + return GNUNET_SYSERR; + ret = GNUNET_OK; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + TALER_amount_hton (&balance_nbo, + &reserve->balance); + expiry_nbo = GNUNET_TIME_absolute_hton (reserve->expiry); + result = TALER_PQ_exec_prepared (session->conn, + "update_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + ret = GNUNET_SYSERR; + } + PQclear (result); + return ret; +} + + +/** + * Insert a incoming transaction into reserves. New reserves are also created + * through this function. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session the database connection handle + * @param reserve the reserve structure. The public key of the reserve should + * be set here. Upon successful execution of this function, the + * balance and expiration of the reserve will be updated. + * @param balance the amount that has to be added to the reserve + * @param expiry the new expiration time for the reserve + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failures + */ +static int +postgres_reserves_in_insert (void *cls, + struct TALER_MINTDB_Session *session, + struct Reserve *reserve, + const struct TALER_Amount *balance, + const struct GNUNET_TIME_Absolute expiry) +{ + struct TALER_AmountNBO balance_nbo; + struct GNUNET_TIME_AbsoluteNBO expiry_nbo; + PGresult *result; + int reserve_exists; + + result = NULL; + if (NULL == reserve) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != postgres_start (cls, + session)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + reserve_exists = postgres_reserve_get (cls, + session, + reserve); + if (GNUNET_SYSERR == reserve_exists) + { + postgres_rollback (cls, + session); + return GNUNET_SYSERR; + } + TALER_amount_hton (&balance_nbo, + balance); + expiry_nbo = GNUNET_TIME_absolute_hton (expiry); + if (GNUNET_NO == reserve_exists) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Reserve does not exist; creating a new one\n"); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (balance_nbo.currency, + TALER_PQ_CURRENCY_LEN), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "create_reserve", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + } + if (NULL != result) + PQclear (result); + result = NULL; + /* create new incoming transaction */ + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&reserve->pub), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&balance_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (&balance_nbo.currency, + TALER_PQ_CURRENCY_LEN), + TALER_PQ_QUERY_PARAM_PTR (&expiry_nbo), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "create_reserves_in_transaction", + params); + if (PGRES_COMMAND_OK != PQresultStatus(result)) + { + QUERY_ERR (result); + goto rollback; + } + PQclear (result); + result = NULL; + if (GNUNET_NO == reserve_exists) + { + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = *balance; + reserve->expiry = expiry; + return GNUNET_OK; + } + /* Update reserve */ + struct Reserve updated_reserve; + updated_reserve.pub = reserve->pub; + + if (GNUNET_OK != + TALER_amount_add (&updated_reserve.balance, + &reserve->balance, + balance)) + { + return GNUNET_SYSERR; + } + updated_reserve.expiry = GNUNET_TIME_absolute_max (expiry, reserve->expiry); + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &updated_reserve)) + goto rollback; + if (GNUNET_OK != postgres_commit (cls, + session)) + return GNUNET_SYSERR; + reserve->balance = updated_reserve.balance; + reserve->expiry = updated_reserve.expiry; + return GNUNET_OK; + + rollback: + PQclear (result); + postgres_rollback (cls, + session); + return GNUNET_SYSERR; +} + + +/** + * Locate the response for a /withdraw request under the + * key of the hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_get_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (h_blind), + TALER_PQ_QUERY_PARAM_END + }; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *denom_sig; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub = NULL; + denom_pub_enc = NULL; + denom_sig_enc = NULL; + result = TALER_PQ_exec_prepared (session->conn, + "get_collectable_blindcoin", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_PQ_RESULT_SPEC_VAR("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_PQ_RESULT_SPEC("reserve_sig", &collectable->reserve_sig), + TALER_PQ_RESULT_SPEC("reserve_pub", &collectable->reserve_pub), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + GNUNET_break (0); + goto cleanup; + } + denom_pub = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + denom_sig = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + if ((NULL == denom_pub) || (NULL == denom_sig)) + { + GNUNET_break (0); + goto cleanup; + } + collectable->denom_pub.rsa_public_key = denom_pub; + collectable->sig.rsa_signature = denom_sig; + ret = GNUNET_YES; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + if (GNUNET_YES != ret) + { if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != denom_sig) + GNUNET_CRYPTO_rsa_signature_free (denom_sig); + } + return ret; +} + + +/** + * Store collectable bit coin under the corresponding + * hash of the blinded message. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param h_blind hash of the blinded message + * @param withdraw amount by which the reserve will be withdrawn with this + * transaction + * @param collectable corresponding collectable coin (blind signature) + * if a coin is found + * @return #GNUNET_SYSERR on internal error + * #GNUNET_NO if the collectable was not found + * #GNUNET_YES on success + */ +static int +postgres_insert_collectable_blindcoin (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *h_blind, + struct TALER_Amount withdraw, + const struct CollectableBlindcoin *collectable) +{ + PGresult *result; + struct Reserve reserve; + char *denom_pub_enc = NULL; + char *denom_sig_enc = NULL; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (collectable->denom_pub.rsa_public_key, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (collectable->sig.rsa_signature, + &denom_sig_enc); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (h_blind), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size - 1), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size - 1), /* DB doesn't like the trailing \0 */ + TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_pub), + TALER_PQ_QUERY_PARAM_PTR (&collectable->reserve_sig), + TALER_PQ_QUERY_PARAM_END + }; + if (GNUNET_OK != postgres_start (cls, + session)) + goto cleanup; + result = TALER_PQ_exec_prepared (session->conn, + "insert_collectable_blindcoin", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto rollback; + } + reserve.pub = collectable->reserve_pub; + if (GNUNET_OK != postgres_reserve_get (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_SYSERR == + TALER_amount_subtract (&reserve.balance, + &reserve.balance, + &withdraw)) + goto rollback; + if (GNUNET_OK != postgres_reserves_update (cls, + session, + &reserve)) + goto rollback; + if (GNUNET_OK == postgres_commit (cls, + session)) + { + ret = GNUNET_OK; + goto cleanup; + } + + rollback: + postgres_rollback (cls, + session); + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + return ret; +} + + +/** + * Get all of the transaction history associated with the specified + * reserve. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to use + * @param reserve_pub public key of the reserve + * @return known transaction history (NULL if reserve is unknown) + */ +static struct ReserveHistory * +postgres_get_reserve_history (void *cls, + struct TALER_MINTDB_Session *session, + const struct TALER_ReservePublicKeyP *reserve_pub) +{ + PGresult *result; + struct ReserveHistory *rh; + struct ReserveHistory *rh_head; + int rows; + int ret; + + result = NULL; + rh = NULL; + rh_head = NULL; + ret = GNUNET_SYSERR; + { + struct BankTransfer *bt; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (reserve_pub), + TALER_PQ_QUERY_PARAM_END + }; + + result = TALER_PQ_exec_prepared (session->conn, + "get_reserves_in_transactions", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Asked to fetch history for an unknown reserve.\n"); + goto cleanup; + } + while (0 < rows) + { + bt = GNUNET_new (struct BankTransfer); + if (GNUNET_OK != TALER_PQ_extract_amount (result, + --rows, + "balance_value", + "balance_fraction", + "balance_currency", + &bt->amount)) + { + GNUNET_free (bt); + GNUNET_break (0); + goto cleanup; + } + bt->reserve_pub = *reserve_pub; + if (NULL != rh_head) + { + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + } + else + { + rh_head = GNUNET_new (struct ReserveHistory); + rh = rh_head; + } + rh_head->type = TALER_MINT_DB_RO_BANK_TO_MINT; + rh_head->details.bank = bt; + } + } + PQclear (result); + result = NULL; + { + struct GNUNET_HashCode blind_ev; + struct TALER_ReserveSignatureP reserve_sig; + struct CollectableBlindcoin *cbc; + char *denom_pub_enc; + char *denom_sig_enc; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (reserve_pub), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "get_reserves_blindcoins", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + QUERY_ERR (result); + goto cleanup; + } + if (0 == (rows = PQntuples (result))) + { + ret = GNUNET_OK; /* Its OK if there are no withdrawls yet */ + goto cleanup; + } + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC ("blind_ev", &blind_ev), + TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &denom_pub_enc, &denom_pub_enc_size), + TALER_PQ_RESULT_SPEC_VAR ("denom_sig", &denom_sig_enc, &denom_sig_enc_size), + TALER_PQ_RESULT_SPEC ("reserve_sig", &reserve_sig), + TALER_PQ_RESULT_SPEC_END + }; + GNUNET_assert (NULL != rh); + GNUNET_assert (NULL != rh_head); + GNUNET_assert (NULL == rh_head->next); + while (0 < rows) + { + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, --rows)) + { + GNUNET_break (0); + goto cleanup; + } + cbc = GNUNET_new (struct CollectableBlindcoin); + cbc->sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (denom_sig_enc, + denom_sig_enc_size); + GNUNET_free (denom_sig_enc); + denom_sig_enc = NULL; + cbc->denom_pub.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (denom_pub_enc, + denom_pub_enc_size); + GNUNET_free (denom_pub_enc); + denom_pub_enc = NULL; + if ( (NULL == cbc->sig.rsa_signature) || + (NULL == cbc->denom_pub.rsa_public_key) ) + { + if (NULL != cbc->sig.rsa_signature) + GNUNET_CRYPTO_rsa_signature_free (cbc->sig.rsa_signature); + if (NULL != cbc->denom_pub.rsa_public_key) + GNUNET_CRYPTO_rsa_public_key_free (cbc->denom_pub.rsa_public_key); + GNUNET_free (cbc); + GNUNET_break (0); + goto cleanup; + } + (void) memcpy (&cbc->h_coin_envelope, &blind_ev, sizeof (blind_ev)); + (void) memcpy (&cbc->reserve_pub, reserve_pub, sizeof (cbc->reserve_pub)); + (void) memcpy (&cbc->reserve_sig, &reserve_sig, sizeof (cbc->reserve_sig)); + rh_head->next = GNUNET_new (struct ReserveHistory); + rh_head = rh_head->next; + rh_head->type = TALER_MINT_DB_RO_WITHDRAW_COIN; + rh_head->details.withdraw = cbc; + } + } + ret = GNUNET_OK; + + cleanup: + if (NULL != result) + PQclear (result); + if (GNUNET_SYSERR == ret) + { + common_free_reserve_history (cls, + rh); + rh = NULL; + } + return rh; +} + + +/** + * Check if we have the specified deposit already in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param deposit deposit to search for + * @return #GNUNET_YES if we know this operation, + * #GNUNET_NO if this deposit is unknown to us + */ +static int +postgres_have_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_PQ_QUERY_PARAM_END + }; + PGresult *result; + int ret; + + ret = GNUNET_SYSERR; + result = TALER_PQ_exec_prepared (session->conn, + "get_deposit", + params); + if (PGRES_TUPLES_OK != + PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + + if (0 == PQntuples (result)) + { + ret = GNUNET_NO; + goto cleanup; + } + ret = GNUNET_YES; + + cleanup: + PQclear (result); + return ret; +} + + +/** + * Insert information about deposited coin into the + * database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session connection to the database + * @param deposit deposit information to store + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +postgres_insert_deposit (void *cls, + struct TALER_MINTDB_Session *session, + const struct Deposit *deposit) +{ + char *denom_pub_enc; + char *denom_sig_enc; + char *json_wire_enc; + PGresult *result; + struct TALER_AmountNBO amount_nbo; + size_t denom_pub_enc_size; + size_t denom_sig_enc_size; + int ret; + + ret = GNUNET_SYSERR; + denom_pub_enc_size = + GNUNET_CRYPTO_rsa_public_key_encode (deposit->coin.denom_pub.rsa_public_key, + &denom_pub_enc); + denom_sig_enc_size = + GNUNET_CRYPTO_rsa_signature_encode (deposit->coin.denom_sig.rsa_signature, + &denom_sig_enc); + json_wire_enc = json_dumps (deposit->wire, JSON_COMPACT); + TALER_amount_hton (&amount_nbo, + &deposit->amount_with_fee); + struct TALER_PQ_QueryParam params[]= { + TALER_PQ_QUERY_PARAM_PTR (&deposit->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_pub_enc, denom_pub_enc_size), + TALER_PQ_QUERY_PARAM_PTR_SIZED (denom_sig_enc, denom_sig_enc_size), + TALER_PQ_QUERY_PARAM_PTR (&deposit->transaction_id), + TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.value), + TALER_PQ_QUERY_PARAM_PTR (&amount_nbo.fraction), + TALER_PQ_QUERY_PARAM_PTR_SIZED (amount_nbo.currency, + TALER_CURRENCY_LEN - 1), + TALER_PQ_QUERY_PARAM_PTR (&deposit->merchant_pub), + TALER_PQ_QUERY_PARAM_PTR (&deposit->h_contract), + TALER_PQ_QUERY_PARAM_PTR (&deposit->h_wire), + TALER_PQ_QUERY_PARAM_PTR (&deposit->csig), + TALER_PQ_QUERY_PARAM_PTR_SIZED (json_wire_enc, + strlen (json_wire_enc)), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, "insert_deposit", params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + goto cleanup; + } + ret = GNUNET_OK; + + cleanup: + PQclear (result); + GNUNET_free_non_null (denom_pub_enc); + GNUNET_free_non_null (denom_sig_enc); + GNUNET_free_non_null (json_wire_enc); + return ret; +} + + +/** + * Lookup refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session[OUT] where to store the result + * @return #GNUNET_YES on success, + * #GNUNET_NO if not found, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_get_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + struct RefreshSession *refresh_session) +{ + // FIXME: check logic! + int res; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_session", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Query failed: %s\n", + PQresultErrorMessage (result)); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + return GNUNET_NO; + + GNUNET_assert (1 == PQntuples (result)); + + /* We're done if the caller is only interested in + * whether the session exists or not */ + + if (NULL == refresh_session) + return GNUNET_YES; + + memset (session, 0, sizeof (struct RefreshSession)); + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("num_oldcoins", &refresh_session->num_oldcoins), + TALER_PQ_RESULT_SPEC("num_newcoins", &refresh_session->num_newcoins), + TALER_PQ_RESULT_SPEC("noreveal_index", &refresh_session->noreveal_index), + TALER_PQ_RESULT_SPEC_END + }; + + res = TALER_PQ_extract_result (result, rs, 0); + + if (GNUNET_OK != res) + { + GNUNET_break (0); + PQclear (result); + return GNUNET_SYSERR; + } + + refresh_session->num_oldcoins = ntohs (refresh_session->num_oldcoins); + refresh_session->num_newcoins = ntohs (refresh_session->num_newcoins); + refresh_session->noreveal_index = ntohs (refresh_session->noreveal_index); + + PQclear (result); + return GNUNET_YES; +} + + +/** + * Store new refresh session data under the given @a session_hash. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database handle to use + * @param session_hash hash over the melt to use to locate the session + * @param refresh_session session data to store + * @return #GNUNET_YES on success, + * #GNUNET_SYSERR on DB failure + */ +static int +postgres_create_refresh_session (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + const struct RefreshSession *refresh_session) +{ + // FIXME: actually store session data! + uint16_t noreveal_index; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&noreveal_index), + TALER_PQ_QUERY_PARAM_END + }; + + noreveal_index = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, 1<<15); + noreveal_index = htonl (noreveal_index); + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_session", + params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Store the given /refresh/melt request in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param oldcoin_index index of the coin to store + * @param melt melt operation details to store; includes + * the session hash of the melt + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + uint16_t oldcoin_index, + const struct RefreshMelt *melt) +{ + // FIXME: check logic! + uint16_t oldcoin_index_nbo = htons (oldcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (melt->coin.denom_pub.rsa_public_key, + &buf); + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(&melt->session_hash), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&melt->coin.coin_pub), + TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_melt", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Get information about melted coin details from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param refresh_session session key of the melt operation + * @param oldcoin_index index of the coin to retrieve + * @param melt melt data to fill in + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_melt (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t oldcoin_index, + struct RefreshMelt *melt) +{ + // FIXME: check logic! + GNUNET_break (0); + return GNUNET_SYSERR; +} + + +/** + * Store in the database which coin(s) we want to create + * in a given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param num_newcoins number of coins to generate, size of the @a denom_pubs array + * @param denom_pubs array denominations of the coins to create + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_insert_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + const struct TALER_DenominationPublicKey *denom_pubs) +{ + // FIXME: check logic: was written for just one COIN! + uint16_t newcoin_index_nbo = htons (num_newcoins); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_public_key_encode (denom_pubs->rsa_public_key, + &buf); + + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR (&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR (session_hash), + TALER_PQ_QUERY_PARAM_PTR_SIZED (buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_order", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Lookup in the database the coins that we want to + * create in the given refresh operation. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index array of the @a denom_pubs array + * @param denom_pubs where to store the deomination keys + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on internal error + */ +static int +postgres_get_refresh_order (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t num_newcoins, + struct TALER_DenominationPublicKey *denom_pubs) +{ + // FIXME: check logic -- was written for just one coin! + char *buf; + size_t buf_size; + uint16_t newcoin_index_nbo = htons (num_newcoins); + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_order", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + /* FIXME: may want to distinguish between different error cases! */ + return GNUNET_SYSERR; + } + GNUNET_assert (1 == PQntuples (result)); + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR ("denom_pub", &buf, &buf_size), + TALER_PQ_RESULT_SPEC_END + }; + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + PQclear (result); + denom_pubs->rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (buf, + buf_size); + GNUNET_free (buf); + return GNUNET_OK; +} + + + +/** + * Store information about the commitment of the + * given coin for the given refresh session in the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_newcoins coin index size of the @a commit_coins array + * @param commit_coins array of coin commitments to store + * @return #GNUNET_OK on success + * #GNUNET_SYSERR on error + */ +static int +postgres_insert_refresh_commit_coins (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int num_newcoins, + const struct RefreshCommitCoin *commit_coins) +{ + // FIXME: check logic! -- was written for single commit_coin! + uint16_t cnc_index_nbo = htons (i); + uint16_t newcoin_index_nbo = htons (num_newcoins); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR_SIZED(commit_coins->coin_ev, commit_coins->coin_ev_size), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR_SIZED (commit_coins->refresh_link->coin_priv_enc, + commit_coins->refresh_link->blinding_key_enc_size + + sizeof (union TALER_CoinSpendPrivateKeyP)), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_commit_coin", + params); + + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain information about the commitment of the + * given coin of the given refresh session from the database. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to refreshed (new) coins + * @param commit_coin[OUT] coin commitment to return + * @return #GNUNET_OK on success + * #GNUNET_NO if not found + * #GNUNET_SYSERR on error + */ +static int +postgres_get_refresh_commit_coins (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int cnc_index, + unsigned int newcoin_index, + struct RefreshCommitCoin *cc) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (cnc_index); + uint16_t newcoin_index_nbo = htons (newcoin_index); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + char *c_buf; + size_t c_buf_size; + char *rl_buf; + size_t rl_buf_size; + struct TALER_RefreshLinkEncrypted *rl; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_commit_coin", + params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("coin_ev", &c_buf, &c_buf_size), + TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &rl_buf, &rl_buf_size), + TALER_PQ_RESULT_SPEC_END + }; + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + if (rl_buf_size < sizeof (union TALER_CoinSpendPrivateKeyP)) + { + GNUNET_free (c_buf); + GNUNET_free (rl_buf); + return GNUNET_SYSERR; + } + rl = TALER_refresh_link_encrypted_decode (rl_buf, + rl_buf_size); + GNUNET_free (rl_buf); + cc->refresh_link = rl; + cc->coin_ev = c_buf; + cc->coin_ev_size = c_buf_size; + return GNUNET_YES; +} + + +/** + * Store the commitment to the given (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param j coin index (2nd dimension), corresponds to melted (old) coins + * @param commit_link link information to store + * @return #GNUNET_SYSERR on internal error, #GNUNET_OK on success + */ +static int +postgres_insert_refresh_commit_links (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int j, + const struct RefreshCommitLink *commit_link) +{ + // FIXME: check logic! + uint16_t cnc_index_nbo = htons (i); + uint16_t oldcoin_index_nbo = htons (j); + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&commit_link->transfer_pub), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&commit_link->shared_secret_enc), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_commit_link", + params); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 != strcmp ("1", PQcmdTuples (result))) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the commited (encrypted) refresh link data + * for the given refresh session. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection to use + * @param session_hash hash to identify refresh session + * @param i set index (1st dimension) + * @param num_links size of the @a commit_link array + * @param links[OUT] array of link information to return + * @return #GNUNET_SYSERR on internal error, + * #GNUNET_NO if commitment was not found + * #GNUNET_OK on success + */ +static int +postgres_get_refresh_commit_links (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + unsigned int i, + unsigned int num_links, + struct RefreshCommitLink *links) +{ + // FIXME: check logic: was written for a single link! + uint16_t cnc_index_nbo = htons (i); + uint16_t oldcoin_index_nbo = htons (num_links); + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&cnc_index_nbo), + TALER_PQ_QUERY_PARAM_PTR(&oldcoin_index_nbo), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, + "get_refresh_commit_link", + params); + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("transfer_pub", &links->transfer_pub), + TALER_PQ_RESULT_SPEC("link_secret_enc", &links->shared_secret_enc), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_YES != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Insert signature of a new coin generated during refresh into + * the database indexed by the refresh session and the index + * of the coin. This data is later used should an old coin + * be used to try to obtain the private keys during "/refresh/link". + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param session_hash hash to identify refresh session + * @param newcoin_index coin index + * @param ev_sig coin signature + * @return #GNUNET_OK on success + */ +static int +postgres_insert_refresh_collectable (void *cls, + struct TALER_MINTDB_Session *session, + const struct GNUNET_HashCode *session_hash, + uint16_t newcoin_index, + const struct TALER_DenominationSignature *ev_sig) +{ + // FIXME: check logic! + uint16_t newcoin_index_nbo = htons (newcoin_index); + char *buf; + size_t buf_size; + PGresult *result; + + buf_size = GNUNET_CRYPTO_rsa_signature_encode (ev_sig->rsa_signature, + &buf); + { + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(session_hash), + TALER_PQ_QUERY_PARAM_PTR(&newcoin_index_nbo), + TALER_PQ_QUERY_PARAM_PTR_SIZED(buf, buf_size), + TALER_PQ_QUERY_PARAM_END + }; + result = TALER_PQ_exec_prepared (session->conn, + "insert_refresh_collectable", + params); + } + GNUNET_free (buf); + if (PGRES_COMMAND_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + PQclear (result); + return GNUNET_OK; +} + + +/** + * Obtain the link data of a coin, that is the encrypted link + * information, the denomination keys and the signatures. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key to use to retrieve linkage data + * @return all known link data for the coin + */ +static struct LinkDataList * +postgres_get_link_data_list (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub) +{ + // FIXME: check logic! + struct LinkDataList *ldl; + struct LinkDataList *pos; + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(coin_pub), + TALER_PQ_QUERY_PARAM_END + }; + PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_link", params); + + ldl = NULL; + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return NULL; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return NULL; + } + + + int i = 0; + + for (i = 0; i < PQntuples (result); i++) + { + struct TALER_RefreshLinkEncrypted *link_enc; + struct GNUNET_CRYPTO_rsa_PublicKey *denom_pub; + struct GNUNET_CRYPTO_rsa_Signature *sig; + char *ld_buf; + size_t ld_buf_size; + char *pk_buf; + size_t pk_buf_size; + char *sig_buf; + size_t sig_buf_size; + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC_VAR("link_vector_enc", &ld_buf, &ld_buf_size), + TALER_PQ_RESULT_SPEC_VAR("denom_pub", &pk_buf, &pk_buf_size), + TALER_PQ_RESULT_SPEC_VAR("ev_sig", &sig_buf, &sig_buf_size), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, i)) + { + PQclear (result); + GNUNET_break (0); + common_free_link_data_list (cls, + ldl); + return NULL; + } + if (ld_buf_size < sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)) + { + PQclear (result); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + common_free_link_data_list (cls, + ldl); + return NULL; + } + // FIXME: use util API for this! + link_enc = GNUNET_malloc (sizeof (struct TALER_RefreshLinkEncrypted) + + ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey)); + link_enc->blinding_key_enc = (const char *) &link_enc[1]; + link_enc->blinding_key_enc_size = ld_buf_size - sizeof (struct GNUNET_CRYPTO_EcdsaPrivateKey); + memcpy (link_enc->coin_priv_enc, + ld_buf, + ld_buf_size); + + sig + = GNUNET_CRYPTO_rsa_signature_decode (sig_buf, + sig_buf_size); + denom_pub + = GNUNET_CRYPTO_rsa_public_key_decode (pk_buf, + pk_buf_size); + GNUNET_free (pk_buf); + GNUNET_free (sig_buf); + GNUNET_free (ld_buf); + if ( (NULL == sig) || + (NULL == denom_pub) ) + { + if (NULL != denom_pub) + GNUNET_CRYPTO_rsa_public_key_free (denom_pub); + if (NULL != sig) + GNUNET_CRYPTO_rsa_signature_free (sig); + GNUNET_free (link_enc); + GNUNET_break (0); + PQclear (result); + common_free_link_data_list (cls, + ldl); + return NULL; + } + pos = GNUNET_new (struct LinkDataList); + pos->next = ldl; + pos->link_data_enc = link_enc; + pos->denom_pub.rsa_public_key = denom_pub; + pos->ev_sig.rsa_signature = sig; + ldl = pos; + } + return ldl; +} + + +/** + * Obtain shared secret and transfer public key from the public key of + * the coin. This information and the link information returned by + * #postgres_get_link_data_list() enable the owner of an old coin to + * determine the private keys of the new coins after the melt. + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub public key of the coin + * @param transfer_pub[OUT] public transfer key + * @param shared_secret_enc[OUT] set to shared secret + * @return #GNUNET_OK on success, + * #GNUNET_NO on failure (not found) + * #GNUNET_SYSERR on internal failure (database issue) + */ +static int +postgres_get_transfer (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub, + struct TALER_TransferPublicKeyP *transfer_pub, + struct TALER_EncryptedLinkSecretP *shared_secret_enc) +{ + // FIXME: check logic! + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_QUERY_PARAM_PTR(coin_pub), + TALER_PQ_QUERY_PARAM_END + }; + + PGresult *result = TALER_PQ_exec_prepared (session->conn, "get_transfer", params); + + if (PGRES_TUPLES_OK != PQresultStatus (result)) + { + BREAK_DB_ERR (result); + PQclear (result); + return GNUNET_SYSERR; + } + + if (0 == PQntuples (result)) + { + PQclear (result); + return GNUNET_NO; + } + + if (1 != PQntuples (result)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "got %d tuples for get_transfer\n", + PQntuples (result)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + struct TALER_PQ_ResultSpec rs[] = { + TALER_PQ_RESULT_SPEC("transfer_pub", transfer_pub), + TALER_PQ_RESULT_SPEC("link_secret_enc", shared_secret_enc), + TALER_PQ_RESULT_SPEC_END + }; + + if (GNUNET_OK != TALER_PQ_extract_result (result, rs, 0)) + { + PQclear (result); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + PQclear (result); + return GNUNET_OK; +} + + +/** + * Compile a list of all (historic) transactions performed + * with the given coin (/refresh/melt and /deposit operations). + * + * @param cls the `struct PostgresClosure` with the plugin-specific state + * @param session database connection + * @param coin_pub coin to investigate + * @return list of transactions, NULL if coin is fresh + */ +static struct TALER_MINT_DB_TransactionList * +postgres_get_coin_transactions (void *cls, + struct TALER_MINTDB_Session *session, + const union TALER_CoinSpendPublicKeyP *coin_pub) +{ + // FIXME: check logic! + GNUNET_break (0); // FIXME: implement! + return NULL; +} + + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MINTDB_Plugin` + */ +void * +libtaler_plugin_mintdb_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_MINTDB_Plugin *plugin; + + pg = GNUNET_new (struct PostgresClosure); + + if (0 != pthread_key_create (&pg->db_conn_threadlocal, + &db_conn_destroy)) + { + TALER_LOG_ERROR ("Cannnot create pthread key.\n"); + return NULL; + } + /* FIXME: use configuration section with "postgres" in its name... */ + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "mint", "db_conn_str", + &pg->connection_cfg_str)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "mint", + "db_conn_str"); + return NULL; + } + plugin = GNUNET_new (struct TALER_MINTDB_Plugin); + plugin->cls = pg; + plugin->get_session = &postgres_get_session; + plugin->drop_temporary = &postgres_drop_temporary; + plugin->create_tables = &postgres_create_tables; + plugin->start = &postgres_start; + plugin->commit = &postgres_commit; + plugin->rollback = &postgres_rollback; + plugin->reserve_get = &postgres_reserve_get; + plugin->reserves_in_insert = &postgres_reserves_in_insert; + plugin->get_collectable_blindcoin = &postgres_get_collectable_blindcoin; + plugin->insert_collectable_blindcoin = &postgres_insert_collectable_blindcoin; + plugin->get_reserve_history = &postgres_get_reserve_history; + plugin->free_reserve_history = &common_free_reserve_history; + plugin->have_deposit = &postgres_have_deposit; + plugin->insert_deposit = &postgres_insert_deposit; + plugin->get_refresh_session = &postgres_get_refresh_session; + plugin->create_refresh_session = &postgres_create_refresh_session; + plugin->insert_refresh_melt = &postgres_insert_refresh_melt; + plugin->get_refresh_melt = &postgres_get_refresh_melt; + plugin->insert_refresh_order = &postgres_insert_refresh_order; + plugin->get_refresh_order = &postgres_get_refresh_order; + plugin->insert_refresh_commit_coins = &postgres_insert_refresh_commit_coins; + plugin->get_refresh_commit_coins = &postgres_get_refresh_commit_coins; + plugin->insert_refresh_commit_links = &postgres_insert_refresh_commit_links; + plugin->get_refresh_commit_links = &postgres_get_refresh_commit_links; + plugin->insert_refresh_collectable = &postgres_insert_refresh_collectable; + plugin->get_link_data_list = &postgres_get_link_data_list; + plugin->free_link_data_list = &common_free_link_data_list; + plugin->get_transfer = &postgres_get_transfer; + // plugin->have_lock = &postgres_have_lock; + // plugin->insert_lock = &postgres_insert_lock; + plugin->get_coin_transactions = &postgres_get_coin_transactions; + plugin->free_coin_transaction_list = &common_free_coin_transaction_list; + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_MINTDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_mintdb_postgres_done (void *cls) +{ + struct TALER_MINTDB_Plugin *plugin = cls; + struct PostgresClosure *pg = plugin->cls; + + GNUNET_free (pg->connection_cfg_str); + GNUNET_free (pg); + GNUNET_free (plugin); + return NULL; +} + +/* end of plugin_mintdb_postgres.c */ diff --git a/src/mint/test_mint_db.c b/src/mintdb/test_mintdb.c diff --git a/src/mint/test_mint_deposits.c b/src/mintdb/test_mintdb_deposits.c diff --git a/src/mint/test_mint_common.c b/src/mintdb/test_mintdb_keyio.c