diff options
64 files changed, 6531 insertions, 5144 deletions
@@ -1,6 +1,3 @@ -sources in src/merchant/ are obsolete, therefore the configure system does not -provide support for building them. - The new merchant's layout is reflected by the directories 'frontend' and 'backend', being respectively the PHP website homepage and the C 'core' which is charge of providing all the cryptographic and DB related primitives. diff --git a/configure.ac b/configure.ac index 67d84f11..67d5450c 100644 --- a/configure.ac +++ b/configure.ac @@ -2,9 +2,9 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([taler], [0.0.0], [taler-bug@gnunet.org]) -AC_CONFIG_SRCDIR([src/merchant/merchant.c]) -AC_CONFIG_HEADERS([taler_config.h]) +AC_INIT([taler-merchant], [0.0.0], [taler-bug@gnunet.org]) +AC_CONFIG_SRCDIR([src/backend/taler-merchant-httpd.c]) +AC_CONFIG_HEADERS([taler_merchant_config.h]) # support for non-recursive builds AM_INIT_AUTOMAKE([subdir-objects]) @@ -50,14 +50,12 @@ AS_IF([test $libgnunetutil != 1], *** ]])]) -# check for libpq (postgresql) +# test for postgres AX_LIB_POSTGRESQL([9.3]) -AS_IF([test ! "$found_postgresql" = "yes"], - [AC_MSG_ERROR([[ -*** -*** You need postgresql / libpq to build this program. -*** ]])]) - +if test "$found_postgresql" = "yes"; then + postgres=true +fi +AM_CONDITIONAL(HAVE_POSTGRESQL, test x$postgres = xtrue) # Check for Taler's libtalerpq libtalerpq=0 @@ -81,13 +79,7 @@ AC_CHECK_HEADERS([taler/taler_pq_lib.h], [], [#ifdef HAVE_GNUNET_PLATFORM_H #include <gnunet/platform.h> #endif]) -AS_IF([test $libtalerpq != 1], - [AC_MSG_ERROR([[ -*** -*** You need libtalerpq to build this program. -*** This library is part of the Taler MINT, available at -*** https://taler.net -*** ]])]) +AM_CONDITIONAL(HAVE_TALERPQ, test x$libtalerpq = x1) # check for libmicrohttpd @@ -134,7 +126,8 @@ AS_IF([test $jansson = 0], *** ]])]) # check for libgnurl -LIBGNURL_CHECK_CONFIG([], [7.34.0], [gnurl=1], [gnurl=0]) +# libgnurl +LIBGNURL_CHECK_CONFIG(,7.34.0,gnurl=1,gnurl=0) if test "$gnurl" = 1 then AM_CONDITIONAL(HAVE_LIBGNURL, true) @@ -142,11 +135,52 @@ then else AM_CONDITIONAL(HAVE_LIBGNURL, false) fi -AS_IF([test $gnurl = 0], - [AC_MSG_ERROR([[ -*** -*** You need libgnurl to build this program. -*** ]])]) + +# libcurl-gnutls +LIBCURL_CHECK_CONFIG(,7.34.0,[curl=true],[curl=false]) +if test "x$curl" = xtrue +then + AC_CHECK_HEADERS([curl/curl.h], + AC_CHECK_DECLS(CURLINFO_TLS_SESSION,[curl=true],[curl=false],[[#include <curl/curl.h>]]), + [curl=false]) + # need libcurl-gnutls.so, everything else is not acceptable + AC_CHECK_LIB([curl-gnutls],[curl_easy_getinfo],,[curl=false]) + # cURL must support CURLINFO_TLS_SESSION, version >= 7.34 + +fi +if test x$curl = xfalse +then + AM_CONDITIONAL(HAVE_LIBCURL, false) +if test "$gnurl" = 0 +then + AC_MSG_WARN([GNUnet requires libcurl-gnutls >= 7.34]) +fi +else + AM_CONDITIONAL(HAVE_LIBCURL, true) + AC_DEFINE([HAVE_LIBCURL],[1],[Have CURL]) +fi + +# libgnurl +if test "x$gnurl" = "x0" +then + if test "x$curl" = "x0" + then + AC_MSG_NOTICE([NOTICE: libgnurl not found. http client support will not be compiled.]) + AC_MSG_WARN([ERROR: libgnurl not found. hostlist daemon will not be compiled, and you probably WANT the hostlist daemon]) + else + AC_MSG_NOTICE([WARNING: libgnurl not found, trying to use libcurl-gnutls instead.]) + fi +fi + +# gcov compilation +AC_MSG_CHECKING(whether to compile with support for code coverage analysis) +AC_ARG_ENABLE([coverage], + AS_HELP_STRING([--enable-coverage], + [compile the library with code coverage support]), + [use_gcov=${enableval}], + [use_gcov=no]) +AC_MSG_RESULT($use_gcov) +AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"]) # Require minimum libgcrypt version need_libgcrypt_version=1.6.1 @@ -180,6 +214,7 @@ AC_CHECK_FUNCS([strdup]) AC_CONFIG_FILES([Makefile src/Makefile src/include/Makefile +src/backenddb/Makefile src/backend/Makefile -src/backend-lib/Makefile]) +src/lib/Makefile]) AC_OUTPUT diff --git a/m4/libcurl.m4 b/m4/libcurl.m4 new file mode 100644 index 00000000..a84077a5 --- /dev/null +++ b/m4/libcurl.m4 @@ -0,0 +1,251 @@ +# LIBCURL_CHECK_CONFIG ([DEFAULT-ACTION], [MINIMUM-VERSION], +# [ACTION-IF-YES], [ACTION-IF-NO]) +# ---------------------------------------------------------- +# David Shaw <dshaw@jabberwocky.com> May-09-2006 +# +# Checks for libcurl. DEFAULT-ACTION is the string yes or no to +# specify whether to default to --with-libcurl or --without-libcurl. +# If not supplied, DEFAULT-ACTION is yes. MINIMUM-VERSION is the +# minimum version of libcurl to accept. Pass the version as a regular +# version number like 7.10.1. If not supplied, any version is +# accepted. ACTION-IF-YES is a list of shell commands to run if +# libcurl was successfully found and passed the various tests. +# ACTION-IF-NO is a list of shell commands that are run otherwise. +# Note that using --without-libcurl does run ACTION-IF-NO. +# +# This macro #defines HAVE_LIBCURL if a working libcurl setup is +# found, and sets @LIBCURL@ and @LIBCURL_CPPFLAGS@ to the necessary +# values. Other useful defines are LIBCURL_FEATURE_xxx where xxx are +# the various features supported by libcurl, and LIBCURL_PROTOCOL_yyy +# where yyy are the various protocols supported by libcurl. Both xxx +# and yyy are capitalized. See the list of AH_TEMPLATEs at the top of +# the macro for the complete list of possible defines. Shell +# variables $libcurl_feature_xxx and $libcurl_protocol_yyy are also +# defined to 'yes' for those features and protocols that were found. +# Note that xxx and yyy keep the same capitalization as in the +# curl-config list (e.g. it's "HTTP" and not "http"). +# +# Users may override the detected values by doing something like: +# LIBCURL="-lcurl" LIBCURL_CPPFLAGS="-I/usr/myinclude" ./configure +# +# For the sake of sanity, this macro assumes that any libcurl that is +# found is after version 7.7.2, the first version that included the +# curl-config script. Note that it is very important for people +# packaging binary versions of libcurl to include this script! +# Without curl-config, we can only guess what protocols are available, +# or use curl_version_info to figure it out at runtime. + +AC_DEFUN([LIBCURL_CHECK_CONFIG], +[ + AH_TEMPLATE([LIBCURL_FEATURE_SSL],[Defined if libcurl supports SSL]) + AH_TEMPLATE([LIBCURL_FEATURE_KRB4],[Defined if libcurl supports KRB4]) + AH_TEMPLATE([LIBCURL_FEATURE_IPV6],[Defined if libcurl supports IPv6]) + AH_TEMPLATE([LIBCURL_FEATURE_LIBZ],[Defined if libcurl supports libz]) + AH_TEMPLATE([LIBCURL_FEATURE_ASYNCHDNS],[Defined if libcurl supports AsynchDNS]) + AH_TEMPLATE([LIBCURL_FEATURE_IDN],[Defined if libcurl supports IDN]) + AH_TEMPLATE([LIBCURL_FEATURE_SSPI],[Defined if libcurl supports SSPI]) + AH_TEMPLATE([LIBCURL_FEATURE_NTLM],[Defined if libcurl supports NTLM]) + + AH_TEMPLATE([LIBCURL_PROTOCOL_HTTP],[Defined if libcurl supports HTTP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_HTTPS],[Defined if libcurl supports HTTPS]) + AH_TEMPLATE([LIBCURL_PROTOCOL_FTP],[Defined if libcurl supports FTP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_FTPS],[Defined if libcurl supports FTPS]) + AH_TEMPLATE([LIBCURL_PROTOCOL_FILE],[Defined if libcurl supports FILE]) + AH_TEMPLATE([LIBCURL_PROTOCOL_TELNET],[Defined if libcurl supports TELNET]) + AH_TEMPLATE([LIBCURL_PROTOCOL_LDAP],[Defined if libcurl supports LDAP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_DICT],[Defined if libcurl supports DICT]) + AH_TEMPLATE([LIBCURL_PROTOCOL_TFTP],[Defined if libcurl supports TFTP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_RTSP],[Defined if libcurl supports RTSP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_POP3],[Defined if libcurl supports POP3]) + AH_TEMPLATE([LIBCURL_PROTOCOL_IMAP],[Defined if libcurl supports IMAP]) + AH_TEMPLATE([LIBCURL_PROTOCOL_SMTP],[Defined if libcurl supports SMTP]) + + AC_ARG_WITH(libcurl, + AC_HELP_STRING([--with-libcurl=PREFIX],[look for the curl library in PREFIX/lib and headers in PREFIX/include]), + [_libcurl_with=$withval],[_libcurl_with=ifelse([$1],,[yes],[$1])]) + + if test "$_libcurl_with" != "no" ; then + + AC_PROG_AWK + + _libcurl_version_parse="eval $AWK '{split(\$NF,A,\".\"); X=256*256*A[[1]]+256*A[[2]]+A[[3]]; print X;}'" + + _libcurl_try_link=yes + + if test -d "$_libcurl_with" ; then + LIBCURL_CPPFLAGS="-I$withval/include" + _libcurl_ldflags="-L$withval/lib" + AC_PATH_PROG([_libcurl_config],[curl-config],[], + ["$withval/bin"]) + else + AC_PATH_PROG([_libcurl_config],[curl-config],[],[$PATH]) + fi + + if test x$_libcurl_config != "x" ; then + AC_CACHE_CHECK([for the version of libcurl], + [libcurl_cv_lib_curl_version], + [libcurl_cv_lib_curl_version=`$_libcurl_config --version | $AWK '{print $[]2}'`]) + + _libcurl_version=`echo $libcurl_cv_lib_curl_version | $_libcurl_version_parse` + _libcurl_wanted=`echo ifelse([$2],,[0],[$2]) | $_libcurl_version_parse` + + if test $_libcurl_wanted -gt 0 ; then + AC_CACHE_CHECK([for libcurl >= version $2], + [libcurl_cv_lib_version_ok], + [ + if test $_libcurl_version -ge $_libcurl_wanted ; then + libcurl_cv_lib_version_ok=yes + else + libcurl_cv_lib_version_ok=no + fi + ]) + fi + + if test $_libcurl_wanted -eq 0 || test x$libcurl_cv_lib_version_ok = xyes ; then + if test x"$LIBCURL_CPPFLAGS" = "x" ; then + LIBCURL_CPPFLAGS=`$_libcurl_config --cflags` + fi + if test x"$LIBCURL" = "x" ; then + LIBCURL=`$_libcurl_config --libs` + + # This is so silly, but Apple actually has a bug in their + # curl-config script. Fixed in Tiger, but there are still + # lots of Panther installs around. + case "${host}" in + powerpc-apple-darwin7*) + LIBCURL=`echo $LIBCURL | sed -e 's|-arch i386||g'` + ;; + esac + fi + + # All curl-config scripts support --feature + _libcurl_features=`$_libcurl_config --feature` + + # Is it modern enough to have --protocols? (7.12.4) + if test $_libcurl_version -ge 461828 ; then + _libcurl_protocols=`$_libcurl_config --protocols` + fi + else + _libcurl_try_link=no + fi + + unset _libcurl_wanted + fi + + if test $_libcurl_try_link = yes ; then + + # we didn't find curl-config, so let's see if the user-supplied + # link line (or failing that, "-lcurl") is enough. + LIBCURL=${LIBCURL-"$_libcurl_ldflags -lcurl"} + + AC_CACHE_CHECK([whether libcurl is usable], + [libcurl_cv_lib_curl_usable], + [ + _libcurl_save_cppflags=$CPPFLAGS + CPPFLAGS="$LIBCURL_CPPFLAGS $CPPFLAGS" + _libcurl_save_libs=$LIBS + LIBS="$LIBCURL $LIBS" + + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <curl/curl.h>]],[[ +/* Try and use a few common options to force a failure if we are + missing symbols or can't link. */ +int x; +curl_easy_setopt(NULL,CURLOPT_URL,NULL); +x=CURL_ERROR_SIZE; +x=CURLOPT_WRITEFUNCTION; +x=CURLOPT_WRITEDATA; +x=CURLOPT_ERRORBUFFER; +x=CURLOPT_STDERR; +x=CURLOPT_VERBOSE; +if (x) ; +]])],libcurl_cv_lib_curl_usable=yes,libcurl_cv_lib_curl_usable=no) + + CPPFLAGS=$_libcurl_save_cppflags + LIBS=$_libcurl_save_libs + unset _libcurl_save_cppflags + unset _libcurl_save_libs + ]) + + if test $libcurl_cv_lib_curl_usable = yes ; then + + # Does curl_free() exist in this version of libcurl? + # If not, fake it with free() + + _libcurl_save_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LIBCURL_CPPFLAGS" + _libcurl_save_libs=$LIBS + LIBS="$LIBS $LIBCURL" + + AC_CHECK_FUNC(curl_free,, + AC_DEFINE(curl_free,free, + [Define curl_free() as free() if our version of curl lacks curl_free.])) + + CPPFLAGS=$_libcurl_save_cppflags + LIBS=$_libcurl_save_libs + unset _libcurl_save_cppflags + unset _libcurl_save_libs + + AC_DEFINE(HAVE_LIBCURL,1, + [Define to 1 if you have a functional curl library.]) + AC_SUBST(LIBCURL_CPPFLAGS) + AC_SUBST(LIBCURL) + + for _libcurl_feature in $_libcurl_features ; do + AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_feature_$_libcurl_feature),[1]) + eval AS_TR_SH(libcurl_feature_$_libcurl_feature)=yes + done + + if test "x$_libcurl_protocols" = "x" ; then + + # We don't have --protocols, so just assume that all + # protocols are available + _libcurl_protocols="HTTP FTP FILE TELNET LDAP DICT TFTP" + + if test x$libcurl_feature_SSL = xyes ; then + _libcurl_protocols="$_libcurl_protocols HTTPS" + + # FTPS wasn't standards-compliant until version + # 7.11.0 (0x070b00 == 461568) + if test $_libcurl_version -ge 461568; then + _libcurl_protocols="$_libcurl_protocols FTPS" + fi + fi + + # RTSP, IMAP, POP3 and SMTP were added in + # 7.20.0 (0x071400 == 463872) + if test $_libcurl_version -ge 463872; then + _libcurl_protocols="$_libcurl_protocols RTSP IMAP POP3 SMTP" + fi + fi + + for _libcurl_protocol in $_libcurl_protocols ; do + AC_DEFINE_UNQUOTED(AS_TR_CPP(libcurl_protocol_$_libcurl_protocol),[1]) + eval AS_TR_SH(libcurl_protocol_$_libcurl_protocol)=yes + done + else + unset LIBCURL + unset LIBCURL_CPPFLAGS + fi + fi + + unset _libcurl_try_link + unset _libcurl_version_parse + unset _libcurl_config + unset _libcurl_feature + unset _libcurl_features + unset _libcurl_protocol + unset _libcurl_protocols + unset _libcurl_version + unset _libcurl_ldflags + fi + + if test x$_libcurl_with = xno || test x$libcurl_cv_lib_curl_usable != xyes ; then + # This is the IF-NO path + ifelse([$4],,:,[$4]) + else + # This is the IF-YES path + ifelse([$3],,:,[$3]) + fi + + unset _libcurl_with +])dnl diff --git a/src/Makefile.am b/src/Makefile.am index 665be7d3..4d26b2c8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,2 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/include -SUBDIRS = include backend-lib backend +SUBDIRS = include backenddb backend diff --git a/src/backend-lib/Makefile.am b/src/backend-lib/Makefile.am deleted file mode 100644 index 583c4d03..00000000 --- a/src/backend-lib/Makefile.am +++ /dev/null @@ -1,27 +0,0 @@ -# This Makefile.am is in the public domain -AM_CPPFLAGS = -I$(top_srcdir)/src/include - -lib_LTLIBRARIES = \ - libtalermerchant.la - -include_HEADERS = \ - taler_merchant_lib.h \ - taler_merchant_contract_lib.h \ - taler_merchant_deposit_lib.h - -libtalermerchant_la_SOURCES = \ - merchant_api_contract.c \ - merchant_api_deposit.c \ - taler_merchant_contract_lib.h \ - taler_merchant_deposit_lib.h \ - merchant_db.c merchant_db.h \ - merchant.h - -libtalermerchant_la_LIBADD = \ - $(LIBGCRYPT_LIBS) \ - -ltalerutil \ - -lgnunetutil \ - -ltalerpq \ - -lgnunetpostgres \ - -lpq \ - -lpthread diff --git a/src/backend-lib/merchant_api_contract.c b/src/backend-lib/merchant_api_contract.c deleted file mode 100644 index 20b69cd3..00000000 --- a/src/backend-lib/merchant_api_contract.c +++ /dev/null @@ -1,60 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.c - * @brief DB work related to contract management - * @author Marcello Stanisci - */ - -#include "platform.h" -#include <jansson.h> -#include <taler/taler_signatures.h> -#include <gnunet/gnunet_util_lib.h> -#include "merchant.h" -#include "merchant_db.h" -#include "taler_merchant_contract_lib.h" - -/** - * Take the global wire details and return a JSON containing them, - * compliantly with the Taler's API. - * @param wire the merchant's wire details - * @param salt the nounce for hashing the wire details with - * @param edate when the beneficiary wants this transfer to take place - * @return JSON representation of the wire details, NULL upon errors - */ - -json_t * -MERCHANT_get_wire_json (const struct MERCHANT_WIREFORMAT_Sepa *wire, - uint64_t salt) - -{ - - json_t *root; - json_t *j_salt; - - j_salt = json_integer (salt); - - if (NULL == (root = json_pack ("{s:s, s:s, s:s, s:s, s:I}", - "type", "SEPA", - "IBAN", wire->iban, - "name", wire->name, - "bic", wire->bic, - "r", json_integer_value (j_salt)))) - return NULL; - - return root; -} diff --git a/src/backend-lib/merchant_api_deposit.c b/src/backend-lib/merchant_api_deposit.c deleted file mode 100644 index a3cae20c..00000000 --- a/src/backend-lib/merchant_api_deposit.c +++ /dev/null @@ -1,111 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.c - * @brief DB and crypto work related to deposit management - * @author Marcello Stanisci - */ - - -#include "platform.h" -#include <jansson.h> -#include <taler/taler_signatures.h> -#include <gnunet/gnunet_util_lib.h> -#include <taler/taler_util.h> -#include "merchant.h" -#include "merchant_db.h" -#include "taler_merchant_contract_lib.h" - -/** -* Verify the signature on a successful deposit permission -* @param h_contract the hashed stringification of this contract -* @param h_wire the hashed 'wire' object holdign the merchant bank's details -* @param timestamp the 32bit wide number representing the number of seconds -* since the Epoch -* @param refund the refund deadline for this deal, expressed in seconds as @a -* timestamp -* @param trans_id an id number for this deal -* @param amount_minus_fee what paid minus its deposit fee -* @param coin_pub the coin's public key -* @param sig the mint's signature -* @param mint_pub mint's key to verify this signature against -* @return GNUNET_OK if the verification succeeds, GNUNET_NO if not, -* GNUNET_SYSERR upon errors -*/ - -uint32_t -MERCHANT_verify_confirmation (const struct GNUNET_HashCode *h_contract, - const struct GNUNET_HashCode *h_wire, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund, - uint64_t trans_id, - const struct TALER_Amount *amount_minus_fee, - const struct TALER_CoinSpendPublicKeyP *coin, - const struct TALER_MerchantPublicKeyP *merchant, - const struct GNUNET_CRYPTO_EddsaSignature *sig, - const struct TALER_MintPublicKeyP *mint_pub) -{ - struct TALER_DepositConfirmationPS dc; - - dc.h_contract = *h_contract; - dc.h_wire = *h_wire; - - dc.merchant = *merchant; - dc.coin_pub = *coin; - - dc.timestamp = GNUNET_TIME_absolute_hton (timestamp); - dc.refund_deadline = GNUNET_TIME_absolute_hton (refund); - TALER_amount_hton (&dc.amount_without_fee, amount_minus_fee); - dc.transaction_id = GNUNET_htonll (trans_id); - - #ifdef DEBUG - char *hwire_enc; - char *hcontract_enc; - char *merchant_enc; - char *coinpub_enc; - - hwire_enc = GNUNET_STRINGS_data_to_string_alloc (h_wire, sizeof (struct GNUNET_HashCode)); - hcontract_enc = GNUNET_STRINGS_data_to_string_alloc (h_contract, sizeof (struct GNUNET_HashCode)); - merchant_enc = GNUNET_STRINGS_data_to_string_alloc (&merchant.eddsa_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); - coinpub_enc = GNUNET_STRINGS_data_to_string_alloc (&coin.eddsa_pub, sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); - - printf ("Signing Confirmation:\nH_wire: %s\nH_contract: %s\nmerchant_pub: %s\ncoin_pub: %s\n" - "timestamp: %llu,\nrefund: %llu,\namount: %s %llu.%lu,\ntrid: %llu\n", - hwire_enc, - hcontract_enc, - merchant_enc, - coinpub_enc, - timestamp_abs.abs_value_us, - refund_abs.abs_value_us, - amount_minus_fee->currency, - amount_minus_fee->value, - amount_minus_fee->fraction, - trans_id); - #endif - - dc.purpose.purpose = htonl (TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT); - dc.purpose.size = htonl (sizeof (struct TALER_DepositConfirmationPS)); - - if (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_verify (TALER_SIGNATURE_MINT_CONFIRM_DEPOSIT, - &dc.purpose, - sig, - &mint_pub->eddsa_pub)) - return GNUNET_NO; - return GNUNET_OK; -} - diff --git a/src/backend-lib/merchant_db.c b/src/backend-lib/merchant_db.c deleted file mode 100644 index f30e3d88..00000000 --- a/src/backend-lib/merchant_db.c +++ /dev/null @@ -1,662 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.c - * @brief database helper functions used by the merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <taler/taler_util.h> -#include <taler/taler_pq_lib.h> -#include "merchant_db.h" - - -#define PQSQL_strerror(kind, cmd, res) \ - GNUNET_log_from (kind, "merchant-db", \ - "SQL %s failed at %s:%u with error: %s", \ - cmd, __FILE__, __LINE__, PQresultErrorMessage (res)); - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Connect to postgresql database - * - * @param cfg the configuration handle - * @return connection to the postgresql database; NULL upon error - */ -PGconn * -MERCHANT_DB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - return GNUNET_POSTGRES_connect (cfg, "merchant-db"); -} - - -/** - * Disconnect from the database - * - * @param conn database handle to close - */ -void -MERCHANT_DB_disconnect (PGconn *conn) -{ - PQfinish (conn); -} - - -/** - * Initialize merchant tables - * - * @param conn the connection handle to postgres db. - * @param tmp GNUNET_YES if the tables are to be made temporary i.e. their - * contents are dropped when the @a conn is closed - * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure - */ -int -MERCHANT_DB_initialize (PGconn *conn, int tmp) -{ - const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; - char *sql; - PGresult *res; - ExecStatusType status; - int ret; - - res = NULL; - (void) GNUNET_asprintf (&sql, - "BEGIN TRANSACTION;" - "CREATE %1$s TABLE IF NOT EXISTS contracts (" - "contract_id INT8 PRIMARY KEY," - "hash BYTEA NOT NULL," - "amount INT8 NOT NULL," - "amount_fraction INT4 NOT NULL," - "amount_currency VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL," - "description TEXT NOT NULL," - "nounce INT8 NOT NULL," - "timestamp INT8 NOT NULL," - "expiry INT8 NOT NULL," - "edate INT8 NOT NULL," - "refund_deadline INT8 NOT NULL," - "product INT8 NOT NULL);" - "CREATE %1$s TABLE IF NOT EXISTS checkouts (" - "coin_pub BYTEA PRIMARY KEY," - "contract_id INT8 REFERENCES contracts(contract_id)," - "amount INT4 NOT NULL," - "amount_fraction INT4 NOT NULL," - "coin_sig BYTEA NOT NULL);" - "CREATE %1$s TABLE IF NOT EXISTS deposits (" - "dep_perm VARCHAR NOT NULL," - "transaction_id INT8," - "pending INT4 NOT NULL," - "mint_url VARCHAR NOT NULL);", - tmp_str); - ret = GNUNET_POSTGRES_exec (conn, sql); - (void) GNUNET_POSTGRES_exec (conn, - (GNUNET_OK == ret) ? "COMMIT;" : "ROLLBACK;"); - GNUNET_free (sql); - if (GNUNET_OK != ret) - return ret; - - while (NULL != (res = PQgetResult (conn))) - { - status = PQresultStatus (res); - PQclear (res); - } - - EXITIF (NULL == (res = PQprepare - (conn, - "contract_create", - "INSERT INTO contracts" - "(contract_id, hash, timestamp, expiry, edate," - "refund_deadline, amount, amount_fraction, amount_currency," - "description, nounce, product) VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)", - 12, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - - /* Query aimed to get the contract's nounce and edate which will be - both used for regenerating a 'wire' JSON object to insert into the - deposit permission. Implicitly, this query will tell whether a contract - was created or not */ - - EXITIF (NULL == (res = PQprepare - (conn, - "get_contract_hash", - "SELECT " - "nounce, edate " - "FROM contracts " - "WHERE (" - "hash=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "get_contract_set", - "SELECT " - "contract_id, nounce, timestamp, edate, " - "refund_deadline FROM contracts " - "WHERE (" - "hash=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "store_deposit_permission", - "INSERT INTO deposits" - "(dep_perm, transaction_id, pending, mint_url) " - "VALUES ($1, $2, $3, $4);", 4, NULL))); - - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - - EXITIF (NULL == (res = PQprepare - (conn, - "update_deposit_permission", - "UPDATE deposits " - "SET pending = $1 " - "WHERE transaction_id = $2", 2, NULL))); - - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - - EXITIF (NULL == (res = PQprepare - (conn, - "get_contract_product", - "SELECT (" - "product" - ") FROM contracts " - "WHERE (" - "contract_id=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "checkout_create", - "INSERT INTO checkouts (" - "coin_pub," - "contract_id," - "amount," - "amount_fraction," - "coin_sig" - ") VALUES (" - "$1, $2, $3, $4, $5" - ")", - 5, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus (res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "get_checkout_product", - "SELECT (" - "product" - ") FROM contracts " - "WHERE " - "contract_id IN (" - "SELECT (contract_id) FROM checkouts " - "WHERE coin_pub=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus (res))); - PQclear (res); - - return GNUNET_OK; - - EXITIF_exit: - if (NULL != res) - { - PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); - PQclear (res); - } - return GNUNET_SYSERR; -} - -/** - * Update the pending column of a deposit permission - * @param conn handle to DB - * @param transaction_id identification number of the deposit to - * update - * @param pending true if still pending, false otherwise (i.e. the - * mint did respond something) - * @return GNUNET_OK if successful, GNUNET_SYSERR upon errors - */ -uint32_t -MERCHANT_DB_update_deposit_permission (PGconn *conn, - uint64_t transaction_id, - unsigned int pending) -{ - PGresult *res; - ExecStatusType status; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_uint32 (&pending), - TALER_PQ_query_param_uint64 (&transaction_id), - TALER_PQ_query_param_end - }; - - res = TALER_PQ_exec_prepared (conn, "update_deposit_permission", params); - status = PQresultStatus (res); - - if (PGRES_COMMAND_OK != status) - { - - const char *sqlstate; - - sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); - if (NULL == sqlstate) - { - /* very unexpected... */ - GNUNET_break (0); - PQclear (res); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Database commit failure: %s\n", - sqlstate); - PQclear (res); - return GNUNET_SYSERR; - } -} - -/** - * Store a deposit permission in DB. To be mainly used if /deposit should - * be retried; also, the merchant can benefit from this information in case - * he needs to later investigate about some transaction_id. - * @param conn DB handle - * @param transaction_id identification number of this payment (which is the - * same id of the related contract) - * @param pending if true, this payment got to a persistent state - * @param which mint is to get this deposit permission - * @return GNUNET_OK if successful, GNUNET_SYSERR upon errors - */ -uint32_t -MERCHANT_DB_store_deposit_permission (PGconn *conn, - const char *deposit_permission, - uint64_t transaction_id, - unsigned int pending, - const char *mint_url) -{ - PGresult *res; - ExecStatusType status; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_fixed_size (deposit_permission, strlen (deposit_permission)), - TALER_PQ_query_param_uint64 (&transaction_id), - TALER_PQ_query_param_uint32 (&pending), - TALER_PQ_query_param_fixed_size (mint_url, strlen (mint_url)), - TALER_PQ_query_param_end - }; - res = TALER_PQ_exec_prepared (conn, "store_deposit_permission", params); - status = PQresultStatus (res); - - if (PGRES_COMMAND_OK != status) - { - const char *sqlstate; - - sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); - if (NULL == sqlstate) - { - /* very unexpected... */ - GNUNET_break (0); - PQclear (res); - return GNUNET_SYSERR; - } - /* 40P01: deadlock, 40001: serialization failure */ - if ( (0 == strcmp (sqlstate, - "23505"))) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Inserting same transaction id twice\n"); - /* Primary key violation */ - PQclear (res); - return GNUNET_SYSERR; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Database commit failure: %s\n", - sqlstate); - PQclear (res); - return GNUNET_SYSERR; - } - - PQclear (res); - return GNUNET_OK; -} - -/** -* Insert a contract record into the database and if successfull -* return the serial number of the inserted row. -* -* @param conn the database connection -* @param timestamp the timestamp of this contract -* @param expiry the time when the contract will expire -* @param edate when the merchant wants to receive the wire transfer -* corresponding to this deal (this value is also a field inside the -* 'wire' JSON format) -* @param refund deadline until which the merchant can return the paid -* amount -* @param amount the taler amount corresponding to the contract -* @param hash of the stringified JSON corresponding to this contract -* @param c_id contract's id -* @param desc descripition of the contract -* @param nounce a random 64-bit nounce -* @param product description to identify a product -* @return GNUNET_OK on success, GNUNET_NO if attempting to insert an -* already inserted @a c_id, GNUNET_SYSERR for other errors. -*/ - -uint32_t -MERCHANT_DB_contract_create (PGconn *conn, - const struct GNUNET_TIME_Absolute timestamp, - const struct GNUNET_TIME_Absolute expiry, - struct GNUNET_TIME_Absolute edate, - struct GNUNET_TIME_Absolute refund, - const struct TALER_Amount *amount, - const struct GNUNET_HashCode *h_contract, - uint64_t c_id, - const char *desc, - uint64_t nounce, - uint64_t product) -{ - PGresult *res; - #if 0 - uint64_t expiry_ms_nbo; - uint64_t value_nbo; - uint32_t fraction_nbo; - uint64_t nounce_nbo; - #endif - ExecStatusType status; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_uint64 (&c_id), - TALER_PQ_query_param_fixed_size (h_contract, sizeof (struct GNUNET_HashCode)), - TALER_PQ_query_param_absolute_time (×tamp), - TALER_PQ_query_param_absolute_time (&expiry), - TALER_PQ_query_param_absolute_time (&edate), - TALER_PQ_query_param_absolute_time (&refund), - TALER_PQ_query_param_amount (amount), - /* A *string* is being put in the following statement, - though the column is declared as *blob*. Will this be - liked by the DB ? */ - TALER_PQ_query_param_fixed_size (desc, strlen (desc)), - TALER_PQ_query_param_uint64 (&nounce), - TALER_PQ_query_param_uint64 (&product), - TALER_PQ_query_param_end - }; - - /* NOTE: the statement is prepared by MERCHANT_DB_initialize function */ - res = TALER_PQ_exec_prepared (conn, "contract_create", params); - status = PQresultStatus (res); - - if (PGRES_COMMAND_OK != status) - { - const char *sqlstate; - - sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); - if (NULL == sqlstate) - { - /* very unexpected... */ - GNUNET_break (0); - PQclear (res); - return GNUNET_SYSERR; - } - /* 40P01: deadlock, 40001: serialization failure */ - if ( (0 == strcmp (sqlstate, - "23505"))) - { - /* Primary key violation */ - PQclear (res); - return GNUNET_NO; - } - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Database commit failure: %s\n", - sqlstate); - PQclear (res); - return GNUNET_SYSERR; - } - - PQclear (res); - return GNUNET_OK; - -} - -long long -MERCHANT_DB_get_contract_product (PGconn *conn, - uint64_t contract_id) -{ - PGresult *res; - uint64_t product; - ExecStatusType status; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_uint64 (&contract_id), - TALER_PQ_query_param_end - }; - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_uint64 ("product", &product), - TALER_PQ_result_spec_end - }; - - contract_id = GNUNET_htonll (contract_id); - res = TALER_PQ_exec_prepared (conn, "get_contract_product", params); - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_PQ_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_ntohll ((uint64_t) product); - - EXITIF_exit: - PQclear (res); - return -1; -} - -unsigned int -MERCHANT_DB_checkout_create (PGconn *conn, - struct GNUNET_CRYPTO_rsa_PublicKey *coin_pub, - uint64_t transaction_id, - struct TALER_Amount *amount, - struct GNUNET_CRYPTO_rsa_Signature *coin_sig) -{ - PGresult *res; - ExecStatusType status; - uint32_t value_nbo; - uint32_t fraction_nbo; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_rsa_public_key (coin_pub), - TALER_PQ_query_param_uint64 (&transaction_id), - TALER_PQ_query_param_uint32 (&value_nbo), - TALER_PQ_query_param_uint32 (&fraction_nbo), - TALER_PQ_query_param_rsa_signature (coin_sig), - TALER_PQ_query_param_end - }; - - transaction_id = GNUNET_htonll (transaction_id); - value_nbo = htonl (amount->value); - fraction_nbo = htonl (amount->fraction); - res = TALER_PQ_exec_prepared (conn, "checkout_create", params); - status = PQresultStatus (res); - EXITIF (PGRES_COMMAND_OK != status); - PQclear (res); - return GNUNET_OK; - - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; -} - - -long long -MERCHANT_DB_get_checkout_product (PGconn *conn, - struct GNUNET_CRYPTO_rsa_PublicKey *coin_pub) -{ - PGresult *res; - ExecStatusType status; - uint64_t product; - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_rsa_public_key (coin_pub), - TALER_PQ_query_param_end - }; - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_uint64 ("product", &product), - TALER_PQ_result_spec_end - }; - - product = -1; - res = TALER_PQ_exec_prepared (conn, "get_checkout_product", params); - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - if (0 == PQntuples (res)) - { - TALER_LOG_DEBUG ("Checkout not found for given coin"); - goto EXITIF_exit; - } - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_PQ_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_ntohll ((uint64_t) product); - - EXITIF_exit: - PQclear (res); - return -1; -} -/* end of merchant-db.c */ - - -/** -* The query gets a contract's nounce and edate used to reproduce -* a 'wire' JSON object. This function is also useful to check whether -* a claimed contract existed or not. -* @param conn handle to the DB -* @param h_contract the parameter for the row to match against -* @param nounce where to store the found nounce -* @param edate where to store the found edate -* @return GNUNET_OK on success, GNUNET_SYSERR upon errors -* -*/ - -uint32_t -MERCHANT_DB_get_contract_values (PGconn *conn, - const struct GNUNET_HashCode *h_contract, - uint64_t *nounce, - struct GNUNET_TIME_Absolute *edate) -{ - PGresult *res; - ExecStatusType status; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_fixed_size (h_contract, sizeof (struct GNUNET_HashCode)), - TALER_PQ_query_param_end - }; - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_uint64 ("nounce", nounce), - TALER_PQ_result_spec_absolute_time ("edate", edate), - TALER_PQ_result_spec_end - }; - - res = TALER_PQ_exec_prepared (conn, "get_contract_hash", params); - - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - if (0 == PQntuples (res)) - { - TALER_LOG_DEBUG ("Contract not found"); - goto EXITIF_exit; - } - - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_PQ_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_OK; - - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; -} - -/** -* Get a set of values representing a contract. This function is meant -* to obsolete the '_get_contract_values' version. -* @param h_contract the hashcode of this contract -* @param contract_handle where to store the results -* @raturn GNUNET_OK in case of success, GNUNET_SYSERR -* upon errors -* -*/ - -uint32_t -MERCHANT_DB_get_contract_handle (PGconn *conn, - const struct GNUNET_HashCode *h_contract, - struct MERCHANT_contract_handle *contract_handle) -{ - struct MERCHANT_contract_handle ch; - PGresult *res; - ExecStatusType status; - - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_fixed_size (h_contract, sizeof (struct GNUNET_HashCode)), - TALER_PQ_query_param_end - }; - - struct TALER_PQ_ResultSpec rs[] = { - TALER_PQ_result_spec_uint64 ("nounce", &ch.nounce), - TALER_PQ_result_spec_absolute_time ("edate", &ch.edate), - TALER_PQ_result_spec_absolute_time ("timestamp", &ch.timestamp), - TALER_PQ_result_spec_absolute_time ("refund_deadline", &ch.refund_deadline), - TALER_PQ_result_spec_uint64 ("contract_id", &ch.contract_id), - TALER_PQ_result_spec_end - }; - - res = TALER_PQ_exec_prepared (conn, "get_contract_set", params); - - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - if (0 == PQntuples (res)) - { - TALER_LOG_DEBUG ("Contract not found"); - goto EXITIF_exit; - } - - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_PQ_extract_result (res, rs, 0)); - *contract_handle = ch; - PQclear (res); - return GNUNET_OK; - - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; -} diff --git a/src/backend-lib/merchant_db.h b/src/backend-lib/merchant_db.h deleted file mode 100644 index aa157712..00000000 --- a/src/backend-lib/merchant_db.h +++ /dev/null @@ -1,198 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.h - * @brief database helper functions used by the merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#ifndef MERCHANT_DB_H -#define MERCHANT_DB_H - -#include <gnunet/gnunet_postgres_lib.h> -#include <taler/taler_util.h> - -/* Set of values that represent a contract. To be expanded on an - as-needed basis */ -struct MERCHANT_contract_handle -{ - /* The nounce used when hashing the wire details - for this contract */ - uint64_t nounce; - - /* The maximum time when the merchant expects the money tranfer - to his bank account to happen */ - struct GNUNET_TIME_Absolute edate; - - /* The time when this contract was generated */ - struct GNUNET_TIME_Absolute timestamp; - - /* The maximum time until which the merchant could issue a - refund to the customer */ - struct GNUNET_TIME_Absolute refund_deadline; - - /* The identification number for this contract */ - uint64_t contract_id; - -}; - -/** - * Connect to postgresql database - * - * @param cfg the configuration handle - * @return connection to the postgresql database; NULL upon error - */ -PGconn * -MERCHANT_DB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg); - - -/** - * Disconnect from the database - * - * @param conn database handle to close - */ -void -MERCHANT_DB_disconnect (PGconn *conn); - - -/** - * Initialize merchant tables - * - * @param conn the connection handle to postgres db. - * @param tmp GNUNET_YES if the tables are to be made temporary i.e. their - * contents are dropped when the @a conn is closed - * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure - */ -int -MERCHANT_DB_initialize (PGconn *conn, int tmp); - - -/** -* Inserts a contract record into the database and if successfull returns the -* serial number of the inserted row. -* -* @param conn the database connection -* @param timestamp the timestamp of this contract -* @param expiry the time when the contract will expire -* @param edate when the merchant wants to receive the wire transfer corresponding -* to this deal (this value is also a field inside the 'wire' JSON format) -* @param refund deadline until which the merchant can return the paid amount -* @param amount the taler amount corresponding to the contract -* @param hash of the stringified JSON corresponding to this contract -* @param c_id contract's id -* @param desc descripition of the contract -* @param nounce a random 64-bit nounce -* @param product description to identify a product -* @return GNUNET_OK on success, GNUNET_SYSERR upon error -*/ - -uint32_t -MERCHANT_DB_contract_create (PGconn *conn, - const struct GNUNET_TIME_Absolute timestamp, - const struct GNUNET_TIME_Absolute expiry, - struct GNUNET_TIME_Absolute edate, - struct GNUNET_TIME_Absolute refund, - const struct TALER_Amount *amount, - const struct GNUNET_HashCode *h_contract, - uint64_t c_id, - const char *desc, - uint64_t nounce, - uint64_t product); - -long long -MERCHANT_DB_get_contract_product (PGconn *conn, - uint64_t contract_id); - -/** - * Update the pending column of a deposit permission - * @param conn handle to DB - * @param transaction_id identification number of the deposit to - * update - * @param pending true if still pending, false otherwise (i.e. the - * mint did respond something) - * @return GNUNET_OK if successful, GNUNET_SYSERR upon errors - */ -uint32_t -MERCHANT_DB_update_deposit_permission (PGconn *conn, - uint64_t transaction_id, - unsigned int pending); - -unsigned int -MERCHANT_DB_checkout_create (PGconn *conn, - struct GNUNET_CRYPTO_rsa_PublicKey *coin_pub, - uint64_t transaction_id, - struct TALER_Amount *amount, - struct GNUNET_CRYPTO_rsa_Signature *coin_sig); - - -long long -MERCHANT_DB_get_checkout_product (PGconn *conn, - struct GNUNET_CRYPTO_rsa_PublicKey *coin_pub); - -/** -* The query gets a contract's nounce and edate used to reproduce -* a 'wire' JSON object. This function is also useful to check whether -* a claimed contract existed or not. -* @param conn handle to the DB -* @param h_contract the parameter for the row to match against -* @param nounce where to store the found nounce -* @param edate where to store the found edate -* @return GNUNET_OK on success, GNUNET_SYSERR upon errors -* -*/ - -uint32_t -MERCHANT_DB_get_contract_values (PGconn *conn, - const struct GNUNET_HashCode *h_contract, - uint64_t *nounce, - struct GNUNET_TIME_Absolute *edate); - -#endif /* MERCHANT_DB_H */ - -/** -* Get a set of values representing a contract. This function is meant -* to obsolete the '_get_contract_values' version. -* @param h_contract the hashcode of this contract -* @param contract_handle where to store the results -* @raturn GNUNET_OK in case of success, GNUNET_SYSERR -* upon errors -* -*/ - -uint32_t -MERCHANT_DB_get_contract_handle (PGconn *conn, - const struct GNUNET_HashCode *h_contract, - struct MERCHANT_contract_handle *contract_handle); - -/** - * Store a deposit permission in DB. To be mainly used if /deposit should - * be retried; also, the merchant can benefit from this information in case - * he needs to later investigate about some transaction_id. - * @param conn DB handle - * @param transaction_id identification number of this payment (which is the - * same id of the related contract) - * @param pending if true, this payment got to a persistent state - * @param which mint is to get this deposit permission - * @return GNUNET_OK if successful, GNUNET_SYSERR upon errors - */ -uint32_t -MERCHANT_DB_store_deposit_permission (PGconn *conn, - const char *deposit_permission, - uint64_t transaction_id, - unsigned int pending, - const char *mint_url); -/* end of merchant-db.h */ diff --git a/src/backend-lib/taler_merchant_contract_lib.h b/src/backend-lib/taler_merchant_contract_lib.h deleted file mode 100644 index 3f95841f..00000000 --- a/src/backend-lib/taler_merchant_contract_lib.h +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Take the global wire details and return a JSON containing them, - * compliantly with the Taler's API. - * @param wire the merchant's wire details - * @param salt the nounce for hashing the wire details with - * @return JSON representation of the wire details, NULL upon errors - */ - -json_t * -MERCHANT_get_wire_json (const struct MERCHANT_WIREFORMAT_Sepa *wire, - uint64_t salt); diff --git a/src/backend-lib/taler_merchant_deposit_lib.h b/src/backend-lib/taler_merchant_deposit_lib.h deleted file mode 100644 index 95bbea78..00000000 --- a/src/backend-lib/taler_merchant_deposit_lib.h +++ /dev/null @@ -1,28 +0,0 @@ -/** -* Verify the signature on a successful deposit permission -* @param h_contract the hashed stringification of this contract -* @param h_wire the hashed 'wire' object holdign the merchant bank's details -* @param timestamp the 32bit wide number representing the number of seconds -* since the Epoch -* @param refund the refund deadline for this deal, expressed in seconds as @a -* timestamp -* @param trans_id an id number for this deal -* @param amount_minus_fee what paid minus its deposit fee -* @param coin_pub the coin's public key -* @param sig the mint's signature -* @param mint_pub mint's key to verify this signature against -* @return GNUNET_OK if the verification succeeds, GNUNET_NO if not, -* GNUNET_SYSERR upon errors -*/ - -uint32_t -MERCHANT_verify_confirmation (const struct GNUNET_HashCode *h_contract, - const struct GNUNET_HashCode *h_wire, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute refund, - uint64_t trans_id, - const struct TALER_Amount *amount_minus_fee, - const struct TALER_CoinSpendPublicKeyP *coin, - const struct TALER_MerchantPublicKeyP *merchant, - const struct GNUNET_CRYPTO_EddsaSignature *sig, - const struct TALER_MintPublicKeyP *mint_pub); diff --git a/src/backend-lib/taler_merchant_lib.h b/src/backend-lib/taler_merchant_lib.h deleted file mode 100644 index ff8d85b7..00000000 --- a/src/backend-lib/taler_merchant_lib.h +++ /dev/null @@ -1,2 +0,0 @@ -#include "taler_merchant_contract_lib.h" -#include "taler_merchant_deposit_lib.h" diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 08601781..a99fab99 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -6,28 +6,18 @@ bin_PROGRAMS = \ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd.c taler-merchant-httpd.h \ - merchant.c merchant.h \ - ../backend-lib/merchant_db.c ../backend-lib/merchant_db.h \ - taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ - taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ - taler-mint-httpd.h \ - taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ - taler-merchant-httpd_contract.c \ - taler-merchant-httpd_contract.h \ - taler-merchant-httpd_pay.c \ - taler-merchant-httpd_pay.h - + taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \ + taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \ + taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ + taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \ + taler-merchant-httpd_mints.c taler-merchant-httpd_mints.h \ + taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \ + taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h taler_merchant_httpd_LDADD = \ - $(LIBGCRYPT_LIBS) \ + $(top_srcdir)/src/backenddb/libtalermerchantdb.la \ + -ltalermint \ -ltalerutil \ -lmicrohttpd \ -ljansson \ - -lcurl \ - -lgnunetutil \ - $(top_srcdir)/src/backend-lib/libtalermerchant.la \ - -ltalermint \ - -ltalerpq \ - -lgnunetpostgres \ - -lpq \ - -lpthread + -lgnunetutil diff --git a/src/backend/QUESTIONS b/src/backend/QUESTIONS deleted file mode 100644 index b992e96f..00000000 --- a/src/backend/QUESTIONS +++ /dev/null @@ -1,6 +0,0 @@ -1. why does the merchant daemon appears to be some executable under some -.libs directory even though it gets launched through an executable located -elsewhere? - -2. why does the httpd prints three times the URL corresponding to GET /some/url -? diff --git a/src/backend/README b/src/backend/README deleted file mode 100644 index 999dd9e6..00000000 --- a/src/backend/README +++ /dev/null @@ -1,7 +0,0 @@ -Here are the files implementing the backend which is in charge of doing -cryptographic calls, binary manipulations and some HTTP/JSON communication. - -NOTE: - -Makefile.am contains some hardcoded paths that need to be tuned to pick -the right files. diff --git a/src/backend/merchant.c b/src/backend/merchant.c deleted file mode 100644 index 02b37fb8..00000000 --- a/src/backend/merchant.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant.c - * @brief Common utility functions for merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include "merchant.h" - - -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Parses mints from the configuration. - * - * @param cfg the configuration - * @param mints the array of mints upon successful parsing. Will be NULL upon - * error. - * @return the number of mints in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Mint **mints) -{ - char *mints_str; - char *token_nf; /* do no free (nf) */ - char *mint_section; - char *mint_hostname; - struct MERCHANT_Mint *r_mints; - struct MERCHANT_Mint mint; - unsigned int cnt; - int OK; - - OK = 0; - mints_str = NULL; - token_nf = NULL; - mint_section = NULL; - mint_hostname = NULL; - r_mints = NULL; - cnt = 0; - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "TRUSTED_MINTS", - &mints_str)); - for (token_nf = strtok (mints_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&mint_section, - "mint-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "HOSTNAME", - &mint_hostname)); - mint.hostname = mint_hostname; - GNUNET_array_append (r_mints, cnt, mint); - mint_hostname = NULL; - GNUNET_free (mint_section); - mint_section = NULL; - } - OK = 1; - - EXITIF_exit: - GNUNET_free_non_null (mints_str); - GNUNET_free_non_null (mint_section); - GNUNET_free_non_null (mint_hostname); - if (!OK) - { - GNUNET_free_non_null (r_mints); - return GNUNET_SYSERR; - } - - *mints = r_mints; - return cnt; -} - -/** - * Parses auditors from the configuration. - * - * @param cfg the configuration - * @param mints the array of auditors upon successful parsing. Will be NULL upon - * error. - * @return the number of auditors in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_auditors (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Auditor **auditors) -{ - char *auditors_str; - char *token_nf; /* do no free (nf) */ - char *auditor_section; - char *auditor_name; - struct MERCHANT_Auditor *r_auditors; - struct MERCHANT_Auditor auditor; - unsigned int cnt; - int OK; - - OK = 0; - auditors_str = NULL; - token_nf = NULL; - auditor_section = NULL; - auditor_name = NULL; - r_auditors = NULL; - cnt = 0; - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "AUDITORS", - &auditors_str)); - for (token_nf = strtok (auditors_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&auditor_section, - "auditor-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - auditor_section, - "NAME", - &auditor_name)); - auditor.name = auditor_name; - GNUNET_array_append (r_auditors, cnt, auditor); - auditor_name = NULL; - GNUNET_free (auditor_section); - auditor_section = NULL; - } - OK = 1; - - EXITIF_exit: - GNUNET_free_non_null (auditors_str); - GNUNET_free_non_null (auditor_section); - GNUNET_free_non_null (auditor_name); - if (!OK) - { - GNUNET_free_non_null (r_auditors); - return GNUNET_SYSERR; - } - - *auditors = r_auditors; - return cnt; -} - - -/** - * Parse the SEPA information from the configuration. If any of the required - * fileds is missing return NULL. - * - * @param cfg the configuration - * @return Sepa details as a structure; NULL upon error - */ -struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - struct MERCHANT_WIREFORMAT_Sepa *wf; - - wf = GNUNET_new (struct MERCHANT_WIREFORMAT_Sepa); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "IBAN", - &wf->iban)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "NAME", - &wf->name)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "BIC", - &wf->bic)); - return wf; - - EXITIF_exit: - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); - return NULL; - -} - - -/** - * Destroy and free resouces occupied by the wireformat structure - * - * @param wf the wireformat structure - */ -void -TALER_MERCHANT_destroy_wireformat_sepa (struct MERCHANT_WIREFORMAT_Sepa *wf) -{ - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); -} - -/* end of merchant.c */ diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf index 4515fd41..80a7b412 100644 --- a/src/backend/merchant.conf +++ b/src/backend/merchant.conf @@ -1,21 +1,52 @@ +# Sample configuration file for a merchant. [merchant] + +# Which port do we run the backend on? (HTTP server) PORT = 9966 + +# FIXME: is this one used? HOSTNAME = localhost -TRUSTED_MINTS = taler + +# Where is our private key? KEYFILE = merchant.priv + +# What currency does this backend accept? CURRENCY = KUDOS + +# FIXME: to be revised +TRUSTED_MINTS = taler + +# How quickly do we want the mint to send us our money? +# Used only if the frontend does not specify a value. +# FIXME: EDATE is a bit short, 'execution_delay'? EDATE = 3 week -AUDITORS = france + +# Which plugin (backend) do we use for the DB. +DB = postgres [mint-taler] -HOSTNAME = mint.demo.taler.net +URI = mint.demo.taler.net +MASTER_KEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 + +# Auditors must be in sections "auditor-", the rest of the section +# name could be anything. +[auditor-ezb] +# Informal name of the auditor. Just for the user. +NAME = European Central Bank + +# URI of the auditor (especially for in the future, when the +# auditor offers an automated issue reporting system). +# Not really used today. +URI = http://taler.ezb.eu/ -[auditor-france] -NAME = Charles De Gaulle +# This is the important bit: the signing key of the auditor. +PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60 -[merchant-db] +# This specifies which database we use. +[merchantdb-postgres] CONFIG = postgres:///talerdemo +# "wire-" sections include wire details, here for SEPA. [wire-sepa] IBAN = DE67830654080004822650 NAME = GNUNET E.V diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index ba359bd1..2cc553e9 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (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 @@ -13,40 +13,40 @@ 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 merchant/backend/taler-merchant-httpd.c * @brief HTTP serving layer intended to perform crypto-work and * communication with the mint * @author Marcello Stanisci + * @author Christian Grothoff */ - #include "platform.h" #include <microhttpd.h> #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_util.h> #include <taler/taler_mint_service.h> -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" +#include "taler_merchantdb_lib.h" #include "taler-merchant-httpd.h" -#include "taler-mint-httpd_mhd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" #include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_pay.h" + + /** - * Our hostname + * Our wire format details in JSON format (with salt). */ -static char *hostname; +struct json_t *j_wire; /** - * The port we are running on + * Hash of our wire format details as given in #j_wire. */ -static long long unsigned port; +struct GNUNET_HashCode h_wire; /** * Merchant's private key @@ -54,30 +54,35 @@ static long long unsigned port; struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; /** - * File holding the merchant's private key + * Merchant's public key */ -char *keyfile; +struct TALER_MerchantPublicKeyP pubkey; /** - * This value tells the mint by which date this merchant would like - * to receive the funds for a deposited payment + * Our hostname */ -struct GNUNET_TIME_Relative edate_delay; +static char *hostname; /** - * To make 'TMH_PARSE_navigate_json ()' compile + * The port we are running on */ -char *TMH_mint_currency_string; +static long long unsigned port; /** - * Trusted mints + * File holding the merchant's private key + */ +static char *keyfile; + +/** + * This value tells the mint by which date this merchant would like + * to receive the funds for a deposited payment */ -struct MERCHANT_Mint *mints; +struct GNUNET_TIME_Relative edate_delay; /** - * Active auditors + * Which currency is supported by this merchant? */ -struct MERCHANT_Auditor *auditors; +char *TMH_merchant_currency_string; /** * Shutdown task identifier @@ -90,31 +95,6 @@ static struct GNUNET_SCHEDULER_Task *shutdown_task; static struct GNUNET_SCHEDULER_Task *mhd_task; /** - * Context "poller" identifier - */ -struct GNUNET_SCHEDULER_Task *poller_task; - -/** - * Our wireformat - */ -struct MERCHANT_WIREFORMAT_Sepa *wire; - -/** - * Salt used to hash the wire object - */ -long long salt; - -/** - * The number of accepted mints - */ -unsigned int nmints; - -/** - * The number of active auditors - */ -unsigned int nauditors; - -/** * Should we do a dry run where temporary tables are used for storing the data. */ static int dry; @@ -127,13 +107,14 @@ static int result; /** * Connection handle to the our database */ -PGconn *db_conn; +struct TALER_MERCHANTDB_Plugin *db; /** * The MHD Daemon */ static struct MHD_Daemon *mhd; + /** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, @@ -190,15 +171,9 @@ url_handler (void *cls, "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0, &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - /* Further test page */ - { "/hello", MHD_HTTP_METHOD_GET, "text/plain", - "Hello, Customer.\n", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - { "/contract", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_contract, MHD_HTTP_OK }, - { "/contract", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, @@ -206,33 +181,24 @@ url_handler (void *cls, { "/pay", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_pay, MHD_HTTP_OK }, - { "/pay", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - {NULL, NULL, NULL, NULL, 0, 0 } }; - static struct TMH_RequestHandler h404 = { "", NULL, "text/html", "<html><title>404: not found</title></html>", 0, &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND }; - - /* Compiler complains about non returning a value in a non-void - declared function: the FIX is to return what the handler for - a particular URL returns */ - struct TMH_RequestHandler *rh; unsigned int i; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Handling request for URL '%s'\n", + "Handling request for URL `%s'\n", url); - for (i=0;NULL != handlers[i].url;i++) { rh = &handlers[i]; @@ -252,43 +218,6 @@ url_handler (void *cls, con_cls, upload_data, upload_data_size); - -} - -/** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. - * - * @param cls closure, will be 'struct MERCHANT_Mint' so that - * when this function gets called, it will change the flag 'pending' - * to 'false'. Note: 'keys' is automatically saved inside the mint's - * handle, which is contained inside 'struct MERCHANT_Mint', when - * this callback is called. Thus, once 'pending' turns 'false', - * it is safe to call 'TALER_MINT_get_keys()' on the mint's handle, - * in order to get the "good" keys. - * - * @param keys information about the various keys used - * by the mint - */ -static void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) -{ - /* HOT UPDATE: the merchants need the denomination keys! - Because it wants to (firstly) verify the deposit confirmation - sent by the mint, and the signed blob depends (among the - other things) on the coin's deposit fee. That information - is never communicated by the wallet to the merchant. - Again, the merchant needs it because it wants to verify that - the wallet didn't exceede the limit imposed by the merchant - on the total deposit fee for a purchase */ - - if (NULL != keys) - { - ((struct MERCHANT_Mint *) cls)->pending = 0; - } - else - printf ("no keys gotten\n"); - } @@ -302,19 +231,6 @@ keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) static void do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { - unsigned int cnt; - - for (cnt = 0; cnt < nmints; cnt++) - { - if (NULL != mints[cnt].conn) - TALER_MINT_disconnect (mints[cnt].conn); - - } - if (NULL != poller_task) - { - GNUNET_SCHEDULER_cancel (poller_task); - poller_task = NULL; - } if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); @@ -325,77 +241,19 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) MHD_stop_daemon (mhd); mhd = NULL; } - if (NULL != db_conn) + if (NULL != db) { - MERCHANT_DB_disconnect (db_conn); - db_conn = NULL; + TALER_MERCHANTDB_plugin_unload (db); + db = NULL; } + TMH_MINTS_done (); + TMH_AUDITORS_done (); if (NULL != keyfile) GNUNET_free (privkey); } /** - * Task that runs the context's event loop using the GNUnet scheduler. - * - * @param cls mint context - * @param tc scheduler context (unused) - */ -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - long timeout; - int max_fd; - fd_set read_fd_set; - fd_set write_fd_set; - fd_set except_fd_set; - struct GNUNET_NETWORK_FDSet *rs; - struct GNUNET_NETWORK_FDSet *ws; - struct GNUNET_TIME_Relative delay; - struct TALER_MINT_Context *ctx; - - ctx = (struct TALER_MINT_Context *) cls; - poller_task = NULL; - TALER_MINT_perform (ctx); - max_fd = -1; - timeout = -1; - FD_ZERO (&read_fd_set); - FD_ZERO (&write_fd_set); - FD_ZERO (&except_fd_set); - TALER_MINT_get_select_info (ctx, - &read_fd_set, - &write_fd_set, - &except_fd_set, - &max_fd, - &timeout); - if (timeout >= 0) - delay = - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout); - else - delay = GNUNET_TIME_UNIT_FOREVER_REL; - rs = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (rs, - &read_fd_set, - max_fd + 1); - ws = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (ws, - &write_fd_set, - max_fd + 1); - poller_task = - GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, - delay, - rs, - ws, - &context_task, - cls); - GNUNET_NETWORK_fdset_destroy (rs); - GNUNET_NETWORK_fdset_destroy (ws); -} - - -/** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the * @a con_cls that might still need to be cleaned up. Call the @@ -451,9 +309,12 @@ run_daemon (void *cls, /** * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. */ void -TM_trigger_daemon () +TMH_trigger_daemon () { GNUNET_SCHEDULER_cancel (mhd_task); run_daemon (NULL, NULL); @@ -461,6 +322,100 @@ TM_trigger_daemon () /** + * Parse the SEPA information from the configuration. If any of the + * required fields is missing return an error. + * + * @param cfg the configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + unsigned long long salt; + char *iban; + char *name; + char *bic; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "wire-sepa", + "SALT", + &salt)) + { + salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, + UINT64_MAX); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No SALT option given in `wire-sepa`, using %llu\n", + (unsigned long long) salt); + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "IBAN", + &iban)) + return GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "NAME", + &name)) + { + GNUNET_free (iban); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "BIC", + &bic)) + { + GNUNET_free (iban); + GNUNET_free (name); + GNUNET_free (bic); + } + j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o}", + "type", "SEPA", + "IBAN", iban, + "name", name, + "bic", bic, + "r", json_integer (salt)); + GNUNET_free (iban); + GNUNET_free (name); + GNUNET_free (bic); + if (NULL == j_wire) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Verify that #j_wire contains a well-formed wire format, and + * update #h_wire to match it (if successful). + * + * @param allowed which wire format is allowed/expected? + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +validate_and_hash_wireformat (const char *allowed) +{ + const char *allowed_arr[] = { + allowed, + NULL + }; + + if (GNUNET_YES != + TALER_json_validate_wireformat (allowed_arr, + j_wire)) + return GNUNET_SYSERR; + if (GNUNET_SYSERR == + TALER_hash_json (j_wire, + &h_wire)) + return MHD_NO; + return GNUNET_OK; +} + + +/** * Function that queries MHD's select sets and * starts the task waiting for them. * @@ -510,55 +465,49 @@ prepare_daemon () } - /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be - * NULL!) + * NULL!) * @param config configuration */ void -run (void *cls, char *const *args, const char *cfgfile, +run (void *cls, + char *const *args, + const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { - - unsigned int cnt; - mints = NULL; - keyfile = NULL; result = GNUNET_SYSERR; shutdown_task = - GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, - NULL); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "merchant launched\n"); - + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, + NULL); EXITIF (GNUNET_SYSERR == - (nmints = - TALER_MERCHANT_parse_mints (config, - &mints))); + TMH_MINTS_init (config)); EXITIF (GNUNET_SYSERR == - (nauditors = - TALER_MERCHANT_parse_auditors (config, - &auditors))); - EXITIF (NULL == - (wire = - TALER_MERCHANT_parse_wireformat_sepa (config))); + TMH_AUDITORS_init (config)); + /* FIXME: for now, we just support SEPA here: */ + EXITIF (GNUNET_OK != + parse_wireformat_sepa (config)); + EXITIF (GNUNET_OK != + validate_and_hash_wireformat ("SEPA")); EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (config, - "merchant", - "KEYFILE", - &keyfile)); + GNUNET_CONFIGURATION_get_value_filename (config, + "merchant", + "KEYFILE", + &keyfile)); EXITIF (NULL == - (privkey = - GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + (privkey = + GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + GNUNET_CRYPTO_eddsa_key_get_public (privkey, + &pubkey.eddsa_pub); EXITIF (NULL == - (db_conn = MERCHANT_DB_connect (config))); + (db = TALER_MERCHANTDB_plugin_load (config))); EXITIF (GNUNET_OK != - MERCHANT_DB_initialize (db_conn, dry)); + db->initialize (db->cls, dry)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_number (config, "merchant", @@ -573,30 +522,13 @@ run (void *cls, char *const *args, const char *cfgfile, GNUNET_CONFIGURATION_get_value_string (config, "merchant", "CURRENCY", - &TMH_mint_currency_string)); + &TMH_merchant_currency_string)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_time (config, - "merchant", - "EDATE", - &edate_delay)); - - salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, - UINT64_MAX); - - for (cnt = 0; cnt < nmints; cnt++) - { - EXITIF (NULL == (mints[cnt].ctx = TALER_MINT_init ())); - mints[cnt].pending = 1; - mints[cnt].conn = TALER_MINT_connect (mints[cnt].ctx, - mints[cnt].hostname, - &keys_mgmt_cb, - &mints[cnt], - TALER_MINT_OPTION_END); - EXITIF (NULL == mints[cnt].conn); - poller_task = - GNUNET_SCHEDULER_add_now (&context_task, mints[cnt].ctx); - } + "merchant", + "EDATE", + &edate_delay)); mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME, port, @@ -609,15 +541,15 @@ run (void *cls, char *const *args, const char *cfgfile, result = GNUNET_OK; mhd_task = prepare_daemon (); - EXITIF_exit: - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); + EXITIF_exit: + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); GNUNET_free_non_null (keyfile); - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); - + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); } + /** * The main function of the serve tool * @@ -628,21 +560,18 @@ run (void *cls, char *const *args, const char *cfgfile, int main (int argc, char *const *argv) { - - static const struct GNUNET_GETOPT_CommandLineOption options[] = { + static const struct GNUNET_GETOPT_CommandLineOption options[] = { {'t', "temp", NULL, gettext_noop ("Use temporary database tables"), GNUNET_NO, &GNUNET_GETOPT_set_one, &dry}, - GNUNET_GETOPT_OPTION_END - }; - + GNUNET_GETOPT_OPTION_END + }; if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-merchant-http", - "Serve merchant's HTTP interface", + "taler-merchant-httpd", + "Taler merchant's HTTP backend interface", options, &run, NULL)) return 3; return (GNUNET_OK == result) ? 0 : 1; - } diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index b60d91bd..25242617 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -18,16 +18,83 @@ * @brief HTTP serving layer mainly intended to communicate with the frontend * @author Marcello Stanisci */ +#ifndef TALER_MERCHANT_HTTPD_H +#define TALER_MERCHANT_HTTPD_H -#include "merchant_db.h" +#include "platform.h" +#include "taler_merchantdb_lib.h" +#include <microhttpd.h> /** - * Kick MHD to run now, to be called after MHD_resume_connection(). + * Shorthand for exit jumps. */ -void -TM_trigger_daemon (void); +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TMH_RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext; /** @@ -41,30 +108,65 @@ typedef void (*TM_ContextCleanup)(struct TM_HandlerContext *hc); +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext { + /** + * Function to execute the handler-specific cleanup of the + * (typically larger) context. + */ TM_ContextCleanup cc; }; -extern struct MERCHANT_Mint *mints; -extern struct MERCHANT_WIREFORMAT_Sepa *wire; +/** + * Our wire format details in JSON format (with salt). + */ +extern json_t *j_wire; + +/** + * Hash of our wire format details as given in #j_wire. + */ +extern struct GNUNET_HashCode h_wire; -extern PGconn *db_conn; +/** + * Our private key (for the merchant to sign contracts). + */ +extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; -extern long long salt; +/** + * Our public key, corresponds to #privkey. + */ +extern struct TALER_MerchantPublicKeyP pubkey; -extern unsigned int nmints; +/** + * Handle to the database backend. + */ +extern struct TALER_MERCHANTDB_Plugin *db; +/** + * If the frontend does NOT specify an execution date, how long should + * we tell the mint to wait to aggregate transactions before + * executing? This delay is added to the current time when we + * generate the advisory execution time for the mint. + */ extern struct GNUNET_TIME_Relative edate_delay; -extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; - -extern struct GNUNET_SCHEDULER_Task *poller_task; +/** + * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. + */ +void +TMH_trigger_daemon (void); -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc); +#endif diff --git a/src/backend/taler-merchant-httpd_auditors.c b/src/backend/taler-merchant-httpd_auditors.c new file mode 100644 index 00000000..84558ed0 --- /dev/null +++ b/src/backend/taler-merchant-httpd_auditors.c @@ -0,0 +1,239 @@ +/* + This file is part of TALER + (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 backend/taler-merchant-httpd_auditors.c + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_auditors.h" + +/** + * Our representation of an auditor. + */ +struct Auditor +{ + /** + * Auditor's legal name. + */ + char *name; + + /** + * Auditor's URI. + */ + char *uri; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP public_key; + +}; + + +/** + * Array of the auditors this merchant is willing to accept. + */ +static struct Auditor *auditors; + +/** + * The length of the #auditors array. + */ +static unsigned int nauditors; + +/** + * JSON representation of the auditors accepted by this mint. + */ +json_t *j_auditors; + + +/** + * Check if the given @a dk issued by mint @a mh is audited by + * an auditor that is acceptable for this merchant. (And if the + * denomination is not yet expired or something silly like that.) + * + * @param mh mint issuing @a dk + * @param dk a denomination issued by @a mh + * @param mint_trusted #GNUNET_YES if the mint of @a dk is trusted by config + * @return #GNUNET_OK if we accept this denomination + */ +int +TMH_AUDITORS_check_dk (struct TALER_MINT_Handle *mh, + const struct TALER_MINT_DenomPublicKey *dk, + int mint_trusted) +{ + const struct TALER_MINT_Keys *keys; + const struct TALER_MINT_AuditorInformation *ai; + unsigned int i; + unsigned int j; + + if (0 == GNUNET_TIME_absolute_get_remaining (dk->deposit_valid_until).rel_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key offered by client has expired for deposits\n"); + return GNUNET_SYSERR; /* expired */ + } + if (GNUNET_YES == mint_trusted) + return GNUNET_OK; + keys = TALER_MINT_get_keys (mh); + if (NULL == keys) + { + /* this should never happen, keys should have been successfully + obtained before we even got into this function */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + for (i=0;i<keys->num_auditors;i++) + { + ai = &keys->auditors[i]; + for (j=0;j<ai->num_denom_keys;j++) + if (ai->denom_keys[j] == dk) + return GNUNET_OK; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key %s offered by client not audited by accepted auditor\n", + GNUNET_h2s (&dk->h_key)); + return GNUNET_NO; +} + + +/** + * Function called on each configuration section. Finds sections + * about auditors and parses the entries. + * + * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` + * @param section name of the section + */ +static void +parse_auditors (void *cls, + const char *section) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + char *pks; + struct Auditor auditor; + + if (0 != strncasecmp (section, + "auditor-", + strlen ("auditor-"))) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "NAME", + &auditor.name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "NAME"); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URI", + &auditor.uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URI"); + GNUNET_free (auditor.name); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "PUBLIC_KEY", + &pks)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "PUBLIC_KEY"); + GNUNET_free (auditor.name); + GNUNET_free (auditor.uri); + return; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (pks, + strlen (pks), + &auditor.public_key.eddsa_pub)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "PUBLIC_KEY", + "valid public key"); + GNUNET_free (auditor.name); + GNUNET_free (auditor.uri); + GNUNET_free (pks); + return; + } + GNUNET_free (pks); + GNUNET_array_append (auditors, + nauditors, + auditor); +} + + +/** + * Parses auditor information from the configuration. + * + * @param cfg the configuration + * @return the number of auditors found; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + unsigned int cnt; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_auditors, + (void *) cfg); + + /* Generate preferred mint(s) array. */ + j_auditors = json_array (); + for (cnt = 0; cnt < nauditors; cnt++) + json_array_append_new (j_auditors, + json_pack ("{s:s, s:o, s:s}", + "name", auditors[cnt].name, + "auditor_pub", TALER_json_from_data (&auditors[cnt].public_key, + sizeof (struct TALER_AuditorPublicKeyP)), + "uri", auditors[cnt].uri)); + return nauditors; +} + + +/** + * Release auditor information state. + */ +void +TMH_AUDITORS_done () +{ + unsigned int i; + + json_decref (j_auditors); + j_auditors = NULL; + for (i=0;i<nauditors;i++) + { + GNUNET_free (auditors[i].name); + GNUNET_free (auditors[i].uri); + } + GNUNET_free (auditors); + auditors = NULL; + nauditors = 0; +} + +/* end of taler-merchant-httpd_auditors.c */ diff --git a/src/backend/taler-merchant-httpd_auditors.h b/src/backend/taler-merchant-httpd_auditors.h new file mode 100644 index 00000000..1a05a78d --- /dev/null +++ b/src/backend/taler-merchant-httpd_auditors.h @@ -0,0 +1,74 @@ +/* + This file is part of TALER + (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 backend/taler-merchant-httpd_auditors.h + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_AUDITORS_H +#define TALER_MERCHANT_HTTPD_AUDITORS_H + +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_mint_service.h> +#include "taler-merchant-httpd.h" + + +/** + * JSON representation of the auditors accepted by this mint. + */ +extern json_t *j_auditors; + + +/** + * Parses auditor information from the configuration. + * + * @param cfg the configuration + * @return the number of auditors found; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Check if the given @a dk issued by mint @a mh is audited by + * an auditor that is acceptable for this merchant. (And if the + * denomination is not yet expired or something silly like that.) + * + * @param mh mint issuing @a dk + * @param dk a denomination issued by @a mh + * @param mint_trusted #GNUNET_YES if the mint of @a dk is trusted by config + * @return #GNUNET_OK if we accept this denomination + */ +int +TMH_AUDITORS_check_dk (struct TALER_MINT_Handle *mh, + const struct TALER_MINT_DenomPublicKey *dk, + int mint_trusted); + + +/** + * Release auditor information state. + */ +void +TMH_AUDITORS_done (void); + + + + +#endif diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c index c42a8b37..e44d3b84 100644 --- a/src/backend/taler-merchant-httpd_contract.c +++ b/src/backend/taler-merchant-httpd_contract.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (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 @@ -13,39 +13,30 @@ 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 merchant/backend/taler-merchant-httpd.c + * @file backend/taler-merchant-httpd_contract.c * @brief HTTP serving layer mainly intended to communicate with the frontend * @author Marcello Stanisci */ - #include "platform.h" -#include <microhttpd.h> #include <jansson.h> -#include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_signatures.h> -#include <taler/taler_amount_lib.h> -#include <taler/taler_json_lib.h> -#include <taler/taler_mint_service.h> -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" #include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" +#include "taler-merchant-httpd_responses.h" -extern struct MERCHANT_Auditor *auditors; -extern unsigned int nauditors; /** - * Manage a contract request. In practical terms, it adds the fields 'mints', - * 'merchant_pub', and 'H_wire' to the contract 'proposition' gotten from the - * frontend. Finally, it adds (outside of the contract) a signature of the - * (hashed stringification) of this contract and the hashed stringification - * of this contract to the final bundle sent back to the frontend. + * Manage a contract request. In practical terms, it adds the fields + * 'mints', 'merchant_pub', and 'H_wire' to the contract 'proposition' + * gotten from the frontend. Finally, it adds (outside of the + * contract) a signature of the (hashed stringification) of the + * contract (and the hashed stringification of this contract as well + * to aid diagnostics) to the final bundle, which is then send back to + * the frontend. + * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] connection_cls the connection's closure (can be updated) @@ -61,18 +52,8 @@ MH_handler_contract (struct TMH_RequestHandler *rh, size_t *upload_data_size) { json_t *root; - json_t *trusted_mints; - json_t *j_auditors; - json_t *auditor; - json_t *mint; - json_t *j_wire; - const struct TALER_MINT_Keys *keys; int res; - int cnt; - struct GNUNET_HashCode h_wire; - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - struct MERCHANT_Contract contract; - char *contract_str; + struct TALER_ContractPS contract; struct GNUNET_CRYPTO_EddsaSignature contract_sig; res = TMH_PARSE_post_json (connection, @@ -82,87 +63,43 @@ MH_handler_contract (struct TMH_RequestHandler *rh, &root); if (GNUNET_SYSERR == res) return MHD_NO; - /* the POST's body has to be further fetched */ if ((GNUNET_NO == res) || (NULL == root)) + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) return MHD_YES; - /* Generate preferred mint(s) array. */ - - trusted_mints = json_array (); - for (cnt = 0; cnt < nmints; cnt++) - { - if (!mints[cnt].pending) - { - keys = TALER_MINT_get_keys (mints[cnt].conn); - mint = json_pack ("{s:s, s:o}", - "url", mints[cnt].hostname, - "master_pub", - TALER_json_from_data - (&keys->master_pub.eddsa_pub, - sizeof (keys->master_pub.eddsa_pub))); - json_array_append_new (trusted_mints, mint); - } - } - j_auditors = json_array (); - for (cnt = 0; cnt < nauditors; cnt++) - { - auditor = json_pack ("{s:s}", - "name", auditors[cnt].name); - json_array_append_new (j_auditors, auditor); - } - - /** - * Return badly if no mints are trusted (or no call to /keys has still - * returned the expected data). WARNING: it - * may be possible that a mint trusted by the wallet is good, but - * still pending; that case must be handled with some "polling-style" - * routine, simply ignored, or ended with an invitation to the wallet - * to just retry later - */ - if (!json_array_size (trusted_mints)) - return MHD_NO; - - /** - * Hard error, no action can be taken by a wallet - */ - if (!json_array_size (j_auditors)) - return MHD_NO; - - json_object_set_new (root, "mints", trusted_mints); - json_object_set_new (root, "auditors", j_auditors); - - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - salt))) - return MHD_NO; - - /* hash wire objcet */ - if (GNUNET_SYSERR == - TALER_hash_json (j_wire, &h_wire)) - return MHD_NO; - + /* add fields to the "root" that the backend should provide */ + json_object_set (root, + "mints", + trusted_mints); + json_object_set (root, + "auditors", + j_auditors); json_object_set_new (root, "H_wire", - TALER_json_from_data (&h_wire, sizeof (h_wire))); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pubkey); + TALER_json_from_data (&h_wire, + sizeof (h_wire))); json_object_set_new (root, "merchant_pub", - TALER_json_from_data (&pubkey, sizeof (pubkey))); - - /* Sign */ - contract_str = json_dumps (root, JSON_COMPACT | JSON_SORT_KEYS); - GNUNET_CRYPTO_hash (contract_str, strlen (contract_str), &contract.h_contract); + TALER_json_from_data (&pubkey, + sizeof (pubkey))); + /* create contract signature */ + GNUNET_assert (GNUNET_OK == + TALER_hash_json (root, + &contract.h_contract)); contract.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); contract.purpose.size = htonl (sizeof (contract)); - GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &contract_sig); - + GNUNET_CRYPTO_eddsa_sign (privkey, + &contract.purpose, + &contract_sig); + /* return final response */ return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_OK, "{s:o, s:o, s:o}", "contract", root, - "sig", TALER_json_from_data - (&contract_sig, sizeof (contract_sig)), - "H_contract", TALER_json_from_data - (&contract.h_contract, - sizeof (contract.h_contract))); - + "sig", TALER_json_from_data (&contract_sig, + sizeof (contract_sig)), + "H_contract", TALER_json_from_data (&contract.h_contract, + sizeof (contract.h_contract))); } + +/* end of taler-merchant-httpd_contract.c */ diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h index 5e72c514..44cef909 100644 --- a/src/backend/taler-merchant-httpd_contract.h +++ b/src/backend/taler-merchant-httpd_contract.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (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 @@ -13,17 +13,15 @@ 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 merchant/backend/taler-merchant-httpd_contract.h + * @file backend/taler-merchant-httpd_contract.h * @brief headers for /contract handler * @author Marcello Stanisci */ - #ifndef TALER_MINT_HTTPD_CONTRACT_H #define TALER_MINT_HTTPD_CONTRACT_H #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" /** * Manage a contract request @@ -33,7 +31,6 @@ * @param[in,out] connection_cls the connection's closure (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * * @return MHD result code */ int diff --git a/src/backend/taler-mint-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c index 419c4fb0..6fd18d9c 100644 --- a/src/backend/taler-mint-httpd_mhd.c +++ b/src/backend/taler-merchant-httpd_mhd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014 GNUnet e.V. + Copyright (C) 2014, 2015 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -13,9 +13,8 @@ You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** - * @file taler-mint-httpd_mhd.c + * @file taler-merchant-httpd_mhd.c * @brief helpers for MHD interaction; these are TALER_MINT_handler_ functions * that generate simple MHD replies that do not require any real operations * to be performed (error handling, static pages, etc.) @@ -24,14 +23,9 @@ * @author Christian Grothoff */ #include "platform.h" -#include <gnunet/gnunet_util_lib.h> #include <jansson.h> -#include <microhttpd.h> -#include <pthread.h> -#include "taler-mint-httpd_responses.h" -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_mhd.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_responses.h" /** diff --git a/src/backend/taler-mint-httpd_mhd.h b/src/backend/taler-merchant-httpd_mhd.h index a9f575df..3fe137db 100644 --- a/src/backend/taler-mint-httpd_mhd.h +++ b/src/backend/taler-merchant-httpd_mhd.h @@ -15,7 +15,7 @@ */ /** - * @file taler-mint-httpd_mhd.h + * @file taler-merchant-httpd_mhd.h * @brief helpers for MHD interaction, used to generate simple responses * @author Florian Dold * @author Benedikt Mueller @@ -25,7 +25,7 @@ #define TALER_MINT_HTTPD_MHD_H #include <gnunet/gnunet_util_lib.h> #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" /** diff --git a/src/backend/taler-merchant-httpd_mints.c b/src/backend/taler-merchant-httpd_mints.c new file mode 100644 index 00000000..e93419cd --- /dev/null +++ b/src/backend/taler-merchant-httpd_mints.c @@ -0,0 +1,530 @@ +/* + This file is part of TALER + (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 backend/taler-merchant-httpd_mints.c + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_mints.h" + + +/** + * How often do we retry fetching /keys? + */ +#define KEYS_RETRY_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60) + + +/** + * Mint + */ +struct Mint; + + +/** + * Information we keep for a pending #MMH_MINTS_find_mint() operation. + */ +struct TMH_MINTS_FindOperation +{ + + /** + * Kept in a DLL. + */ + struct TMH_MINTS_FindOperation *next; + + /** + * Kept in a DLL. + */ + struct TMH_MINTS_FindOperation *prev; + + /** + * Function to call with the result. + */ + TMH_MINTS_FindContinuation fc; + + /** + * Closure for @e fc. + */ + void *fc_cls; + + /** + * Mint we wait for the /keys for. + */ + struct Mint *my_mint; + + /** + * Task scheduled to asynchrnously return the result. + */ + struct GNUNET_SCHEDULER_Task *at; + +}; + + +/** + * Mint + */ +struct Mint +{ + + /** + * Kept in a DLL. + */ + struct Mint *next; + + /** + * Kept in a DLL. + */ + struct Mint *prev; + + /** + * Head of FOs pending for this mint. + */ + struct TMH_MINTS_FindOperation *fo_head; + + /** + * Tail of FOs pending for this mint. + */ + struct TMH_MINTS_FindOperation *fo_tail; + + /** + * (base) URI of the mint. + */ + char *uri; + + /** + * A connection to this mint + */ + struct TALER_MINT_Handle *conn; + + /** + * Master public key, guaranteed to be set ONLY for + * trusted mints. + */ + struct TALER_MasterPublicKeyP master_pub; + + /** + * At what time should we try to fetch /keys again? + */ + struct GNUNET_TIME_Absolute retry_time; + + /** + * Flag which indicates whether some HTTP transfer between + * this merchant and the mint is still ongoing + */ + int pending; + + /** + * #GNUNET_YES if this mint is from our configuration and + * explicitly trusted, #GNUNET_NO if we need to check each + * key to be sure it is trusted. + */ + int trusted; + +}; + + +/** + * Context for all mint operations (useful to the event loop) + */ +static struct TALER_MINT_Context *ctx; + +/** + * Task we use to drive the interaction with this mint. + */ +static struct GNUNET_SCHEDULER_Task *poller_task; + +/** + * Head of mints we know about. + */ +static struct Mint *mint_head; + +/** + * Tail of mints we know about. + */ +static struct Mint *mint_tail; + +/** + * List of our trusted mints for inclusion in contracts. + */ +json_t *trusted_mints; + + +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure, will be `struct Mint` so that + * when this function gets called, it will change the flag 'pending' + * to 'false'. Note: 'keys' is automatically saved inside the mint's + * handle, which is contained inside 'struct Mint', when + * this callback is called. Thus, once 'pending' turns 'false', + * it is safe to call 'TALER_MINT_get_keys()' on the mint's handle, + * in order to get the "good" keys. + * @param keys information about the various keys used + * by the mint + */ +static void +keys_mgmt_cb (void *cls, + const struct TALER_MINT_Keys *keys) +{ + struct Mint *mint = cls; + struct TMH_MINTS_FindOperation *fo; + + if (NULL != keys) + { + mint->pending = GNUNET_NO; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to fetch /keys from `%s'\n", + mint->uri); + TALER_MINT_disconnect (mint->conn); + mint->conn = NULL; + mint->pending = GNUNET_SYSERR; /* failed hard */ + mint->retry_time = GNUNET_TIME_relative_to_absolute (KEYS_RETRY_FREQ); + } + while (NULL != (fo = mint->fo_head)) + { + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + fo->fc (fo->fc_cls, + (NULL != keys) ? mint->conn : NULL, + mint->trusted); + GNUNET_free (fo); + } +} + + +/** + * Task that runs the mint's event loop using the GNUnet scheduler. + * + * @param cls a `struct Mint *` + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + long timeout; + int max_fd; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + struct GNUNET_NETWORK_FDSet *rs; + struct GNUNET_NETWORK_FDSet *ws; + struct GNUNET_TIME_Relative delay; + + poller_task = NULL; + TALER_MINT_perform (ctx); + max_fd = -1; + timeout = -1; + FD_ZERO (&read_fd_set); + FD_ZERO (&write_fd_set); + FD_ZERO (&except_fd_set); + TALER_MINT_get_select_info (ctx, + &read_fd_set, + &write_fd_set, + &except_fd_set, + &max_fd, + &timeout); + if (timeout >= 0) + delay = + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + delay = GNUNET_TIME_UNIT_FOREVER_REL; + rs = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (rs, + &read_fd_set, + max_fd + 1); + ws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (ws, + &write_fd_set, + max_fd + 1); + poller_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + delay, + rs, + ws, + &context_task, + NULL); + GNUNET_NETWORK_fdset_destroy (rs); + GNUNET_NETWORK_fdset_destroy (ws); +} + + +/** + * Task to return find operation result asynchronously to caller. + * + * @param cls a `struct TMH_MINTS_FindOperation` + * @param tc unused + */ +static void +return_result (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TMH_MINTS_FindOperation *fo = cls; + struct Mint *mint = fo->my_mint; + + fo->at = NULL; + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + fo->fc (fo->fc_cls, + (GNUNET_SYSERR == mint->pending) ? NULL : mint->conn, + mint->trusted); + GNUNET_free (fo); + GNUNET_SCHEDULER_cancel (poller_task); + GNUNET_SCHEDULER_add_now (&context_task, + NULL); +} + + +/** + * Find a mint that matches @a chosen_mint. If we cannot connect + * to the mint, or if it is not acceptable, @a fc is called with + * NULL for the mint. + * + * @param chosen_mint URI of the mint we would like to talk to + * @param fc function to call with the handles for the mint + * @param fc_cls closure for @a fc + * @return NULL on error + */ +struct TMH_MINTS_FindOperation * +TMH_MINTS_find_mint (const char *chosen_mint, + TMH_MINTS_FindContinuation fc, + void *fc_cls) +{ + struct Mint *mint; + struct TMH_MINTS_FindOperation *fo; + + if (NULL == ctx) + { + GNUNET_break (0); + return NULL; + } + /* Check if the mint is known */ + for (mint = mint_head; NULL != mint; mint = mint->next) + /* test it by checking public key --- FIXME: hostname or public key!? + Should probably be URI, not hostname anyway! */ + if (0 == strcmp (mint->uri, + chosen_mint)) + break; + if (NULL == mint) + { + /* This is a new mint */ + mint = GNUNET_new (struct Mint); + mint->uri = GNUNET_strdup (chosen_mint); + mint->pending = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (mint_head, + mint_tail, + mint); + } + + /* check if we should resume this mint */ + if ( (GNUNET_SYSERR == mint->pending) && + (0 == GNUNET_TIME_absolute_get_remaining (mint->retry_time).rel_value_us) ) + mint->pending = GNUNET_YES; + + + fo = GNUNET_new (struct TMH_MINTS_FindOperation); + fo->fc = fc; + fo->fc_cls = fc_cls; + fo->my_mint = mint; + GNUNET_CONTAINER_DLL_insert (mint->fo_head, + mint->fo_tail, + fo); + + if (GNUNET_NO == mint->pending) + { + /* We are not currently waiting for a reply, immediately + return result */ + fo->at = GNUNET_SCHEDULER_add_now (&return_result, + fo); + return fo; + } + + /* If new or resumed, retry fetching /keys */ + if ( (NULL == mint->conn) && + (GNUNET_YES == mint->pending) ) + { + mint->conn = TALER_MINT_connect (ctx, + mint->uri, + &keys_mgmt_cb, + mint, + TALER_MINT_OPTION_END); + GNUNET_break (NULL != mint->conn); + } + return fo; +} + + +/** + * Abort pending find operation. + * + * @param fo handle to operation to abort + */ +void +TMH_MINTS_find_mint_cancel (struct TMH_MINTS_FindOperation *fo) +{ + struct Mint *mint = fo->my_mint; + + if (NULL != fo->at) + { + GNUNET_SCHEDULER_cancel (fo->at); + fo->at = NULL; + } + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + GNUNET_free (fo); +} + + +/** + * Function called on each configuration section. Finds sections + * about mints and parses the entries. + * + * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` + * @param section name of the section + */ +static void +parse_mints (void *cls, + const char *section) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + char *uri; + char *mks; + struct Mint *mint; + + if (0 != strncasecmp (section, + "mint-", + strlen ("mint-"))) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URI", + &uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URI"); + return; + } + mint = GNUNET_new (struct Mint); + mint->uri = uri; + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "MASTER_KEY", + &mks)) + { + if (GNUNET_OK == + GNUNET_CRYPTO_eddsa_public_key_from_string (mks, + strlen (mks), + &mint->master_pub.eddsa_pub)) + { + mint->trusted = GNUNET_YES; + } + else + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "MASTER_KEY", + _("ill-formed key")); + } + GNUNET_free (mks); + } + GNUNET_CONTAINER_DLL_insert (mint_head, + mint_tail, + mint); + mint->pending = GNUNET_YES; + mint->conn = TALER_MINT_connect (ctx, + mint->uri, + &keys_mgmt_cb, + mint, + TALER_MINT_OPTION_END); + GNUNET_break (NULL != mint->conn); +} + + +/** + * Parses "trusted" mints listed in the configuration. + * + * @param cfg the configuration + * @return #GNUNET_OK on success; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_MINTS_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct Mint *mint; + json_t *j_mint; + + ctx = TALER_MINT_init (); + if (NULL == ctx) + return GNUNET_SYSERR; + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_mints, + (void *) cfg); + /* build JSON with list of trusted mints */ + trusted_mints = json_array (); + for (mint = mint_head; NULL != mint; mint = mint->next) + { + if (GNUNET_YES != mint->trusted) + continue; + j_mint = json_pack ("{s:s, s:o}", + "url", mint->uri, + "master_pub", TALER_json_from_data (&mint->master_pub, + sizeof (struct TALER_MasterPublicKeyP))); + json_array_append_new (trusted_mints, + j_mint); + } + poller_task = GNUNET_SCHEDULER_add_now (&context_task, + NULL); + return GNUNET_OK; +} + + +/** + * Function called to shutdown the mints subsystem. + */ +void +TMH_MINTS_done () +{ + struct Mint *mint; + + while (NULL != (mint = mint_head)) + { + GNUNET_CONTAINER_DLL_remove (mint_head, + mint_tail, + mint); + if (NULL != mint->conn) + TALER_MINT_disconnect (mint->conn); + GNUNET_free (mint->uri); + GNUNET_free (mint); + } + if (NULL != poller_task) + { + GNUNET_SCHEDULER_cancel (poller_task); + poller_task = NULL; + } + TALER_MINT_fini (ctx); +} diff --git a/src/backend/taler-merchant-httpd_mints.h b/src/backend/taler-merchant-httpd_mints.h new file mode 100644 index 00000000..a4684f59 --- /dev/null +++ b/src/backend/taler-merchant-httpd_mints.h @@ -0,0 +1,105 @@ +/* + This file is part of TALER + (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 backend/taler-merchant-httpd_mints.h + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_MINTS_H +#define TALER_MERCHANT_HTTPD_MINTS_H + +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <curl/curl.h> +#include <taler/taler_util.h> +#include <taler/taler_mint_service.h> +#include "taler-merchant-httpd.h" + + +/** + * List of our trusted mints in JSON format for inclusion in contracts. + */ +extern json_t *trusted_mints; + + +/** + * Parses "trusted" mints listed in the configuration. + * + * @param cfg the configuration + * @return #GNUNET_OK on success; #GNUNET_SYSERR upon error in + * parsing or initialization. + */ +int +TMH_MINTS_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Function called to shutdown the mints subsystem. + */ +void +TMH_MINTS_done (void); + + +/** + * Function called with the result of a #TMH_MINTS_find_mint() + * operation. + * + * @param cls closure + * @param mh handle to the mint context + * @param mint_trusted #GNUNET_YES if this mint is trusted by config + */ +typedef void +(*TMH_MINTS_FindContinuation)(void *cls, + struct TALER_MINT_Handle *mh, + int mint_trusted); + + +/** + * Information we keep for a pending #MMH_MINTS_find_mint() operation. + */ +struct TMH_MINTS_FindOperation; + + +/** + * Find a mint that matches @a chosen_mint. If we cannot connect + * to the mint, or if it is not acceptable, @a fc is called with + * NULL for the mint. + * + * @param chosen_mint URI of the mint we would like to talk to + * @param fc function to call with the handles for the mint + * @param fc_cls closure for @a fc + * + * FIXME: should probably return a value to *cancel* the + * operation in case MHD connection goes down and needs to + * free fc_cls. + */ +struct TMH_MINTS_FindOperation * +TMH_MINTS_find_mint (const char *chosen_mint, + TMH_MINTS_FindContinuation fc, + void *fc_cls); + + +/** + * Abort pending find operation. + * + * @param fo handle to operation to abort + */ +void +TMH_MINTS_find_mint_cancel (struct TMH_MINTS_FindOperation *fo); + + +#endif diff --git a/src/backend/taler-mint-httpd_parsing.c b/src/backend/taler-merchant-httpd_parsing.c index 9efd6c23..14a87ff4 100644 --- a/src/backend/taler-mint-httpd_parsing.c +++ b/src/backend/taler-merchant-httpd_parsing.c @@ -21,19 +21,18 @@ * @author Benedikt Mueller * @author Christian Grothoff */ - #include "platform.h" #include <gnunet/gnunet_util_lib.h> -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" /* Although the following declaration isn't in any case useful to a merchant's activity, it's needed here to make the function 'TMH_PARSE_nagivate_json ()' compile fine; so its value will be kept on some merchant's accepted currency. For multi currencies merchants, that of course would require a patch */ +extern char *TMH_merchant_currency_string; -extern char *TMH_mint_currency_string; /** * Initial size for POST request buffer. */ @@ -145,6 +144,7 @@ buffer_append (struct Buffer *buf, return GNUNET_OK; } + /** * Function called whenever we are done with a request * to clean up our state. @@ -164,6 +164,7 @@ TMH_PARSE_post_cleanup_callback (void *con_cls) } } + /** * Release all memory allocated for the variable-size fields in * the parser specification. @@ -249,6 +250,7 @@ release_data (struct TMH_PARSE_FieldSpecification *spec, } } + /** * Process a POST request containing a JSON object. This function * realizes an MHD POST processor that will (incrementally) process @@ -350,6 +352,7 @@ TMH_PARSE_post_json (struct MHD_Connection *connection, return GNUNET_YES; } + /** * Generate line in parser specification for string. The returned * string is already nul-terminated internally by JSON, so no length @@ -634,7 +637,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, case TMH_PARSE_JNC_RET_STRING: { - void **where = va_arg (argp, void **); + void **where = va_arg (argp, void **); *where = (void*) json_string_value (root); ret = GNUNET_OK; } @@ -876,7 +879,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, break; } if (0 != strcmp (where->currency, - TMH_mint_currency_string)) + TMH_merchant_currency_string)) { GNUNET_break_op (0); ret = (MHD_YES != diff --git a/src/backend/taler-mint-httpd_parsing.h b/src/backend/taler-merchant-httpd_parsing.h index dae65092..dae65092 100644 --- a/src/backend/taler-mint-httpd_parsing.h +++ b/src/backend/taler-merchant-httpd_parsing.h diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index 1951d2d0..319a7001 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -14,81 +14,86 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ /** - * @file merchant/backend/taler-merchant-httpd.c - * @brief HTTP serving layer mainly intended to communicate with the frontend + * @file backend/taler-merchant-httpd_pay.c + * @brief handling of /pay requests * @author Marcello Stanisci + * @author Christian Grothoff */ - #include "platform.h" -#include <microhttpd.h> #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_signatures.h> -#include <taler/taler_amount_lib.h> #include <taler/taler_json_lib.h> #include <taler/taler_mint_service.h> #include "taler-merchant-httpd.h" -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "taler-mint-httpd_mhd.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" +#include "taler_merchantdb_lib.h" + + +/** + * Information we keep for an individual call to the /pay handler. + */ +struct PayContext; /** - * Fetch the deposit fee related to the given coin aggregate. - * @param connection the connection to send an error response to - * @param coin_aggregate a coin "aggregate" is the JSON set of - * values contained in a single cell of the 'coins' array sent - * in a payment - * @param deposit_fee where to store the resulting deposit fee - * @param mint_index the index which points the chosen mint within - * the global 'mints' array - * @return GNUNET_OK if successful, GNUNET_NO if the data supplied - * is invalid (including the case when the key is not found), - * GNUNET_SYSERR upon internal errors + * Information kept during a /pay request for each coin. */ -static int -deposit_fee_from_coin_aggregate (struct MHD_Connection *connection, - json_t *coin_aggregate, - struct TALER_Amount *deposit_fee, - unsigned int mint_index) +struct MERCHANT_DepositConfirmation { - int res; - const struct TALER_MINT_Keys *keys; - const struct TALER_MINT_DenomPublicKey *denom_details; + + /** + * Reference to the main PayContext + */ + struct PayContext *pc; + + /** + * Handle to the deposit operation we are performing for + * this coin, NULL after the operation is done. + */ + struct TALER_MINT_DepositHandle *dh; + + /** + * Denomination of this coin. + */ struct TALER_DenominationPublicKey denom; - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom), - TMH_PARSE_MEMBER_END - }; - - res = TMH_PARSE_json_data (connection, - coin_aggregate, - spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - if (1 == mints[mint_index].pending) - return GNUNET_SYSERR; - keys = TALER_MINT_get_keys (mints[mint_index].conn); - denom_details = TALER_MINT_get_denomination_key (keys, &denom); - if (NULL == denom_details) - { - TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:o}", - "hint", "unknown denom to mint", - "denom_pub", TALER_json_from_rsa_public_key (denom.rsa_public_key)); - return GNUNET_NO; - } - *deposit_fee = denom_details->fee_deposit; - return GNUNET_OK; -} + /** + * Amount "f" that this coin contributes to the overall payment. + * This amount includes the deposit fee. + */ + struct TALER_Amount percoin_amount; + + /** + * Amount this coin contributes to the total purchase price. + */ + struct TALER_Amount amount_without_fee; + + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature using the @e denom key over the @e coin_pub. + */ + struct TALER_DenominationSignature ub_sig; + + /** + * Signature of the coin's private key over the contract. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Offset of this coin into the `dc` array of all coins in the + * @e pc. + */ + unsigned int index; + +}; /** @@ -103,7 +108,7 @@ struct PayContext struct TM_HandlerContext hc; /** - * Pointer to the global (malloc'd) array of all coins outcomes + * Array with @e coins_cnt coins we are despositing. */ struct MERCHANT_DepositConfirmation *dc; @@ -113,146 +118,452 @@ struct PayContext struct MHD_Connection *connection; /** + * Handle to the mint that we are doing the payment with. + * (initially NULL while @e fo is trying to find a mint). + */ + struct TALER_MINT_Handle *mh; + + /** + * Handle for operation to lookup /keys (and auditors) from + * the mint used for this transaction; NULL if no operation is + * pending. + */ + struct TMH_MINTS_FindOperation *fo; + + /** * Placeholder for #TMH_PARSE_post_json() to keep its internal state. */ void *json_parse_context; /** - * Response to return, NULL if we don't have one yet. + * Mint URI given in @e root. */ - struct MHD_Response *response; + char *chosen_mint; /** - * Transaction id + * Transaction ID given in @e root. */ uint64_t transaction_id; /** - * How many coins this paymen is made of. + * Maximum fee the merchant is willing to pay, from @e root. + * Note that IF the total fee of the mint is higher, that is + * acceptable to the merchant if the customer is willing to + * pay the difference (i.e. amount - max_fee <= actual-amount - actual-fee). + */ + struct TALER_Amount max_fee; + + /** + * Amount from @e root. This is the amount the merchant expects + * to make, minus @e max_fee. + */ + struct TALER_Amount amount; + + /** + * Timestamp from @e root. + */ + struct GNUNET_TIME_Absolute timestamp; + + /** + * Refund deadline from @e root. + */ + struct GNUNET_TIME_Absolute refund_deadline; + + /** + * "H_contract" from @e root. + */ + struct GNUNET_HashCode h_contract; + + /** + * Execution date. How soon would the merchant like the + * transaction to be executed? (Can be given by the frontend + * or be determined by our configuration via #edate_delay.) + */ + struct GNUNET_TIME_Absolute edate; + + /** + * Response to return, NULL if we don't have one yet. + */ + struct MHD_Response *response; + + /** + * Number of coins this payment is made of. Length + * of the @e dc array. */ unsigned int coins_cnt; /** + * Number of transactions still pending. Initially set to + * @e coins_cnt, decremented on each transaction that + * successfully finished. + */ + unsigned int pending; + + /** * HTTP status code to use for the reply, i.e 200 for "OK". * Special value UINT_MAX is used to indicate hard errors - * (no reply, return MHD_NO). + * (no reply, return #MHD_NO). */ unsigned int response_code; }; + /** - * Information needed by a single /deposit callback to refer to its - * own coin inside the confirmations array, namely `struct MERCHANT_DepositConfirmation *dc` - * above. Note: this information can NOT be shared between all the callbacks. + * Resume the given pay context and send the given response. + * Stores the response in the @a pc and signals MHD to resume + * the connection. Also ensures MHD runs immediately. + * + * @param pc payment context + * @param response_code response code to use + * @param response response data to send back */ -struct DepositCallbackContext +static void +resume_pay_with_response (struct PayContext *pc, + unsigned int response_code, + struct MHD_Response *response) { + pc->response_code = response_code; + pc->response = response; + MHD_resume_connection (pc->connection); + TMH_trigger_daemon (); /* we resumed, kick MHD */ +} - /** - * Offset of this coin into the array of all coins outcomes - */ - unsigned int index; - /** - * Reference to the main PayContext - */ - struct PayContext *pc; +/** + * Abort all pending /deposit operations. + * + * @param pc pay context to abort + */ +static void +abort_deposit (struct PayContext *pc) +{ + unsigned int i; + + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dci = &pc->dc[i]; + + if (NULL != dci->dh) + { + TALER_MINT_deposit_cancel (dci->dh); + dci->dh = NULL; + } + } +} -}; /** * Callback to handle a deposit permission's response. * - * @param cls see `struct MERCHANT_DepositConfirmationCls` (i.e. a poinetr to the global - * array of confirmations and an index for this call in that array). That way, the last - * executed callback can detect that no other confirmations are on the way, and can pack - * a response for the wallet - * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; - * 0 if the mint's reply is bogus (fails to follow the protocol) - * @param proof the received JSON reply, should be kept as proof (and, in case of errors, - * be forwarded to the customer) + * @param cls a `struct MERCHANT_DepositConfirmation` (i.e. a pointer + * into the global array of confirmations and an index for this call + * in that array). That way, the last executed callback can detect + * that no other confirmations are on the way, and can pack a response + * for the wallet + * @param http_status HTTP response code, #MHD_HTTP_OK + * (200) for successful deposit; 0 if the mint's reply is bogus (fails + * to follow the protocol) + * @param proof the received JSON reply, + * should be kept as proof (and, in case of errors, be forwarded to + * the customer) */ static void deposit_cb (void *cls, unsigned int http_status, json_t *proof) { - struct DepositCallbackContext *dcc = cls; - int i; - - /*FIXME the index is the same for every individual cb */ - if (GNUNET_SYSERR == - MERCHANT_DB_update_deposit_permission (db_conn, - dcc->pc->transaction_id, - 0)) - /* TODO */ - printf ("db error\n"); - dcc->pc->dc[dcc->index].ackd = 1; - dcc->pc->dc[dcc->index].exit_status = http_status; - dcc->pc->dc[dcc->index].proof = proof; - - /* loop through the confirmation array and return accordingly */ - for (i = 0; i < dcc->pc->coins_cnt; i++) + struct MERCHANT_DepositConfirmation *dc = cls; + struct PayContext *pc = dc->pc; + + dc->dh = NULL; + pc->pending--; + if (MHD_HTTP_OK != http_status) { - /* just return if there is at least one coin to be still - confirmed */ - if (! dcc->pc->dc[i].ackd) - { - printf ("still vacant coins\n"); - return; - } + /* Transaction failed; stop all other ongoing deposits */ + abort_deposit (pc); + /* Forward error including 'proof' for the body */ + resume_pay_with_response (pc, + http_status, + TMH_RESPONSE_make_json (proof)); + return; } - - printf ("All /deposit(s) ack'd\n"); - - dcc->pc->response = MHD_create_response_from_buffer (strlen ("All coins ack'd by the mint\n"), - "All coins ack'd by the mint\n", - MHD_RESPMEM_MUST_COPY); - dcc->pc->response_code = MHD_HTTP_OK; - /* Clean up what we can already */ - MHD_resume_connection (dcc->pc->connection); - TM_trigger_daemon (); /* we resumed, kick MHD */ + /* store result to DB */ + if (GNUNET_OK != + db->store_payment (db->cls, + &pc->h_contract, + &h_wire, + pc->transaction_id, + pc->timestamp, + pc->refund_deadline, + &dc->amount_without_fee, + &dc->coin_pub, + proof)) + { + /* internal error */ + abort_deposit (pc); + /* Forward error including 'proof' for the body */ + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("Merchant database error")); + return; + } + if (0 != pc->pending) + return; /* still more to do */ + resume_pay_with_response (pc, + MHD_HTTP_OK, + MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT)); } +/** + * Custom cleanup routine for a `struct PayContext`. + * + * @param hc the `struct PayContext` to clean up. + */ static void pay_context_cleanup (struct TM_HandlerContext *hc) { - int i; struct PayContext *pc = (struct PayContext *) hc; - - for (i = 0; i < pc->coins_cnt; i++) - GNUNET_free_non_null (pc->dc[i].dcc); + unsigned int i; TMH_PARSE_post_cleanup_callback (pc->json_parse_context); + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; - #if 0 + if (NULL != dc->dh) + { + TALER_MINT_deposit_cancel (dc->dh); + dc->dh = NULL; + } + if (NULL != dc->denom.rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (dc->denom.rsa_public_key); + dc->denom.rsa_public_key = NULL; + } + if (NULL != dc->ub_sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature); + dc->ub_sig.rsa_signature = NULL; + } + } + GNUNET_free_non_null (pc->dc); + if (NULL != pc->fo) + { + TMH_MINTS_find_mint_cancel (pc->fo); + pc->fo = NULL; + } if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ - MHD_destroy_response (pc->response); pc->response = NULL; } - #endif - - GNUNET_free_non_null (pc->dc); GNUNET_free (pc); } /** + * Function called with the result of our mint lookup. + * + * @param cls the `struct PayContext` + * @param mh NULL if mint was not found to be acceptable + * @param mint_trusted #GNUNET_YES if this mint is trusted by config + */ +static void +process_pay_with_mint (void *cls, + struct TALER_MINT_Handle *mh, + int mint_trusted) +{ + struct PayContext *pc = cls; + struct TALER_Amount acc_fee; + struct TALER_Amount acc_amount; + const struct TALER_MINT_Keys *keys; + unsigned int i; + + pc->fo = NULL; + if (NULL == mh) + { + /* The mint on offer is not in the set of our (trusted) + mints. Reject the payment. */ + GNUNET_break_op (0); + resume_pay_with_response (pc, + MHD_HTTP_PRECONDITION_FAILED, + TMH_RESPONSE_make_external_error ("mint not supported")); + return; + } + pc->mh = mh; + + keys = TALER_MINT_get_keys (mh); + if (NULL == keys) + { + GNUNET_break (0); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("no keys")); + return; + } + + /* Total up the fees and the value of the deposited coins! */ + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + const struct TALER_MINT_DenomPublicKey *denom_details; + + denom_details = TALER_MINT_get_denomination_key (keys, + &dc->denom); + if (NULL == denom_details) + { + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:o}", + "hint", "unknown denom to mint", + "denom_pub", TALER_json_from_rsa_public_key (dc->denom.rsa_public_key))); + return; + } + if (GNUNET_OK != + TMH_AUDITORS_check_dk (mh, + denom_details, + mint_trusted)) + { + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:o}", + "hint", "no acceptable auditor for denomination", + "denom_pub", TALER_json_from_rsa_public_key (dc->denom.rsa_public_key))); + return; + } + if (0 == i) + { + acc_fee = denom_details->fee_deposit; + acc_amount = dc->percoin_amount; + } + else + { + if ( (GNUNET_OK != + TALER_amount_add (&acc_fee, + &denom_details->fee_deposit, + &acc_fee)) || + (GNUNET_OK != + TALER_amount_add (&acc_amount, + &dc->percoin_amount, + &acc_amount)) ) + { + GNUNET_break_op (0); + /* Overflow in these amounts? Very strange. */ + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_internal_error ("Overflow adding up amounts")); + return; + } + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&dc->amount_without_fee, + &dc->percoin_amount, + &denom_details->fee_deposit)) + { + GNUNET_break_op (0); + /* fee higher than residual coin value, makes no sense. */ + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_internal_error ("Fee higher than coin value")); + return; + } + } + + /* Now check that the customer paid enough for the full contract */ + if (-1 == TALER_amount_cmp (&pc->max_fee, + &acc_fee)) + { + /* acc_fee > max_fee, customer needs to cover difference */ + struct TALER_Amount excess_fee; + struct TALER_Amount total_needed; + + /* compute fee amount to be covered by customer */ + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&excess_fee, + &acc_fee, + &pc->max_fee)); + /* add that to the total */ + if (GNUNET_OK != + TALER_amount_add (&total_needed, + &excess_fee, + &pc->amount)) + { + GNUNET_break (0); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("overflow")); + return; + } + /* check if total payment sufficies */ + if (-1 == TALER_amount_cmp (&acc_amount, + &total_needed)) + { + resume_pay_with_response (pc, + MHD_HTTP_NOT_ACCEPTABLE, + TMH_RESPONSE_make_external_error ("insufficient funds (including excessive mint fees to be covered by customer)")); + return; + } + } + else + { + /* fees are acceptable, we cover them all; let's check the amount */ + if (-1 == TALER_amount_cmp (&acc_amount, + &pc->amount)) + { + resume_pay_with_response (pc, + MHD_HTTP_NOT_ACCEPTABLE, + TMH_RESPONSE_make_external_error ("insufficient funds")); + return; + } + } + + /* Initiate /deposit operation for all coins */ + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + + dc->dh = TALER_MINT_deposit (mh, + &dc->percoin_amount, + pc->edate, + j_wire, + &pc->h_contract, + &dc->coin_pub, + &dc->ub_sig, + &dc->denom, + pc->timestamp, + pc->transaction_id, + &pubkey, + pc->refund_deadline, + &dc->coin_sig, + &deposit_cb, + dc); + if (NULL == dc->dh) + { + resume_pay_with_response (pc, + MHD_HTTP_SERVICE_UNAVAILABLE, + TMH_RESPONSE_make_json_pack ("{s:s, s:i}", + "mint", pc->chosen_mint, + "transaction_id", pc->transaction_id)); + return; + } + } +} + + +/** * Accomplish this payment. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] connection_cls the connection's closure - * (can be updated) + * (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a - * upload_data + * upload_data * @return MHD result code */ int @@ -263,53 +574,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh, size_t *upload_data_size) { struct PayContext *pc; - json_t *root; - json_t *coins; - char *chosen_mint; - json_t *coin_aggregate; - json_t *wire_details; - unsigned int mint_index; /*a cell in the global array*/ - unsigned int coins_index; - unsigned int coins_cnt; - uint64_t transaction_id; int res; - struct TALER_MINT_DepositHandle *dh; - struct TALER_Amount max_fee; - struct TALER_Amount acc_fee; - struct TALER_Amount coin_fee; - struct TALER_Amount amount; - struct TALER_Amount percoin_amount; - struct GNUNET_TIME_Absolute edate; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_TIME_Absolute refund_deadline; - struct TALER_MerchantPublicKeyP pubkey; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_DenominationPublicKey denom_pub; - struct TALER_DenominationSignature ub_sig; - struct TALER_CoinSpendSignatureP coin_sig; - struct GNUNET_HashCode h_contract; - struct MERCHANT_DepositConfirmation *dc; - - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_array ("coins", &coins), - TMH_PARSE_member_string ("mint", &chosen_mint), - TMH_PARSE_member_amount ("max_fee", &max_fee), - TMH_PARSE_member_amount ("amount", &amount), - TMH_PARSE_member_time_abs ("timestamp", ×tamp), - TMH_PARSE_member_time_abs ("refund_deadline", &refund_deadline), - TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), - TMH_PARSE_member_fixed ("H_contract", &h_contract), - TMH_PARSE_MEMBER_END - }; - - struct TMH_PARSE_FieldSpecification coin_aggregate_spec[] = { - TMH_PARSE_member_amount ("f", &percoin_amount), - TMH_PARSE_member_fixed ("coin_pub", &coin_pub.eddsa_pub), - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom_pub), - TMH_PARSE_member_denomination_signature ("ub_sig", &ub_sig), - TMH_PARSE_member_fixed ("coin_sig", &coin_sig.eddsa_signature), - TMH_PARSE_MEMBER_END - }; + json_t *root; if (NULL == *connection_cls) { @@ -325,21 +591,17 @@ MH_handler_pay (struct TMH_RequestHandler *rh, } if (0 != pc->response_code) { + /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == pc->response_code) return MHD_NO; /* hard error */ - /* We are *done* processing the request, just queue the response (!) */ res = MHD_queue_response (connection, pc->response_code, pc->response); - #if 0 - if (pc->response != NULL) + if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ MHD_destroy_response (pc->response); pc->response = NULL; } - #endif return res; } @@ -349,201 +611,109 @@ MH_handler_pay (struct TMH_RequestHandler *rh, upload_data_size, &root); if (GNUNET_SYSERR == res) - return MHD_NO; - /* the POST's body has to be further fetched */ + return MHD_NO; /* error parsing JSON */ if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - res = TMH_PARSE_json_data (connection, - root, - spec); - - if (GNUNET_YES != res) - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - - /* 1 Check if the chosen mint is among the merchant's preferred. - - An error in this case could be due to: - - * the wallet indicated a non existent mint - * the wallet indicated a non trusted mint + return MHD_YES; /* the POST's body has to be further fetched */ - NOTE: by preventively checking this, the merchant - avoids getting HTTP response codes from random - websites that may mislead the wallet in the way - of managing the error. Of course, that protect the - merchant from POSTing coins to untrusted mints. - - */ - - for (mint_index = 0; mint_index <= nmints; mint_index++) + /* Got the JSON upload, parse it */ { - /* no mint found in array */ - if (mint_index == nmints) + json_t *coins; + json_t *coin; + unsigned int coins_index; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_array ("coins", &coins), + TMH_PARSE_member_string ("mint", &pc->chosen_mint), + TMH_PARSE_member_uint64 ("transaction_id", &pc->transaction_id), + TMH_PARSE_member_amount ("max_fee", &pc->max_fee), + TMH_PARSE_member_amount ("amount", &pc->amount), + TMH_PARSE_member_time_abs ("timestamp", &pc->timestamp), + TMH_PARSE_member_time_abs ("refund_deadline", &pc->refund_deadline), + TMH_PARSE_member_fixed ("H_contract", &pc->h_contract), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_YES != res) { - mint_index = -1; - break; + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } - /* test it by checking public key */ - if (0 == strcmp (mints[mint_index].hostname, - chosen_mint)) - break; - - } - - if (-1 == mint_index) - return TMH_RESPONSE_reply_external_error (connection, "unknown mint"); - - /* no 'edate' from frontend. Generate it here; it will be timestamp - + a edate delay supplied in config file */ - if (NULL == json_object_get (root, "edate")) - { - edate = GNUNET_TIME_absolute_add (timestamp, edate_delay); - if (-1 == json_object_set (root, "edate", TALER_json_from_abs (edate))) - return MHD_NO; - } - - coins_cnt = json_array_size (coins); - - if (0 == coins_cnt) - return TMH_RESPONSE_reply_external_error (connection, "no coins given"); - - json_array_foreach (coins, coins_index, coin_aggregate) - { - res = deposit_fee_from_coin_aggregate (connection, - coin_aggregate, - &coin_fee, - mint_index); - if (GNUNET_NO == res) - return MHD_YES; - if (GNUNET_SYSERR == res) - return MHD_NO; - - if (0 == coins_index) - acc_fee = coin_fee; + /* 'edate' is optional, if it is not present, generate it here; it + will be timestamp plus the edate_delay supplied in config + file */ + if (NULL == json_object_get (root, "edate")) + { + pc->edate = GNUNET_TIME_absolute_add (pc->timestamp, + edate_delay); + } else - TALER_amount_add (&acc_fee, - &acc_fee, - &coin_fee); - } - - - if (-1 == TALER_amount_cmp (&max_fee, &acc_fee)) - return MHD_HTTP_NOT_ACCEPTABLE; - - /* cutting off unneeded fields from deposit permission as - gotten from the wallet */ - if (-1 == json_object_del (root, "mint")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'mint' field"); - if (-1 == json_object_del (root, "coins")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'coins' field"); - - /* adding our public key to deposit permission */ - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pubkey.eddsa_pub); - json_object_set_new (root, - "merchant_pub", - TALER_json_from_data (&pubkey, sizeof (pubkey))); - - wire_details = MERCHANT_get_wire_json (wire, salt); - /* since memory is zero'd out by GNUNET_malloc, any 'ackd' field will be - (implicitly) set to false */ - dc = GNUNET_malloc (coins_cnt * sizeof (struct MERCHANT_DepositConfirmation)); - if (NULL == dc) - return TMH_RESPONSE_reply_internal_error (connection, "memory failure"); - - /* DEBUG CHECKPOINT: return a provisory fullfilment page to the wallet - to test the reception of coins array */ - - #ifdef COINSCHECKPOINT - rh->data = "Coins received\n"; - return TMH_MHD_handler_static_response (rh, - connection, - connection_cls, - upload_data, - upload_data_size); - - #endif - - /* suspend connection until the last coin has been ack'd to the cb. - That last cb will finally resume the connection and send back a response */ - MHD_suspend_connection (connection); - - pc->dc = dc; - pc->coins_cnt = coins_cnt; - pc->transaction_id = transaction_id; + { + struct TMH_PARSE_FieldSpecification espec[] = { + TMH_PARSE_member_time_abs ("edate", &pc->edate), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + root, + espec); + if (GNUNET_YES != res) + { + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + } - json_array_foreach (coins, coins_index, coin_aggregate) - { + pc->coins_cnt = json_array_size (coins); + if (0 == pc->coins_cnt) + { + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "no coins given"); + } + /* note: 1 coin = 1 deposit confirmation expected */ + pc->dc = GNUNET_new_array (pc->coins_cnt, + struct MERCHANT_DepositConfirmation); - /* 3 For each coin in DB - - a. Generate a deposit permission - b. store it in DB - c. POST to the mint (see mint-lib for this) - (retry until getting a persisten state) - */ - - /* a */ - if (-1 == json_object_update (root, coin_aggregate)) - return TMH_RESPONSE_reply_internal_error (connection, - "deposit permission not generated for storing"); - - /* b */ - char *deposit_permission_str = json_dumps (root, JSON_COMPACT); - if (GNUNET_OK != MERCHANT_DB_store_deposit_permission (db_conn, - deposit_permission_str, - transaction_id, - 1, - mints[mint_index].hostname)) - return TMH_RESPONSE_reply_internal_error (connection, "internal DB failure"); - res = TMH_PARSE_json_data (connection, - coin_aggregate, - coin_aggregate_spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - /* c */ - struct DepositCallbackContext *percoin_dcc = GNUNET_new (struct DepositCallbackContext); - pc->dc[coins_index].dcc = percoin_dcc; - percoin_dcc->index = coins_index; - percoin_dcc->pc = pc; - - dh = TALER_MINT_deposit (mints[mint_index].conn, - &percoin_amount, - edate, - wire_details, - &h_contract, - &coin_pub, - &ub_sig, - &denom_pub, - timestamp, - transaction_id, - &pubkey, - refund_deadline, - &coin_sig, - &deposit_cb, - percoin_dcc); /*FIXME TODO instantiate an individual cls for each - cb: each of them needs an index which points the - array of all the confirmations */ - if (NULL == dh) + json_array_foreach (coins, coins_index, coin) { - MHD_resume_connection (connection); - return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_SERVICE_UNAVAILABLE, - "{s:s, s:i}", - "mint", mints[mint_index].hostname, - "transaction_id", transaction_id); + struct MERCHANT_DepositConfirmation *dc = &pc->dc[coins_index]; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_denomination_public_key ("denom_pub", &dc->denom), + TMH_PARSE_member_amount ("f", &dc->percoin_amount), + TMH_PARSE_member_fixed ("coin_pub", &dc->coin_pub), + TMH_PARSE_member_denomination_signature ("ub_sig", &dc->ub_sig), + TMH_PARSE_member_fixed ("coin_sig", &dc->coin_sig), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + coin, + spec); + if (GNUNET_YES != res) + { + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + dc->index = coins_index; + dc->pc = pc; } } - GNUNET_SCHEDULER_cancel (poller_task); - GNUNET_SCHEDULER_add_now (context_task, mints[mint_index].ctx); - return MHD_YES; + /* Find the responsible mint, this may take a while... */ + pc->pending = pc->coins_cnt; + pc->fo = TMH_MINTS_find_mint (pc->chosen_mint, + &process_pay_with_mint, + pc); - /* 4 Return response code: success, or whatever data the - mint sent back regarding some bad coin */ + /* ... so we suspend connection until the last coin has been ack'd + or until we have encountered a hard error. Eventually, we will + resume the connection and send back a response using + #resume_pay_with_response(). */ + MHD_suspend_connection (connection); + json_decref (root); + return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_pay.h b/src/backend/taler-merchant-httpd_pay.h index 6a796e06..0836f9ba 100644 --- a/src/backend/taler-merchant-httpd_pay.h +++ b/src/backend/taler-merchant-httpd_pay.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (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 @@ -13,17 +13,16 @@ 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 merchant/backend/taler-merchant-httpd_contract.h + * @file backend/taler-merchant-httpd_pay.h * @brief headers for /pay handler * @author Marcello Stanisci */ - #ifndef TALER_MINT_HTTPD_PAY_H #define TALER_MINT_HTTPD_PAY_H #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" + /** * Manage a payment @@ -33,7 +32,6 @@ * @param[in,out] connection_cls the connection's closure (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * * @return MHD result code */ int diff --git a/src/backend/taler-mint-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c index 00a4d25f..d8ba1170 100644 --- a/src/backend/taler-mint-httpd_responses.c +++ b/src/backend/taler-merchant-httpd_responses.c @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ /** - * @file taler-mint-httpd_responses.c + * @file taler-merchant-httpd_responses.c * @brief API for generating the various replies of the mint; these * functions are called TMH_RESPONSE_reply_ and they generate * and queue MHD response objects for a given connection. @@ -23,27 +23,22 @@ * @author Christian Grothoff */ #include "platform.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_responses.h" #include <taler/taler_util.h> #include <gnunet/gnunet_util_lib.h> /** - * Send JSON object as response. + * Make JSON response object. * - * @param connection the MHD connection * @param json the json object - * @param response_code the http response code - * @return MHD result code + * @return MHD response object */ -int -TMH_RESPONSE_reply_json (struct MHD_Connection *connection, - const json_t *json, - unsigned int response_code) +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json) { struct MHD_Response *resp; char *json_str; - int ret; json_str = json_dumps (json, JSON_INDENT(2)); GNUNET_assert (NULL != json_str); @@ -53,11 +48,34 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, { free (json_str); GNUNET_break (0); - return MHD_NO; + return NULL; } (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); + return resp; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + int ret; + + resp = TMH_RESPONSE_make_json (json); + if (NULL == resp) + return MHD_NO; ret = MHD_queue_response (connection, response_code, resp); @@ -67,6 +85,40 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, /** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + va_list argp; + struct MHD_Response *ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, 0, fmt, argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TMH_RESPONSE_make_json (json); + json_decref (json); + return ret; +} + + +/** * Function to call to handle the request by building a JSON * reply from a format string and varargs. * @@ -106,6 +158,22 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, return ret; } + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "internal error", + "hint", hint); +} + + /** * Send a response indicating an internal error. * @@ -124,6 +192,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, "hint", hint); } + /** * Send a response indicating that the request was too big. * @@ -186,6 +255,7 @@ TMH_RESPONSE_add_global_headers (struct MHD_Response *response) "close"); } + /** * Send a response indicating an external error. * @@ -203,4 +273,21 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, "error", "client error", "hint", hint); } + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "client error", + "hint", hint); +} + + /* end of taler-mint-httpd_responses.c */ diff --git a/src/backend/taler-mint-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h index f947bd57..7240d601 100644 --- a/src/backend/taler-mint-httpd_responses.h +++ b/src/backend/taler-merchant-httpd_responses.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014 GNUnet e.V. + Copyright (C) 2014, 2015 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -15,7 +15,7 @@ */ /** - * @file taler-mint-httpd_responses.h + * @file taler-merchant-httpd_responses.h * @brief API for generating the various replies of the mint; these * functions are called TMH_RESPONSE_reply_ and they generate * and queue MHD response objects for a given connection. @@ -31,6 +31,16 @@ #include <pthread.h> /** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json); + + +/** * Send JSON object as response. * * @param connection the MHD connection @@ -45,6 +55,18 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, /** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...); + + +/** * Function to call to handle the request by building a JSON * reply from a format string and varargs. * @@ -70,6 +92,7 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, int TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); + /** * Send a response indicating an internal error. * @@ -80,6 +103,18 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); int TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint); + + /** * Send a response indicating an external error. * @@ -90,6 +125,18 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint); + + /** * Send a response indicating that the request was too big. * @@ -99,6 +146,7 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + /** * Add headers we want to return in every response. * Useful for testing, like if we want to always close diff --git a/src/backend/taler-mint-httpd.h b/src/backend/taler-mint-httpd.h deleted file mode 100644 index ad8702f0..00000000 --- a/src/backend/taler-mint-httpd.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-mint-httpd.h - * @brief Global declarations for the mint - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - * - * FIXME: Consider which of these need to really be globals... - */ -#ifndef TALER_MINT_HTTPD_H -#define TALER_MINT_HTTPD_H - -#include <microhttpd.h> - -/** - * @brief Struct describing an URL and the handler for it. - */ -struct TMH_RequestHandler -{ - - /** - * URL the handler is for. - */ - const char *url; - - /** - * Method the handler is for, NULL for "all". - */ - const char *method; - - /** - * Mime type to use in reply (hint, can be NULL). - */ - const char *mime_type; - - /** - * Raw data for the @e handler - */ - const void *data; - - /** - * Number of bytes in @e data, 0 for 0-terminated. - */ - size_t data_size; - - /** - * Function to call to handle the request. - * - * @param rh this struct - * @param mime_type the @e mime_type for the reply (hint, can be NULL) - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @return MHD result code - */ - int (*handler)(struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - - /** - * Default response code. - */ - int response_code; -}; - - -#endif diff --git a/src/backenddb/Makefile.am b/src/backenddb/Makefile.am new file mode 100644 index 00000000..e70ad206 --- /dev/null +++ b/src/backenddb/Makefile.am @@ -0,0 +1,39 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +plugindir = $(libdir)/taler + +if HAVE_POSTGRESQL +if HAVE_TALERPQ +plugin_LTLIBRARIES = \ + libtaler_plugin_merchantdb_postgres.la +endif +endif + +lib_LTLIBRARIES = \ + libtalermerchantdb.la + +libtalermerchantdb_la_SOURCES = \ + merchantdb_plugin.c + +libtalermerchantdb_la_LIBADD = \ + $(LIBGCRYPT_LIBS) \ + -ltalerutil \ + -lgnunetutil + +libtalermerchantdb_la_LDFLAGS = \ + $(POSTGRESQL_LDFLAGS) \ + -version-info 0:0:0 \ + -no-undefined + +libtaler_plugin_merchantdb_postgres_la_SOURCES = \ + plugin_merchantdb_postgres.c +libtaler_plugin_merchantdb_postgres_la_LIBADD = \ + $(LTLIBINTL) +libtaler_plugin_merchantdb_postgres_la_LDFLAGS = \ + $(TALER_PLUGIN_LDFLAGS) \ + -ltalerpq \ + -ltalerutil \ + -lpq \ + -lgnunetpostgres \ + -lgnunetutil $(XLIB) diff --git a/src/backenddb/merchantdb_plugin.c b/src/backenddb/merchantdb_plugin.c new file mode 100644 index 00000000..62a1a193 --- /dev/null +++ b/src/backenddb/merchantdb_plugin.c @@ -0,0 +1,150 @@ +/* + 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 merchantdb/merchantdb_plugin.c + * @brief Logic to load database plugin + * @author Christian Grothoff + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include <taler/taler_util.h> +#include "taler_merchantdb_plugin.h" +#include <ltdl.h> + + +/** + * Initialize the plugin. + * + * @param cfg configuration to use + * @return #GNUNET_OK on success + */ +struct TALER_MERCHANTDB_Plugin * +TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + char *plugin_name; + char *lib_name; + struct GNUNET_CONFIGURATION_Handle *cfg_dup; + struct TALER_MERCHANTDB_Plugin *plugin; + + if (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (cfg, + "merchant", + "db", + &plugin_name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "merchant", + "db"); + return NULL; + } + (void) GNUNET_asprintf (&lib_name, + "libtaler_plugin_merchantdb_%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_MERCHANTDB_plugin_unload (struct TALER_MERCHANTDB_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); +} + + +/** + * Libtool search path before we started. + */ +static char *old_dlsearchpath; + + +/** + * 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 merchantdb_plugin.c */ diff --git a/src/backenddb/plugin_merchantdb_postgres.c b/src/backenddb/plugin_merchantdb_postgres.c new file mode 100644 index 00000000..2b4f41ec --- /dev/null +++ b/src/backenddb/plugin_merchantdb_postgres.c @@ -0,0 +1,244 @@ +/* + This file is part of TALER + (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 merchant/plugin_merchantdb_postgres.c + * @brief database helper functions for postgres used by the merchant + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_postgres_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_pq_lib.h> +#include "taler_merchantdb_plugin.h" + + +/** + * Type of the "cls" argument given to each of the functions in + * our API. + */ +struct PostgresClosure +{ + + /** + * Postgres connection handle. + */ + PGconn *conn; + +}; + + +#define PQSQL_strerror(kind, cmd, res) \ + GNUNET_log_from (kind, "merchant-db", \ + "SQL %s failed at %s:%u with error: %s", \ + cmd, __FILE__, __LINE__, PQresultErrorMessage (res)); + + +/** + * Initialize merchant tables + * + * @param cls closure our `struct Plugin` + * @param tmp #GNUNET_YES if the tables are to be made temporary i.e. their + * contents are dropped when the database connection is closed + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ +static int +postgres_initialize (void *cls, + int tmp) +{ + struct PostgresClosure *pg = cls; + const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; + char *sql; + PGresult *res; + ExecStatusType status; + int ret; + + GNUNET_asprintf (&sql, + "CREATE %1$s TABLE IF NOT EXISTS payments (" + "h_contract BYTEA NOT NULL," + "h_wire BYTEA NOT NULL," + "transaction_id INT8 PRIMARY KEY," + "timestamp INT8 NOT NULL," + "refund_deadline INT8 NOT NULL," + "amount_without_fee_val INT8 NOT NULL," + "amount_without_fee_frac INT4 NOT NULL," + "amount_without_fee_curr VARCHAR(" TALER_CURRENCY_LEN_STR ") NOT NULL," + "coin_pub BYTEA NOT NULL," + "mint_proof BYTEA NOT NULL);", + tmp_str); + ret = GNUNET_POSTGRES_exec (pg->conn, + sql); + GNUNET_free (sql); + if (GNUNET_OK != ret) + return ret; + if ( (NULL == (res = PQprepare (pg->conn, + "insert_payment", + "INSERT INTO payments" + "(h_contract" + ",h_wire" + ",transaction_id" + ",timestamp" + ",refund_deadline" + ",amount_without_fee_val" + ",amount_without_fee_frac" + ",amount_without_fee_curr" + ",coin_pub" + ",mint_proof) VALUES " + "($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", + 10, NULL))) || + (PGRES_COMMAND_OK != (status = PQresultStatus(res))) ) + { + if (NULL != res) + { + PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); + PQclear (res); + } + return GNUNET_SYSERR; + } + PQclear (res); + return GNUNET_OK; +} + + +/** + * Insert payment confirmation from the mint into the database. + * + * @param cls our plugin handle + * @param h_contract hash of the contract + * @param h_wire hash of our wire details + * @param transaction_id of the contract + * @param timestamp time of the confirmation + * @param refund refund deadline + * @param amount_without_fee amount the mint will deposit + * @param coin_pub public key of the coin + * @param mint_proof proof from the mint that coin was accepted + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ +static int +postgres_store_payment (void *cls, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + uint64_t transaction_id, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *mint_proof) +{ + struct PostgresClosure *pg = cls; + PGresult *res; + ExecStatusType status; + + struct TALER_PQ_QueryParam params[] = { + TALER_PQ_query_param_auto_from_type (h_contract), + TALER_PQ_query_param_auto_from_type (h_wire), + TALER_PQ_query_param_uint64 (&transaction_id), + TALER_PQ_query_param_absolute_time (×tamp), + TALER_PQ_query_param_absolute_time (&refund), + TALER_PQ_query_param_amount (amount_without_fee), + TALER_PQ_query_param_auto_from_type (coin_pub), + TALER_PQ_query_param_json (mint_proof), + TALER_PQ_query_param_end + }; + + res = TALER_PQ_exec_prepared (pg->conn, + "insert_payment", + params); + status = PQresultStatus (res); + + if (PGRES_COMMAND_OK != status) + { + const char *sqlstate; + + sqlstate = PQresultErrorField (res, PG_DIAG_SQLSTATE); + if (NULL == sqlstate) + { + /* very unexpected... */ + GNUNET_break (0); + PQclear (res); + return GNUNET_SYSERR; + } + /* 40P01: deadlock, 40001: serialization failure */ + if ( (0 == strcmp (sqlstate, + "23505"))) + { + /* Primary key violation */ + PQclear (res); + return GNUNET_NO; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Database commit failure: %s\n", + sqlstate); + PQclear (res); + return GNUNET_SYSERR; + } + + PQclear (res); + return GNUNET_OK; +} + + +/** + * Initialize Postgres database subsystem. + * + * @param cls a configuration instance + * @return NULL on error, otherwise a `struct TALER_MERCHANTDB_Plugin` + */ +void * +libtaler_plugin_merchantdb_postgres_init (void *cls) +{ + struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct PostgresClosure *pg; + struct TALER_MERCHANTDB_Plugin *plugin; + + pg = GNUNET_new (struct PostgresClosure); + if (GNUNET_OK != + GNUNET_CONFIGURATION_have_value (cfg, + "merchantdb-postgres", + "CONFIG")) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "merchantdb-postgres", + "CONFIG"); + return NULL; + } + pg->conn = GNUNET_POSTGRES_connect (cfg, "merchantdb-postgres"); + plugin = GNUNET_new (struct TALER_MERCHANTDB_Plugin); + plugin->cls = pg; + plugin->initialize = &postgres_initialize; + plugin->store_payment = &postgres_store_payment; + + return plugin; +} + + +/** + * Shutdown Postgres database subsystem. + * + * @param cls a `struct TALER_MERCHANTDB_Plugin` + * @return NULL (always) + */ +void * +libtaler_plugin_merchantdb_postgres_done (void *cls) +{ + struct TALER_MERCHANTDB_Plugin *plugin = cls; + struct PostgresClosure *pg = plugin->cls; + + PQfinish (pg->conn); + GNUNET_free (pg); + GNUNET_free (plugin); + return NULL; +} diff --git a/src/frontend/fullfillment.php b/src/frontend/fullfillment.php index d7677aa7..09de3fbf 100644 --- a/src/frontend/fullfillment.php +++ b/src/frontend/fullfillment.php @@ -27,6 +27,13 @@ $cli_debug = false; $backend_test = true; +function generate_msg ($link){ + $msg = "Thanks for donating to " . $_SESSION['receiver'] . "."; + if (false != $link) + $msg .= " Check our latest <a href=\"" . $link . "\">news!</a>"; + return $msg; +} + if ($_GET['cli_debug'] == 'yes') $cli_debug = true; @@ -38,11 +45,23 @@ if ($_GET['backend_test'] == 'no') session_start(); - if (! isset ($_SESSION['payment_ok'])) echo "Please land here after a successful payment!"; -else - echo "Thanks for donating to " . $_SESSION['receiver']; +else{ + $news = false; + switch ($_SESSION['receiver']){ + case "Taler": + $news = "https://taler.net/news"; + break; + case "GNUnet": + $news = "https://gnunet.org/"; + break; + case "Tor": + $news = "https://www.torproject.org/press/press.html.en"; + break; + } + echo generate_msg ($news); +} ?> diff --git a/src/include/Makefile.am b/src/include/Makefile.am index 3db59c88..f62655b3 100644 --- a/src/include/Makefile.am +++ b/src/include/Makefile.am @@ -1,2 +1,9 @@ +# This Makefile.am is in the public domain EXTRA_DIST = \ - platform.h + platform.h + +talerincludedir = $(includedir)/taler + +talerinclude_HEADERS = \ + taler_merchantdb_lib.h \ + taler_merchant_service.h diff --git a/src/include/merchant.h b/src/include/merchant.h deleted file mode 100644 index eb430360..00000000 --- a/src/include/merchant.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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/merchant.h - * @brief Common utility functions for merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#ifndef MERCHANT_H -#define MERCHANT_H - -#include <gnunet/gnunet_util_lib.h> -#include <taler/taler_mint_service.h> -#include "merchant.h" - -/** - * Macro to round microseconds to seconds in GNUNET_TIME_* structs. - */ -#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000) - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Outcome of a /deposit request for a coin. Typically forming an array enclosed - * into the unique PayContext - */ -struct MERCHANT_DepositConfirmation -{ - /** - * Reference to the per-deposit-handler Context. Needed by the - * cleanup function to get it freed - */ - struct DepositCallbackContext *dcc; - - /** - * True if this coin's outcome has been read from - * its cb - */ - unsigned int ackd; - - /** - * The mint's response to this /deposit - */ - unsigned int exit_status; - - /** - * The mint's response body (JSON). Mainly useful in case - * some callback needs to send back to the to the wallet the - * outcome of an erroneous coin - */ - json_t *proof; - -}; - - -/** - * Mint - */ -struct MERCHANT_Mint -{ - /** - * Hostname - */ - char *hostname; - - /** - * Flag which indicates whether some HTTP transfer between - * this merchant and the mint is still ongoing - */ - int pending; - - /** - * A connection to this mint - */ - struct TALER_MINT_Handle *conn; - - /** - * This mint's context (useful to the event loop) - */ - struct TALER_MINT_Context *ctx; - -}; - -struct MERCHANT_Auditor -{ - /** - * Auditor's legal name - */ - char *name; - -}; - -/** - * The contract sent by the merchant to the wallet - */ -struct MERCHANT_Contract -{ - /** - * Purpose header for the signature over contract - */ - struct GNUNET_CRYPTO_EccSignaturePurpose purpose; - - /** - * Hash of the JSON contract in UTF-8 including 0-termination, - * using JSON_COMPACT | JSON_SORT_KEYS - */ - struct GNUNET_HashCode h_contract; - -}; - - - -/** - * Parses mints from the configuration. - * - * @param cfg the configuration - * @param mints the array of mints upon successful parsing. Will be NULL upon - * error. - * @return the number of mints in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Mint **mints); - -/** - * Parses auditors from the configuration. - * - * @param cfg the configuration - * @param mints the array of auditors upon successful parsing. Will be NULL upon - * error. - * @return the number of auditors in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_auditors (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Auditor **auditors); - -GNUNET_NETWORK_STRUCT_BEGIN -struct MERCHANT_WIREFORMAT_Sepa -{ - /** - * The international bank account number - */ - char *iban; - - /** - * Name of the bank account holder - */ - char *name; - - /** - *The bank identification code - */ - char *bic; - - /** - * The latest payout date when the payment corresponding to this account has - * to take place. A value of 0 indicates a transfer as soon as possible. - */ - struct GNUNET_TIME_AbsoluteNBO payout; -}; -GNUNET_NETWORK_STRUCT_END - -/** - * Parse the SEPA information from the configuration. If any of the required - * fileds is missing return NULL. - * - * @param cfg the configuration - * @return Sepa details as a structure; NULL upon error - */ -struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); - - -/** - * Destroy and free resouces occupied by the wireformat structure - * - * @param wf the wireformat structure - */ -void -TALER_MERCHANT_destroy_wireformat_sepa (struct MERCHANT_WIREFORMAT_Sepa *wf); - -#endif /* MERCHANT_H */ diff --git a/src/include/platform.h b/src/include/platform.h index 4cba7abf..6e4baec6 100644 --- a/src/include/platform.h +++ b/src/include/platform.h @@ -18,7 +18,7 @@ * @file include/platform.h * @brief This file contains the includes and definitions which are used by the * rest of the modules - * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Sree Harsha Totakura <sreeharsha@totakura.in> */ #ifndef PLATFORM_H_ @@ -28,7 +28,7 @@ #ifndef HAVE_USED_CONFIG_H # define HAVE_USED_CONFIG_H # ifdef HAVE_CONFIG_H -# include "taler_config.h" +# include "taler_merchant_config.h" # endif #endif diff --git a/src/include/taler_merchant_service.h b/src/include/taler_merchant_service.h new file mode 100644 index 00000000..8c9957c3 --- /dev/null +++ b/src/include/taler_merchant_service.h @@ -0,0 +1,292 @@ +/* + 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 Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file include/taler_merchant_service.h + * @brief C interface of libtalermerchant, a C library to use merchant's HTTP API + * @author Christian Grothoff + */ +#ifndef _TALER_MERCHANT_SERVICE_H +#define _TALER_MERCHANT_SERVICE_H + +#include <taler/taler_util.h> + +/* ********************* event loop *********************** */ + +/** + * @brief Handle to this library context. This is where the + * main event loop logic lives. + */ +struct TALER_MERCHANT_Context; + + +/** + * Initialise a context. A context should be used for each thread and should + * not be shared among multiple threads. + * + * @return the context, NULL on error (failure to initialize) + */ +struct TALER_MERCHANT_Context * +TALER_MERCHANT_init (void); + + +/** + * Obtain the information for a select() call to wait until + * #TALER_MERCHANT_perform() is ready again. Note that calling + * any other TALER_MERCHANT-API may also imply that the library + * is again ready for #TALER_MERCHANT_perform(). + * + * Basically, a client should use this API to prepare for select(), + * then block on select(), then call #TALER_MERCHANT_perform() and then + * start again until the work with the context is done. + * + * This function will NOT zero out the sets and assumes that @a max_fd + * and @a timeout are already set to minimal applicable values. It is + * safe to give this API FD-sets and @a max_fd and @a timeout that are + * already initialized to some other descriptors that need to go into + * the select() call. + * + * @param ctx context to get the event loop information for + * @param read_fd_set will be set for any pending read operations + * @param write_fd_set will be set for any pending write operations + * @param except_fd_set is here because curl_multi_fdset() has this argument + * @param max_fd set to the highest FD included in any set; + * if the existing sets have no FDs in it, the initial + * value should be "-1". (Note that `max_fd + 1` will need + * to be passed to select().) + * @param timeout set to the timeout in milliseconds (!); -1 means + * no timeout (NULL, blocking forever is OK), 0 means to + * proceed immediately with #TALER_MERCHANT_perform(). + */ +void +TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + int *max_fd, + long *timeout); + + +/** + * Run the main event loop for the Taler interaction. + * + * @param ctx the library context + */ +void +TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx); + + +/** + * Cleanup library initialisation resources. This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_MERCHANT_fini (struct TALER_MERCHANT_Context *ctx); + + +/* ********************* /pay *********************** */ + + +/** + * @brief Handle to a /pay operation at a merchant. Note that we use + * the same handle for interactions with frontends (API for wallets) + * or backends (API for frontends). The difference is that for the + * frontend API, we need the private keys of the coins, while for + * the backend API we need the public keys and signatures received + * from the wallet. Also, the frontend returns a redirect URI on + * success, while the backend just returns a success status code. + */ +struct TALER_MERCHANT_Pay; + + +/** + * Callbacks of this type are used to serve the result of submitting a + * /pay request to a merchant. + * + * @param cls closure + * @param http_status HTTP response code, 200 or 300-level response codes + * can indicate success, depending on whether the interaction + * was with a merchant frontend or backend; + * 0 if the merchant's reply is bogus (fails to follow the protocol) + * @param redirect_uri URI for the redirect, if the request was successful and we were talking to a frontend; + * NULL if the request failed or if were were talking to a backend + * @param obj the received JSON reply, with error details if the request failed + */ +typedef void +(*TALER_MERCHANT_PayCallback) (void *cls, + unsigned int http_status, + const char *redirect_uri, + json_t *obj); + + +/** + * Information we need from the wallet for each coin when doing the + * payment. + */ +struct TALER_MERCHANT_PayCoin +{ + /** + * Denomination key with which the coin is signed + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Mint’s unblinded signature of the coin + */ + struct TALER_DenominationSignature denom_sig; + + /** + * Coin's private key. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Amount this coin is to contribute (including fee). + */ + struct TALER_Amount amount_with_fee; + + /** + * Amount this coin is to contribute (without fee). + */ + struct TALER_Amount amount_without_fee; +}; + + +/** + * Pay a merchant. API for wallets that have the coin's private keys. + * + * @param merchant the merchant context + * @param mint_uri URI of the mint that the coins belong to + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant, + const char *mint_uri, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute timestamp, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + unsigned int num_coins, + const struct TALER_MERCHANT_PayCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls); + + +/** + * Information we need from the frontend when forwarding + * a payment to the backend. + */ +struct TALER_MERCHANT_PaidCoin +{ + /** + * Denomination key with which the coin is signed + */ + struct TALER_DenominationPublicKey denom_pub; + + /** + * Mint’s unblinded signature of the coin + */ + struct TALER_DenominationSignature denom_sig; + + /** + * Coin's public key. + */ + struct TALER_CoinSpendPrivateKeyP coin_pub; + + /** + * Coin's signature key. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Amount this coin is to contribute (including fee). + */ + struct TALER_Amount amount_with_fee; + + /** + * Amount this coin is to contribute (without fee). + */ + struct TALER_Amount amount_without_fee; + +}; + + +/** + * Pay a merchant. API for frontends talking to backends. Here, + * the frontend does not have the coin's private keys, but just + * the public keys and signatures. Note the sublte difference + * in the type of @a coins compared to #TALER_MERCHANT_pay(). + * + * @param merchant the merchant context + * @param mint_uri URI of the mint that the coins belong to + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant, + const char *mint_uri, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute timestamp, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + unsigned int num_coins, + const struct TALER_MERCHANT_PaidCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls); + + +/** + * Cancel a /pay request. Note that if you cancel a request like + * this, you have no assurance that the request has not yet been + * forwarded to the merchant. Thus, the payment may still succeed or + * fail. Re-issue the original /pay request to resume/retry and + * obtain a definitive result, or /refresh the coins involved to + * ensure that the merchant can no longer complete the payment. + * + * @param wh the wire information request handle + */ +void +TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *ph); + + +#endif /* _TALER_MERCHANT_SERVICE_H */ diff --git a/src/include/taler_merchantdb_lib.h b/src/include/taler_merchantdb_lib.h new file mode 100644 index 00000000..f64a9bfa --- /dev/null +++ b/src/include/taler_merchantdb_lib.h @@ -0,0 +1,54 @@ +/* + This file is part of TALER + (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_merchantdb_lib.h + * @brief database helper functions used by the merchant backend + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + */ +#ifndef TALER_MERCHANTDB_LIB_H +#define TALER_MERCHANTDB_LIB_H + +#include <taler/taler_util.h> +#include "taler_merchantdb_plugin.h" + +/** + * Handle to interact with the database. + */ +struct TALER_MERCHANTDB_Plugin; + +/** + * Connect to postgresql database + * + * @param cfg the configuration handle + * @return connection to the database; NULL upon error + */ +struct TALER_MERCHANTDB_Plugin * +TALER_MERCHANTDB_plugin_load (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Disconnect from the database + * + * @param dbh database handle to close + */ +void +TALER_MERCHANTDB_plugin_unload (struct TALER_MERCHANTDB_Plugin *dbh); + + +#endif /* MERCHANT_DB_H */ + +/* end of taler_merchantdb_lib.h */ diff --git a/src/include/taler_merchantdb_plugin.h b/src/include/taler_merchantdb_plugin.h new file mode 100644 index 00000000..f237b8d7 --- /dev/null +++ b/src/include/taler_merchantdb_plugin.h @@ -0,0 +1,88 @@ +/* + 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_merchantdb_plugin.h + * @brief database access for the merchant + * @author Florian Dold + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANTDB_PLUGIN_H +#define TALER_MERCHANTDB_PLUGIN_H + +#include <gnunet/gnunet_util_lib.h> + +/** + * Handle to interact with the database. + */ +struct TALER_MERCHANTDB_Plugin; + +/** + * Handle to interact with the database. + */ +struct TALER_MERCHANTDB_Plugin +{ + + /** + * Closure for all callbacks. + */ + void *cls; + + /** + * Name of the library which generated this plugin. Set by the + * plugin loader. + */ + char *library_name; + + /** + * Initialize merchant tables + * + * @param cls closure + * @param tmp #GNUNET_YES if the tables are to be made temporary i.e. their + * contents are dropped when the @a conn is closed + * @return #GNUNET_OK upon success; #GNUNET_SYSERR upon failure + */ + int + (*initialize) (void *cls, + int tmp); + + /** + * Insert payment confirmation from the mint into the database. + * + * @param cls closure + * @param h_contract hash of the contract + * @param h_wire hash of our wire details + * @param transaction_id of the contract + * @param timestamp time of the confirmation + * @param refund refund deadline + * @param amount_without_fee amount the mint will deposit + * @param coin_pub public key of the coin + * @param mint_proof proof from mint that coin was accepted + * @return #GNUNET_OK on success, #GNUNET_SYSERR upon error + */ + int + (*store_payment) (void *cls, + const struct GNUNET_HashCode *h_contract, + const struct GNUNET_HashCode *h_wire, + uint64_t transaction_id, + struct GNUNET_TIME_Absolute timestamp, + struct GNUNET_TIME_Absolute refund, + const struct TALER_Amount *amount_without_fee, + const struct TALER_CoinSpendPublicKeyP *coin_pub, + json_t *mint_proof); + +}; + +#endif diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am new file mode 100644 index 00000000..8696db40 --- /dev/null +++ b/src/lib/Makefile.am @@ -0,0 +1,47 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = \ + libtalermerchant.la + +libtalermerchant_la_LDFLAGS = \ + -version-info 0:0:0 \ + -no-undefined + +libtalermerchant_la_SOURCES = \ + merchant_api_context.c merchant_api_context.h \ + merchant_api_json.c merchant_api_json.h \ + merchant_api_pay.c + +libtalermerchant_la_LIBADD = \ + -lgnunetutil \ + -ljansson \ + $(XLIB) + +if HAVE_LIBCURL +libtalermerchant_la_LIBADD += -lcurl +else +if HAVE_LIBGNURL +libtalermerchant_la_LIBADD += -lgnurl +endif +endif + +check_PROGRAMS = \ + test_merchant_api + +TESTS = \ + $(check_PROGRAMS) + +test_merchant_api_SOURCES = \ + test_merchant_api.c +test_merchant_api_LDADD = \ + libtalermerchant.la \ + $(LIBGCRYPT_LIBS) \ + $(top_builddir)/src/util/libtalerutil.la \ + -lgnunetutil \ + -ljansson diff --git a/src/lib/merchant_api_context.c b/src/lib/merchant_api_context.c new file mode 100644 index 00000000..5b8b9e4f --- /dev/null +++ b/src/lib/merchant_api_context.c @@ -0,0 +1,525 @@ +/* + 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 merchant-lib/merchant_api_context.c + * @brief Implementation of the context part of the merchant's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include "taler_merchant_service.h" +#include "merchant_api_context.h" + + +/** + * Log error related to CURL operations. + * + * @param type log level + * @param function which function failed to run + * @param code what was the curl error code + */ +#define CURL_STRERROR(type, function, code) \ + GNUNET_log (type, \ + "Curl function `%s' has failed at `%s:%d' with error: %s\n", \ + function, __FILE__, __LINE__, curl_easy_strerror (code)); + +/** + * Print JSON parsing related error information + */ +#define JSON_WARN(error) \ + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ + "JSON parsing failed at %s:%u: %s (%s)\n", \ + __FILE__, __LINE__, error.text, error.source) + + +/** + * Failsafe flag. Raised if our constructor fails to initialize + * the Curl library. + */ +static int TALER_MERCHANT_curl_fail; + + +/** + * Jobs are CURL requests running within a `struct TALER_MERCHANT_Context`. + */ +struct MAC_Job +{ + + /** + * We keep jobs in a DLL. + */ + struct MAC_Job *next; + + /** + * We keep jobs in a DLL. + */ + struct MAC_Job *prev; + + /** + * Easy handle of the job. + */ + CURL *easy_handle; + + /** + * Context this job runs in. + */ + struct TALER_MERCHANT_Context *ctx; + + /** + * Function to call upon completion. + */ + MAC_JobCompletionCallback jcc; + + /** + * Closure for @e jcc. + */ + void *jcc_cls; + +}; + + +/** + * Context + */ +struct TALER_MERCHANT_Context +{ + /** + * Curl multi handle + */ + CURLM *multi; + + /** + * Curl share handle + */ + CURLSH *share; + + /** + * We keep jobs in a DLL. + */ + struct MAC_Job *jobs_head; + + /** + * We keep jobs in a DLL. + */ + struct MAC_Job *jobs_tail; + + /** + * HTTP header "application/json", created once and used + * for all requests that need it. + */ + struct curl_slist *json_header; + +}; + + +/** + * Initialise this library. This function should be called before using any of + * the following functions. + * + * @return library context + */ +struct TALER_MERCHANT_Context * +TALER_MERCHANT_init () +{ + struct TALER_MERCHANT_Context *ctx; + CURLM *multi; + CURLSH *share; + + if (TALER_MERCHANT_curl_fail) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Curl was not initialised properly\n"); + return NULL; + } + if (NULL == (multi = curl_multi_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create a Curl multi handle\n"); + return NULL; + } + if (NULL == (share = curl_share_init ())) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to create a Curl share handle\n"); + return NULL; + } + ctx = GNUNET_new (struct TALER_MERCHANT_Context); + ctx->multi = multi; + ctx->share = share; + GNUNET_assert (NULL != (ctx->json_header = + curl_slist_append (NULL, + "Content-Type: application/json"))); + return ctx; +} + + +/** + * Schedule a CURL request to be executed and call the given @a jcc + * upon its completion. Note that the context will make use of the + * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can + * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument + * from a valid @a eh afterwards. + * + * This function modifies the CURL handle to add the + * "Content-Type: application/json" header if @a add_json is set. + * + * @param ctx context to execute the job in + * @param eh curl easy handle for the request, will + * be executed AND cleaned up + * @param add_json add "application/json" content type header + * @param jcc callback to invoke upon completion + * @param jcc_cls closure for @a jcc + */ +struct MAC_Job * +MAC_job_add (struct TALER_MERCHANT_Context *ctx, + CURL *eh, + int add_json, + MAC_JobCompletionCallback jcc, + void *jcc_cls) +{ + struct MAC_Job *job; + + if (GNUNET_YES == add_json) + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_HTTPHEADER, + ctx->json_header)); + + job = GNUNET_new (struct MAC_Job); + job->easy_handle = eh; + job->ctx = ctx; + job->jcc = jcc; + job->jcc_cls = jcc_cls; + GNUNET_CONTAINER_DLL_insert (ctx->jobs_head, + ctx->jobs_tail, + job); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_PRIVATE, + job)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_SHARE, + ctx->share)); + GNUNET_assert (CURLM_OK == + curl_multi_add_handle (ctx->multi, + eh)); + return job; +} + + +/** + * Obtain the `jcc_cls` argument from an `eh` that was + * given to #MAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #MAC_job_add(). + */ +void * +MAC_easy_to_closure (CURL *eh) +{ + struct MAC_Job *job; + + GNUNET_assert (CURLE_OK == + curl_easy_getinfo (eh, + CURLINFO_PRIVATE, + (char **) &job)); + return job->jcc_cls; +} + + +/** + * Cancel a job. Must only be called before the job completion + * callback is called for the respective job. + * + * @param job job to cancel + */ +void +MAC_job_cancel (struct MAC_Job *job) +{ + struct TALER_MERCHANT_Context *ctx = job->ctx; + + GNUNET_CONTAINER_DLL_remove (ctx->jobs_head, + ctx->jobs_tail, + job); + GNUNET_assert (CURLM_OK == + curl_multi_remove_handle (ctx->multi, + job->easy_handle)); + curl_easy_cleanup (job->easy_handle); + GNUNET_free (job); +} + + +/** + * Run the main event loop for the Taler interaction. + * + * @param ctx the library context + */ +void +TALER_MERCHANT_perform (struct TALER_MERCHANT_Context *ctx) +{ + CURLMsg *cmsg; + struct MAC_Job *job; + int n_running; + int n_completed; + + (void) curl_multi_perform (ctx->multi, + &n_running); + while (NULL != (cmsg = curl_multi_info_read (ctx->multi, + &n_completed))) + { + /* Only documented return value is CURLMSG_DONE */ + GNUNET_break (CURLMSG_DONE == cmsg->msg); + GNUNET_assert (CURLE_OK == + curl_easy_getinfo (cmsg->easy_handle, + CURLINFO_PRIVATE, + (char *) &job)); + GNUNET_assert (job->ctx == ctx); + job->jcc (job->jcc_cls, + cmsg->easy_handle); + MAC_job_cancel (job); + } +} + + +/** + * Obtain the information for a select() call to wait until + * #TALER_MERCHANT_perform() is ready again. Note that calling + * any other TALER_MERCHANT-API may also imply that the library + * is again ready for #TALER_MERCHANT_perform(). + * + * Basically, a client should use this API to prepare for select(), + * then block on select(), then call #TALER_MERCHANT_perform() and then + * start again until the work with the context is done. + * + * This function will NOT zero out the sets and assumes that @a max_fd + * and @a timeout are already set to minimal applicable values. It is + * safe to give this API FD-sets and @a max_fd and @a timeout that are + * already initialized to some other descriptors that need to go into + * the select() call. + * + * @param ctx context to get the event loop information for + * @param read_fd_set will be set for any pending read operations + * @param write_fd_set will be set for any pending write operations + * @param except_fd_set is here because curl_multi_fdset() has this argument + * @param max_fd set to the highest FD included in any set; + * if the existing sets have no FDs in it, the initial + * value should be "-1". (Note that `max_fd + 1` will need + * to be passed to select().) + * @param timeout set to the timeout in milliseconds (!); -1 means + * no timeout (NULL, blocking forever is OK), 0 means to + * proceed immediately with #TALER_MERCHANT_perform(). + */ +void +TALER_MERCHANT_get_select_info (struct TALER_MERCHANT_Context *ctx, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + int *max_fd, + long *timeout) +{ + GNUNET_assert (CURLM_OK == + curl_multi_fdset (ctx->multi, + read_fd_set, + write_fd_set, + except_fd_set, + max_fd)); + GNUNET_assert (CURLM_OK == + curl_multi_timeout (ctx->multi, + timeout)); + if ( (-1 == (*timeout)) && + (NULL != ctx->jobs_head) ) + *timeout = 1000 * 60 * 5; /* curl is not always good about giving timeouts */ +} + + +/** + * Cleanup library initialisation resources. This function should be called + * after using this library to cleanup the resources occupied during library's + * initialisation. + * + * @param ctx the library context + */ +void +TALER_MERCHANT_fini (struct TALER_MERCHANT_Context *ctx) +{ + /* all jobs must have been cancelled at this time, assert this */ + GNUNET_assert (NULL == ctx->jobs_head); + curl_share_cleanup (ctx->share); + curl_multi_cleanup (ctx->multi); + curl_slist_free_all (ctx->json_header); + GNUNET_free (ctx); +} + + +/** + * Callback used when downloading the reply to an HTTP request. + * Just appends all of the data to the `buf` in the + * `struct MAC_DownloadBuffer` for further processing. The size of + * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if + * the download exceeds this size, we abort with an error. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct KeysRequest` + * @return number of bytes processed from @a bufptr + */ +size_t +MAC_download_cb (char *bufptr, + size_t size, + size_t nitems, + void *cls) +{ + struct MAC_DownloadBuffer *db = cls; + size_t msize; + void *buf; + + if (0 == size * nitems) + { + /* Nothing (left) to do */ + return 0; + } + msize = size * nitems; + if ( (msize + db->buf_size) >= GNUNET_MAX_MALLOC_CHECKED) + { + db->eno = ENOMEM; + return 0; /* signals an error to curl */ + } + db->buf = GNUNET_realloc (db->buf, + db->buf_size + msize); + buf = db->buf + db->buf_size; + memcpy (buf, bufptr, msize); + db->buf_size += msize; + return msize; +} + + +/** + * Obtain information about the final result about the + * HTTP download. If the download was successful, parses + * the JSON in the @a db and returns it. Also returns + * the HTTP @a response_code. If the download failed, + * the return value is NULL. The response code is set + * in any case, on download errors to zero. + * + * Calling this function also cleans up @a db. + * + * @param db download buffer + * @param eh CURL handle (to get the response code) + * @param[out] response_code set to the HTTP response code + * (or zero if we aborted the download, i.e. + * because the response was too big, or if + * the JSON we received was malformed). + * @return NULL if downloading a JSON reply failed + */ +json_t * +MAC_download_get_result (struct MAC_DownloadBuffer *db, + CURL *eh, + long *response_code) +{ + json_t *json; + json_error_t error; + char *ct; + + if ( (CURLE_OK != + curl_easy_getinfo (eh, + CURLINFO_CONTENT_TYPE, + &ct)) || + (NULL == ct) || + (0 != strcasecmp (ct, + "application/json")) ) + { + /* No content type or explicitly not JSON, refuse to parse + (but keep response code) */ + if (CURLE_OK != + curl_easy_getinfo (eh, + CURLINFO_RESPONSE_CODE, + response_code)) + { + /* unexpected error... */ + GNUNET_break (0); + *response_code = 0; + } + return NULL; + } + + json = NULL; + if (0 == db->eno) + { + json = json_loadb (db->buf, + db->buf_size, + JSON_REJECT_DUPLICATES | JSON_DISABLE_EOF_CHECK, + &error); + if (NULL == json) + { + JSON_WARN (error); + *response_code = 0; + } + } + GNUNET_free_non_null (db->buf); + db->buf = NULL; + db->buf_size = 0; + if (NULL != json) + { + if (CURLE_OK != + curl_easy_getinfo (eh, + CURLINFO_RESPONSE_CODE, + response_code)) + { + /* unexpected error... */ + GNUNET_break (0); + *response_code = 0; + } + } + return json; +} + + +/** + * Initial global setup logic, specifically runs the Curl setup. + */ +__attribute__ ((constructor)) +void +TALER_MERCHANT_constructor__ (void) +{ + CURLcode ret; + + if (CURLE_OK != (ret = curl_global_init (CURL_GLOBAL_DEFAULT))) + { + CURL_STRERROR (GNUNET_ERROR_TYPE_ERROR, + "curl_global_init", + ret); + TALER_MERCHANT_curl_fail = 1; + } +} + + +/** + * Cleans up after us, specifically runs the Curl cleanup. + */ +__attribute__ ((destructor)) +void +TALER_MERCHANT_destructor__ (void) +{ + if (TALER_MERCHANT_curl_fail) + return; + curl_global_cleanup (); +} + +/* end of merchant_api_context.c */ diff --git a/src/lib/merchant_api_context.h b/src/lib/merchant_api_context.h new file mode 100644 index 00000000..e54d34cc --- /dev/null +++ b/src/lib/merchant_api_context.h @@ -0,0 +1,169 @@ +/* + 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 merchant-lib/merchant_api_context.h + * @brief Internal interface to the context part of the merchant's HTTP API + * @author Sree Harsha Totakura <sreeharsha@totakura.in> + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include <taler/taler_signatures.h> + + +/** + * Entry in the context's job queue. + */ +struct MAC_Job; + +/** + * Function to call upon completion of a job. + * + * @param cls closure + * @param eh original easy handle (for inspection) + */ +typedef void +(*MAC_JobCompletionCallback)(void *cls, + CURL *eh); + + +/** + * Schedule a CURL request to be executed and call the given @a jcc + * upon its completion. Note that the context will make use of the + * CURLOPT_PRIVATE facility of the CURL @a eh. Applications can + * instead use #MAC_easy_to_closure to extract the @a jcc_cls argument + * from a valid @a eh afterwards. + * + * This function modifies the CURL handle to add the + * "Content-Type: application/json" header if @a add_json is set. + * + * @param ctx context to execute the job in + * @param eh curl easy handle for the request, will + * be executed AND cleaned up + * @param add_json add "application/json" content type header + * @param jcc callback to invoke upon completion + * @param jcc_cls closure for @a jcc + */ +struct MAC_Job * +MAC_job_add (struct TALER_MERCHANT_Context *ctx, + CURL *eh, + int add_json, + MAC_JobCompletionCallback jcc, + void *jcc_cls); + + +/** + * Obtain the `jcc_cls` argument from an `eh` that was + * given to #MAC_job_add(). + * + * @param eh easy handle that was used + * @return the `jcc_cls` that was given to #MAC_job_add(). + */ +void * +MAC_easy_to_closure (CURL *eh); + + +/** + * Cancel a job. Must only be called before the job completion + * callback is called for the respective job. + * + * @param job job to cancel + */ +void +MAC_job_cancel (struct MAC_Job *job); + + +/** + * @brief Buffer data structure we use to buffer the HTTP download + * before giving it to the JSON parser. + */ +struct MAC_DownloadBuffer +{ + + /** + * Download buffer + */ + void *buf; + + /** + * The size of the download buffer + */ + size_t buf_size; + + /** + * Error code (based on libc errno) if we failed to download + * (i.e. response too large). + */ + int eno; + +}; + + +/** + * Callback used when downloading the reply to an HTTP request. + * Just appends all of the data to the `buf` in the + * `struct MAC_DownloadBuffer` for further processing. The size of + * the download is limited to #GNUNET_MAX_MALLOC_CHECKED, if + * the download exceeds this size, we abort with an error. + * + * Should be used by the various routines as the + * CURLOPT_WRITEFUNCTION. A `struct MAC_DownloadBuffer` needs to be + * passed to the CURLOPT_WRITEDATA. + * + * Afterwards, `eno` needs to be checked to ensure that the download + * completed correctly. + * + * @param bufptr data downloaded via HTTP + * @param size size of an item in @a bufptr + * @param nitems number of items in @a bufptr + * @param cls the `struct KeysRequest` + * @return number of bytes processed from @a bufptr + */ +size_t +MAC_download_cb (char *bufptr, + size_t size, + size_t nitems, + void *cls); + + +/** + * Obtain information about the final result about the + * HTTP download. If the download was successful, parses + * the JSON in the @a db and returns it. Also returns + * the HTTP @a response_code. If the download failed, + * the return value is NULL. The response code is set + * in any case, on download errors to zero. + * + * Calling this function also cleans up @a db. + * + * @param db download buffer + * @param eh CURL handle (to get the response code) + * @param[out] response_code set to the HTTP response code + * (or zero if we aborted the download, i.e. + * because the response was too big, or if + * the JSON we received was malformed). + * @return NULL if downloading a JSON reply failed + */ +json_t * +MAC_download_get_result (struct MAC_DownloadBuffer *db, + CURL *eh, + long *response_code); + + +/* end of merchant_api_context.h */ diff --git a/src/lib/merchant_api_json.c b/src/lib/merchant_api_json.c new file mode 100644 index 00000000..9d03db65 --- /dev/null +++ b/src/lib/merchant_api_json.c @@ -0,0 +1,491 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/merchant_api_json.c + * @brief functions to parse incoming requests (JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include "merchant_api_json.h" + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return offset in @a spec where parsing failed, -1 on success (!) + */ +static int +parse_json (json_t *root, + struct MAJ_Specification *spec) +{ + int i; + json_t *pos; /* what's our current position? */ + + pos = root; + for (i=0;MAJ_CMD_END != spec[i].cmd;i++) + { + pos = json_object_get (root, + spec[i].field); + if (NULL == pos) + { + GNUNET_break_op (0); + return i; + } + switch (spec[i].cmd) + { + case MAJ_CMD_END: + GNUNET_assert (0); + return i; + case MAJ_CMD_AMOUNT: + if (GNUNET_OK != + TALER_json_to_amount (pos, + spec[i].details.amount)) + { + GNUNET_break_op (0); + return i; + } + break; + case MAJ_CMD_TIME_ABSOLUTE: + if (GNUNET_OK != + TALER_json_to_abs (pos, + spec[i].details.abs_time)) + { + GNUNET_break_op (0); + return i; + } + break; + + case MAJ_CMD_STRING: + { + const char *str; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + *spec[i].details.strptr = str; + } + break; + + case MAJ_CMD_BINARY_FIXED: + { + const char *str; + int res; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + spec[i].details.fixed_data.dest, + spec[i].details.fixed_data.dest_size); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + return i; + } + } + break; + + case MAJ_CMD_BINARY_VARIABLE: + { + const char *str; + size_t size; + void *data; + int res; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + size = (strlen (str) * 5) / 8; + if (size >= 1024) + { + GNUNET_break_op (0); + return i; + } + data = GNUNET_malloc (size); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + data, + size); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (data); + return i; + } + *spec[i].details.variable_data.dest_p = data; + *spec[i].details.variable_data.dest_size_p = size; + } + break; + + case MAJ_CMD_RSA_PUBLIC_KEY: + { + size_t size; + const char *str; + int res; + void *buf; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + size = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (size); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + size); + if (GNUNET_OK != res) + { + GNUNET_free (buf); + GNUNET_break_op (0); + return i; + } + *spec[i].details.rsa_public_key + = GNUNET_CRYPTO_rsa_public_key_decode (buf, + size); + GNUNET_free (buf); + if (NULL == spec[i].details.rsa_public_key) + { + GNUNET_break_op (0); + return i; + } + } + break; + + case MAJ_CMD_RSA_SIGNATURE: + { + size_t size; + const char *str; + int res; + void *buf; + + str = json_string_value (pos); + if (NULL == str) + { + GNUNET_break_op (0); + return i; + } + size = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (size); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + size); + if (GNUNET_OK != res) + { + GNUNET_free (buf); + GNUNET_break_op (0); + return i; + } + *spec[i].details.rsa_signature + = GNUNET_CRYPTO_rsa_signature_decode (buf, + size); + GNUNET_free (buf); + if (NULL == spec[i].details.rsa_signature) + return i; + } + break; + + case MAJ_CMD_UINT16: + { + json_int_t val; + + if (! json_is_integer (pos)) + { + GNUNET_break_op (0); + return i; + } + val = json_integer_value (pos); + if ( (0 > val) || (val > UINT16_MAX) ) + { + GNUNET_break_op (0); + return i; + } + *spec[i].details.u16 = (uint16_t) val; + } + break; + + case MAJ_CMD_JSON_OBJECT: + { + if (! (json_is_object (pos) || json_is_array (pos)) ) + { + GNUNET_break_op (0); + return i; + } + json_incref (pos); + *spec[i].details.obj = pos; + } + break; + + default: + GNUNET_break (0); + return i; + } + } + return -1; /* all OK! */ +} + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + * @param end number of elements in @a spec to process + */ +static void +parse_free (struct MAJ_Specification *spec, + int end) +{ + int i; + + for (i=0;i<end;i++) + { + switch (spec[i].cmd) + { + case MAJ_CMD_END: + GNUNET_assert (0); + return; + case MAJ_CMD_AMOUNT: + break; + case MAJ_CMD_TIME_ABSOLUTE: + break; + case MAJ_CMD_BINARY_FIXED: + break; + case MAJ_CMD_STRING: + break; + case MAJ_CMD_BINARY_VARIABLE: + GNUNET_free (*spec[i].details.variable_data.dest_p); + *spec[i].details.variable_data.dest_p = NULL; + *spec[i].details.variable_data.dest_size_p = 0; + break; + case MAJ_CMD_RSA_PUBLIC_KEY: + GNUNET_CRYPTO_rsa_public_key_free (*spec[i].details.rsa_public_key); + *spec[i].details.rsa_public_key = NULL; + break; + case MAJ_CMD_RSA_SIGNATURE: + GNUNET_CRYPTO_rsa_signature_free (*spec[i].details.rsa_signature); + *spec[i].details.rsa_signature = NULL; + break; + case MAJ_CMD_JSON_OBJECT: + json_decref (*spec[i].details.obj); + *spec[i].details.obj = NULL; + break; + default: + GNUNET_break (0); + break; + } + } +} + + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +MAJ_parse_json (const json_t *root, + struct MAJ_Specification *spec) +{ + int ret; + + ret = parse_json ((json_t *) root, + spec); + if (-1 == ret) + return GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "JSON field `%s` (%d) had unexpected value\n", + spec[ret].field, + ret); + parse_free (spec, ret); + return GNUNET_SYSERR; +} + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +MAJ_parse_free (struct MAJ_Specification *spec) +{ + int i; + + for (i=0;MAJ_CMD_END != spec[i].cmd;i++) ; + parse_free (spec, i); +} + + +/** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, + const char **strptr) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_STRING, + .field = name, + .details.strptr = strptr + }; + return ret; +} + + +/** + * Specification for parsing an absolute time value. + * + * @param name name of the JSON field + * @param at where to store the absolute time found under @a name + */ +struct MAJ_Specification +MAJ_spec_absolute_time (const char *name, + struct GNUNET_TIME_Absolute *at) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_TIME_ABSOLUTE, + .field = name, + .details.abs_time = at + }; + return ret; +} + + +/** + * Specification for parsing an amount value. + * + * @param name name of the JSON field + * @param amount where to store the amount found under @a name + */ +struct MAJ_Specification +MAJ_spec_amount (const char *name, + struct TALER_Amount *amount) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_AMOUNT, + .field = name, + .details.amount = amount + }; + return ret; +} + + +/** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, + uint16_t *u16) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_UINT16, + .field = name, + .details.u16 = u16 + }; + return ret; +} + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, + json_t **jsonp) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_JSON_OBJECT, + .field = name, + .details.obj = jsonp + }; + return ret; +} + + +/** + * Specification for parsing an RSA public key. + * + * @param name name of the JSON field + * @param pk where to store the RSA key found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_public_key (const char *name, + struct GNUNET_CRYPTO_rsa_PublicKey **pk) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_RSA_PUBLIC_KEY, + .field = name, + .details.rsa_public_key = pk + }; + return ret; +} + + +/** + * Specification for parsing an RSA signature. + * + * @param name name of the JSON field + * @param sig where to store the RSA signature found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_signature (const char *name, + struct GNUNET_CRYPTO_rsa_Signature **sig) +{ + struct MAJ_Specification ret = + { + .cmd = MAJ_CMD_RSA_SIGNATURE, + .field = name, + .details.rsa_signature = sig + }; + return ret; +} + + +/* end of merchant_api_json.c */ diff --git a/src/lib/merchant_api_json.h b/src/lib/merchant_api_json.h new file mode 100644 index 00000000..5f5265cf --- /dev/null +++ b/src/lib/merchant_api_json.h @@ -0,0 +1,331 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file lib/merchant_api_json.h + * @brief functions to parse incoming requests (JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include <jansson.h> + + +/** + * Enumeration with the various commands for the + * #MAJ_parse_json interpreter. + */ +enum MAJ_Command +{ + + /** + * End of command list. + */ + MAJ_CMD_END, + + /** + * Parse amount at current position. + */ + MAJ_CMD_AMOUNT, + + /** + * Parse absolute time at current position. + */ + MAJ_CMD_TIME_ABSOLUTE, + + /** + * Parse fixed binary value at current position. + */ + MAJ_CMD_BINARY_FIXED, + + /** + * Parse variable-size binary value at current position. + */ + MAJ_CMD_BINARY_VARIABLE, + + /** + * Parse RSA public key at current position. + */ + MAJ_CMD_RSA_PUBLIC_KEY, + + /** + * Parse RSA signature at current position. + */ + MAJ_CMD_RSA_SIGNATURE, + + /** + * Parse `const char *` JSON string at current position. + */ + MAJ_CMD_STRING, + + /** + * Parse `uint16_t` integer at the current position. + */ + MAJ_CMD_UINT16, + + /** + * Parse JSON object at the current position. + */ + MAJ_CMD_JSON_OBJECT, + + /** + * Parse ??? at current position. + */ + MAJ_CMD_C + +}; + + +/** + * @brief Entry in parser specification for #MAJ_parse_json. + */ +struct MAJ_Specification +{ + + /** + * Command to execute. + */ + enum MAJ_Command cmd; + + /** + * Name of the field to access. + */ + const char *field; + + /** + * Further details for the command. + */ + union { + + /** + * Where to store amount for #MAJ_CMD_AMOUNT. + */ + struct TALER_Amount *amount; + + /** + * Where to store time, for #MAJ_CMD_TIME_ABSOLUTE. + */ + struct GNUNET_TIME_Absolute *abs_time; + + /** + * Where to write binary data, for #MAJ_CMD_BINARY_FIXED. + */ + struct { + /** + * Where to write the data. + */ + void *dest; + + /** + * How many bytes to write to @e dest. + */ + size_t dest_size; + + } fixed_data; + + /** + * Where to write binary data, for #MAJ_CMD_BINARY_VARIABLE. + */ + struct { + /** + * Where to store the pointer with the data (is allocated). + */ + void **dest_p; + + /** + * Where to store the number of bytes allocated at `*dest`. + */ + size_t *dest_size_p; + + } variable_data; + + /** + * Where to store the RSA public key for #MAJ_CMD_RSA_PUBLIC_KEY + */ + struct GNUNET_CRYPTO_rsa_PublicKey **rsa_public_key; + + /** + * Where to store the RSA signature for #MAJ_CMD_RSA_SIGNATURE + */ + struct GNUNET_CRYPTO_rsa_Signature **rsa_signature; + + /** + * Details for #MAJ_CMD_EDDSA_SIGNATURE + */ + struct { + + /** + * Where to store the purpose. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose **purpose_p; + + /** + * Key to verify the signature against. + */ + const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key; + + } eddsa_signature; + + /** + * Where to store a pointer to the string. + */ + const char **strptr; + + /** + * Where to store 16-bit integer. + */ + uint16_t *u16; + + /** + * Where to store a JSON object. + */ + json_t **obj; + + } details; + +}; + + +/** + * Navigate and parse data in a JSON tree. + * + * @param root the JSON node to start the navigation at. + * @param spec parse specification array + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +int +MAJ_parse_json (const json_t *root, + struct MAJ_Specification *spec); + + +/** + * Free all elements allocated during a + * #MAJ_parse_json() operation. + * + * @param spec specification of the parse operation + */ +void +MAJ_parse_free (struct MAJ_Specification *spec); + + +/** + * End of a parser specification. + */ +#define MAJ_spec_end { .cmd = MAJ_CMD_END } + +/** + * Fixed size object (in network byte order, encoded using Crockford + * Base32hex encoding). + * + * @param name name of the JSON field + * @param obj pointer where to write the data (type of `*obj` will determine size) + */ +#define MAJ_spec_fixed_auto(name,obj) { .cmd = MAJ_CMD_BINARY_FIXED, .field = name, .details.fixed_data.dest = obj, .details.fixed_data.dest_size = sizeof (*obj) } + + +/** + * Variable size object (in network byte order, encoded using Crockford + * Base32hex encoding). + * + * @param name name of the JSON field + * @param obj pointer where to write the data (a `void **`) + * @param size where to store the number of bytes allocated for @a obj (of type `size_t *` + */ +#define MAJ_spec_varsize(name,obj,size) { .cmd = MAJ_CMD_BINARY_VARIABLE, .field = name, .details.variable_data.dest_p = obj, .details.variable_data.dest_size_p = size } + + +/** + * The expected field stores a string. + * + * @param name name of the JSON field + * @param strptr where to store a pointer to the field + */ +struct MAJ_Specification +MAJ_spec_string (const char *name, + const char **strptr); + + +/** + * Absolute time. + * + * @param name name of the JSON field + * @param[out] at where to store the absolute time found under @a name + */ +struct MAJ_Specification +MAJ_spec_absolute_time (const char *name, + struct GNUNET_TIME_Absolute *at); + + +/** + * 16-bit integer. + * + * @param name name of the JSON field + * @param[out] u16 where to store the integer found under @a name + */ +struct MAJ_Specification +MAJ_spec_uint16 (const char *name, + uint16_t *u16); + + +/** + * JSON object. + * + * @param name name of the JSON field + * @param[out] jsonp where to store the JSON found under @a name + */ +struct MAJ_Specification +MAJ_spec_json (const char *name, + json_t **jsonp); + + +/** + * Specification for parsing an amount value. + * + * @param name name of the JSON field + * @param amount where to store the amount under @a name + */ +struct MAJ_Specification +MAJ_spec_amount (const char *name, + struct TALER_Amount *amount); + + +/** + * Specification for parsing an RSA public key. + * + * @param name name of the JSON field + * @param pk where to store the RSA key found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_public_key (const char *name, + struct GNUNET_CRYPTO_rsa_PublicKey **pk); + + +/** + * Specification for parsing an RSA signature. + * + * @param name name of the JSON field + * @param sig where to store the RSA signature found under @a name + */ +struct MAJ_Specification +MAJ_spec_rsa_signature (const char *name, + struct GNUNET_CRYPTO_rsa_Signature **sig); + + + + +/* end of merchant_api_json.h */ diff --git a/src/lib/merchant_api_pay.c b/src/lib/merchant_api_pay.c new file mode 100644 index 00000000..e33da423 --- /dev/null +++ b/src/lib/merchant_api_pay.c @@ -0,0 +1,372 @@ +/* + 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 lib/merchant_api_pay.c + * @brief Implementation of the /pay request of the merchant's HTTP API + * @author Christian Grothoff + */ +#include "platform.h" +#include <curl/curl.h> +#include <jansson.h> +#include <microhttpd.h> /* just for HTTP status codes */ +#include <gnunet/gnunet_util_lib.h> +#include "taler_merchant_service.h" +#include "merchant_api_json.h" +#include "merchant_api_context.h" +#include <taler/taler_signatures.h> + + +/** + * @brief A Pay Handle + */ +struct TALER_MERCHANT_Pay +{ + + /** + * The url for this request. + */ + char *url; + + /** + * JSON encoding of the request to POST. + */ + char *json_enc; + + /** + * Handle for the request. + */ + struct MAC_Job *job; + + /** + * Function to call with the result. + */ + TALER_MERCHANT_PayCallback cb; + + /** + * Closure for @a cb. + */ + void *cb_cls; + + /** + * Download buffer + */ + struct MAC_DownloadBuffer db; + +}; + + + +/** + * Function called when we're done processing the + * HTTP /pay request. + * + * @param cls the `struct TALER_MERCHANT_Pay` + * @param eh the curl request handle + */ +static void +handle_pay_finished (void *cls, + CURL *eh) +{ + struct TALER_MERCHANT_Pay *ph = cls; + long response_code; + json_t *json; + + ph->job = NULL; + json = MAC_download_get_result (&ph->db, + eh, + &response_code); + switch (response_code) + { + case 0: + break; + case MHD_HTTP_OK: + break; + case MHD_HTTP_BAD_REQUEST: + /* This should never happen, either us or the merchant is buggy + (or API version conflict); just pass JSON reply to the application */ + break; + case MHD_HTTP_FORBIDDEN: + break; + case MHD_HTTP_UNAUTHORIZED: + /* Nothing really to verify, merchant says one of the signatures is + invalid; as we checked them, this should never happen, we + should pass the JSON reply to the application */ + break; + case MHD_HTTP_NOT_FOUND: + /* Nothing really to verify, this should never + happen, we should pass the JSON reply to the application */ + break; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + /* Server had an internal issue; we should retry, but this API + leaves this to the application */ + break; + default: + /* unexpected response code */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u\n", + response_code); + GNUNET_break (0); + response_code = 0; + break; + } + ph->cb (ph->cb_cls, + response_code, + json); + json_decref (json); + TALER_MERCHANT_pay_cancel (ph); +} + + +/** + * Pay a merchant. API for wallets that have the coin's private keys. + * + * @param merchant the merchant context + * @param mint_uri URI of the mint that the coins belong to + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_wallet (struct TALER_MERCHANT_Context *merchant, + const char *mint_uri, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute timestamp, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + unsigned int num_coins, + const struct TLAER_MERCHANT_PayCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls) +{ + struct TALER_MERCHANT_Pay *ph; + json_t *pay_obj; + json_t *j_coins; + CURL *eh; + struct GNUNET_HashCode h_wire; + struct TALER_Amount amount_without_fee; + unsigned int i; + struct TALER_DepositRequestPS dr; + + dr.purpose.purpose = htonl (TALER_SIGNATURE_WALLET_COIN_DEPOSIT); + dr.purpose.size = htonl (sizeof (struct TALER_DepositRequestPS)); + dr.h_contract = *h_contract; + dr.h_wire = *h_wire; + dr.timestamp = GNUNET_TIME_absolute_hton (timestamp); + dr.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + dr.transaction_id = GNUNET_htonll (transaction_id); + dr.merchant = *merchant_pub; + j_coins = json_array (); + for (i=0;i<num_coins;i++) + { + json_t *j_coin; + const struct TALER_MERCHANT_PayCoin *pc = &coins[i]; + struct TALER_CoinSpendSignatureP coin_sig; + struct TALER_Amount fee; + + /* prepare 'dr' for this coin to generate coin signature */ + GNUNET_CRYPTO_ecdhe_key_get_public (&pc->coin_priv.edche_priv, + &dr.coin_pub.ecdhe_pub); + TALER_amount_hton (&dr.amount_with_fee, + &pc->amount_with_fee); + if (GNUNET_SYSERR == + TALER_amount_subtract (&fee, + &pc->amount_with_fee, + &pc->fee)) + { + /* Integer underflow, fee larger than total amount? + This should not happen (client violated API!) */ + GNUNET_break (0); + json_decref (j_coins); + return NULL; + } + TALER_amount_hton (&dr.deposit_fee, + &fee); + GNUNET_CRYPTO_eddsa_sign (&pc->coin_priv.eddsa_priv, + &dr.purpose, + &coin_sig.eddsa_sig); + + /* create JSON for this coin */ + j_coin = json_pack ("{s:o, s:o," /* f/coin_pub */ + " s:o, s:o," /* denom_pub / ub_sig */ + " s:o}", /* coin_sig */ + "f", TALER_json_from_amount (&pc->amount_with_fee), + "coin_pub", TALER_json_from_data (&dr.coin_pub, + sizeof (struct TALER_CoinSpendPublicKeyP)), + "denom_pub", TALER_json_from_rsa_public_key (pc->denom_pub.rsa_public_key), + "ub_sig", TALER_json_from_rsa_signature (pc->denom_sig.rsa_signature), + "coin_sig", TALER_json_from_data (&coin_sig, + sizeof (coin_sig)) + ); + json_array_append (j_coins, + j_coin); + } + + + pay_obj = json_pack ("{s:o, s:o," /* H_wire/H_contract */ + " s:I, s:o," /* transaction id, timestamp */ + " s:o, s:s," /* refund_deadline, mint */ + " s:o, s:o," /* coins, max_fee */ + " s:o}", /* amount */ + "H_wire", TALER_json_from_data (&h_wire, + sizeof (h_wire)), + "H_contract", TALER_json_from_data (h_contract, + sizeof (struct GNUNET_HashCode)), + "transaction_id", (json_int_t) transaction_id, + "timestamp", TALER_json_from_abs (timestamp), + "refund_deadline", TALER_json_from_abs (refund_deadline), + "mint", mint_uri, + "coins", j_coins, + "max_fee", TALER_json_from_amount (max_fee), + "amount", TALER_json_from_amount (amount) + ); + + // optionally: add edate! "edate", TALER_json_from_abs (wire_deadline), + + ph = GNUNET_new (struct TALER_MERCHANT_Pay); +#if 0 + ph->merchant = merchant; + ph->cb = cb; + ph->cb_cls = cb_cls; + ph->url = MAH_path_to_url (merchant, "/pay"); + ph->depconf.purpose.size = htonl (sizeof (struct TALER_PayConfirmationPS)); + ph->depconf.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONFIRM_PAY); + ph->depconf.h_contract = *h_contract; + ph->depconf.h_wire = h_wire; + ph->depconf.transaction_id = GNUNET_htonll (transaction_id); + ph->depconf.timestamp = GNUNET_TIME_absolute_hton (timestamp); + ph->depconf.refund_deadline = GNUNET_TIME_absolute_hton (refund_deadline); + TALER_amount_subtract (&amount_without_fee, + amount, + &dki->fee_pay); + TALER_amount_hton (&ph->depconf.amount_without_fee, + &amount_without_fee); + ph->depconf.coin_pub = *coin_pub; + ph->depconf.merchant = *merchant_pub; + ph->amount_with_fee = *amount; + ph->coin_value = dki->value; + + eh = curl_easy_init (); + GNUNET_assert (NULL != (ph->json_enc = + json_dumps (pay_obj, + JSON_COMPACT))); + json_decref (pay_obj); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_URL, + ph->url)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDS, + ph->json_enc)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_POSTFIELDSIZE, + strlen (ph->json_enc))); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEFUNCTION, + &MAC_download_cb)); + GNUNET_assert (CURLE_OK == + curl_easy_setopt (eh, + CURLOPT_WRITEDATA, + &ph->db)); + ctx = MAH_handle_to_context (merchant); + ph->job = MAC_job_add (ctx, + eh, + GNUNET_YES, + &handle_pay_finished, + ph); + return ph; +#endif + return NULL; +} + + + +/** + * Pay a merchant. API for frontends talking to backends. Here, + * the frontend does not have the coin's private keys, but just + * the public keys and signatures. Note the sublte difference + * in the type of @a coins compared to #TALER_MERCHANT_pay(). + * + * @param merchant the merchant context + * @param mint_uri URI of the mint that the coins belong to + * @param h_wire hash of the merchant’s account details + * @param h_contract hash of the contact of the merchant with the customer + * @param timestamp timestamp when the contract was finalized, must match approximately the current time of the merchant + * @param transaction_id transaction id for the transaction between merchant and customer + * @param merchant_pub the public key of the merchant (used to identify the merchant for refund requests) + * @param refund_deadline date until which the merchant can issue a refund to the customer via the merchant (can be zero if refunds are not allowed) + * @param num_coins number of coins used to pay + * @param coins array of coins we use to pay + * @param coin_sig the signature made with purpose #TALER_SIGNATURE_WALLET_COIN_DEPOSIT made by the customer with the coin’s private key. + * @param pay_cb the callback to call when a reply for this request is available + * @param pay_cb_cls closure for @a pay_cb + * @return a handle for this request + */ +struct TALER_MERCHANT_Pay * +TALER_MERCHANT_pay_frontend (struct TALER_MERCHANT_Context *merchant, + const char *mint_uri, + const struct GNUNET_HashCode *h_wire, + const struct GNUNET_HashCode *h_contract, + struct GNUNET_TIME_Absolute timestamp, + uint64_t transaction_id, + const struct TALER_MerchantPublicKeyP *merchant_pub, + struct GNUNET_TIME_Absolute refund_deadline, + unsigned int num_coins, + const struct TALER_MERCHANT_PaidCoin *coins, + TALER_MERCHANT_PayCallback pay_cb, + void *pay_cb_cls) +{ + GNUNET_break (0); // FIXME: not implemented! + return NULL; +} + + +/** + * Cancel a pay permission request. This function cannot be used + * on a request handle if a response is already served for it. + * + * @param pay the pay permission request handle + */ +void +TALER_MERCHANT_pay_cancel (struct TALER_MERCHANT_Pay *pay) +{ + if (NULL != pay->job) + { + MAC_job_cancel (pay->job); + pay->job = NULL; + } + GNUNET_free_non_null (pay->db.buf); + GNUNET_free (pay->url); + GNUNET_free (pay->json_enc); + GNUNET_free (pay); +} + + +/* end of merchant_api_pay.c */ diff --git a/src/lib/test_merchant_api.c b/src/lib/test_merchant_api.c new file mode 100644 index 00000000..c264f9a3 --- /dev/null +++ b/src/lib/test_merchant_api.c @@ -0,0 +1,1345 @@ +/* + 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 merchant/test_merchant_api.c + * @brief testcase to test merchant's HTTP API interface + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler_util.h" +#include "taler_signatures.h" +#include "taler_mint_service.h" +#include "taler_merchant_service.h" +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> + +/** + * Main execution context for the main loop of the mint. + */ +static struct TALER_MINT_Context *ctx; + +/** + * Handle to access the mint. + */ +static struct TALER_MINT_Handle *mint; + +/** + * Main execution context for the main loop of the mint. + */ +static struct TALER_MERCHANT_Context *merchant; + +/** + * Task run on shutdown. + */ +static struct GNUNET_SCHEDULER_Task *shutdown_task; + +/** + * Task that runs the main event loop. + */ +static struct GNUNET_SCHEDULER_Task *ctx_task; + +/** + * Result of the testcases, #GNUNET_OK on success + */ +static int result; + + +/** + * Opcodes for the interpreter. + */ +enum OpCode +{ + /** + * Termination code, stops the interpreter loop (with success). + */ + OC_END = 0, + + /** + * Add funds to a reserve by (faking) incoming wire transfer. + */ + OC_ADMIN_ADD_INCOMING, + + /** + * Check status of a reserve. + */ + OC_WITHDRAW_STATUS, + + /** + * Withdraw a coin from a reserve. + */ + OC_WITHDRAW_SIGN, + + /** + * Pay with coins. + */ + OC_PAY + +}; + + +/** + * Structure specifying details about a coin to be melted. + * Used in a NULL-terminated array as part of command + * specification. + */ +struct MeltDetails +{ + + /** + * Amount to melt (including fee). + */ + const char *amount; + + /** + * Reference to reserve_withdraw operations for coin to + * be used for the /refresh/melt operation. + */ + const char *coin_ref; + +}; + + +/** + * Information about a fresh coin generated by the refresh operation. + */ +struct FreshCoin +{ + + /** + * If @e amount is NULL, this specifies the denomination key to + * use. Otherwise, this will be set (by the interpreter) to the + * denomination PK matching @e amount. + */ + const struct TALER_MINT_DenomPublicKey *pk; + + /** + * Set (by the interpreter) to the mint's signature over the + * coin's public key. + */ + struct TALER_DenominationSignature sig; + + /** + * Set (by the interpreter) to the coin's private key. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + +}; + + +/** + * Details for a mint operation to execute. + */ +struct Command +{ + /** + * Opcode of the command. + */ + enum OpCode oc; + + /** + * Label for the command, can be NULL. + */ + const char *label; + + /** + * Which response code do we expect for this command? + */ + unsigned int expected_response_code; + + /** + * Details about the command. + */ + union + { + + /** + * Information for a #OC_ADMIN_ADD_INCOMING command. + */ + struct + { + + /** + * Label to another admin_add_incoming command if we + * should deposit into an existing reserve, NULL if + * a fresh reserve should be created. + */ + const char *reserve_reference; + + /** + * String describing the amount to add to the reserve. + */ + const char *amount; + + /** + * Wire details (JSON). + */ + const char *wire; + + /** + * Set (by the interpreter) to the reserve's private key + * we used to fill the reserve. + */ + struct TALER_ReservePrivateKeyP reserve_priv; + + /** + * Set to the API's handle during the operation. + */ + struct TALER_MINT_AdminAddIncomingHandle *aih; + + } admin_add_incoming; + + /** + * Information for a #OC_WITHDRAW_STATUS command. + */ + struct + { + + /** + * Label to the #OC_ADMIN_ADD_INCOMING command which + * created the reserve. + */ + const char *reserve_reference; + + /** + * Set to the API's handle during the operation. + */ + struct TALER_MINT_ReserveStatusHandle *wsh; + + /** + * Expected reserve balance. + */ + const char *expected_balance; + + } reserve_status; + + /** + * Information for a #OC_WITHDRAW_SIGN command. + */ + struct + { + + /** + * Which reserve should we withdraw from? + */ + const char *reserve_reference; + + /** + * String describing the denomination value we should withdraw. + * A corresponding denomination key must exist in the mint's + * offerings. Can be NULL if @e pk is set instead. + */ + const char *amount; + + /** + * If @e amount is NULL, this specifies the denomination key to + * use. Otherwise, this will be set (by the interpreter) to the + * denomination PK matching @e amount. + */ + const struct TALER_MINT_DenomPublicKey *pk; + + /** + * Set (by the interpreter) to the mint's signature over the + * coin's public key. + */ + struct TALER_DenominationSignature sig; + + /** + * Set (by the interpreter) to the coin's private key. + */ + struct TALER_CoinSpendPrivateKeyP coin_priv; + + /** + * Blinding key used for the operation. + */ + struct TALER_DenominationBlindingKey blinding_key; + + /** + * Withdraw handle (while operation is running). + */ + struct TALER_MINT_ReserveWithdrawHandle *wsh; + + } reserve_withdraw; + + /** + * Information for a #OC_DEPOSIT command. + */ + struct + { + + /** + * Amount to pay. + */ + const char *amount; + + /** + * Reference to a reserve_withdraw operation for a coin to + * be used for the /deposit operation. + */ + const char *coin_ref; + + /** + * If this @e coin_ref refers to an operation that generated + * an array of coins, this value determines which coin to use. + */ + unsigned int coin_idx; + + /** + * JSON string describing the merchant's "wire details". + */ + const char *wire_details; + + /** + * JSON string describing the contract between the two parties. + */ + const char *contract; + + /** + * Transaction ID to use. + */ + uint64_t transaction_id; + + /** + * Relative time (to add to 'now') to compute the refund deadline. + * Zero for no refunds. + */ + struct GNUNET_TIME_Relative refund_deadline; + + /** + * Set (by the interpreter) to a fresh private key of the merchant, + * if @e refund_deadline is non-zero. + */ + struct TALER_MerchantPrivateKeyP merchant_priv; + + /** + * Deposit handle while operation is running. + */ + struct TALER_MINT_DepositHandle *dh; + + } pay; + + } details; + +}; + + +/** + * State of the interpreter loop. + */ +struct InterpreterState +{ + /** + * Keys from the mint. + */ + const struct TALER_MINT_Keys *keys; + + /** + * Commands the interpreter will run. + */ + struct Command *commands; + + /** + * Interpreter task (if one is scheduled). + */ + struct GNUNET_SCHEDULER_Task *task; + + /** + * Instruction pointer. Tells #interpreter_run() which + * instruction to run next. + */ + unsigned int ip; + +}; + + +/** + * Task that runs the context's event loop with the GNUnet scheduler. + * + * @param cls unused + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Run the context task, the working set has changed. + */ +static void +trigger_context_task () +{ + GNUNET_SCHEDULER_cancel (ctx_task); + ctx_task = GNUNET_SCHEDULER_add_now (&context_task, + NULL); +} + + +/** + * The testcase failed, return with an error code. + * + * @param is interpreter state to clean up + */ +static void +fail (struct InterpreterState *is) +{ + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Find a command by label. + * + * @param is interpreter state to search + * @param label label to look for + * @return NULL if command was not found + */ +static const struct Command * +find_command (const struct InterpreterState *is, + const char *label) +{ + unsigned int i; + const struct Command *cmd; + + if (NULL == label) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Attempt to lookup command for empty label\n"); + return NULL; + } + for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) + if ( (NULL != cmd->label) && + (0 == strcmp (cmd->label, + label)) ) + return cmd; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command not found: %s\n", + label); + return NULL; +} + + +/** + * Run the main interpreter loop that performs mint operations. + * + * @param cls contains the `struct InterpreterState` + * @param tc scheduler context + */ +static void +interpreter_run (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Function called upon completion of our /admin/add/incoming request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param full_response full response from the mint (for logging, in case of errors) + */ +static void +add_incoming_cb (void *cls, + unsigned int http_status, + json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.admin_add_incoming.aih = NULL; + if (MHD_HTTP_OK != http_status) + { + GNUNET_break (0); + fail (is); + return; + } + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Check if the given historic event @a h corresponds to the given + * command @a cmd. + * + * @param h event in history + * @param cmd an #OC_ADMIN_ADD_INCOMING command + * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not + */ +static int +compare_admin_add_incoming_history (const struct TALER_MINT_ReserveHistory *h, + const struct Command *cmd) +{ + struct TALER_Amount amount; + + if (TALER_MINT_RTT_DEPOSIT != h->type) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (cmd->details.admin_add_incoming.amount, + &amount)); + if (0 != TALER_amount_cmp (&amount, + &h->amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Check if the given historic event @a h corresponds to the given + * command @a cmd. + * + * @param h event in history + * @param cmd an #OC_WITHDRAW_SIGN command + * @return #GNUNET_OK if they match, #GNUNET_SYSERR if not + */ +static int +compare_reserve_withdraw_history (const struct TALER_MINT_ReserveHistory *h, + const struct Command *cmd) +{ + struct TALER_Amount amount; + struct TALER_Amount amount_with_fee; + + if (TALER_MINT_RTT_WITHDRAWAL != h->type) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (cmd->details.reserve_withdraw.amount, + &amount)); + GNUNET_assert (GNUNET_OK == + TALER_amount_add (&amount_with_fee, + &amount, + &cmd->details.reserve_withdraw.pk->fee_withdraw)); + if (0 != TALER_amount_cmp (&amount_with_fee, + &h->amount)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Function called with the result of a /reserve/status request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param[in] json original response in JSON format (useful only for diagnostics) + * @param balance current balance in the reserve, NULL on error + * @param history_length number of entries in the transaction history, 0 on error + * @param history detailed transaction history, NULL on error + */ +static void +reserve_status_cb (void *cls, + unsigned int http_status, + json_t *json, + const struct TALER_Amount *balance, + unsigned int history_length, + const struct TALER_MINT_ReserveHistory *history) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + struct Command *rel; + unsigned int i; + unsigned int j; + struct TALER_Amount amount; + + cmd->details.reserve_status.wsh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + GNUNET_break (0); + json_dumpf (json, stderr, 0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + /* FIXME: note that history events may come in a different + order than the commands. However, for now this works... */ + j = 0; + for (i=0;i<is->ip;i++) + { + switch ((rel = &is->commands[i])->oc) + { + case OC_ADMIN_ADD_INCOMING: + if ( ( (NULL != rel->label) && + (0 == strcmp (cmd->details.reserve_status.reserve_reference, + rel->label) ) ) || + ( (NULL != rel->details.admin_add_incoming.reserve_reference) && + (0 == strcmp (cmd->details.reserve_status.reserve_reference, + rel->details.admin_add_incoming.reserve_reference) ) ) ) + { + if (GNUNET_OK != + compare_admin_add_incoming_history (&history[j], + rel)) + { + GNUNET_break (0); + fail (is); + return; + } + j++; + } + break; + case OC_WITHDRAW_SIGN: + if (0 == strcmp (cmd->details.reserve_status.reserve_reference, + rel->details.reserve_withdraw.reserve_reference)) + { + if (GNUNET_OK != + compare_reserve_withdraw_history (&history[j], + rel)) + { + GNUNET_break (0); + fail (is); + return; + } + j++; + } + break; + default: + /* unreleated, just skip */ + break; + } + } + if (j != history_length) + { + GNUNET_break (0); + fail (is); + return; + } + if (NULL != cmd->details.reserve_status.expected_balance) + { + GNUNET_assert (GNUNET_OK == + TALER_string_to_amount (cmd->details.reserve_status.expected_balance, + &amount)); + if (0 != TALER_amount_cmp (&amount, + balance)) + { + GNUNET_break (0); + fail (is); + return; + } + } + break; + default: + /* Unsupported status code (by test harness) */ + GNUNET_break (0); + break; + } + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function called upon completion of our /reserve/withdraw request. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful status request + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param sig signature over the coin, NULL on error + * @param full_response full response from the mint (for logging, in case of errors) + */ +static void +reserve_withdraw_cb (void *cls, + unsigned int http_status, + const struct TALER_DenominationSignature *sig, + json_t *full_response) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.reserve_withdraw.wsh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (full_response, stderr, 0); + GNUNET_break (0); + fail (is); + return; + } + switch (http_status) + { + case MHD_HTTP_OK: + if (NULL == sig) + { + GNUNET_break (0); + fail (is); + return; + } + cmd->details.reserve_withdraw.sig.rsa_signature + = GNUNET_CRYPTO_rsa_signature_dup (sig->rsa_signature); + break; + case MHD_HTTP_PAYMENT_REQUIRED: + /* nothing to check */ + break; + default: + /* Unsupported status code (by test harness) */ + GNUNET_break (0); + break; + } + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function called with the result of a /pay operation. + * + * @param cls closure with the interpreter state + * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; + * 0 if the mint's reply is bogus (fails to follow the protocol) + * @param obj the received JSON reply, should be kept as proof (and, in case of errors, + * be forwarded to the customer) + */ +static void +pay_cb (void *cls, + unsigned int http_status, + const char *redirect_uri, + json_t *obj) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + + cmd->details.deposit.dh = NULL; + if (cmd->expected_response_code != http_status) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unexpected response code %u to command %s\n", + http_status, + cmd->label); + json_dumpf (obj, stderr, 0); + fail (is); + return; + } + is->ip++; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Find denomination key matching the given amount. + * + * @param keys array of keys to search + * @param amount coin value to look for + * @return NULL if no matching key was found + */ +static const struct TALER_MINT_DenomPublicKey * +find_pk (const struct TALER_MINT_Keys *keys, + const struct TALER_Amount *amount) +{ + unsigned int i; + struct GNUNET_TIME_Absolute now; + struct TALER_MINT_DenomPublicKey *pk; + char *str; + + now = GNUNET_TIME_absolute_get (); + for (i=0;i<keys->num_denom_keys;i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + (now.abs_value_us >= pk->valid_from.abs_value_us) && + (now.abs_value_us < pk->withdraw_valid_until.abs_value_us) ) + return pk; + } + /* do 2nd pass to check if expiration times are to blame for failure */ + str = TALER_amount_to_string (amount); + for (i=0;i<keys->num_denom_keys;i++) + { + pk = &keys->denom_keys[i]; + if ( (0 == TALER_amount_cmp (amount, + &pk->value)) && + ( (now.abs_value_us < pk->valid_from.abs_value_us) || + (now.abs_value_us > pk->withdraw_valid_until.abs_value_us) ) ) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Have denomination key for `%s', but with wrong expiration range %llu vs [%llu,%llu)\n", + str, + now.abs_value_us, + pk->valid_from.abs_value_us, + pk->withdraw_valid_until.abs_value_us); + GNUNET_free (str); + return NULL; + } + } + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No denomination key for amount %s found\n", + str); + GNUNET_free (str); + return NULL; +} + + +/** + * Run the main interpreter loop that performs mint operations. + * + * @param cls contains the `struct InterpreterState` + * @param tc scheduler context + */ +static void +interpreter_run (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct InterpreterState *is = cls; + struct Command *cmd = &is->commands[is->ip]; + const struct Command *ref; + struct TALER_ReservePublicKeyP reserve_pub; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute execution_date; + json_t *wire; + + is->task = NULL; + if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) + { + fprintf (stderr, + "Test aborted by shutdown request\n"); + fail (is); + return; + } + switch (cmd->oc) + { + case OC_END: + result = GNUNET_OK; + GNUNET_SCHEDULER_shutdown (); + return; + case OC_ADMIN_ADD_INCOMING: + if (NULL != + cmd->details.admin_add_incoming.reserve_reference) + { + ref = find_command (is, + cmd->details.admin_add_incoming.reserve_reference); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); + cmd->details.admin_add_incoming.reserve_priv + = ref->details.admin_add_incoming.reserve_priv; + } + else + { + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.admin_add_incoming.reserve_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.admin_add_incoming.reserve_priv.eddsa_priv, + &reserve_pub.eddsa_pub); + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.admin_add_incoming.amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.admin_add_incoming.amount, + is->ip); + fail (is); + return; + } + wire = json_loads (cmd->details.admin_add_incoming.wire, + JSON_REJECT_DUPLICATES, + NULL); + if (NULL == wire) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse wire details `%s' at %u\n", + cmd->details.admin_add_incoming.wire, + is->ip); + fail (is); + return; + } + execution_date = GNUNET_TIME_absolute_get (); + TALER_round_abs_time (&execution_date); + cmd->details.admin_add_incoming.aih + = TALER_MINT_admin_add_incoming (mint, + &reserve_pub, + &amount, + execution_date, + wire, + &add_incoming_cb, + is); + if (NULL == cmd->details.admin_add_incoming.aih) + { + GNUNET_break (0); + fail (is); + return; + } + trigger_context_task (); + return; + case OC_WITHDRAW_STATUS: + GNUNET_assert (NULL != + cmd->details.reserve_status.reserve_reference); + ref = find_command (is, + cmd->details.reserve_status.reserve_reference); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); + GNUNET_CRYPTO_eddsa_key_get_public (&ref->details.admin_add_incoming.reserve_priv.eddsa_priv, + &reserve_pub.eddsa_pub); + cmd->details.reserve_status.wsh + = TALER_MINT_reserve_status (mint, + &reserve_pub, + &reserve_status_cb, + is); + trigger_context_task (); + return; + case OC_WITHDRAW_SIGN: + GNUNET_assert (NULL != + cmd->details.reserve_withdraw.reserve_reference); + ref = find_command (is, + cmd->details.reserve_withdraw.reserve_reference); + GNUNET_assert (NULL != ref); + GNUNET_assert (OC_ADMIN_ADD_INCOMING == ref->oc); + if (NULL != cmd->details.reserve_withdraw.amount) + { + if (GNUNET_OK != + TALER_string_to_amount (cmd->details.reserve_withdraw.amount, + &amount)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to parse amount `%s' at %u\n", + cmd->details.reserve_withdraw.amount, + is->ip); + fail (is); + return; + } + cmd->details.reserve_withdraw.pk = find_pk (is->keys, + &amount); + } + if (NULL == cmd->details.reserve_withdraw.pk) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to determine denomination key at %u\n", + is->ip); + fail (is); + return; + } + + /* create coin's private key */ + { + struct GNUNET_CRYPTO_EddsaPrivateKey *priv; + + priv = GNUNET_CRYPTO_eddsa_key_create (); + cmd->details.reserve_withdraw.coin_priv.eddsa_priv = *priv; + GNUNET_free (priv); + } + GNUNET_CRYPTO_eddsa_key_get_public (&cmd->details.reserve_withdraw.coin_priv.eddsa_priv, + &coin_pub.eddsa_pub); + cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key + = GNUNET_CRYPTO_rsa_blinding_key_create (GNUNET_CRYPTO_rsa_public_key_len (cmd->details.reserve_withdraw.pk->key.rsa_public_key)); + cmd->details.reserve_withdraw.wsh + = TALER_MINT_reserve_withdraw (mint, + cmd->details.reserve_withdraw.pk, + &ref->details.admin_add_incoming.reserve_priv, + &cmd->details.reserve_withdraw.coin_priv, + &cmd->details.reserve_withdraw.blinding_key, + &reserve_withdraw_cb, + is); + if (NULL == cmd->details.reserve_withdraw.wsh) + { + GNUNET_break (0); + fail (is); + return; + } + trigger_context_task (); + return; + case OC_PAY: + { + GNUNET_break (0); // FIXME: not implemented! + trigger_context_task (); + return; + } + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown instruction %d at %u (%s)\n", + cmd->oc, + is->ip, + cmd->label); + fail (is); + return; + } + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Function run when the test terminates (good or bad). + * Cleans up our state. + * + * @param cls the interpreter state. + * @param tc unused + */ +static void +do_shutdown (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct InterpreterState *is = cls; + struct Command *cmd; + unsigned int i; + + shutdown_task = NULL; + for (i=0;OC_END != (cmd = &is->commands[i])->oc;i++) + { + switch (cmd->oc) + { + case OC_END: + GNUNET_assert (0); + break; + case OC_ADMIN_ADD_INCOMING: + if (NULL != cmd->details.admin_add_incoming.aih) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_admin_add_incoming_cancel (cmd->details.admin_add_incoming.aih); + cmd->details.admin_add_incoming.aih = NULL; + } + break; + case OC_WITHDRAW_STATUS: + if (NULL != cmd->details.reserve_status.wsh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_reserve_status_cancel (cmd->details.reserve_status.wsh); + cmd->details.reserve_status.wsh = NULL; + } + break; + case OC_WITHDRAW_SIGN: + if (NULL != cmd->details.reserve_withdraw.wsh) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Command %u (%s) did not complete\n", + i, + cmd->label); + TALER_MINT_reserve_withdraw_cancel (cmd->details.reserve_withdraw.wsh); + cmd->details.reserve_withdraw.wsh = NULL; + } + if (NULL != cmd->details.reserve_withdraw.sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (cmd->details.reserve_withdraw.sig.rsa_signature); + cmd->details.reserve_withdraw.sig.rsa_signature = NULL; + } + if (NULL != cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key) + { + GNUNET_CRYPTO_rsa_blinding_key_free (cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key); + cmd->details.reserve_withdraw.blinding_key.rsa_blinding_key = NULL; + } + break; + case OC_PAY: + GNUNET_break (0); // FIXME: not implemented + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unknown instruction %d at %u (%s)\n", + cmd->oc, + i, + cmd->label); + break; + } + } + if (NULL != is->task) + { + GNUNET_SCHEDULER_cancel (is->task); + is->task = NULL; + } + GNUNET_free (is); + if (NULL != ctx_task) + { + GNUNET_SCHEDULER_cancel (ctx_task); + ctx_task = NULL; + } + if (NULL != mint) + { + TALER_MINT_disconnect (mint); + mint = NULL; + } + if (NULL != ctx) + { + TALER_MINT_fini (ctx); + ctx = NULL; + } +} + + +/** + * Functions of this type are called to provide the retrieved signing and + * denomination keys of the mint. No TALER_MINT_*() functions should be called + * in this callback. + * + * @param cls closure + * @param keys information about keys of the mint + */ +static void +cert_cb (void *cls, + const struct TALER_MINT_Keys *keys) +{ + struct InterpreterState *is = cls; + + /* check that keys is OK */ +#define ERR(cond) do { if(!(cond)) break; GNUNET_break (0); GNUNET_SCHEDULER_shutdown(); return; } while (0) + ERR (NULL == keys); + ERR (0 == keys->num_sign_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u signing keys\n", + keys->num_sign_keys); + ERR (0 == keys->num_denom_keys); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Read %u denomination keys\n", + keys->num_denom_keys); +#undef ERR + + /* run actual tests via interpreter-loop */ + is->keys = keys; + is->task = GNUNET_SCHEDULER_add_now (&interpreter_run, + is); +} + + +/** + * Task that runs the context's event loop with the GNUnet scheduler. + * + * @param cls unused + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + long timeout; + int max_fd; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + struct GNUNET_NETWORK_FDSet *rs; + struct GNUNET_NETWORK_FDSet *ws; + struct GNUNET_TIME_Relative delay; + + ctx_task = NULL; + TALER_MINT_perform (ctx); + max_fd = -1; + timeout = -1; + FD_ZERO (&read_fd_set); + FD_ZERO (&write_fd_set); + FD_ZERO (&except_fd_set); + TALER_MINT_get_select_info (ctx, + &read_fd_set, + &write_fd_set, + &except_fd_set, + &max_fd, + &timeout); + if (timeout >= 0) + delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + delay = GNUNET_TIME_UNIT_FOREVER_REL; + rs = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (rs, + &read_fd_set, + max_fd + 1); + ws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (ws, + &write_fd_set, + max_fd + 1); + ctx_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + delay, + rs, + ws, + &context_task, + cls); + GNUNET_NETWORK_fdset_destroy (rs); + GNUNET_NETWORK_fdset_destroy (ws); +} + + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +static void +run (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct InterpreterState *is; + static struct Command commands[] = + { + /* Fill reserve with EUR:5.01, as withdraw fee is 1 ct per config */ + { .oc = OC_ADMIN_ADD_INCOMING, + .label = "create-reserve-1", + .expected_response_code = MHD_HTTP_OK, + .details.admin_add_incoming.wire = "{ \"type\":\"TEST\", \"bank\":\"source bank\", \"account\":42 }", + .details.admin_add_incoming.amount = "EUR:5.01" }, + /* Withdraw a 5 EUR coin, at fee of 1 ct */ + { .oc = OC_WITHDRAW_SIGN, + .label = "withdraw-coin-1", + .expected_response_code = MHD_HTTP_OK, + .details.reserve_withdraw.reserve_reference = "create-reserve-1", + .details.reserve_withdraw.amount = "EUR:5" }, + /* Check that deposit and withdraw operation are in history, and + that the balance is now at zero */ + { .oc = OC_WITHDRAW_STATUS, + .label = "withdraw-status-1", + .expected_response_code = MHD_HTTP_OK, + .details.reserve_status.reserve_reference = "create-reserve-1", + .details.reserve_status.expected_balance = "EUR:0" }, + /* Try to pay with the 5 EUR coin (in full) */ + { .oc = OC_PAY, + .label = "deposit-simple", + .expected_response_code = MHD_HTTP_OK, + .details.deposit.amount = "EUR:5", + .details.deposit.coin_ref = "withdraw-coin-1", + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.transaction_id = 1 }, + + /* Try to double-spend the 5 EUR coin with different wire details */ + { .oc = OC_PAY, + .label = "deposit-double-1", + .expected_response_code = MHD_HTTP_FORBIDDEN, + .details.deposit.amount = "EUR:5", + .details.deposit.coin_ref = "withdraw-coin-1", + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":43 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.transaction_id = 1 }, + /* Try to double-spend the 5 EUR coin at the same merchant (but different + transaction ID) */ + { .oc = OC_PAY, + .label = "deposit-double-2", + .expected_response_code = MHD_HTTP_FORBIDDEN, + .details.deposit.amount = "EUR:5", + .details.deposit.coin_ref = "withdraw-coin-1", + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":1 } }", + .details.deposit.transaction_id = 2 }, + /* Try to double-spend the 5 EUR coin at the same merchant (but different + contract) */ + { .oc = OC_PAY, + .label = "deposit-double-3", + .expected_response_code = MHD_HTTP_FORBIDDEN, + .details.deposit.amount = "EUR:5", + .details.deposit.coin_ref = "withdraw-coin-1", + .details.deposit.wire_details = "{ \"type\":\"TEST\", \"bank\":\"dest bank\", \"account\":42 }", + .details.deposit.contract = "{ \"items\"={ \"name\":\"ice cream\", \"value\":2 } }", + .details.deposit.transaction_id = 1 }, + + { .oc = OC_END } + }; + + is = GNUNET_new (struct InterpreterState); + is->commands = commands; + + ctx = TALER_MINT_init (); + GNUNET_assert (NULL != ctx); + ctx_task = GNUNET_SCHEDULER_add_now (&context_task, + ctx); + mint = TALER_MINT_connect (ctx, + "http://localhost:8081", + &cert_cb, is, + TALER_MINT_OPTION_END); + GNUNET_assert (NULL != mint); + shutdown_task + = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 150), + &do_shutdown, is); +} + + +/** + * Main function for the testcase for the mint API. + * + * @param argc expected to be 1 + * @param argv expected to only contain the program name + */ +int +main (int argc, + char * const *argv) +{ + struct GNUNET_OS_Process *proc; + struct GNUNET_OS_Process *mintd; + struct GNUNET_OS_Process *merchantd; + + GNUNET_log_setup ("test-mint-api", + "WARNING", + NULL); + proc = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-mint-keyup", + "taler-mint-keyup", + "-d", "test-mint-home", + "-m", "test-mint-home/master.priv", + NULL); + GNUNET_OS_process_wait (proc); + GNUNET_OS_process_destroy (proc); + mintd = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-mint-httpd", + "taler-mint-httpd", + "-d", "test-mint-home", + NULL); + merchantd = GNUNET_OS_start_process (GNUNET_NO, + GNUNET_OS_INHERIT_STD_ALL, + NULL, NULL, NULL, + "taler-merchant-httpd", + "taler-merchant-httpd", + "-c", "test-merchant-home", + NULL); + /* give child time to start and bind against the socket */ + fprintf (stderr, "Waiting for taler-mint-httpd to be ready"); + do + { + fprintf (stderr, "."); + sleep (1); + } + while (0 != system ("wget -q -t 1 -T 1 http://127.0.0.1:8081/keys -o /dev/null -O /dev/null")); + fprintf (stderr, "\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_run (&run, NULL); + GNUNET_OS_process_kill (mintd, + SIGTERM); + GNUNET_OS_process_wait (mintd); + GNUNET_OS_process_destroy (mintd); + return (GNUNET_OK == result) ? 0 : 1; +} + +/* end of test_merchant_api.c */ diff --git a/src/merchant/.gitignore b/src/merchant/.gitignore deleted file mode 100644 index ebe41680..00000000 --- a/src/merchant/.gitignore +++ /dev/null @@ -1 +0,0 @@ -taler-merchant-* diff --git a/src/merchant/Makefile.am b/src/merchant/Makefile.am deleted file mode 100644 index fbe80e0a..00000000 --- a/src/merchant/Makefile.am +++ /dev/null @@ -1,55 +0,0 @@ -AM_CPPFLAGS = -I$(top_srcdir)/src/include $(POSTGRESQL_CPPFLAGS) - -MERCHANT_DB = merchant_db.c merchant_db.h -bin_PROGRAMS = \ - taler-merchant-dbinit \ - taler-merchant-serve - -taler_merchant_dbinit_SOURCES = \ - taler_merchant_dbinit.c \ - $(MERCHANT_DB) -taler_merchant_dbinit_LDADD = \ - -lpq \ - -lgnunetutil \ - -ltalerutil \ - -ltalerpq \ - -lgnunetpostgres -taler_merchant_dbinit_LDFLAGS = \ - $(POSTGRESQL_LDFLAGS) - -check_PROGRAMS = \ - test-merchant \ - test-merchant-db - -test_merchant_SOURCES = \ - merchant.c \ - test_merchant.c -test_merchant_LDADD = \ - -ltalerutil \ - -lgnunetutil - -test_merchant_db_SOURCES = \ - $(MERCHANT_DB) \ - test_merchant_db.c -test_merchant_db_LDADD = \ - -ltalerutil \ - -ltalerpq \ - -lgnunetutil \ - -lgnunetpostgres \ - -lpq - -taler_merchant_serve_SOURCES = \ - taler_merchant_serve.c \ - merchant.c merchant.h \ - $(MERCHANT_DB) -taler_merchant_serve_LDADD = \ - -lpq \ - -lgnunetutil \ - -lgnunetpostgres \ - -lmicrohttpd \ - -ltalermint \ - -ltalerutil \ - -ltalerpq \ - -ljansson -taler_merchant_serve_LDFLAGS = \ - $(POSTGRESQL_LDFLAGS) diff --git a/src/merchant/merchant.c b/src/merchant/merchant.c deleted file mode 100644 index f124a030..00000000 --- a/src/merchant/merchant.c +++ /dev/null @@ -1,173 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant.c - * @brief Common utility functions for merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include "merchant.h" - - -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Parses mints from the configuration. - * - * @param cfg the configuration - * @param mints the array of mints upon successful parsing. Will be NULL upon - * error. - * @return the number of mints in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_MintInfo **mints) -{ - char *mints_str; - char *token_nf; /* do no free (nf) */ - char *mint_section; - char *mint_hostname; - char *mint_pubkey_enc; - struct MERCHANT_MintInfo *r_mints; - struct MERCHANT_MintInfo mint; - unsigned long long mint_port; - unsigned int cnt; - int OK; - - OK = 0; - mints_str = NULL; - token_nf = NULL; - mint_section = NULL; - mint_hostname = NULL; - mint_pubkey_enc = NULL; - r_mints = NULL; - cnt = 0; - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "TRUSTED_MINTS", - &mints_str)); - for (token_nf = strtok (mints_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&mint_section, - "mint-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "HOSTNAME", - &mint_hostname)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cfg, - mint_section, - "PORT", - &mint_port)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "PUBKEY", - &mint_pubkey_enc)); - EXITIF (GNUNET_OK != - GNUNET_CRYPTO_eddsa_public_key_from_string (mint_pubkey_enc, - strlen (mint_pubkey_enc), - &mint.pubkey)); - mint.hostname = mint_hostname; - mint.port = (uint16_t) mint_port; - GNUNET_array_append (r_mints, cnt, mint); - mint_hostname = NULL; - GNUNET_free (mint_pubkey_enc); - mint_pubkey_enc = NULL; - GNUNET_free (mint_section); - mint_section = NULL; - } - OK = 1; - - EXITIF_exit: - GNUNET_free_non_null (mints_str); - GNUNET_free_non_null (mint_section); - GNUNET_free_non_null (mint_hostname); - GNUNET_free_non_null (mint_pubkey_enc); - if (!OK) - { - GNUNET_free_non_null (r_mints); - return GNUNET_SYSERR; - } - - *mints = r_mints; - return cnt; -} - - -/** - * Parse the SEPA information from the configuration. If any of the required - * fileds is missing return NULL. - * - * @param cfg the configuration - * @return Sepa details as a structure; NULL upon error - */ -struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - struct MERCHANT_WIREFORMAT_Sepa *wf; - - wf = GNUNET_new (struct MERCHANT_WIREFORMAT_Sepa); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "IBAN", - &wf->iban)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "NAME", - &wf->name)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "BIC", - &wf->bic)); - return wf; - - EXITIF_exit: - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); - return NULL; - -} - - -/** - * Destroy and free resouces occupied by the wireformat structure - * - * @param wf the wireformat structure - */ -void -TALER_MERCHANT_destroy_wireformat_sepa (struct MERCHANT_WIREFORMAT_Sepa *wf) -{ - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); -} - -/* end of merchant.c */ diff --git a/src/merchant/merchant.h b/src/merchant/merchant.h deleted file mode 100644 index c66131ed..00000000 --- a/src/merchant/merchant.h +++ /dev/null @@ -1,110 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant.c - * @brief Common utility functions for merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#ifndef MERCHANT_H -#define MERCHANT_H - -#include <gnunet/gnunet_common.h> -#include <gnunet/gnunet_crypto_lib.h> - -/** - * A mint - */ -struct MERCHANT_MintInfo { - /** - * Hostname - */ - char *hostname; - - /** - * The public key of the mint - */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - - /** - * The port where the mint's service is running - */ - uint16_t port; - -}; - - -/** - * Parses mints from the configuration. - * - * @param cfg the configuration - * @param mints the array of mints upon successful parsing. Will be NULL upon - * error. - * @return the number of mints in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_MintInfo **mints); - - -GNUNET_NETWORK_STRUCT_BEGIN -struct MERCHANT_WIREFORMAT_Sepa -{ - /** - * The international bank account number - */ - char *iban; - - /** - * Name of the bank account holder - */ - char *name; - - /** - *The bank identification code - */ - char *bic; - - /** - * The latest payout date when the payment corresponding to this account has - * to take place. A value of 0 indicates a transfer as soon as possible. - */ - struct GNUNET_TIME_AbsoluteNBO payout; -}; -GNUNET_NETWORK_STRUCT_END - -/** - * Parse the SEPA information from the configuration. If any of the required - * fileds is missing return NULL. - * - * @param cfg the configuration - * @return Sepa details as a structure; NULL upon error - */ -struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); - - -/** - * Destroy and free resouces occupied by the wireformat structure - * - * @param wf the wireformat structure - */ -void -TALER_MERCHANT_destroy_wireformat_sepa (struct MERCHANT_WIREFORMAT_Sepa *wf); - -#endif /* MERCHANT_H */ diff --git a/src/merchant/merchant_db.c b/src/merchant/merchant_db.c deleted file mode 100644 index e87dbcc8..00000000 --- a/src/merchant/merchant_db.c +++ /dev/null @@ -1,347 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.c - * @brief database helper functions used by the merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <taler/taler_util.h> -#include <taler/db_pq.h> -#include "merchant_db.h" - - -#define PQSQL_strerror(kind, cmd, res) \ - GNUNET_log_from (kind, "merchant-db", \ - "SQL %s failed at %s:%u with error: %s", \ - cmd, __FILE__, __LINE__, PQresultErrorMessage (res)); - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Connect to postgresql database - * - * @param cfg the configuration handle - * @return connection to the postgresql database; NULL upon error - */ -PGconn * -MERCHANT_DB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - return GNUNET_POSTGRES_connect (cfg, "merchant-db"); -} - - -/** - * Disconnect from the database - * - * @param conn database handle to close - */ -void -MERCHANT_DB_disconnect (PGconn *conn) -{ - PQfinish (conn); -} - - -/** - * Initialise merchant tables - * - * @param conn the connection handle to postgres db. - * @param tmp GNUNET_YES if the tables are to be made temporary i.e. their - * contents are dropped when the @a conn is closed - * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure - */ -int -MERCHANT_DB_initialise (PGconn *conn, int tmp) -{ - const char *tmp_str = (1 == tmp) ? "TEMPORARY" : ""; - char *sql; - PGresult *res; - ExecStatusType status; - int ret; - - res = NULL; - (void) GNUNET_asprintf (&sql, - "BEGIN TRANSACTION;" - "CREATE %1$s TABLE IF NOT EXISTS contracts (" - "transaction_id SERIAL8 PRIMARY KEY," - "amount INT4 NOT NULL," - "amount_fraction INT4 NOT NULL," - "description TEXT NOT NULL," - "nounce BYTEA NOT NULL," - "expiry INT8 NOT NULL," - "product INT8 NOT NULL);" - "CREATE %1$s TABLE IF NOT EXISTS checkouts (" - "coin_pub BYTEA PRIMARY KEY," - "transaction_id INT8 REFERENCES contracts(transaction_id)," - "amount INT4 NOT NULL," - "amount_fraction INT4 NOT NULL," - "coin_sig BYTEA NOT NULL);", - tmp_str); - ret = GNUNET_POSTGRES_exec (conn, sql); - (void) GNUNET_POSTGRES_exec (conn, - (GNUNET_OK == ret) ? "COMMIT;" : "ROLLBACK"); - GNUNET_free (sql); - if (GNUNET_OK != ret) - return ret; - - while (NULL != (res = PQgetResult (conn))) - { - status = PQresultStatus (res); - PQclear (res); - } - - EXITIF (NULL == (res = PQprepare - (conn, - "contract_create", - "INSERT INTO contracts" - "(amount, amount_fraction, description," - "nounce, expiry, product) VALUES" - "($1, $2, $3, $4, $5, $6)" - "RETURNING transaction_id", - 6, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "get_contract_product", - "SELECT (" - "product" - ") FROM contracts " - "WHERE (" - "transaction_id=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus(res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "checkout_create", - "INSERT INTO checkouts (" - "coin_pub," - "transaction_id," - "amount," - "amount_fraction," - "coin_sig" - ") VALUES (" - "$1, $2, $3, $4, $5" - ")", - 5, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus (res))); - PQclear (res); - - EXITIF (NULL == (res = PQprepare - (conn, - "get_checkout_product", - "SELECT (" - "product" - ") FROM contracts " - "WHERE " - "transaction_id IN (" - "SELECT (transaction_id) FROM checkouts " - "WHERE coin_pub=$1" - ")", - 1, NULL))); - EXITIF (PGRES_COMMAND_OK != (status = PQresultStatus (res))); - PQclear (res); - - return GNUNET_OK; - - EXITIF_exit: - if (NULL != res) - { - PQSQL_strerror (GNUNET_ERROR_TYPE_ERROR, "PQprepare", res); - PQclear (res); - } - return GNUNET_SYSERR; -} - - -/** - * Inserts a contract record into the database and if successfull returns the - * serial number of the inserted row. - * - * @param conn the database connection - * @param expiry the time when the contract will expire - * @param amount the taler amount corresponding to the contract - * @param desc descripition of the contract - * @param nounce a random 64-bit nounce - * @param product description to identify a product - * @return -1 upon error; the serial id of the inserted contract upon success - */ -long long -MERCHANT_DB_contract_create (PGconn *conn, - struct GNUNET_TIME_Absolute expiry, - struct TALER_Amount *amount, - const char *desc, - uint64_t nounce, - uint64_t product) -{ - PGresult *res; - uint64_t expiry_ms_nbo; - uint32_t value_nbo; - uint32_t fraction_nbo; - uint64_t nounce_nbo; - ExecStatusType status; - long long id; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (&value_nbo), - TALER_DB_QUERY_PARAM_PTR (&fraction_nbo), - TALER_DB_QUERY_PARAM_PTR_SIZED (desc, strlen(desc)), - TALER_DB_QUERY_PARAM_PTR (&nounce_nbo), - TALER_DB_QUERY_PARAM_PTR (&expiry_ms_nbo), - TALER_DB_QUERY_PARAM_PTR (&product), - TALER_DB_QUERY_PARAM_END - }; - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("transaction_id", &id), - TALER_DB_RESULT_SPEC_END - }; - - expiry_ms_nbo = GNUNET_htonll (expiry.abs_value_us); - value_nbo = htonl (amount->value); - fraction_nbo = htonl (amount->fraction); - nounce_nbo = GNUNET_htonll (nounce); - product = GNUNET_htonll (product); - res = TALER_DB_exec_prepared (conn, "contract_create", params); - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_DB_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_ntohll ((uint64_t) id); - - EXITIF_exit: - PQclear (res); - return -1; -} - -long long -MERCHANT_DB_get_contract_product (PGconn *conn, - uint64_t contract_id) -{ - PGresult *res; - int64_t product; - ExecStatusType status; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (&contract_id), - TALER_DB_QUERY_PARAM_END - }; - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("product", &product), - TALER_DB_RESULT_SPEC_END - }; - - contract_id = GNUNET_htonll (contract_id); - res = TALER_DB_exec_prepared (conn, "get_contract_product", params); - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_DB_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_ntohll ((uint64_t) product); - - EXITIF_exit: - PQclear (res); - return -1; -} - -unsigned int -MERCHANT_DB_checkout_create (PGconn *conn, - struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, - uint64_t transaction_id, - struct TALER_Amount *amount, - struct GNUNET_CRYPTO_EddsaSignature *coin_sig) -{ - PGresult *res; - ExecStatusType status; - uint32_t value_nbo; - uint32_t fraction_nbo; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (coin_pub), - TALER_DB_QUERY_PARAM_PTR (&transaction_id), - TALER_DB_QUERY_PARAM_PTR (&value_nbo), - TALER_DB_QUERY_PARAM_PTR (&fraction_nbo), - TALER_DB_QUERY_PARAM_PTR (coin_sig), - TALER_DB_QUERY_PARAM_END - }; - - transaction_id = GNUNET_htonll (transaction_id); - value_nbo = htonl (amount->value); - fraction_nbo = htonl (amount->fraction); - res = TALER_DB_exec_prepared (conn, "checkout_create", params); - status = PQresultStatus (res); - EXITIF (PGRES_COMMAND_OK != status); - PQclear (res); - return GNUNET_OK; - - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; -} - - -long long -MERCHANT_DB_get_checkout_product (PGconn *conn, - struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub) -{ - PGresult *res; - ExecStatusType status; - uint64_t product; - struct TALER_DB_QueryParam params[] = { - TALER_DB_QUERY_PARAM_PTR (coin_pub), - TALER_DB_QUERY_PARAM_END - }; - struct TALER_DB_ResultSpec rs[] = { - TALER_DB_RESULT_SPEC("product", &product), - TALER_DB_RESULT_SPEC_END - }; - - product = -1; - res = TALER_DB_exec_prepared (conn, "get_checkout_product", params); - status = PQresultStatus (res); - EXITIF (PGRES_TUPLES_OK != status); - if (0 == PQntuples (res)) - { - char *coin_pub_enc; - coin_pub_enc = GNUNET_CRYPTO_eddsa_public_key_to_string (coin_pub); - LOG_DEBUG ("Checkout not found for given coin: %s\n", - coin_pub_enc); - GNUNET_free (coin_pub_enc); - goto EXITIF_exit; - } - EXITIF (1 != PQntuples (res)); - EXITIF (GNUNET_YES != TALER_DB_extract_result (res, rs, 0)); - PQclear (res); - return GNUNET_ntohll ((uint64_t) product); - - EXITIF_exit: - PQclear (res); - return -1; -} -/* end of merchant-db.c */ diff --git a/src/merchant/merchant_db.h b/src/merchant/merchant_db.h deleted file mode 100644 index 734d547f..00000000 --- a/src/merchant/merchant_db.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/merchant_db.h - * @brief database helper functions used by the merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#ifndef MERCHANT_DB_H -#define MERCHANT_DB_H - -#include <gnunet/gnunet_postgres_lib.h> -#include <taler/taler_util.h> - -/** - * Connect to postgresql database - * - * @param cfg the configuration handle - * @return connection to the postgresql database; NULL upon error - */ -PGconn * -MERCHANT_DB_connect (const struct GNUNET_CONFIGURATION_Handle *cfg); - - -/** - * Disconnect from the database - * - * @param conn database handle to close - */ -void -MERCHANT_DB_disconnect (PGconn *conn); - - -/** - * Initialise merchant tables - * - * @param conn the connection handle to postgres db. - * @param tmp GNUNET_YES if the tables are to be made temporary i.e. their - * contents are dropped when the @a conn is closed - * @return GNUNET_OK upon success; GNUNET_SYSERR upon failure - */ -int -MERCHANT_DB_initialise (PGconn *conn, int tmp); - - -/** - * Inserts a contract record into the database and if successfull returns the - * serial number of the inserted row. - * - * @param conn the database connection - * @param expiry the time when the contract will expire - * @param amount the taler amount corresponding to the contract - * @param desc descripition of the contract - * @param nounce a random 64-bit nounce - * @param product description to identify a product - * @return -1 upon error; the serial id of the inserted contract upon success - */ -long long -MERCHANT_DB_contract_create (PGconn *conn, - struct GNUNET_TIME_Absolute expiry, - struct TALER_Amount *amount, - const char *desc, - uint64_t nounce, - uint64_t product); - -long long -MERCHANT_DB_get_contract_product (PGconn *conn, - uint64_t contract_id); - -unsigned int -MERCHANT_DB_checkout_create (PGconn *conn, - struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub, - uint64_t transaction_id, - struct TALER_Amount *amount, - struct GNUNET_CRYPTO_EddsaSignature *coin_sig); - - -long long -MERCHANT_DB_get_checkout_product (PGconn *conn, - struct GNUNET_CRYPTO_EddsaPublicKey *coin_pub); - -#endif /* MERCHANT_DB_H */ - -/* end of merchant-db.h */ diff --git a/src/merchant/taler_merchant_dbinit.c b/src/merchant/taler_merchant_dbinit.c deleted file mode 100644 index e6d0af9d..00000000 --- a/src/merchant/taler_merchant_dbinit.c +++ /dev/null @@ -1,79 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/taler_merchant_dbinit.c - * @brief Program to initialise merchant database - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include "merchant_db.h" - - -/** - * Global execution result - */ -static int result; - - -/** - * Main function that will be run by the scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param config configuration - */ -static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *config) -{ - PGconn *conn; - - conn = MERCHANT_DB_connect (config); - if (NULL == conn) - return; - if (GNUNET_OK == MERCHANT_DB_initialise (conn, GNUNET_NO)) - result = GNUNET_OK; - MERCHANT_DB_disconnect (conn); -} - - -/** - * The main function - * - * @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_END - }; - - result = GNUNET_SYSERR; - if (GNUNET_OK != - GNUNET_PROGRAM_run (argc, argv, "taler-merchant-dbinit", - gettext_noop - ("Initialise Taler Merchant's database"), - options, &run, NULL)) - return 3; - return (GNUNET_OK == result) ? 0 : 1; -} diff --git a/src/merchant/taler_merchant_serve.c b/src/merchant/taler_merchant_serve.c deleted file mode 100644 index 0f00a8c2..00000000 --- a/src/merchant/taler_merchant_serve.c +++ /dev/null @@ -1,1539 +0,0 @@ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <microhttpd.h> -#include <taler/taler_util.h> -#include "merchant.h" -#include "merchant_db.h" -#include <taler/taler_mint_service.h> -#include <taler/taler_signatures.h> -#include <taler/taler_json_lib.h> - - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Shorthand for exit jumps due to protocol exceptions resulting from client's - * mistakes - */ -#define EXITIF_OP(cond) \ - do { \ - if (cond) { GNUNET_break_op (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Print JSON parsing related error information - */ -#define WARN_JSON(error) \ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ - "JSON parsing failed at %s:%u: %s (%s)", \ - __FILE__, __LINE__, error.text, error.source) - -/** - * Macro to round microseconds to seconds in GNUNET_TIME_* structs. - */ -#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000) - - -struct ContractData -{ - char *product; -}; - - -GNUNET_NETWORK_STRUCT_BEGIN - -struct Contract -{ - /** - * The signature of the merchant for this contract - */ - struct GNUNET_CRYPTO_EddsaSignature sig; - - /** - * Purpose header for the signature over contract - */ - struct GNUNET_CRYPTO_EccSignaturePurpose purpose; - - /** - * The transaction identifier - */ - char m[13]; - - /** - * Expiry time - */ - struct GNUNET_TIME_AbsoluteNBO t; - - /** - * The invoice amount - */ - struct TALER_AmountNBO amount; - - /** - * The hash of the preferred wire format + nounce - */ - struct GNUNET_HashCode h_wire; - - /** - * The contract data - */ - char a[]; -}; - -GNUNET_NETWORK_STRUCT_END - -/** - * A download object - */ -struct Download { - struct Download *next; - struct Download *prev; - char *filename; - struct MHD_Response *resp; - unsigned int id; -}; - -/** - * DLL for downloadable objects - */ -struct Download *dwn_head; -struct Download *dwn_tail; - -/** - * MHD response object for listing all products - */ -struct MHD_Response *list_products_resp; - -/** - * Number of files we make available for downloading - */ -static unsigned int ndownloads; - - -/** - * Context information of the mints we trust - */ -struct Mint -{ - /** - * Public key of this mint - */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - - /** - * Connection handle to this mint - */ - struct TALER_MINT_Handle *conn; -}; - -/** - * Hashmap to store the mint context information - */ -static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; - -/** - * Our private key - */ -struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; - -/** - * Connection handle to the our database - */ -PGconn *db_conn; - -/** - * The MHD Daemon - */ -static struct MHD_Daemon *mhd; - -/** - * Our wireformat - */ -static struct MERCHANT_WIREFORMAT_Sepa *wire; - -/** - * Hash of the wireformat - */ -static struct GNUNET_HashCode h_wire; - -/** - * Shutdown task identifier - */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; - -/** - * Task for calling the select on MHD's sockets - */ -static struct GNUNET_SCHEDULER_Task *select_task; - -/** - * The port we are running on - */ -static long long unsigned port; - -/** - * Mint context - */ -static struct TALER_MINT_Context *mctx; - -/** - * Our hostname - */ -static char *hostname; - -/** - * Directory of data items to serve - */ -static char *data_dir; - -/** - * Should we do a dry run where temporary tables are used for storing the data. - */ -static int dry; - -/** - * Global return code - */ -static int result; - - - -/** - * Send JSON object as response. Decreases the reference count of the - * JSON object. - * - * @param connection the MHD connection - * @param json the json object - * @param status_code the http status code - * @return MHD result code - */ -static int -send_response_json (struct MHD_Connection *connection, - json_t *json, - unsigned int status_code) -{ - struct MHD_Response *resp; - char *json_str; - - json_str = json_dumps (json, JSON_INDENT(2)); - json_decref (json); - resp = MHD_create_response_from_buffer (strlen (json_str), json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - return MHD_NO; - return MHD_queue_response (connection, status_code, resp); -} - - -/* ************ JSON post-processing logic; FIXME: why do we use JSON here!? ********** */ - - -/** - * Initial size for POST - * request buffer. - */ -#define REQUEST_BUFFER_INITIAL 1024 - -/** - * Maximum POST request size - */ -#define REQUEST_BUFFER_MAX (1024*1024) - - -/** - * Buffer for POST requests. - */ -struct Buffer -{ - /** - * Allocated memory - */ - char *data; - - /** - * Number of valid bytes in buffer. - */ - size_t fill; - - /** - * Number of allocated bytes in buffer. - */ - size_t alloc; -}; - - -/** - * Initialize a buffer. - * - * @param buf the buffer to initialize - * @param data the initial data - * @param data_size size of the initial data - * @param alloc_size size of the buffer - * @param max_size maximum size that the buffer can grow to - * @return a GNUnet result code - */ -static int -buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) -{ - if (data_size > max_size || alloc_size > max_size) - return GNUNET_SYSERR; - if (data_size > alloc_size) - alloc_size = data_size; - buf->data = GNUNET_malloc (alloc_size); - memcpy (buf->data, data, data_size); - return GNUNET_OK; -} - - -/** - * Free the data in a buffer. Does *not* free - * the buffer object itself. - * - * @param buf buffer to de-initialize - */ -static void -buffer_deinit (struct Buffer *buf) -{ - GNUNET_free (buf->data); - buf->data = NULL; -} - - -/** - * Append data to a buffer, growing the buffer if necessary. - * - * @param buf the buffer to append to - * @param data the data to append - * @param size the size of @a data - * @param max_size maximum size that the buffer can grow to - * @return GNUNET_OK on success, - * GNUNET_NO if the buffer can't accomodate for the new data - * GNUNET_SYSERR on fatal error (out of memory?) - */ -static int -buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) -{ - if (buf->fill + data_size > max_size) - return GNUNET_NO; - if (data_size + buf->fill > buf->alloc) - { - char *new_buf; - size_t new_size = buf->alloc; - while (new_size < buf->fill + data_size) - new_size += 2; - if (new_size > max_size) - return GNUNET_NO; - new_buf = GNUNET_malloc (new_size); - memcpy (new_buf, buf->data, buf->fill); - buf->data = new_buf; - buf->alloc = new_size; - } - memcpy (buf->data + buf->fill, data, data_size); - buf->fill += data_size; - return GNUNET_OK; -} - - - -/** - * Process a POST request containing a JSON object. - * - * @param connection the MHD connection - * @param con_cs the closure (contains a 'struct Buffer *') - * @param upload_data the POST data - * @param upload_data_size the POST data size - * @param json the JSON object for a completed request - * - * @returns - * GNUNET_YES if json object was parsed - * GNUNET_NO is request incomplete or invalid - * GNUNET_SYSERR on internal error - */ -static int -process_post_json (struct MHD_Connection *connection, - void **con_cls, - const char *upload_data, - size_t *upload_data_size, - json_t **json) -{ - struct Buffer *r = *con_cls; - - if (NULL == *con_cls) - { - /* We are seeing a fresh POST request. */ - - r = GNUNET_new (struct Buffer); - if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, - REQUEST_BUFFER_INITIAL, REQUEST_BUFFER_MAX)) - { - *con_cls = NULL; - buffer_deinit (r); - GNUNET_free (r); - return GNUNET_SYSERR; - } - *upload_data_size = 0; - *con_cls = r; - return GNUNET_NO; - } - if (0 != *upload_data_size) - { - /* We are seeing an old request with more data available. */ - - if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, - REQUEST_BUFFER_MAX)) - { - /* Request too long or we're out of memory. */ - - *con_cls = NULL; - buffer_deinit (r); - GNUNET_free (r); - return GNUNET_SYSERR; - } - *upload_data_size = 0; - return GNUNET_NO; - } - - /* We have seen the whole request. */ - - *json = json_loadb (r->data, r->fill, 0, NULL); - buffer_deinit (r); - GNUNET_free (r); - if (NULL == *json) - { - struct MHD_Response *resp; - int ret; - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); - resp = MHD_create_response_from_buffer (strlen ("parse error"), - "parse error", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_BAD_REQUEST, - resp); - MHD_destroy_response (resp); - return ret; - } - *con_cls = NULL; - - return GNUNET_YES; -} - - -/* ************** END of JSON POST processing logic ************ */ - - -static struct GNUNET_HashCode -hash_wireformat (uint64_t nounce) -{ - struct GNUNET_HashContext *hc; - struct GNUNET_HashCode hash; - - hc = GNUNET_CRYPTO_hash_context_start (); - GNUNET_CRYPTO_hash_context_read (hc, wire->iban, strlen (wire->iban)); - GNUNET_CRYPTO_hash_context_read (hc, wire->name, strlen (wire->name)); - GNUNET_CRYPTO_hash_context_read (hc, wire->bic, strlen (wire->bic)); - nounce = GNUNET_htonll (nounce); - GNUNET_CRYPTO_hash_context_read (hc, &nounce, sizeof (nounce)); - GNUNET_CRYPTO_hash_context_finish (hc, &hash); - return hash; -} - - -static json_t * -build_json_contract (struct Contract *contract) -{ - return json_pack ("{s:s, s:o, s:o, s:s, s:o, s:o}", - "transaction_id", contract->m, - "expiry", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (contract->t)), - "amount", TALER_JSON_from_amount (TALER_amount_ntoh (contract->amount)), - "description", contract->a, - "H_wire", TALER_JSON_from_data (&contract->h_wire, sizeof (struct GNUNET_HashCode)), - "msig", TALER_JSON_from_data (&contract->sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); -} - -/** - * Cleeanup entries in the peer map. - * - * @param cls closure - * @param key current public key - * @param value value in the hash map - * @return #GNUNET_YES if we should continue to - * iterate, - * #GNUNET_NO if not. - */ -static int -mints_cleanup_iterator (void *cls, - const struct GNUNET_PeerIdentity *key, - void *value) -{ - struct Mint *mint = value; - - if (NULL != mint->conn) - TALER_MINT_disconnect (mint->conn); - GNUNET_CONTAINER_multipeermap_remove (mints_map, key, mint); - GNUNET_free (mint); - return GNUNET_YES; -} - - -/** - * Shutdown task - * - * @param cls NULL - * @param tc scheduler task context - */ -static void -do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct Download *dwn; - - shutdown_task = NULL; - if (NULL != select_task) - { - GNUNET_SCHEDULER_cancel (select_task); - select_task = NULL; - } - if (NULL != list_products_resp) - { - MHD_destroy_response (list_products_resp); - list_products_resp = NULL; - } - if (NULL != mhd) - { - MHD_stop_daemon (mhd); - mhd = NULL; - } - if (NULL != db_conn) - { - MERCHANT_DB_disconnect (db_conn); - db_conn = NULL; - } - if (NULL != mints_map) - { - GNUNET_CONTAINER_multipeermap_iterate (mints_map, - &mints_cleanup_iterator, - NULL); - GNUNET_CONTAINER_multipeermap_destroy (mints_map); - mints_map = NULL; - } - if (NULL != mctx) - { - TALER_MINT_cleanup (mctx); - mctx = NULL; - } - if (NULL != wire) - { - TALER_MERCHANT_destroy_wireformat_sepa (wire); - wire = NULL; - } - while (NULL != (dwn = dwn_head)) - { - GNUNET_CONTAINER_DLL_remove (dwn_head, dwn_tail, dwn); - if (NULL != dwn->resp) - MHD_destroy_response (dwn->resp); - GNUNET_free (dwn->filename); - GNUNET_free (dwn); - } -} - - -/** - * Get the MHD's sockets which are to be called with select() and schedule the - * select task. - * - * @return GNUNET_YES upon success; GNUNET_NO upon error, in this case the - * select task will not be queued. - */ -static int -poll_mhd (); - - -/** - * One of the MHD's sockets are ready. Call MHD_run_from_select (). - * - * @param cls NULL - * @param tc scheduler task context - */ -static void -run_mhd (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - fd_set fd_rs; - fd_set fd_ws; - select_task = NULL; - if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) - return; - FD_ZERO (&fd_rs); - FD_ZERO (&fd_ws); - if (0 != (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason)) - fd_rs = tc->read_ready->sds; - if (0 != (GNUNET_SCHEDULER_REASON_WRITE_READY & tc->reason)) - fd_ws = tc->write_ready->sds; - EXITIF (MHD_YES != MHD_run_from_select (mhd, - &fd_rs, - &fd_ws, - NULL)); - EXITIF (GNUNET_NO == poll_mhd ()); - return; - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); -} - - -/** - * Get the MHD's sockets which are to be called with select() and schedule the - * select task. - * - * @return GNUNET_YES upon success; GNUNET_NO upon error, in this case the - * select task will not be queued. - */ -static int -poll_mhd () -{ - struct GNUNET_NETWORK_FDSet rs; - struct GNUNET_NETWORK_FDSet ws; - fd_set fd_rs; - fd_set fd_ws; - fd_set fd_es; - struct GNUNET_TIME_Relative delay; - unsigned long long timeout; - int max_fd; - - FD_ZERO (&fd_rs); - FD_ZERO (&fd_ws); - FD_ZERO (&fd_es); - max_fd = 0; - if (MHD_YES != MHD_get_fdset (mhd, - &fd_rs, - &fd_ws, - &fd_es, - &max_fd)) - return GNUNET_SYSERR; - GNUNET_NETWORK_fdset_zero (&rs); - GNUNET_NETWORK_fdset_zero (&ws); - GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1); - GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1); - if (MHD_NO == MHD_get_timeout (mhd, &timeout)) - timeout = 0; - if (0 == timeout) - delay = GNUNET_TIME_UNIT_FOREVER_REL; - else - delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout); - if (NULL != select_task) - GNUNET_SCHEDULER_cancel (select_task); - select_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_KEEP, - delay, - &rs, - &ws, - &run_mhd, - NULL); - return GNUNET_OK; -} - -static int -failure_resp (struct MHD_Connection *connection, unsigned int status) -{ - static char page_404[]="\ -<!DOCTYPE html> \ -<html><title>Resource not found</title><body><center> \ -<h3>The resource you are looking for is not found.</h3> \ -</center></body></html>"; - static char page_500[]="\ -<!DOCTYPE html> <html><title>Internal Server Error</title><body><center> \ -<h3>The server experienced an internal error and hence cannot serve your \ -request</h3></center></body></html>"; - struct MHD_Response *resp; - char *page; - size_t size; -#define PAGE(number) \ - do {page=page_ ## number; size=sizeof(page_ ## number)-1;} while(0) - - GNUNET_assert (400 <= status); - resp = NULL; - switch (status) - { - case 404: - PAGE(404); - break; - default: - status = 500; - case 500: - PAGE(500); - } -#undef PAGE - - EXITIF (NULL == (resp = MHD_create_response_from_buffer (size, - page, - MHD_RESPMEM_PERSISTENT))); - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - MHD_destroy_response (resp); - return GNUNET_OK; - - EXITIF_exit: - if (NULL != resp) - MHD_destroy_response (resp); - return GNUNET_SYSERR; -} - - -/** - * Iterator over key-value pairs. This iterator - * can be used to iterate over all of the cookies, - * headers, or POST-data fields of a request, and - * also to iterate over the headers that have been - * added to a response. - * - * @param cls closure - * @param kind kind of the header we are looking at - * @param key key for the value, can be an empty string - * @param value corresponding value, can be NULL - * @return #MHD_YES to continue iterating, - * #MHD_NO to abort the iteration - * @ingroup request - */ -static int -get_contract_values_iter (void *cls, - enum MHD_ValueKind kind, - const char *key, const char *value) -{ - unsigned long long id; - uint64_t *product = cls; - -#define STR_PRODUCT "product" - if (0 == strncasecmp (key, STR_PRODUCT, sizeof (STR_PRODUCT) - 1)) - { - if (1 > sscanf (value, "%llu", &id)) - return GNUNET_NO; - *product = (uint64_t) id; - } - return GNUNET_YES; -} - -#if 0 -static const char * -uint64_to_enc (uint64_t i) -{ - static char buf[14]; - i = GNUNET_htonll (i); - GNUNET_break (NULL != - GNUNET_STRINGS_data_to_string (&i, sizeof (i), buf, sizeof (buf))); - buf[13] = '\0'; - return buf; -} - -static uint64_t -enc_to_uint64 (const char *enc) -{ - uint64_t i; - GNUNET_break (GNUNET_OK == - GNUNET_STRINGS_string_to_data (enc, strlen(enc), &i, sizeof - (i))); - return GNUNET_ntohll (i); -} -#endif - -/** - * Prepare a contract, store it in database and send the corresponding JSON. - * - * @param connection MHD connection handle - * @param _resp pointer to hold the result response upon success - * @return the status code 200 when a contract is generated; 404 when the - * product is not found or upon other errors. - */ -static unsigned int -handle_get_contract (struct MHD_Connection *connection, - struct MHD_Response **_resp) -{ - struct MHD_Response *resp; - struct Contract *contract; - struct GNUNET_TIME_Absolute expiry; - struct TALER_Amount amount; - char *template = "A contract from GNUnet e.V thanking you for a" - " donation of the aforementioned amount. As a token of gratitude, upon" - " successful payment, you may download your image at " - "`http://%s:%u/download?ref=[]'"; - char *desc; - json_t *json; - char *json_str; - uint64_t nounce; - uint64_t product; - uint64_t contract_id_nbo; - long long contract_id; - unsigned int ret; - - resp = NULL; - contract = NULL; - desc = NULL; - ret = 400; - product = UINT64_MAX; - MHD_get_connection_values (connection, MHD_GET_ARGUMENT_KIND, - &get_contract_values_iter, &product); - if (UINT64_MAX == product) - goto EXITIF_exit; - - expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), - GNUNET_TIME_UNIT_DAYS); - ROUND_TO_SECS (expiry, abs_value_us); - amount.value = 1; - amount.fraction = 0; - strcpy (amount.currency, "EUR"); - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - - /* Prepare contract */ - (void) GNUNET_asprintf (&desc, - template, - hostname, - port); - contract_id = MERCHANT_DB_contract_create (db_conn, - expiry, - &amount, - desc, - nounce, - product); - EXITIF (-1 == contract_id); - contract_id_nbo = GNUNET_htonll ((uint64_t) contract_id); - contract = GNUNET_malloc (sizeof (struct Contract) + strlen (desc) + 1); - contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - contract->purpose.size = htonl (sizeof (struct Contract) - - offsetof (struct Contract, purpose) - + strlen (desc) + 1); - GNUNET_STRINGS_data_to_string (&contract_id_nbo, sizeof (contract_id_nbo), - contract->m, sizeof (contract->m)); - contract->t = GNUNET_TIME_absolute_hton (expiry); - (void) strcpy (contract->a, desc); - contract->h_wire = hash_wireformat (nounce); - contract->amount = TALER_amount_hton (amount); - GNUNET_CRYPTO_eddsa_sign (privkey, &contract->purpose, &contract->sig); - json = build_json_contract (contract); - json_str = json_dumps (json, JSON_INDENT(2)); - json_decref (json); - resp = MHD_create_response_from_buffer (strlen (json_str), json_str, - MHD_RESPMEM_MUST_FREE); - ret = 200; - - EXITIF_exit: - GNUNET_free_non_null (desc); - if (NULL != resp) - *_resp = resp; - if (NULL != contract) - { - GNUNET_free (contract); - } - return ret; -} - -static struct Download * -find_product (unsigned int id) -{ - struct Download *dwn; - - for (dwn = dwn_head; NULL != dwn; dwn = dwn->next) - { - if (dwn->id == id) - return dwn; - } - return NULL; -} - - -static int -get_download_ref (void *cls, - enum MHD_ValueKind kind, - const char *key, const char *value) -{ - char **coin_pub_enc = cls; - - if (0 == strncasecmp (key, "ref", sizeof ("ref"))) - { - *coin_pub_enc = GNUNET_strdup (value); - return MHD_NO; - } - return MHD_YES; -} - -static unsigned int -handle_download (struct MHD_Connection *conn, - struct MHD_Response **_resp) -{ - char *coin_pub_enc; - struct Download *item; - struct GNUNET_DISK_FileHandle *fh; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - long long product_id; - off_t size; - int ret; - - coin_pub_enc = NULL; - ret = MHD_HTTP_NOT_FOUND; - MHD_get_connection_values (conn, MHD_GET_ARGUMENT_KIND, - &get_download_ref, &coin_pub_enc); - LOG_WARNING ("Trying to start downloading with coin: %s\n", coin_pub_enc); - EXITIF (NULL == coin_pub_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_public_key_from_string (coin_pub_enc, - strlen (coin_pub_enc), - &coin_pub)); - product_id = MERCHANT_DB_get_checkout_product (db_conn, - &coin_pub); - EXITIF (-1 == product_id); - EXITIF (NULL == (item = find_product ((unsigned int) product_id))); - if (NULL != item->resp) - { - *_resp = item->resp; - ret = MHD_HTTP_OK; - goto EXITIF_exit; - } - fh = GNUNET_DISK_file_open (item->filename, - GNUNET_DISK_OPEN_READ, - GNUNET_DISK_PERM_USER_READ); - GNUNET_assert (NULL != fh); - GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_handle_size (fh, - &size)); - item->resp = MHD_create_response_from_fd (size, fh->fd); - GNUNET_assert (MHD_NO != MHD_add_response_header (item->resp, - "Content-Type", - "image/jpeg")); - GNUNET_free (fh); - - EXITIF_exit: - GNUNET_free_non_null (coin_pub_enc); - return ret; -} - -struct CheckoutCtx -{ - /* FIXME: Hook into a DLL for cleaner shutdown */ - struct MHD_Connection *conn; - struct TALER_MINT_DepositHandle *dh; - struct Download *product; - char *coin_pub_enc; - uint64_t transaction_id; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - struct GNUNET_CRYPTO_EddsaSignature coin_sig; - struct TALER_Amount amount; - struct GNUNET_SCHEDULER_Task *timeout_task; - -}; - - -/** - * Callbacks of this type are used to serve the result of submitting a deposit - * permission object to a mint - * - * @param cls closure - * @param status 1 for successful deposit, 2 for retry, 0 for failure - * @param obj the received JSON object; can be NULL if it cannot be constructed - * from the reply - * @param emsg in case of unsuccessful deposit, this contains a human readable - * explanation. - */ -static void -checkout_status (void *cls, int status, json_t *obj, char *emsg) -{ - struct CheckoutCtx *ctx = cls; - const char *tmplt_download_page = - "<!DOCTYPE HTML><html>" - "<body>You are being redirected to the product download page<br>" - "If your browser is unable to redirect, you may click " - "<a href=\"%s\">here</a> to download.</body>" - "</html>"; - char *download_page; - char *location; - struct MHD_Response *resp; - int size; - - LOG_DEBUG ("Processing checkout request reply\n"); - GNUNET_SCHEDULER_cancel (ctx->timeout_task); - ctx->timeout_task = NULL; - download_page = NULL; - location = NULL; - switch (status) - { - case 1: - { - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - - GNUNET_assert (GNUNET_SYSERR != - GNUNET_CRYPTO_eddsa_public_key_from_string - (ctx->coin_pub_enc, - strlen (ctx->coin_pub_enc), - &coin_pub)); - /* FIXME: Put the contract into the checkout DB. */ - } - /* redirect with HTTP FOUND 302 to the product download page */ - GNUNET_assert (NULL != obj); - GNUNET_assert (0 < (size = GNUNET_asprintf (&location, - "/download?ref=%s", - ctx->coin_pub_enc))); - GNUNET_assert (0 < (size = GNUNET_asprintf (&download_page, - tmplt_download_page, - location))); - resp = MHD_create_response_from_buffer (size, - download_page, - MHD_RESPMEM_MUST_FREE); - /* IMP: do not free `download_page' */ - GNUNET_assert (NULL != resp); - GNUNET_assert (MHD_NO != MHD_add_response_header (resp, - "Location", - location)); - GNUNET_assert (MHD_YES == MHD_queue_response (ctx->conn, - MHD_HTTP_FOUND, - resp)); - MHD_destroy_response (resp); - GNUNET_free (location); - location = NULL; - resp = NULL; -#if 0 - struct Download *product; - struct GNUNET_DISK_FileHandle *fh; - GNUNET_assert (NULL != (product = ctx->product)); - if (NULL != product->resp) - { - MHD_queue_response (ctx->conn, MHD_HTTP_OK, product->resp); - break; - } - fh = GNUNET_DISK_file_open (product->filename, - GNUNET_DISK_OPEN_READ, - GNUNET_DISK_PERM_USER_READ); - GNUNET_assert (NULL != fh); - GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_handle_size (fh, - &size)); - product->resp = MHD_create_response_from_fd (size, fh->fd); - GNUNET_assert (MHD_NO != MHD_add_response_header (product->resp, - "Content-Type", - "image/jpeg")); - GNUNET_free (fh); - MHD_queue_response (ctx->conn, MHD_HTTP_OK, product->resp); -#endif - - break; - case 2: - send_response_json (ctx->conn, - json_pack ("{s:s}", "status", "pending"), - 200); /* FIXME: Send Image data */ - break; - case 0: - send_response_json (ctx->conn, - json_pack ("{s:s s:s}", - "status", "failed", - "error", (NULL != emsg) ? emsg : "unknown"), - 400); /* FIXME */ - break; - default: - GNUNET_assert (0); /* should never reach */ - } - GNUNET_free (ctx->coin_pub_enc); - GNUNET_free (ctx); - if (GNUNET_SYSERR == poll_mhd ()) - { - GNUNET_break (0); - GNUNET_SCHEDULER_shutdown (); - } -} - -static void -checkout_status_timedout (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct CheckoutCtx *ctx = cls; - - LOG_DEBUG ("Checkout request timed out\n"); - ctx->timeout_task = NULL; - TALER_MINT_deposit_submit_cancel (ctx->dh); - ctx->dh = NULL; - send_response_json (ctx->conn, - json_pack ("{s:s}", "error", "timeout"), - 400); /* FIXME */ - GNUNET_free (ctx->coin_pub_enc); - GNUNET_free (ctx); - EXITIF (GNUNET_SYSERR == poll_mhd ()); - return; - - EXITIF_exit: - GNUNET_SCHEDULER_shutdown (); -} - -static int -handle_checkout (struct MHD_Connection *conn, - json_t *checkout_json) -{ - struct CheckoutCtx *ctx; - const char *pkey_enc; - const char *tid_enc; - const char *emsg; - const char *coin_pub_enc; - const char *coin_sig_enc; - struct Mint *mint; - struct Download *product; - struct GNUNET_CRYPTO_EddsaPublicKey pkey; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - struct GNUNET_CRYPTO_EddsaSignature coin_sig; - uint64_t tid; - uint64_t product_id; - json_error_t jerror; - unsigned int status; - - coin_pub_enc = NULL; - emsg = "Public key of Mint is missing in the request"; - status = MHD_HTTP_BAD_REQUEST; - if (-1 == json_unpack_ex (checkout_json, - &jerror, - 0, - "{s:s s:s s:s s:s}", - "mint_pub", &pkey_enc, - "transaction_id", &tid_enc, - "coin_pub", &coin_pub_enc, - "coin_sig", &coin_sig_enc)) - { - WARN_JSON (jerror); - goto EXITIF_exit; - } - - EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data - (tid_enc, strlen (tid_enc), &tid, sizeof (tid))); - tid = GNUNET_ntohll (tid); - - emsg = "Public key of the coin is missing/malformed in the request"; - EXITIF (NULL == coin_pub_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_public_key_from_string (coin_pub_enc, - strlen (coin_pub_enc), - &coin_pub)); - - emsg = "Signature of the coin is missing/malformed in the request"; - EXITIF (NULL == coin_sig_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_STRINGS_string_to_data (coin_sig_enc, strlen (coin_sig_enc), - &coin_sig, sizeof (coin_sig))); - - emsg = "Contract not found"; - status = MHD_HTTP_NOT_FOUND; - LOG_DEBUG ("Looking for product associated with transaction %u\n", tid); - EXITIF (-1 == (product_id = MERCHANT_DB_get_contract_product (db_conn, tid))); - - emsg = "Could not find the downloadable product. Sorry :("; - EXITIF (NULL == (product = find_product (product_id))); - - emsg = "Invalid public key given for a mint"; - EXITIF (52 != strlen (pkey_enc)); - EXITIF (GNUNET_SYSERR == GNUNET_STRINGS_string_to_data (pkey_enc, 52, - &pkey, sizeof (pkey))); - - emsg = "The provided mint is not trusted by us"; - status = MHD_HTTP_FORBIDDEN; - EXITIF (NULL == (mint = - GNUNET_CONTAINER_multipeermap_get (mints_map, - (const struct - GNUNET_PeerIdentity *) - &pkey))); - - LOG_DEBUG ("Creating a new checkout request\n"); - ctx = GNUNET_new (struct CheckoutCtx); - ctx->product = product; - ctx->conn = conn; - ctx->coin_pub_enc = GNUNET_strdup (coin_pub_enc); - ctx->transaction_id = tid; - ctx->coin_pub = coin_pub; - ctx->coin_sig = coin_sig; - /* FIXME: parse amount */ - /* ctx->amount = ?? */ - ctx->dh = TALER_MINT_deposit_submit_json (mint->conn, - checkout_status, - ctx, - checkout_json); - ctx->timeout_task = GNUNET_SCHEDULER_add_delayed - (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 3), - &checkout_status_timedout, ctx); - return MHD_YES; - - EXITIF_exit: - json_decref (checkout_json); - return send_response_json (conn, - json_pack ("{s:s s:s}", - "status", "failed", - "error", emsg), - status); -} - -/** - * A client has requested the given url using the given method - * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, - * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback - * must call MHD callbacks to provide content to give back to the - * client and return an HTTP status code (i.e. #MHD_HTTP_OK, - * #MHD_HTTP_NOT_FOUND, etc.). - * - * @param cls argument given together with the function - * pointer when the handler was registered with MHD - * @param url the requested url - * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, - * #MHD_HTTP_METHOD_PUT, etc.) - * @param version the HTTP version string (i.e. - * #MHD_HTTP_VERSION_1_1) - * @param upload_data the data being uploaded (excluding HEADERS, - * for a POST that fits into memory and that is encoded - * with a supported encoding, the POST data will NOT be - * given in upload_data and is instead available as - * part of #MHD_get_connection_values; very large POST - * data *will* be made available incrementally in - * @a upload_data) - * @param upload_data_size set initially to the size of the - * @a upload_data provided; the method must update this - * value to the number of bytes NOT processed; - * @param con_cls pointer that the callback can set to some - * address and that will be preserved by MHD for future - * calls for this request; since the access handler may - * be called many times (i.e., for a PUT/POST operation - * with plenty of upload data) this allows the application - * to easily associate some request-specific state. - * If necessary, this state can be cleaned up in the - * global #MHD_RequestCompletedCallback (which - * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). - * Initially, `*con_cls` will be NULL. - * @return #MHD_YES if the connection was handled successfully, - * #MHD_NO if the socket must be closed due to a serios - * error while handling the request - */ -static int -url_handler (void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ -#define URL_PRODUCTS "/products" -#define URL_CONTRACT "/contract" -#define URL_CHECKOUT "/checkout" -#define URL_HTTPTEST "/httptest" -#define URL_DOWNLOAD "/download" -#define STR_404_NOTFOUND "The requested resource is not found" - struct MHD_Response *resp; - int no_destroy; - unsigned int status; - - resp = NULL; - status = 404; - no_destroy = 0; - LOG_DEBUG ("request for URL `%s'\n", url); - - if (0 == strncasecmp (url, URL_PRODUCTS, sizeof (URL_PRODUCTS))) - { - /* parse for /contract */ - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - resp = list_products_resp; - no_destroy = 1; - status = 200; - } - else - GNUNET_break (0); /* FIXME: implement for post */ - } - - if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) - { - /* parse for /contract */ - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = handle_get_contract (connection, &resp); - else - GNUNET_break (0); /* FIXME: implement for post */ - } - - if (0 == strncasecmp (url, URL_CHECKOUT, sizeof (URL_CHECKOUT))) - { - json_t *checkout_obj; - int ret; - /* parse for /checkout */ - ret = process_post_json (connection, - con_cls, - upload_data, - upload_data_size, - &checkout_obj); - if (GNUNET_SYSERR == ret) - return MHD_NO; - if (GNUNET_NO == ret) - return MHD_YES; - /* Handle the response in the request handler */ - ret = handle_checkout (connection, checkout_obj); - return ret; - } - - if (0 == strncasecmp (url, URL_HTTPTEST, sizeof (URL_HTTPTEST))) - { - static char page[]="\ -<!DOCTYPE html> \ -<html><title>HTTP Test page</title><body><center><h3>HTTP Test page</h3> \ -</center></body></html>"; - resp = MHD_create_response_from_buffer (sizeof (page) - 1, - page, - MHD_RESPMEM_PERSISTENT); - EXITIF (NULL == resp); - } - - if ((0 == strncasecmp (url, URL_DOWNLOAD, sizeof (URL_DOWNLOAD))) - && (0 == strcmp (MHD_HTTP_METHOD_GET, method))) - { - status = handle_download (connection, &resp); - if (status != MHD_HTTP_OK) - no_destroy = 1; - } - if (NULL != resp) - { - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - if (!no_destroy) - MHD_destroy_response (resp); - } - else - EXITIF (GNUNET_OK != failure_resp (connection, status)); - return MHD_YES; - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); - return MHD_NO; -} - - -/** - * Callback for catching serious error conditions from MHD. - * - * @param cls user specified value - * @param file where the error occured - * @param line where the error occured - * @param reason error detail, may be NULL - */ -static void -mhd_panic_cb (void *cls, - const char *file, - unsigned int line, - const char *reason) -{ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "MHD panicked at %s:%u: %s", - file, line, reason); - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); -} - -/** - * Function called with a filename. - * - * @param cls closure - * @param filename complete filename (absolute path) - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -static int -add_download_file (void *cls, const char *filename) -{ - struct Download *dwn; - - dwn = GNUNET_new (struct Download); - dwn->filename = GNUNET_strdup (filename); - dwn->id = ndownloads++; - GNUNET_CONTAINER_DLL_insert (dwn_head, dwn_tail, dwn); - return GNUNET_OK; -} - - -/** - * Function to build a MHD response object to list products - * - * @return GNUNET_OK upon success; GNUNET_SYSERR otherwise - */ -static int -build_list_product_response () -{ - const char *header = "\ -<!DOCTYPE html> \ -<html><title>Products List</title> \ -<body><center><ol>"; - char **partials; - const char *footer = "</ol></center></body></html>"; - char *page; - struct Download *dwn; - size_t size; - unsigned int cnt; - int psize; - unsigned int header_size; - unsigned int footer_size; - unsigned int *partial_sizes; - int ret; - - ret = GNUNET_SYSERR; - GNUNET_assert (NULL == list_products_resp); - header_size = strlen (header); - footer_size = strlen (footer); - size = header_size; - size += footer_size; - partials = GNUNET_malloc (sizeof (char *) * ndownloads); - partial_sizes = GNUNET_malloc (sizeof (unsigned int) * ndownloads); - EXITIF (0 == ndownloads); - for (cnt = 0, dwn = dwn_head; cnt < ndownloads; cnt++, dwn=dwn->next) - { - EXITIF (NULL == dwn); - psize = GNUNET_asprintf (&partials[cnt], - "<li><a href=\"/contract?product=%u\">%s</a></li>", - cnt, - GNUNET_STRINGS_get_short_name (dwn->filename)); - EXITIF (psize < 0); - size += psize; - partial_sizes [cnt] = psize; - } - page = GNUNET_malloc (size); - size = 0; - (void) memcpy (page, header, header_size); - size += header_size; - for (cnt = 0; cnt < ndownloads; cnt++) - { - (void) memcpy (page + size, partials[cnt], partial_sizes[cnt]); - size += partial_sizes[cnt]; - } - (void) memcpy (page + size, footer, footer_size); - size += footer_size; - list_products_resp = MHD_create_response_from_buffer (size, page, MHD_RESPMEM_MUST_FREE); - ret = GNUNET_OK; - - EXITIF_exit: - for (cnt = 0; cnt < ndownloads; cnt++) - GNUNET_free_non_null (partials[cnt]); - GNUNET_free_non_null (partials); - GNUNET_free_non_null (partial_sizes); - return ret; -} - -/** - * Main function that will be run by the scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param config configuration - */ -static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *config) -{ - char *keyfile; - struct MERCHANT_MintInfo *mint_infos; - unsigned int nmints; - unsigned int cnt; - - result = GNUNET_SYSERR; - keyfile = NULL; - mint_infos = NULL; - shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, NULL); - if (NULL == data_dir) - { - LOG_ERROR ("Data directory for download files is missing. It can be given with the `-d' option\n"); - goto EXITIF_exit; - } - EXITIF (GNUNET_SYSERR == (nmints = TALER_MERCHANT_parse_mints (config, - &mint_infos))); - EXITIF (NULL == (wire = TALER_MERCHANT_parse_wireformat_sepa (config))); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (config, - "merchant", - "KEYFILE", - &keyfile)); - EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); - EXITIF (0 == GNUNET_DISK_directory_scan (data_dir, - &add_download_file, - NULL)); - EXITIF (GNUNET_SYSERR == build_list_product_response ()); - EXITIF (NULL == (db_conn = MERCHANT_DB_connect (config))); - EXITIF (GNUNET_OK != MERCHANT_DB_initialise (db_conn, dry)); - EXITIF (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_number (config, - "merchant", - "port", - &port)); - EXITIF (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_string (config, - "merchant", - "hostname", - &hostname)); - EXITIF (NULL == (mctx = TALER_MINT_init ())); - EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); - for (cnt = 0; cnt < nmints; cnt++) - { - struct Mint *mint; - - mint = GNUNET_new (struct Mint); - mint->pubkey = mint_infos[cnt].pubkey; - mint->conn = TALER_MINT_connect (mctx, - mint_infos[cnt].hostname, - mint_infos[cnt].port, - &mint->pubkey); - EXITIF (NULL == mint->conn); - EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put - (mints_map, - (struct GNUNET_PeerIdentity *) &mint->pubkey, - mint, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); - } - MHD_set_panic_func (&mhd_panic_cb, NULL); - mhd = MHD_start_daemon (MHD_USE_DEBUG, //| MHD_USE_TCP_FASTOPEN, - (unsigned short) port, - NULL, NULL, - &url_handler, NULL, - //MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE, - //(unsigned int) 16, - MHD_OPTION_END); - EXITIF (NULL == mhd); - EXITIF (GNUNET_SYSERR == poll_mhd ()); - GNUNET_CRYPTO_hash (wire, sizeof (*wire), &h_wire); - result = GNUNET_OK; - - EXITIF_exit: - if (NULL != mint_infos) - { - for (cnt = 0; cnt < nmints; cnt++) - GNUNET_free (mint_infos[cnt].hostname); - GNUNET_free (mint_infos); - } - GNUNET_free_non_null (keyfile); - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); -} - - -/** - * The main function of the serve tool - * - * @param argc number of arguments from the command line - * @param argv command line arguments - * @return 0 ok, 1 on error - */ -int -main (int argc, char *const *argv) -{ - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - {'t', "temp", NULL, - gettext_noop ("Use temporary database tables"), GNUNET_NO, - &GNUNET_GETOPT_set_one, &dry}, - {'d', "dir", "DIRECTORY", - gettext_noop ("Directory of the data files to serve"), GNUNET_YES, - &GNUNET_GETOPT_set_string, &data_dir}, - GNUNET_GETOPT_OPTION_END - }; - - if (GNUNET_OK != - GNUNET_PROGRAM_run (argc, argv, - "taler-merchant-serve", - "Serve merchant's HTTP interface", - options, &run, NULL)) - return 3; - return (GNUNET_OK == result) ? 0 : 1; -} diff --git a/src/merchant/test_merchant.c b/src/merchant/test_merchant.c deleted file mode 100644 index bcec09ae..00000000 --- a/src/merchant/test_merchant.c +++ /dev/null @@ -1,96 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/test_merchant.c - * @brief File to test merchant-internal helper functions. - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include "merchant.h" - -/** - * Array of parsed mints - */ -struct MERCHANT_MintInfo *mints; - -/** - * Number of mints in the above array - */ -int n_mints; - -/** - * Test result - */ -static int result; - -static void -do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - unsigned int cnt; - - for (cnt=0; cnt < n_mints; cnt++) - GNUNET_free (mints[cnt].hostname); - GNUNET_free_non_null (mints); - mints = 0; -} - - -/** - * Main function that will be run by the scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param config configuration - */ -static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *config) -{ - - mints = NULL; - n_mints = GNUNET_SYSERR; - n_mints = TALER_MERCHANT_parse_mints (config, &mints); - GNUNET_assert (GNUNET_SYSERR != n_mints); - GNUNET_assert (NULL != mints); - result = GNUNET_OK; - GNUNET_SCHEDULER_add_now (&do_shutdown, NULL); -} - -int -main (int argc, char *const argv[]) -{ - char *argv2[] = { - "test-merchant", - "-c", "test_merchant.conf", - NULL - }; - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_END - }; - - result = GNUNET_SYSERR; - if (GNUNET_OK != - GNUNET_PROGRAM_run ((sizeof (argv2) / sizeof (char *)) - 1, - argv2, "test-merchant", - "File to test merchant-internal helper functions.", - options, &run, NULL)) - return 3; - return (GNUNET_OK == result) ? 0 : 1; -} diff --git a/src/merchant/test_merchant.conf b/src/merchant/test_merchant.conf deleted file mode 100644 index 7d32769a..00000000 --- a/src/merchant/test_merchant.conf +++ /dev/null @@ -1,36 +0,0 @@ -# Sample configuration file test-merchant testcase -[merchant] -PORT = 4251 -HOSTNAME = localhost -# List of mints the merchant trusts delimited by a single space -TRUSTED_MINTS = nayapaisa - -#nayapaisa nyadirahim - -# Our secret key file -KEYFILE = test_merchant.ecc - -[mint-taler] -HOSTNAME = localhost -PORT = 4241 -PUBKEY = 8MF3D4V40K4J7654Q7NT3YHZVJ22SJAMBG07GEQ0QSXHWF4M9X6G - -[mint-nayapaisa] -HOSTNAME = localhost -PORT = 4241 -# The public key of this mint -PUBKEY = 6ZE0HEY2M0FWP61M0470HYBF4K6RRD5DP54372PD2TN9N9VX2VJG - -[mint-nyadirahim] -HOSTNAME = nyadirahim.org -PORT = 4241 -# The public key of this mint -PUBKEY = 7995WKK71KPKTBBMA5BHNBSZFGNRZPYNXDJMQ8EK86V9598H03TG - -[merchant-db] -CONFIG = postgres:///taler - -[wire-sepa] -IBAN = DE67830654080004822650 -NAME = GNUNET E.V -BIC = GENODEF1SRL diff --git a/src/merchant/test_merchant.ecc b/src/merchant/test_merchant.ecc deleted file mode 100644 index ac8ee6e9..00000000 --- a/src/merchant/test_merchant.ecc +++ /dev/null @@ -1,2 +0,0 @@ -MY{.ӂce -ف
\ No newline at end of file diff --git a/src/merchant/test_merchant_db.c b/src/merchant/test_merchant_db.c deleted file mode 100644 index caaa1be4..00000000 --- a/src/merchant/test_merchant_db.c +++ /dev/null @@ -1,147 +0,0 @@ -/* - This file is part of TALER - (C) 2014 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 merchant/test_merchant_db.c - * @brief File to test merchant database helper functions. - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <taler/taler_util.h> -#include "merchant_db.h" - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Macro to round microseconds to seconds in GNUNET_TIME_* structs. - */ -#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000); - -/** - * The database handle - */ -PGconn *conn; - -/** - * Test result - */ -static int result; - -static void -do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - if (NULL != conn) - MERCHANT_DB_disconnect (conn); - conn = NULL; -} - - -/** - * Main function that will be run by the scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param config configuration - */ -static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *config) -{ - struct GNUNET_TIME_Absolute expiry; - struct TALER_Amount amount; - char *desc = "A contract from GNUnet e.V to say a big Thank You for a donation of the aforementioned amount."; - uint64_t nounce; - uint64_t product; - long long transaction_id; - - conn = MERCHANT_DB_connect (config); - EXITIF (NULL == conn); - GNUNET_SCHEDULER_add_now (&do_shutdown, NULL); - EXITIF (GNUNET_OK != MERCHANT_DB_initialise (conn, GNUNET_YES)); - expiry = GNUNET_TIME_absolute_get (); - expiry = GNUNET_TIME_absolute_add (expiry, GNUNET_TIME_UNIT_DAYS); - ROUND_TO_SECS (expiry, abs_value_us); - amount.value = 1; - amount.fraction = 0; - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - product = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - product &= (UINT64_MAX >> 1); - EXITIF (-1 == (transaction_id = MERCHANT_DB_contract_create (conn, - expiry, - &amount, - desc, - nounce, - product))); - { - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - struct GNUNET_CRYPTO_EddsaSignature coin_sig; - long long paid_product; - - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &coin_pub, sizeof (coin_pub)); - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &coin_sig, sizeof (coin_sig)); - EXITIF (GNUNET_SYSERR == MERCHANT_DB_checkout_create (conn, - &coin_pub, - transaction_id, - &amount, - &coin_sig)); - EXITIF (-1 == (paid_product = MERCHANT_DB_get_checkout_product (conn, - &coin_pub))); - EXITIF (paid_product < 0); - EXITIF (((uint64_t) paid_product) != product); - /* We should get -1 for product if a coin is not paid to us */ - GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, - &coin_pub, sizeof (coin_pub)); - EXITIF (-1 != (product = MERCHANT_DB_get_checkout_product (conn, - &coin_pub))); - } - result = GNUNET_OK; - - EXITIF_exit: - return; -} - -int -main (int argc, char *const argv[]) -{ - char *argv2[] = { - "test-merchant-db", - "-c", "test_merchant.conf", - NULL - }; - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - GNUNET_GETOPT_OPTION_END - }; - - result = GNUNET_SYSERR; - if (GNUNET_OK != - GNUNET_PROGRAM_run ((sizeof (argv2) / sizeof (char *)) - 1, - argv2, "test-merchant-db", - "File to test merchant database helper functions.", - options, &run, NULL)) - return 3; - return (GNUNET_OK == result) ? 0 : 1; -} diff --git a/src/tests/test_contract.c b/src/tests/test_contract.c index 147ea4f3..68572a38 100644 --- a/src/tests/test_contract.c +++ b/src/tests/test_contract.c @@ -54,13 +54,13 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) if (NULL != db_conn) { - MERCHANT_DB_disconnect (db_conn); + TALER_MERCHANTDB_disconnect (db_conn); db_conn = NULL; } } extern uint32_t -MERCHANT_DB_get_contract_values (PGconn *conn, +TALER_MERCHANTDB_contract_get_values (PGconn *conn, const struct GNUNET_HashCode *h_contract, uint64_t *nounce, struct GNUNET_TIME_Absolute *edate); @@ -116,8 +116,8 @@ run (void *cls, char *const *args, const char *cfgfile, wire = NULL; - db_conn = MERCHANT_DB_connect (config); - if (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_NO)) + db_conn = TALER_MERCHANTDB_connect (config); + if (GNUNET_OK != TALER_MERCHANTDB_initialize (db_conn, GNUNET_NO)) { printf ("no db init'd\n"); result = GNUNET_SYSERR; @@ -293,7 +293,7 @@ run (void *cls, char *const *args, const char *cfgfile, printf ("contract string : %s\n", aa); GNUNET_CRYPTO_hash (aa, strlen (aa) + 1, &h_contract_str); - if (GNUNET_SYSERR == MERCHANT_DB_get_contract_values (db_conn, &h_contract_str, &nounce, &edate)) + if (GNUNET_SYSERR == TALER_MERCHANTDB_contract_get_values (db_conn, &h_contract_str, &nounce, &edate)) printf ("no hash found\n"); else { |