diff options
48 files changed, 3814 insertions, 2839 deletions
diff --git a/configure.ac b/configure.ac index 55d1e196..67d84f11 100644 --- a/configure.ac +++ b/configure.ac @@ -180,7 +180,6 @@ AC_CHECK_FUNCS([strdup]) AC_CONFIG_FILES([Makefile src/Makefile src/include/Makefile -src/tests/Makefile src/backend/Makefile src/backend-lib/Makefile]) AC_OUTPUT diff --git a/src/Makefile.am b/src/Makefile.am index 9a5b5646..665be7d3 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 tests +SUBDIRS = include backend-lib backend diff --git a/src/backend-lib/Makefile.am b/src/backend-lib/Makefile.am index e4cdb7a3..583c4d03 100644 --- a/src/backend-lib/Makefile.am +++ b/src/backend-lib/Makefile.am @@ -6,12 +6,14 @@ lib_LTLIBRARIES = \ include_HEADERS = \ taler_merchant_lib.h \ - taler_merchant_contract_lib.h + taler_merchant_contract_lib.h \ + taler_merchant_deposit_lib.h libtalermerchant_la_SOURCES = \ - taler-merchant-httpd_contract.c \ - taler-merchant-httpd_deposit.c \ + 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 diff --git a/src/backend-lib/merchant_api_contract.c b/src/backend-lib/merchant_api_contract.c new file mode 100644 index 00000000..20b69cd3 --- /dev/null +++ b/src/backend-lib/merchant_api_contract.c @@ -0,0 +1,60 @@ +/* + 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/taler-merchant-httpd_deposit.c b/src/backend-lib/merchant_api_deposit.c index 41d4ca3d..a3cae20c 100644 --- a/src/backend-lib/taler-merchant-httpd_deposit.c +++ b/src/backend-lib/merchant_api_deposit.c @@ -1,3 +1,26 @@ +/* + 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> diff --git a/src/backend-lib/merchant_db.c b/src/backend-lib/merchant_db.c index 035a6646..415ff223 100644 --- a/src/backend-lib/merchant_db.c +++ b/src/backend-lib/merchant_db.c @@ -104,7 +104,12 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) "contract_id INT8 REFERENCES contracts(contract_id)," "amount INT4 NOT NULL," "amount_fraction INT4 NOT NULL," - "coin_sig BYTEA NOT NULL);", + "coin_sig BYTEA NOT NULL);" + "CREATE %1$s TABLE IF NOT EXISTS deposits (" + "dep_perm TEXT NOT NULL," + "transaction_id INT8 PRIMARY KEY," + "pending INT4 NOT NULL," + "mint_url TEXT NOT NULL);", tmp_str); ret = GNUNET_POSTGRES_exec (conn, sql); (void) GNUNET_POSTGRES_exec (conn, @@ -165,6 +170,28 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) 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" @@ -218,6 +245,115 @@ MERCHANT_DB_initialize (PGconn *conn, int tmp) 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_uint64 (&transaction_id), + TALER_PQ_query_param_uint32 (&pending), + }; + + 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 @@ -314,9 +450,6 @@ MERCHANT_DB_contract_create (PGconn *conn, PQclear (res); return GNUNET_OK; - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; } long long diff --git a/src/backend-lib/merchant_db.h b/src/backend-lib/merchant_db.h index 540bddbd..c748002e 100644 --- a/src/backend-lib/merchant_db.h +++ b/src/backend-lib/merchant_db.h @@ -164,4 +164,21 @@ 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-httpd_contract.c b/src/backend-lib/taler-merchant-httpd_contract.c deleted file mode 100644 index 5f6744aa..00000000 --- a/src/backend-lib/taler-merchant-httpd_contract.c +++ /dev/null @@ -1,121 +0,0 @@ -#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 nounce 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 nounce, - const struct GNUNET_TIME_Absolute edate) - -{ - - json_t *root; - json_t *j_edate; - json_t *j_nounce; - - j_nounce = json_integer (nounce); - j_edate = TALER_json_from_abs (edate); - - if (NULL == (root = json_pack ("{s:s, s:s, s:s, s:s, s:o, s:I}", - "type", "SEPA", - "IBAN", wire->iban, - "name", wire->name, - "bic", wire->bic, - "edate", j_edate, - "r", json_integer_value (j_nounce)))) - return NULL; - - return root; -} - - - -/** -* Take from the frontend the (partly) generated contract and fill -* the missing values in it; for example, the SEPA details. -* Moreover, it stores the contract in the DB. -* @param j_contract parsed contract, originated by the frontend. It will be -* hold the new values. -* @param db_conn the handle to the local DB -* @param contract where to store the (subset of the) contract to be (still) signed -* @param timestamp contract's timestamp (shall be generated by the merchant) -* @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 nounce the nounce used to hash the wire details -* @param a will be pointed to the (allocated) stringified 0-terminated contract -* @return GNUNET_OK on success, GNUNET_NO if attempting to double insert the -* same contract, GNUNET_SYSERR in case of other (mostly DB related) errors. -*/ - -/** -* TODO: inspect reference counting and, accordingly, free those json_t*(s) -* still allocated */ - -uint32_t -MERCHANT_handle_contract (json_t *j_contract, - PGconn *db_conn, - struct Contract *contract, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute expiry, - struct GNUNET_TIME_Absolute edate, - struct GNUNET_TIME_Absolute refund, - char **a, - uint64_t nounce) -{ - json_t *j_amount; - json_int_t j_product_id; - json_int_t j_trans_id; - char *contract_str; - - struct TALER_Amount amount; - - - - /* Extracting values useful for DB work. Only gettable from the JSON - since they are generated by the frontend */ - if (-1 == json_unpack (j_contract, "{s:o, s:I, s:{s:I}}", - "amount", &j_amount, - "trans_id", &j_trans_id, - "details", "product_id", - &j_product_id)) - { - printf ("no unpacking\n"); - return GNUNET_SYSERR; - } - - TALER_json_to_amount (j_amount, &amount); - contract_str = json_dumps (j_contract, JSON_COMPACT | JSON_PRESERVE_ORDER); - *a = contract_str; - GNUNET_CRYPTO_hash (contract_str, strlen (contract_str) + 1, - &contract->h_contract_details); - contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - contract->purpose.size = htonl (sizeof (struct Contract)); - - // DB mgmt - return MERCHANT_DB_contract_create (db_conn, - timestamp, - expiry, - edate, - refund, - &amount, - &contract->h_contract_details, - (uint64_t) j_trans_id, // safe? - contract_str, - nounce, - (uint64_t) j_product_id); -} diff --git a/src/backend-lib/taler_merchant_contract_lib.h b/src/backend-lib/taler_merchant_contract_lib.h index ca799ff2..3f95841f 100644 --- a/src/backend-lib/taler_merchant_contract_lib.h +++ b/src/backend-lib/taler_merchant_contract_lib.h @@ -1,65 +1,11 @@ /** - * The contract sent by the merchant to the wallet - */ -struct 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 encoding with sorted fields. - */ - struct GNUNET_HashCode h_contract_details; - -}; - -/** * 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 nounce the nounce for hashing the wire details with - * @param edate when the beneficiary wants this transfer to take place + * @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 nounce, - struct GNUNET_TIME_Absolute edate); - -/** -* Take from the frontend the (partly) generated contract and fill -* the missing values in it; for example, the SEPA details. -* Moreover, it stores the contract in the DB. -* @param j_contract parsed contract, originated by the frontend. It will be -* hold the new values. -* @param db_conn the handle to the local DB -* @param contract where to store the (subset of the) contract to be (still) signed -* @param timestamp contract's timestamp (shall be generated by the merchant) -* @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 nounce the nounce used to hash the wire details -* @param a will be pointed to the (allocated) stringified 0-terminated contract -* @return GNUNET_OK on success, GNUNET_NO if attempting to double insert the -* same contract, GNUNET_SYSERR in case of other (mostly DB related) errors. -*/ - -/** -* TODO: inspect reference counting and, accordingly, free those json_t*(s) -* still allocated */ - -uint32_t -MERCHANT_handle_contract (json_t *j_contract, - PGconn *db_conn, - struct Contract *contract, - struct GNUNET_TIME_Absolute timestamp, - struct GNUNET_TIME_Absolute expiry, - struct GNUNET_TIME_Absolute edate, - struct GNUNET_TIME_Absolute refund, - char **a, - uint64_t nounce); + uint64_t salt); diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 6d38a37e..d5b283b8 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -10,7 +10,13 @@ taler_merchant_httpd_SOURCES = \ ../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 \ - ../backend-lib/taler-merchant-httpd_contract.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_LDADD = \ $(LIBGCRYPT_LIBS) \ diff --git a/src/backend/melted/Makefile.am b/src/backend/melted/Makefile.am deleted file mode 100644 index 66d784ec..00000000 --- a/src/backend/melted/Makefile.am +++ /dev/null @@ -1,62 +0,0 @@ -# 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 - -bin_PROGRAMS = \ - taler-mint-httpd \ - taler-merchant-httpd - -taler_mint_httpd_SOURCES = \ - taler-mint-httpd.c taler-mint-httpd.h \ - taler-mint-httpd_keystate.c taler-mint-httpd_keystate.h \ - taler-mint-httpd_db.c taler-mint-httpd_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_mhd.c taler-mint-httpd_mhd.h \ - taler-mint-httpd_admin.c taler-mint-httpd_admin.h \ - taler-mint-httpd_deposit.c taler-mint-httpd_deposit.h \ - taler-mint-httpd_withdraw.c taler-mint-httpd_withdraw.h \ - taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h -taler_mint_httpd_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/mintdb/libtalermintdb.la \ - -lmicrohttpd \ - -ljansson \ - -lgnunetutil \ - -lpthread -taler_merchant_httpd_SOURCES = \ - taler-merchant-httpd.c \ - merchant.c merchant.h \ - merchant_db.c merchant_db.h \ - taler-mint-httpd_keystate.c taler-mint-httpd_keystate.h \ - taler-mint-httpd_db.c taler-mint-httpd_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_mhd.c taler-mint-httpd_mhd.h \ - taler-mint-httpd_admin.c taler-mint-httpd_admin.h \ - taler-mint-httpd_deposit.c taler-mint-httpd_deposit.h \ - taler-mint-httpd_withdraw.c taler-mint-httpd_withdraw.h \ - taler-mint-httpd_refresh.c taler-mint-httpd_refresh.h - -taler_merchant_httpd_LDADD = \ - $(LIBGCRYPT_LIBS) \ - $(top_builddir)/src/util/libtalerutil.la \ - $(top_builddir)/src/mintdb/libtalermintdb.la \ - -lmicrohttpd \ - -ljansson \ - -lgnunetutil \ - -ltalermint \ - -ltalerpq \ - -lgnunetpostgres \ - -lpq \ - -lpthread - -if HAVE_DEVELOPER -taler_mint_httpd_SOURCES += \ - taler-mint-httpd_test.c taler-mint-httpd_test.h -endif diff --git a/src/backend/melted/README b/src/backend/melted/README deleted file mode 100644 index 45c4fb80..00000000 --- a/src/backend/melted/README +++ /dev/null @@ -1,9 +0,0 @@ -Since the merchant's backend makes use of several routines that are native -to the mint (in particular, those aimed to parse JSON object in HTTP POSTs), -and since there is no possibility to export some of them in a shared library, -then the files in this directory need to be moved *inside* the mint/src/mint -directory in order to be compiled. Use 'copy.sh SRC DST' (STILL NOT TESTED) -to copy the files in the desired location. - -Lastly, passing CFLAGS='-I/usr/include/postgresql' to ./configure seems to be -mandatory, in order to compile the merchant http daemon. diff --git a/src/backend/melted/copy.sh b/src/backend/melted/copy.sh deleted file mode 100755 index 570bf545..00000000 --- a/src/backend/melted/copy.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash -# copy all the backend relevant files from the first to the second -# directory given in the arguments. -# The intended use is to move back and forth those files -# (since they are git'ed on the merchant's repository) as long as not -# configure flag like --enable-merchant will be available from the mint - -# STILL NOT TESTED - -cp -t $2 \ -$1/Makefile.am \ -$1/merchant.c \ -$1/merchant_db.c \ -$1/merchant_db.h \ -$1/merchant.h \ -$1/taler-merchant-httpd.c \ -$1/merchant.conf diff --git a/src/backend/melted/merchant.c b/src/backend/melted/merchant.c deleted file mode 100644 index f124a030..00000000 --- a/src/backend/melted/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/backend/melted/merchant.conf b/src/backend/melted/merchant.conf deleted file mode 100644 index 3b637448..00000000 --- a/src/backend/melted/merchant.conf +++ /dev/null @@ -1,18 +0,0 @@ -[merchant] -PORT = 9966 -HOSTNAME = localhost -TRUSTED_MINTS = taler -KEYFILE = merchant.priv - -[mint-taler] -HOSTNAME = demo.taler.net -PORT = 80 -PUBKEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 - -[merchant-db] -CONFIG = postgres:///taler - -[wire-sepa] -IBAN = DE67830654080004822650 -NAME = GNUNET E.V -BIC = GENODEF1SRL diff --git a/src/backend/melted/merchant.h b/src/backend/melted/merchant.h deleted file mode 100644 index c66131ed..00000000 --- a/src/backend/melted/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/backend/melted/merchant_db.c b/src/backend/melted/merchant_db.c deleted file mode 100644 index 274de25a..00000000 --- a/src/backend/melted/merchant_db.c +++ /dev/null @@ -1,354 +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," - "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," - "expiry 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);", - 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, amount, amount_fraction, amount_currency," - "description, nounce, expiry, product) VALUES" - "($1, $2, $3, $4, $5, $6, $7, $8)", - 8, 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; -} - - -/** - * 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 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 *expiry, - const struct TALER_Amount *amount, - 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; - - #if 0 - /* - NOTE: the conversion to nl(l) happens *inside* the query param helpers; since - the policy imposes this format for storing values. */ - value_nbo = GNUNET_htonll (amount->value); - fraction_nbo = GNUNET_htonll (amount->fraction); - nounce_nbo = GNUNET_htonll (nounce); - expiry_ms_nbo = GNUNET_htonll (expiry.abs_value_us); - product = GNUNET_htonll (product); - #endif - - /* ported. To be tested/compiled */ - struct TALER_PQ_QueryParam params[] = { - TALER_PQ_query_param_uint64 (&c_id), - TALER_PQ_query_param_amount (amount), - /* a *string* is being put in the following statement, - though the API talks about a *blob*. Will this be liked by - the DB ? */ - // the following inserts a string as a blob. Will Taler provide a param-from-string helper? - TALER_PQ_query_param_fixed_size (desc, strlen(desc)), - TALER_PQ_query_param_uint64 (&nounce), - TALER_PQ_query_param_absolute_time (expiry), - 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); - EXITIF (PGRES_COMMAND_OK != status); - PQclear (res); - return GNUNET_OK; - - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; -} - -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 */ diff --git a/src/backend/melted/merchant_db.h b/src/backend/melted/merchant_db.h deleted file mode 100644 index a723b229..00000000 --- a/src/backend/melted/merchant_db.h +++ /dev/null @@ -1,101 +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); - - -/** - * 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 expiry the time when the contract will expire - * @param amount the taler amount corresponding to the contract - * @param c_id this contract's identification number - * @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 *expiry, - const struct TALER_Amount *amount, - 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); - -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); - -#endif /* MERCHANT_DB_H */ - -/* end of merchant-db.h */ diff --git a/src/backend/melted/myconf.sh b/src/backend/melted/myconf.sh deleted file mode 100644 index 0b7d2594..00000000 --- a/src/backend/melted/myconf.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -./configure CFLAGS='-I/usr/include/postgresql' diff --git a/src/backend/melted/taler-merchant-httpd.c b/src/backend/melted/taler-merchant-httpd.c deleted file mode 100644 index 46379809..00000000 --- a/src/backend/melted/taler-merchant-httpd.c +++ /dev/null @@ -1,821 +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/backend/taler-merchant-httpd.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 <taler/taler_json_lib.h> -#include <taler/taler_mint_service.h> -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_mhd.h" -#include "taler-mint-httpd_admin.h" -#include "taler-mint-httpd_deposit.h" -#include "taler-mint-httpd_withdraw.h" -#include "taler-mint-httpd_refresh.h" -#include "taler-mint-httpd_keystate.h" -#include "taler-mint-httpd_responses.h" -#include "merchant.h" -#include "merchant_db.h" - -extern struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); - -/** - * 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) - -/** - * Our hostname - */ -static char *hostname; - -/** - * The port we are running on - */ -static long long unsigned port; - -/** - * Merchant's private key - */ -struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; - -/** - * The MHD Daemon - */ -static struct MHD_Daemon *mhd; - -/** - * Connection handle to the our database - */ -PGconn *db_conn; - -/** - * Which currency is used by this mint? - * (verbatim copy from mint's code, just to make this - * merchant's source compile) - */ -char *TMH_mint_currency_string; - -/* As above */ -struct TALER_MINTDB_Plugin *TMH_plugin; - - -/** - * As above, though the merchant does need some form of - * configuration - */ -struct GNUNET_CONFIGURATION_Handle *cfg; - - -/** - * As above - */ -int TMH_test_mode; - - -/** - * As above - */ -char *TMH_mint_directory; - - -/** - * As above - */ -struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key; - -/** - * As above - */ -char *TMH_expected_wire_format; - -/** - * Shutdown task identifier - */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; - -/** - * Our wireformat - */ -static struct MERCHANT_WIREFORMAT_Sepa *wire; - -/** - * Hash of the wireformat - */ -static struct GNUNET_HashCode h_wire; - -/** - * Should we do a dry run where temporary tables are used for storing the data. - */ -static int dry; - -/** - * Global return code - */ -static int result; - -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 - -/** - * Mint context - */ -static struct TALER_MINT_Context *mctx; - -/** - * 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; - -/** -* Return the given message to the other end of connection -* @msg (0-terminated) message to show -* @param connection a MHD connection -* @param resp where to store the response for the calling function -* @return HTTP status code reflecting the operation outcome -* -*/ - -static unsigned int -generate_message (struct MHD_Response **resp, const char *msg) // this parameter was preceded by a '_' in its original file. Why? -{ - - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg, - MHD_RESPMEM_PERSISTENT); - ret = 200; - return ret; - - -} - -/** -* Generate the 'hello world' response -* @param connection a MHD connection -* @param resp where to store the response for the calling function -* @return HTTP status code reflecting the operation outcome -* -*/ - -static unsigned int -generate_hello (struct MHD_Response **resp) // this parameter was preceded by a '_' in its original file. Why? -{ - - const char *hello = "Hello customer\n"; - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, - MHD_RESPMEM_PERSISTENT); - ret = 200; - return ret; - - -} - -/** - * 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 (); -} - -/** -* Manage a non 200 HTTP status. I.e. it shows a 'failure' page to -* the client -* @param connection the channel thorugh which send the message -* @status the HTTP status to examine -* @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error -* -*/ - -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; -} - - -/** -* Generate the hash containing the information (= a nounce + merchant's IBAN) to -* redeem money from mint in a subsequent /deposit operation -* @param nounce the nounce -* @return the hash to be included in the contract's blob -* -*/ - -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; -} - - - -/* -* Make a binary blob representing a contract, store it into the DB, sign it -* and return a pointer to it. -* @param a 0-terminated string representing the description of this -* @param c_id contract id provided by the frontend -* purchase (it should contain a human readable description of the good -* in question) -* @param product some product numerical id. Its indended use is to link the -* good, or service being sold to some entry in the DB managed by the frontend -* @price the cost of this good or service -* @return pointer to the allocated contract (which has a field, 'sig', holding -* its own signature), NULL upon errors -*/ - -struct Contract * -generate_and_store_contract (const char *a, uint64_t c_id, uint64_t product, struct TALER_Amount *price) -{ - - struct Contract *contract; - struct GNUNET_TIME_Absolute expiry; - uint64_t nounce; - uint64_t contract_id_nbo; - - expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), - GNUNET_TIME_UNIT_DAYS); - ROUND_TO_SECS (expiry, abs_value_us); - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - EXITIF (GNUNET_SYSERR == MERCHANT_DB_contract_create (db_conn, - &expiry, - price, - c_id, - a, - nounce, - product)); - contract_id_nbo = GNUNET_htonll ((uint64_t) c_id); - contract = GNUNET_malloc (sizeof (struct Contract) + strlen (a) + 1); - contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - contract->purpose.size = htonl (sizeof (struct Contract) - - offsetof (struct Contract, purpose) - + strlen (a) + 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, a); - contract->h_wire = hash_wireformat (nounce); - TALER_amount_hton (&contract->amount, price); - GNUNET_CRYPTO_eddsa_sign (privkey, &contract->purpose, &contract->sig); - return contract; - - /* legacy from old merchant */ - EXITIF_exit: - if (NULL != contract) - { - GNUNET_free (contract); - } - return NULL; -} - - -/** - * 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 **connection_cls) -{ - - unsigned int status; - unsigned int no_destroy; - json_int_t prod_id; - json_int_t contract_id; - struct Contract *contract; - struct MHD_Response *resp; - struct TALER_Amount price; - struct GNUNET_CRYPTO_EddsaPublicKey pub; - json_t *json_price; - json_t *root; - json_t *contract_enc; - json_t *sig_enc; - json_t *eddsa_pub_enc; - json_t *response; - - int res; - const char *desc; - - #define URL_HELLO "/hello" - #define URL_CONTRACT "/contract" - no_destroy = 0; - resp = NULL; - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = generate_hello (&resp); //TBD - else - GNUNET_break (0); - } - - // to be called by the frontend passing all the product's information - // which are relevant for the contract's generation - if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = generate_message (&resp, "Sorry, only POST is allowed"); - else - res = TMH_PARSE_post_json (connection, - connection_cls, - upload_data, - upload_data_size, - &root); - - if (GNUNET_SYSERR == res) - { - status = generate_message (&resp, "unable to parse JSON root"); - return MHD_NO; - - } - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - /* The frontend should supply a JSON in the follwoing format: - - { - - "desc" : string human-readable describing this deal, - "product" : uint64-like integer referring to the product in some - DB adminstered by the frontend, - "cid" : uint64-like integer, this contract's id - "price" : a 'struct TALER_Amount' in the Taler compliant JSON format } - - */ - - #if 0 - /*res = json_typeof (root); <- seg fault*/ - json_int_t id; - const char *desc_test; - const char *cur_test; - json_t *id_json; - json_t *desc_json; - json_t *cur_json; - id_json = json_object_get (root, "product"); - desc_json = json_object_get (root, "desc"); - id = json_integer_value (id_json); - desc_test = json_string_value (desc_json); - json_price = json_object_get (root, "price"); - json_typeof (json_price); - cur_json = json_object_get (json_price, "currency"); - cur_test = json_string_value (cur_json); - printf ("id is %" JSON_INTEGER_FORMAT "\n", id); - printf ("desc is %s\n", desc_test); - TALER_json_to_amount (json_price, &price); - printf ("cur_test is %s\n", price.currency); - json_error_t err; - if (res = json_unpack_ex (root, &err, JSON_VALIDATE_ONLY, "{s:s, s:I, s:o}", - "desc", - //&desc, - "product", - //&prod_id, - "price"//, - //json_price - )) - #else - if ((res = json_unpack (root, "{s:s, s:I, s:I, s:o}", - "desc", - &desc, - "product", - &prod_id, - "cid", - &contract_id, - "price", - &json_price - ))) - #endif - - - /* still not possible to return a taler-compliant error message - since this JSON format is not among the taler officials ones */ - { - status = generate_message (&resp, "unable to parse /contract JSON\n"); - } - else - { - - if (GNUNET_OK != TALER_json_to_amount (json_price, &price)) - {/* still not possible to return a taler-compliant error message - since this JSON format is not among the taler officials ones */ - status = generate_message (&resp, "unable to parse `price' field in /contract JSON");} - else - { - /* Let's generate this contract! */ - if (NULL == (contract = generate_and_store_contract (desc, contract_id, prod_id, &price))) - { - /* status equals 500, so the user will get a "Internal server error" */ - //failure_resp (connection, status); - status = generate_message (&resp, "unable to generate and store this contract"); - //return MHD_YES; - - } - else - { - json_decref (root); - json_decref (json_price); - - printf ("Good contract\n"); - /* the contract is good and stored in DB, produce now JSON to return. - As of now, the format is {"contract" : base32contract, - "sig" : contractSignature, - "eddsa_pub" : keyToCheckSignatureAgainst - } - - */ - - sig_enc = TALER_json_from_eddsa_sig (&contract->purpose, &contract->sig); - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); - eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); - /* cutting of the signature at the beginning */ - contract_enc = TALER_json_from_data (&contract->purpose, sizeof (*contract) - - offsetof (struct Contract, purpose) - + strlen (desc) +1); - response = json_pack ("{s:o, s:o, s:o}", "contract", contract_enc, "sig", sig_enc, - "eddsa_pub", eddsa_pub_enc); - TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); - printf ("Got something?\n"); - return MHD_YES; - /* FRONTIER - CODE ABOVE STILL NOT TESTED */ - } - } - } - } - - 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; -} - -/** - * Shutdown task (magically invoked when the application is being - * quit) - * - * @param cls NULL - * @param tc scheduler task context - */ -static void -do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - - if (NULL != mhd) - { - MHD_stop_daemon (mhd); - mhd = NULL; - } - - if (NULL != db_conn) - { - MERCHANT_DB_disconnect (db_conn); - db_conn = NULL; - } - - -} - - - -/** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. - * - * @param cls closure - * @param keys information about the various keys used - * by the mint - */ -void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) -{ - /* which kind of mint's keys a merchant should need? Sign - keys? It has already the mint's (master?) public key from - the conf file */ - return; - -} - - - -/** - * 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; - unsigned int nmints; - unsigned int cnt; - struct MERCHANT_MintInfo *mint_infos; - void *keys_mgmt_cls; - - mint_infos = NULL; - keyfile = NULL; - result = GNUNET_SYSERR; - shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, NULL); - 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 (NULL == (db_conn = MERCHANT_DB_connect (config))); - EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_NO)); - 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; - /* port this to the new API */ - mint->conn = TALER_MINT_connect (mctx, - mint_infos[cnt].hostname, - &keys_mgmt_cb, - keys_mgmt_cls); /*<- safe?segfault friendly?*/ - - /* NOTE: the keys mgmt callback should roughly do what the following lines do */ - EXITIF (NULL == mint->conn); - - EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put - (mints_map, - (struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey, - mint, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); - } - - - mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY, - port, - NULL, NULL, - &url_handler, NULL, - MHD_OPTION_END); - - - EXITIF (NULL == mhd); - /* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that - mandatory ? */ - GNUNET_CRYPTO_hash (wire, sizeof (*wire), &h_wire); - result = GNUNET_OK; - - EXITIF_exit: - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); - 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}, - 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/backend/merchant.c b/src/backend/merchant.c index f124a030..02b37fb8 100644 --- a/src/backend/merchant.c +++ b/src/backend/merchant.c @@ -42,16 +42,14 @@ */ int TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_MintInfo **mints) + struct MERCHANT_Mint **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; + struct MERCHANT_Mint *r_mints; + struct MERCHANT_Mint mint; unsigned int cnt; int OK; @@ -60,7 +58,6 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, 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, @@ -78,26 +75,9 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *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; } @@ -107,7 +87,6 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, 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); @@ -118,6 +97,73 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, 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 diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf index 3b637448..68aeacfe 100644 --- a/src/backend/merchant.conf +++ b/src/backend/merchant.conf @@ -3,14 +3,18 @@ PORT = 9966 HOSTNAME = localhost TRUSTED_MINTS = taler KEYFILE = merchant.priv +CURRENCY = KUDOS +EDATE = 3 week +AUDITORS = france [mint-taler] -HOSTNAME = demo.taler.net -PORT = 80 -PUBKEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 +HOSTNAME = localmint + +[auditor-france] +NAME = Charles De Gaulle [merchant-db] -CONFIG = postgres:///taler +CONFIG = postgres:///talerdemo [wire-sepa] IBAN = DE67830654080004822650 diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 2b489040..ae9a4613 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -16,7 +16,8 @@ /** * @file merchant/backend/taler-merchant-httpd.c - * @brief HTTP serving layer mainly intended to communicate with the frontend + * @brief HTTP serving layer intended to perform crypto-work and + * communication with the mint * @author Marcello Stanisci */ @@ -25,24 +26,16 @@ #include <jansson.h> #include <gnunet/gnunet_util_lib.h> #include <curl/curl.h> -#include <taler/taler_json_lib.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" - -extern struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) +#include "taler-mint-httpd_mhd.h" +#include "taler-merchant-httpd_contract.h" +#include "taler-merchant-httpd_pay.h" /** * Our hostname @@ -65,270 +58,80 @@ struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; char *keyfile; /** - * The MHD Daemon - */ -static struct MHD_Daemon *mhd; - -/** - * Connection handle to the our database - */ -PGconn *db_conn; - -/** - * merchant's conf handle + * Mint context */ -struct GNUNET_CONFIGURATION_Handle *cfg; +static struct TALER_MINT_Context *mctx; /** - * Shutdown task identifier + * This value tells the mint by which date this merchant would like + * to receive the funds for a deposited payment */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; +struct GNUNET_TIME_Relative edate_delay; /** - * Our wireformat + * To make 'TMH_PARSE_navigate_json ()' compile */ -static struct MERCHANT_WIREFORMAT_Sepa *wire; +char *TMH_mint_currency_string; /** - * Should we do a dry run where temporary tables are used for storing the data. + * Trusted mints */ -static int dry; +struct MERCHANT_Mint *mints; /** - * Global return code + * Active auditors */ -static int result; +struct MERCHANT_Auditor *auditors; /** - * Mint context - */ -static struct TALER_MINT_Context *mctx; - -/** - * Context information of the mints we trust + * Shutdown task identifier */ -struct Mint -{ - /** - * Public key of this mint - */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - - /** - * Connection handle to this mint - */ - struct TALER_MINT_Handle *conn; -}; +static struct GNUNET_SCHEDULER_Task *shutdown_task; /** - * Hashmap to store the mint context information + * Context "poller" identifier */ -static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; +static struct GNUNET_SCHEDULER_Task *poller_task; /** - * Hashmap (with 'big entries') to make a mint's base URL - * to point to some mint-describing structure + * Our wireformat */ -static struct GNUNET_CONTAINER_MultiHashMap *mints_hashmap; - - +struct MERCHANT_WIREFORMAT_Sepa *wire; /** - * Mints' URL,port,key triples + * Salt used to hash the wire object */ -struct MERCHANT_MintInfo *mint_infos; +long long salt; /** * The number of accepted mints */ unsigned int nmints; -struct Mint_Response -{ - char *ptr; - size_t size; - -}; - - /** - * Generate the 'hello world' response - * @param connection a MHD connection - * @param resp where to store the response for the calling function. - * Note that in its original implementation this parameter was preceeded - * by a '_'. Still not clear why. - * @return HTTP status code reflecting the operation outcome - * + * The number of active auditors */ -static unsigned int -generate_hello (struct MHD_Response **resp) -{ - - const char *hello = "Hello customer\n"; - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, - MHD_RESPMEM_PERSISTENT); - ret = 200; - return ret; -} +unsigned int nauditors; /** - * Return the given message to the other end of connection - * @msg (0-terminated) message to show - * @param connection a MHD connection - * @param resp where to store the response for the calling function - * @return HTTP status code reflecting the operation outcome - * + * Should we do a dry run where temporary tables are used for storing the data. */ -static unsigned int -generate_message (struct MHD_Response **resp, const char *msg) -{ - - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg, - MHD_RESPMEM_MUST_FREE); - ret = 200; - return ret; -} +static int dry; /** - * Callback to pass to curl used to store a HTTP response - * in a custom memory location. - * See http://curl.haxx.se/libcurl/c/getinmemory.html for a - * detailed example - * - * @param contents the data gotten so far from the server - * @param size symbolic (arbitrarily chosen by libcurl) unit - * of bytes - * @param nmemb factor to multiply by @a size to get the real - * size of @a contents - * @param userdata a pointer to a memory location which remains - * the same across all the calls to this callback (i.e. it has - * to be grown at each invocation of this callback) - * @return number of written bytes - * See http://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html - * for official documentation - * + * Global return code */ -size_t -get_response_in_memory (char *contents, - size_t size, - size_t nmemb, - void *userdata) -{ - struct Mint_Response *mr; - size_t realsize; - - realsize = size * nmemb; - mr = userdata; - mr->ptr = realloc (mr->ptr, mr->size + realsize + 1); - - if (mr->ptr == NULL) { - printf ("Out of memory, could not get in memory mint's" - "response"); - return 0; - } - memcpy(&(mr->ptr[mr->size]), contents, realsize); - mr->size += realsize; - mr->ptr[mr->size] = 0; - - return realsize; - -} +static int result; -#ifdef PANIC_MGMT /** - * 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 + * Connection handle to the our database */ -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 (); -} -#endif +PGconn *db_conn; /** - * Manage a non 200 HTTP status. I.e. it shows a 'failure' page to - * the client - * @param connection the channel thorugh which send the message - * @status the HTTP status to examine - * @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error + * The MHD Daemon */ -static int -failure_resp (struct MHD_Connection *connection, unsigned int status) -{ - static char page_MHD_HTTP_NOT_FOUND[]="\ -<!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_MHD_HTTP_BAD_REQUEST[]="\ -<!DOCTYPE html> \ -<html><title>Bad request</title><body><center> \ -<h3>Malformed POSTed JSON.</h3> \ -</center></body></html>"; -static char page_MHD_HTTP_METHOD_NOT_ALLOWED[]="\ -<!DOCTYPE html> \ -<html><title>Method NOT allowed</title><body><center> \ -<h3>ONLY POSTs are allowed.</h3> \ -</center></body></html>"; - static char page_MHD_HTTP_INTERNAL_SERVER_ERROR[]="\ -<!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 (MHD_HTTP_BAD_REQUEST <= status); - resp = NULL; - switch (status) - { - case MHD_HTTP_NOT_FOUND : - PAGE(MHD_HTTP_NOT_FOUND); - break; - case MHD_HTTP_BAD_REQUEST: - PAGE(MHD_HTTP_BAD_REQUEST); - break; - case MHD_HTTP_METHOD_NOT_ALLOWED: - PAGE(MHD_HTTP_METHOD_NOT_ALLOWED); - break; - default: - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - PAGE(MHD_HTTP_INTERNAL_SERVER_ERROR); - } -#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; -} - +static struct MHD_Daemon *mhd; /** * A client has requested the given url using the given method @@ -377,423 +180,116 @@ url_handler (void *cls, const char *version, const char *upload_data, size_t *upload_data_size, - void **connection_cls) + void **con_cls) { - - /*printf ("%s\n", url);*/ - unsigned long status; - unsigned int no_destroy; - struct GNUNET_CRYPTO_EddsaSignature c_sig; - struct GNUNET_CRYPTO_EddsaSignature deposit_confirm_sig; - struct GNUNET_CRYPTO_EddsaPublicKey pub; - #ifdef OBSOLETE - struct ContractNBO contract; - #else - struct Contract contract; - #endif - struct MHD_Response *resp; - json_t *root; - json_t *j_sig_enc; - json_t *j_h_contract; - json_t *j_tmp; - json_t *eddsa_pub_enc; - json_t *response; - json_t *j_mints; - json_t *j_mint; - json_t *j_wire; - int cnt; /* loop counter */ - char *deposit_body; - json_t *j_contract_add; - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Absolute expiry; - struct GNUNET_TIME_Absolute edate; - struct GNUNET_TIME_Absolute refund; - struct GNUNET_HashCode h_json_wire; - json_t *j_h_json_wire; - struct curl_slist *slist; - char *contract_str; - struct GNUNET_HashCode h_contract_str; - struct MERCHANT_contract_handle ch; - struct TALER_MintPublicKeyP mint_pub; - uint64_t nounce; - - CURL *curl; - CURLcode curl_res; - - uint32_t res = GNUNET_SYSERR; - - #define URL_HELLO "/hello" - #define URL_CONTRACT "/contract" - #define URL_PAY "/pay" - no_destroy = 0; - resp = NULL; - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - - if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = generate_hello (&resp); - else - { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - } - } - - if (0 == strncasecmp (url, URL_PAY, sizeof (URL_PAY))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - goto end; - - } - else - res = TMH_PARSE_post_json (connection, - connection_cls, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_BAD_REQUEST; - goto end; - } - - /* the POST's body has to be further fetched */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - /* Firstly, check if the wallet is paying against an approved - mint */ - json_t *j_chosen_mint; - j_chosen_mint = json_object_get (root, "mint"); - struct GNUNET_HashCode hash_key; - char *chosen_mint; - - chosen_mint = json_string_value (j_chosen_mint); - GNUNET_CRYPTO_hash (chosen_mint, strlen (chosen_mint), &hash_key); - - if (NULL == - GNUNET_CONTAINER_multihashmap_get (mints_hashmap, &hash_key)) - { - printf ("Untrusted mint\n"); - status = MHD_HTTP_FORBIDDEN; - goto end; - - } - - /* NOTE: from now on, the mint's base URL is pointed by 'chosen_mint' */ - - /* The merchant will only add its 'wire' object to the JSON - it got from the wallet */ - - /* Get this dep. perm.'s H_contract */ - - if (NULL == (j_h_contract = json_object_get (root, "H_contract"))) + static struct TMH_RequestHandler handlers[] = { - printf ("H_contract field missing\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - TALER_json_to_data (j_h_contract, &h_contract_str, sizeof (struct GNUNET_HashCode)); + /* Landing page, tell humans to go away. */ + { "/", MHD_HTTP_METHOD_GET, "text/plain", + "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 }, - nounce = 0; - edate.abs_value_us = 0; + /* Further test page */ + { "/hello", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, Customer.\n", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - if (GNUNET_SYSERR == - MERCHANT_DB_get_contract_values (db_conn, - &h_contract_str, - &nounce, - &edate)) - { - printf ("not existing contract\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } + { "/contract", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &MH_handler_contract, MHD_HTTP_OK }, - /* Reproducing the wire object */ - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - nounce, - edate))) + { "/contract", NULL, "text/plain", + "Only POST is allowed", 0, + &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - { - printf ("wire object not reproduced\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } + { "/pay", MHD_HTTP_METHOD_POST, "application/json", + NULL, 0, + &MH_handler_pay, MHD_HTTP_OK }, - if (-1 == json_object_set (root, "wire", j_wire)) - { - printf ("depperm not augmented\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } + { "/pay", NULL, "text/plain", + "Only POST is allowed", 0, + &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - /* POST to mint's "/deposit" */ - curl = curl_easy_init (); - struct Mint_Response mr; - mr.ptr = malloc(1); - mr.size = 0; + {NULL, NULL, NULL, NULL, 0, 0 } + }; - if (curl) + static struct TMH_RequestHandler h404 = { - - char deposit_url[strlen (chosen_mint) + strlen ("http://") + strlen ("/deposit") + 1]; - sprintf (deposit_url, "http://%s/deposit", chosen_mint); - slist = curl_slist_append (slist, "Content-type: application/json"); - curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist); - - curl_easy_setopt (curl, CURLOPT_URL, deposit_url); - curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_response_in_memory); - curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *) &mr); - - /* NOTE: hopefully, this string won't need any URL-encoding, since as for the - Jansson specs, any space and-or newline are not in place using JSON_COMPACT - flag */ - deposit_body = json_dumps (root, JSON_COMPACT | JSON_PRESERVE_ORDER); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, deposit_body); - - curl_res = curl_easy_perform (curl); - - curl_slist_free_all(slist); - if(curl_res != CURLE_OK) - { - printf ("deposit not sent\n"); - goto end; - } - else - printf ("\ndeposit request issued\n"); - - curl_easy_cleanup(curl); - - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); - - /* If the request was successful, the deposit confirmation - has to be verified*/ - - if (MHD_HTTP_OK != result) - /* Jump here to error mgmt, see issue #3800 (TODO) */ - - if (GNUNET_SYSERR == - MERCHANT_DB_get_contract_handle (db_conn, &h_contract_str, &ch)) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - root = json_loads (mr.ptr, 0, NULL); - j_tmp = json_object_get (root, "sig"); - TALER_json_to_data (j_tmp, - &deposit_confirm_sig, - sizeof (struct GNUNET_CRYPTO_EddsaSignature)); - j_tmp = json_object_get (root, "pub"); - - TALER_json_to_data (j_tmp, - &mint_pub.eddsa_pub, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); - - - /* The following check could be done once the merchant is able to - retrieve the deposit fee for each coin it gets a payment with. - That happens because the signature made on a deposit confirmation - uses, among its values of interest, the subtraction of the deposit - fee from the used coin. That fee is never communicated by the wallet - to the merchant, so the only way for the merchant to get this - information is to fetch all the mint's keys, namely it needs to - invoke "/keys", and store what gotten in its DB */ - - #ifdef DENOMMGMT - if (GNUNET_NO == - MERCHANT_verify_confirmation (&h_contract_str, - &h_json_wire, - ch.timestamp, - ch.refund_deadline, - ch.contract_id, - amount_minus_fee, /* MISSING */ - coin_pub, /* MISSING */ - &pub, - &deposit_confirm_sig, - &mint_pub)) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - - } - - #endif - - /* Place here the successful message to issue to the frontend */ - status = MHD_HTTP_OK; - generate_message (&resp, "fullfillment page here"); - GNUNET_free (mr.ptr); - - } + "", 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; - /* - * To be called by the frontend passing the contract with some "holes" - * which will be completed, stored in DB, signed, and returned - * - */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Handling request for URL '%s'\n", + url); - if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) + for (i=0;NULL != handlers[i].url;i++) { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - goto end; - - } - else - res = TMH_PARSE_post_json (connection, - connection_cls, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_BAD_REQUEST; - goto end; - } - - - /* the POST's body has to be fetched furthermore */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - j_mints = json_array (); - for (cnt = 0; cnt < nmints; cnt++) - { - j_mint = json_pack ("{s:s}", - mint_infos[cnt].hostname, - GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey)); - json_array_append_new (j_mints, j_mint); - - } - - /* timestamp */ - now = GNUNET_TIME_absolute_get (); - /* expiry */ - expiry = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - /* edate, note: this value must be generated now (and not when the - wallet sends back a deposit permission because the hashed 'wire' object, - which carries this values in it, has to be included in the signed bundle - by the wallet) */ - edate = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - refund = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - - TALER_round_abs_time (&now); - TALER_round_abs_time (&expiry); - TALER_round_abs_time (&edate); - TALER_round_abs_time (&refund); - - /* getting the SEPA-aware JSON */ - /* nounce for hashing the wire object */ - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - - /* get wire object */ - - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - nounce, - edate))) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - /* hash wire objcet */ - if (GNUNET_SYSERR == TALER_hash_json (j_wire, &h_json_wire)) - goto end; - - j_h_json_wire = TALER_json_from_data ((void *) &h_json_wire, sizeof (struct GNUNET_HashCode)); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); - eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); - - if (NULL == (j_contract_add = json_pack ("{s:s, s:s, s:o, s:o, s:o}", - "merchant_pub", json_string_value (eddsa_pub_enc), - "H_wire", json_string_value (j_h_json_wire), - "timestamp", TALER_json_from_abs (now), - "refund", TALER_json_from_abs (refund), - "mints", j_mints))) - { - printf ("BAD contract enhancement\n"); - goto end; - } - - /* melt to what received from the wallet */ - if (-1 == json_object_update (root, j_contract_add)) - { - printf ("depperm response not built\n"); - goto end; - } - - res = MERCHANT_handle_contract (root, - db_conn, - &contract, - now, - expiry, - edate, - refund, - &contract_str, - nounce); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - if (GNUNET_NO == res) - { - status = MHD_HTTP_METHOD_NOT_ACCEPTABLE; - goto end; - } - - GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &c_sig); - GNUNET_CRYPTO_hash (contract_str, strlen (contract_str) + 1, &h_contract_str); - - j_sig_enc = TALER_json_from_eddsa_sig (&contract.purpose, &c_sig); - - response = json_pack ("{s:o, s:o, s:o}", - "contract", root, - "sig", j_sig_enc, - "h_contract", - TALER_json_from_data ((void *) &h_contract_str, sizeof (struct GNUNET_HashCode))); - - GNUNET_free (contract_str); - - TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); - return MHD_YES; - + rh = &handlers[i]; + if ( (0 == strcasecmp (url, + rh->url)) && + ( (NULL == rh->method) || + (0 == strcasecmp (method, + rh->method)) ) ) + return rh->handler (rh, + connection, + con_cls, + upload_data, + upload_data_size); } + return TMH_MHD_handler_static_response (&h404, + connection, + con_cls, + upload_data, + upload_data_size); - end: +} - if (NULL != resp) +/** + * 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) { - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - return MHD_YES; - if (!no_destroy) - MHD_destroy_response (resp); + ((struct MERCHANT_Mint *) cls)->pending = 0; } else - { - - EXITIF (GNUNET_OK != failure_resp (connection, status)); - return MHD_YES; - - } - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); - return MHD_NO; + printf ("no keys gotten\n"); } + /** * Shutdown task (magically invoked when the application is being * quit) @@ -804,6 +300,19 @@ url_handler (void *cls, 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) { @@ -816,37 +325,100 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) MERCHANT_DB_disconnect (db_conn); db_conn = NULL; } - if (keyfile != NULL) + if (NULL != keyfile) GNUNET_free (privkey); - - } /** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. + * Task that runs the context's event loop with the GNUnet scheduler. * - * @param cls closure - * @param keys information about the various keys used - * by the mint + * @param cls unused + * @param tc scheduler context (unused) */ static void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) { - /* which kind of mint's keys a merchant should need? Sign - keys? It has already the mint's master key from the conf file */ - return; - + 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 (mctx); + 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 (mctx, + &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 + * respective function to free the memory. + * + * @param cls client-defined closure + * @param connection connection handle + * @param con_cls value as set by the last call to + * the #MHD_AccessHandlerCallback + * @param toe reason for request termination + * @see #MHD_OPTION_NOTIFY_COMPLETED + * @ingroup request + */ +static void +handle_mhd_completion_callback (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + if (NULL == *con_cls) + return; + TMH_PARSE_post_cleanup_callback (*con_cls); + *con_cls = 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 cfgfile name of the configuration file used (for saving, can be + * NULL!) * @param config configuration */ void @@ -855,88 +427,87 @@ run (void *cls, char *const *args, const char *cfgfile, { unsigned int cnt; - void *keys_mgmt_cls; - - keys_mgmt_cls = NULL; - mint_infos = NULL; + mints = NULL; keyfile = NULL; result = GNUNET_SYSERR; - shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, NULL); - 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 (NULL == (db_conn = MERCHANT_DB_connect (config))); - EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_YES)); + shutdown_task = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, + NULL); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "merchant launched\n"); + + EXITIF (GNUNET_SYSERR == + (nmints = + TALER_MERCHANT_parse_mints (config, + &mints))); + EXITIF (GNUNET_SYSERR == + (nauditors = + TALER_MERCHANT_parse_auditors (config, + &auditors))); + 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 (NULL == + (db_conn = MERCHANT_DB_connect (config))); + EXITIF (GNUNET_OK != + MERCHANT_DB_initialize (db_conn, GNUNET_YES)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_number (config, "merchant", - "port", + "PORT", &port)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_string (config, "merchant", - "hostname", + "HOSTNAME", &hostname)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (config, + "merchant", + "CURRENCY", + &TMH_mint_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); EXITIF (NULL == (mctx = TALER_MINT_init ())); - /* Still not used */ - EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); - /* Used when the wallet points out which mint it want to deal with. - That indication is made through the mint's base URL, which will be - the hash-key for this table */ - EXITIF (NULL == (mints_hashmap = GNUNET_CONTAINER_multihashmap_create (nmints, GNUNET_NO))); - for (cnt = 0; cnt < nmints; cnt++) { - struct Mint *mint; - struct GNUNET_HashCode mint_key; - - mint = GNUNET_new (struct Mint); - mint->pubkey = mint_infos[cnt].pubkey; - /* port this to the new API */ - mint->conn = TALER_MINT_connect (mctx, - mint_infos[cnt].hostname, - &keys_mgmt_cb, - keys_mgmt_cls); /*<- safe?segfault friendly?*/ - - /* NOTE: the keys mgmt callback should roughly do what the following lines do */ - EXITIF (NULL == mint->conn); - - EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put - (mints_map, - (struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey, - mint, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); - - /* 1 create hash key - 2 create big entry - 3 put - */ - GNUNET_CRYPTO_hash (mint_infos[cnt].hostname, - strlen (mint_infos[cnt].hostname), - &mint_key); - GNUNET_CONTAINER_multihashmap_put (mints_hashmap, - &mint_key, - &mint_infos[cnt], - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + mints[cnt].pending = 1; + mints[cnt].conn = TALER_MINT_connect (mctx, + mints[cnt].hostname, + &keys_mgmt_cb, + &mints[cnt]); + EXITIF (NULL == mints[cnt].conn); + poller_task = + GNUNET_SCHEDULER_add_now (&context_task, mctx); } mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY, port, NULL, NULL, &url_handler, NULL, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, NULL, + MHD_OPTION_END); EXITIF (NULL == mhd); - - /* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that - mandatory ? */ result = GNUNET_OK; EXITIF_exit: @@ -969,7 +540,7 @@ main (int argc, char *const *argv) if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-merchant-serve", + "taler-merchant-http", "Serve merchant's HTTP interface", options, &run, NULL)) return 3; diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c new file mode 100644 index 00000000..e4b556a4 --- /dev/null +++ b/src/backend/taler-merchant-httpd_contract.c @@ -0,0 +1,173 @@ +/* + 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/backend/taler-merchant-httpd.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" + +extern struct MERCHANT_Mint *mints; +extern struct MERCHANT_Auditor *auditors; +extern struct GNUNET_CRYPTO_EddsaPrivateKey privkey; +extern const struct MERCHANT_WIREFORMAT_Sepa *wire; +extern unsigned int nmints; +extern unsigned int nauditors; +extern PGconn *db_conn; +extern long long salt; + +/** + * 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. + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +MH_handler_contract (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + 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 GNUNET_CRYPTO_EddsaSignature contract_sig; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + /* 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; + + json_object_set_new (root, + "H_wire", + TALER_json_from_data (&h_wire, sizeof (h_wire))); + + GNUNET_CRYPTO_eddsa_key_get_public (&privkey, &pubkey); + 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); + contract.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); + contract.purpose.size = htonl (sizeof (contract)); + GNUNET_CRYPTO_eddsa_sign (&privkey, &contract.purpose, &contract_sig); + + 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))); + +} diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h new file mode 100644 index 00000000..5e72c514 --- /dev/null +++ b/src/backend/taler-merchant-httpd_contract.h @@ -0,0 +1,46 @@ +/* + 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/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" + +/** + * Manage a contract request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * + * @return MHD result code + */ +int +MH_handler_contract (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/backend/taler-merchant-httpd_obsolete.c b/src/backend/taler-merchant-httpd_obsolete.c new file mode 100644 index 00000000..a96dad59 --- /dev/null +++ b/src/backend/taler-merchant-httpd_obsolete.c @@ -0,0 +1,986 @@ +/* + 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/backend/taler-merchant-httpd.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_json_lib.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" + +extern struct MERCHANT_WIREFORMAT_Sepa * +TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +/** + * Our hostname + */ +static char *hostname; + +/** + * The port we are running on + */ +static long long unsigned port; + +/** + * Merchant's private key + */ +struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; + +/** + * File holding the merchant's private key + */ +char *keyfile; + +/** + * The MHD Daemon + */ +static struct MHD_Daemon *mhd; + +/** + * Connection handle to the our database + */ +PGconn *db_conn; + +/** + * merchant's conf handle + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Shutdown task identifier + */ +static struct GNUNET_SCHEDULER_Task *shutdown_task; + +/** + * Our wireformat + */ +static struct MERCHANT_WIREFORMAT_Sepa *wire; + +/** + * Should we do a dry run where temporary tables are used for storing the data. + */ +static int dry; + +/** + * Global return code + */ +static int result; + +/** + * Mint context + */ +static struct TALER_MINT_Context *mctx; + +/** + * 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; + +/** + * Hashmap (with 'big entries') to make a mint's base URL + * to point to some mint-describing structure + */ +static struct GNUNET_CONTAINER_MultiHashMap *mints_hashmap; + +/** + * Mints' URL,port,key triples + */ +struct MERCHANT_MintInfo *mint_infos; + +/** + * The number of accepted mints + */ +unsigned int nmints; + +struct Mint_Response +{ + char *ptr; + size_t size; + +}; + + +/** + * Generate the 'hello world' response + * @param connection a MHD connection + * @param resp where to store the response for the calling function. + * Note that in its original implementation this parameter was preceeded + * by a '_'. Still not clear why. + * @return HTTP status code reflecting the operation outcome + * + */ +static unsigned int +generate_hello (struct MHD_Response **resp) +{ + + const char *hello = "Hello customer\n"; + unsigned int ret; + + *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, + MHD_RESPMEM_PERSISTENT); + ret = 200; + return ret; +} + +/** + * Return the given message to the other end of connection + * @msg (0-terminated) message to show + * @param connection a MHD connection + * @param resp where to store the response for the calling function + * @return HTTP status code reflecting the operation outcome + * + */ +static unsigned int +generate_message (struct MHD_Response **resp, const char *msg) +{ + + unsigned int ret; + + *resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg, + MHD_RESPMEM_MUST_FREE); + ret = 200; + return ret; +} + +/** + * Callback to pass to curl used to store a HTTP response + * in a custom memory location. + * See http://curl.haxx.se/libcurl/c/getinmemory.html for a + * detailed example + * + * @param contents the data gotten so far from the server + * @param size symbolic (arbitrarily chosen by libcurl) unit + * of bytes + * @param nmemb factor to multiply by @a size to get the real + * size of @a contents + * @param userdata a pointer to a memory location which remains + * the same across all the calls to this callback (i.e. it has + * to be grown at each invocation of this callback) + * @return number of written bytes + * See http://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html + * for official documentation + * + */ +size_t +get_response_in_memory (char *contents, + size_t size, + size_t nmemb, + void *userdata) +{ + struct Mint_Response *mr; + size_t realsize; + + realsize = size * nmemb; + mr = userdata; + mr->ptr = realloc (mr->ptr, mr->size + realsize + 1); + + if (mr->ptr == NULL) { + printf ("Out of memory, could not get in memory mint's" + "response"); + return 0; + } + memcpy(&(mr->ptr[mr->size]), contents, realsize); + mr->size += realsize; + mr->ptr[mr->size] = 0; + + return realsize; + +} + +#ifdef PANIC_MGMT +/** + * 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 (); +} +#endif + +/** + * Manage a non 200 HTTP status. I.e. it shows a 'failure' page to + * the client + * @param connection the channel thorugh which send the message + * @status the HTTP status to examine + * @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error + */ +static int +failure_resp (struct MHD_Connection *connection, unsigned int status) +{ + static char page_MHD_HTTP_NOT_FOUND[]="\ +<!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_MHD_HTTP_BAD_REQUEST[]="\ +<!DOCTYPE html> \ +<html><title>Bad request</title><body><center> \ +<h3>Malformed POSTed JSON.</h3> \ +</center></body></html>"; +static char page_MHD_HTTP_METHOD_NOT_ALLOWED[]="\ +<!DOCTYPE html> \ +<html><title>Method NOT allowed</title><body><center> \ +<h3>ONLY POSTs are allowed.</h3> \ +</center></body></html>"; + static char page_MHD_HTTP_INTERNAL_SERVER_ERROR[]="\ +<!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 (MHD_HTTP_BAD_REQUEST <= status); + resp = NULL; + switch (status) + { + case MHD_HTTP_NOT_FOUND : + PAGE(MHD_HTTP_NOT_FOUND); + break; + case MHD_HTTP_BAD_REQUEST: + PAGE(MHD_HTTP_BAD_REQUEST); + break; + case MHD_HTTP_METHOD_NOT_ALLOWED: + PAGE(MHD_HTTP_METHOD_NOT_ALLOWED); + break; + default: + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + PAGE(MHD_HTTP_INTERNAL_SERVER_ERROR); + } +#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; +} + + +/** + * 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 **connection_cls) +{ + + /*printf ("%s\n", url);*/ + unsigned long status; + unsigned int no_destroy; + struct GNUNET_CRYPTO_EddsaSignature c_sig; + struct GNUNET_CRYPTO_EddsaSignature deposit_confirm_sig; + struct GNUNET_CRYPTO_EddsaPublicKey pub; + #ifdef OBSOLETE + struct ContractNBO contract; + #else + struct Contract contract; + #endif + struct MHD_Response *resp; + json_t *root; + json_t *j_sig_enc; + json_t *j_h_contract; + json_t *j_tmp; + json_t *eddsa_pub_enc; + json_t *response; + json_t *j_mints; + json_t *j_mint; + json_t *j_wire; + int cnt; /* loop counter */ + char *deposit_body; + json_t *j_contract_add; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute expiry; + struct GNUNET_TIME_Absolute edate; + struct GNUNET_TIME_Absolute refund; + struct GNUNET_HashCode h_json_wire; + json_t *j_h_json_wire; + struct curl_slist *slist; + char *contract_str; + struct GNUNET_HashCode h_contract_str; + struct MERCHANT_contract_handle ch; + struct TALER_MintPublicKeyP mint_pub; + uint64_t nounce; + + CURL *curl; + CURLcode curl_res; + + uint32_t res = GNUNET_SYSERR; + + #define URL_HELLO "/hello" + #define URL_CONTRACT "/contract" + #define URL_PAY "/pay" + no_destroy = 0; + resp = NULL; + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + + if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + status = generate_hello (&resp); + else + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + } + } + + if (0 == strncasecmp (url, URL_PAY, sizeof (URL_PAY))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + goto end; + + } + else + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_BAD_REQUEST; + goto end; + } + + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + /* Firstly, check if the wallet is paying against an approved + mint */ + json_t *j_chosen_mint; + j_chosen_mint = json_object_get (root, "mint"); + struct GNUNET_HashCode hash_key; + char *chosen_mint; + + chosen_mint = json_string_value (j_chosen_mint); + GNUNET_CRYPTO_hash (chosen_mint, strlen (chosen_mint), &hash_key); + + if (NULL == + GNUNET_CONTAINER_multihashmap_get (mints_hashmap, &hash_key)) + { + printf ("Untrusted mint\n"); + status = MHD_HTTP_FORBIDDEN; + goto end; + + } + + /* NOTE: from now on, the mint's base URL is pointed by 'chosen_mint' */ + + /* The merchant will only add its 'wire' object to the JSON + it got from the wallet */ + + /* Get this dep. perm.'s H_contract */ + + if (NULL == (j_h_contract = json_object_get (root, "H_contract"))) + { + printf ("H_contract field missing\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + TALER_json_to_data (j_h_contract, &h_contract_str, sizeof (struct GNUNET_HashCode)); + + nounce = 0; + edate.abs_value_us = 0; + + if (GNUNET_SYSERR == + MERCHANT_DB_get_contract_values (db_conn, + &h_contract_str, + &nounce, + &edate)) + { + printf ("not existing contract\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* Reproducing the wire object */ + if (NULL == (j_wire = MERCHANT_get_wire_json (wire, + nounce, + edate))) + + { + printf ("wire object not reproduced\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + if (-1 == json_object_set (root, "wire", j_wire)) + { + printf ("depperm not augmented\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* POST to mint's "/deposit" */ + curl = curl_easy_init (); + + struct Mint_Response mr; + mr.ptr = malloc(1); + mr.size = 0; + + if (curl) + { + + char deposit_url[strlen (chosen_mint) + strlen ("http://") + strlen ("/deposit") + 1]; + sprintf (deposit_url, "http://%s/deposit", chosen_mint); + slist = curl_slist_append (slist, "Content-type: application/json"); + curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist); + + curl_easy_setopt (curl, CURLOPT_URL, deposit_url); + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_response_in_memory); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *) &mr); + + /* NOTE: hopefully, this string won't need any URL-encoding, since as for the + Jansson specs, any space and-or newline are not in place using JSON_COMPACT + flag */ + deposit_body = json_dumps (root, JSON_COMPACT | JSON_PRESERVE_ORDER); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, deposit_body); + + curl_res = curl_easy_perform (curl); + + curl_slist_free_all(slist); + if(curl_res != CURLE_OK) + { + printf ("deposit not sent\n"); + goto end; + } + else + printf ("\ndeposit request issued\n"); + + curl_easy_cleanup(curl); + + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); + + /* If the request was successful, the deposit confirmation + has to be verified*/ + + if (MHD_HTTP_OK != result) + /* Jump here to error mgmt, see issue #3800 (TODO) */ + + if (GNUNET_SYSERR == + MERCHANT_DB_get_contract_handle (db_conn, &h_contract_str, &ch)) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + root = json_loads (mr.ptr, 0, NULL); + j_tmp = json_object_get (root, "sig"); + TALER_json_to_data (j_tmp, + &deposit_confirm_sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + j_tmp = json_object_get (root, "pub"); + + TALER_json_to_data (j_tmp, + &mint_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + + GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); + + + /* The following check could be done once the merchant is able to + retrieve the deposit fee for each coin it gets a payment with. + That happens because the signature made on a deposit confirmation + uses, among its values of interest, the subtraction of the deposit + fee from the used coin. That fee is never communicated by the wallet + to the merchant, so the only way for the merchant to get this + information is to fetch all the mint's keys, namely it needs to + invoke "/keys", and store what gotten in its DB */ + + #ifdef DENOMMGMT + if (GNUNET_NO == + MERCHANT_verify_confirmation (&h_contract_str, + &h_json_wire, + ch.timestamp, + ch.refund_deadline, + ch.contract_id, + amount_minus_fee, /* MISSING */ + coin_pub, /* MISSING */ + &pub, + &deposit_confirm_sig, + &mint_pub)) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + + } + + #endif + + /* Place here the successful message to issue to the frontend */ + status = MHD_HTTP_OK; + generate_message (&resp, "fullfillment page here"); + GNUNET_free (mr.ptr); + + } + + + } + + /* + * To be called by the frontend passing the contract with some "holes" + * which will be completed, stored in DB, signed, and returned + * + */ + + if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + goto end; + + } + else + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_BAD_REQUEST; + goto end; + } + + + /* the POST's body has to be fetched furthermore */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + j_mints = json_array (); + for (cnt = 0; cnt < nmints; cnt++) + { + j_mint = json_pack ("{s:s}", + mint_infos[cnt].hostname, + GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey)); + json_array_append_new (j_mints, j_mint); + + } + + /* timestamp */ + now = GNUNET_TIME_absolute_get (); + /* expiry */ + expiry = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + /* edate, note: this value must be generated now (and not when the + wallet sends back a deposit permission because the hashed 'wire' object, + which carries this values in it, has to be included in the signed bundle + by the wallet) */ + edate = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + refund = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + + TALER_round_abs_time (&now); + TALER_round_abs_time (&expiry); + TALER_round_abs_time (&edate); + TALER_round_abs_time (&refund); + + /* getting the SEPA-aware JSON */ + /* nounce for hashing the wire object */ + nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); + + /* get wire object */ + + if (NULL == (j_wire = MERCHANT_get_wire_json (wire, + nounce, + edate))) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* hash wire objcet */ + if (GNUNET_SYSERR == TALER_hash_json (j_wire, &h_json_wire)) + goto end; + + j_h_json_wire = TALER_json_from_data ((void *) &h_json_wire, sizeof (struct GNUNET_HashCode)); + + GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); + eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); + + if (NULL == (j_contract_add = json_pack ("{s:s, s:s, s:o, s:o, s:o}", + "merchant_pub", json_string_value (eddsa_pub_enc), + "H_wire", json_string_value (j_h_json_wire), + "timestamp", TALER_json_from_abs (now), + "refund", TALER_json_from_abs (refund), + "mints", j_mints))) + { + printf ("BAD contract enhancement\n"); + goto end; + } + + /* melt to what received from the wallet */ + if (-1 == json_object_update (root, j_contract_add)) + { + printf ("depperm response not built\n"); + goto end; + } + + res = MERCHANT_handle_contract (root, + db_conn, + &contract, + now, + expiry, + edate, + refund, + &contract_str, + nounce); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + if (GNUNET_NO == res) + { + status = MHD_HTTP_METHOD_NOT_ACCEPTABLE; + goto end; + } + + GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &c_sig); + GNUNET_CRYPTO_hash (contract_str, strlen (contract_str) + 1, &h_contract_str); + + j_sig_enc = TALER_json_from_eddsa_sig (&contract.purpose, &c_sig); + + response = json_pack ("{s:o, s:o, s:o}", + "contract", root, + "sig", j_sig_enc, + "h_contract", + TALER_json_from_data ((void *) &h_contract_str, sizeof (struct GNUNET_HashCode))); + + GNUNET_free (contract_str); + + TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); + return MHD_YES; + + } + + end: + + if (NULL != resp) + { + EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); + return MHD_YES; + 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; +} + +/** + * Shutdown task (magically invoked when the application is being + * quit) + * + * @param cls NULL + * @param tc scheduler task context + */ +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + + if (NULL != mhd) + { + MHD_stop_daemon (mhd); + mhd = NULL; + } + + if (NULL != db_conn) + { + MERCHANT_DB_disconnect (db_conn); + db_conn = NULL; + } + if (keyfile != NULL) + GNUNET_free (privkey); + + +} + +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure + * @param keys information about the various keys used + * by the mint + */ +static void +keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) +{ + /* which kind of mint's keys a merchant should need? Sign + keys? It has already the mint's master key from the conf file */ + + /* HOT UPDATE: the merchants needs 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 */ + + + return; + +} + +/** + * 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 + */ +void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + + unsigned int cnt; + void *keys_mgmt_cls; + + keys_mgmt_cls = NULL; + mint_infos = NULL; + keyfile = NULL; + result = GNUNET_SYSERR; + shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, NULL); + 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 (NULL == (db_conn = MERCHANT_DB_connect (config))); + EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_YES)); + 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 ())); + /* Still not used */ + EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); + /* Used when the wallet points out which mint it want to deal with. + That indication is made through the mint's base URL, which will be + the hash-key for this table */ + EXITIF (NULL == (mints_hashmap = GNUNET_CONTAINER_multihashmap_create (nmints, GNUNET_NO))); + + for (cnt = 0; cnt < nmints; cnt++) + { + struct Mint *mint; + struct GNUNET_HashCode mint_key; + + mint = GNUNET_new (struct Mint); + mint->pubkey = mint_infos[cnt].pubkey; + /* port this to the new API */ + mint->conn = TALER_MINT_connect (mctx, + mint_infos[cnt].hostname, + &keys_mgmt_cb, + keys_mgmt_cls); /*<- safe?segfault friendly?*/ + + /* NOTE: the keys mgmt callback should roughly do what the following lines do */ + EXITIF (NULL == mint->conn); + + EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put + (mints_map, + (struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey, + mint, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); + + /* 1 create hash key + 2 create big entry + 3 put + */ + GNUNET_CRYPTO_hash (mint_infos[cnt].hostname, + strlen (mint_infos[cnt].hostname), + &mint_key); + GNUNET_CONTAINER_multihashmap_put (mints_hashmap, + &mint_key, + &mint_infos[cnt], + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + } + + mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY, + port, + NULL, NULL, + &url_handler, NULL, + MHD_OPTION_END); + + EXITIF (NULL == mhd); + + /* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that + mandatory ? */ + result = GNUNET_OK; + + EXITIF_exit: + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); + 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}, + 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/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c new file mode 100644 index 00000000..c96476ac --- /dev/null +++ b/src/backend/taler-merchant-httpd_pay.c @@ -0,0 +1,368 @@ +/* + 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/backend/taler-merchant-httpd.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" + +extern struct MERCHANT_Mint *mints; +extern const struct MERCHANT_WIREFORMAT_Sepa *wire; +extern PGconn *db_conn; +extern long long salt; +extern unsigned int nmints; +extern struct GNUNET_TIME_Relative edate_delay; +extern struct GNUNET_CRYPTO_EddsaPrivateKey privkey; + + + +/** + * 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 + */ +int +deposit_fee_from_coin_aggregate (struct MHD_Connection *connection, + json_t *coin_aggregate, + struct TALER_Amount *deposit_fee, + unsigned int mint_index) +{ + int res; + const struct TALER_MINT_Keys *keys; + const struct TALER_MINT_DenomPublicKey *denom_details; + 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 */ + + /*printf ("mint %s (%d), pends: %d\n", + mints[mint_index].hostname, + mint_index, + mints[mint_index].pending);*/ + + 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; +} + + +/** + * Callback to handle a deposit permission's response. How does this behave if the mint + * goes offline during a call? + * @param cls closure (in our invocation it will be the transaction_id, so the cb can refer + * to the right DB row for this deposit permission) + * @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) + */ +void +deposit_cb (void *cls, unsigned int http_status, json_t *proof) +{ + if (MHD_HTTP_OK == http_status) + ; /* set pending to false in DB and notify the frontend with "OK" */ + else + ; /* set a timeout to retry */ +} + +/** + * 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) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a + * upload_data + * @return MHD result code + */ +int +MH_handler_pay (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + + 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; /*a cell in the global array*/ + unsigned int coins_cnt; /*a cell in the global array*/ + 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 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 MERCHANT_DepositConfirmationCls *dccls; + + 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_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", &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 + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + return MHD_NO; + /* the POST's body has to be further fetched */ + 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 + + 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++) + { + /* no mint found in array */ + if (mint_index == nmints) + { + mint_index = -1; + break; + } + + /* 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; + 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"); + json_array_foreach (coins, coins_index, coin_aggregate) + { + + /* 3 For each coin in DB + + a. Generate a deposit permission + b. store it and its tid 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 */ + + dccls = GNUNET_malloc (sizeof (struct MERCHANT_DepositConfirmationCls)); + dccls->index = coins_index; + dccls->dc = dc; + + dh = TALER_MINT_deposit (mints[mint_index].conn, + &amount, + edate, + wire_details, + &h_contract, + &coin_pub, + &ub_sig, + &denom_pub, + timestamp, + transaction_id, + &pubkey, + refund_deadline, + &coin_sig, + deposit_cb, + dccls); /*may be destroyed by the time the cb gets called..*/ + if (NULL == dh) + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_SERVICE_UNAVAILABLE, + "{s:s, s:i}", + "mint", mints[mint_index].hostname, + "transaction_id", transaction_id); + } + + /* 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); + return MHD_YES; + + /* 4 Return response code: success, or whatever data the + mint sent back regarding some bad coin */ +} diff --git a/src/backend/taler-merchant-httpd_pay.h b/src/backend/taler-merchant-httpd_pay.h new file mode 100644 index 00000000..6a796e06 --- /dev/null +++ b/src/backend/taler-merchant-httpd_pay.h @@ -0,0 +1,46 @@ +/* + 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/backend/taler-merchant-httpd_contract.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" + +/** + * Manage a 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) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * + * @return MHD result code + */ +int +MH_handler_pay (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + +#endif diff --git a/src/backend/taler-mint-httpd.h b/src/backend/taler-mint-httpd.h new file mode 100644 index 00000000..ad8702f0 --- /dev/null +++ b/src/backend/taler-mint-httpd.h @@ -0,0 +1,85 @@ +/* + 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/backend/taler-mint-httpd_mhd.c b/src/backend/taler-mint-httpd_mhd.c new file mode 100644 index 00000000..419c4fb0 --- /dev/null +++ b/src/backend/taler-mint-httpd_mhd.c @@ -0,0 +1,154 @@ +/* + This file is part of TALER + Copyright (C) 2014 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_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.) + * @author Florian Dold + * @author Benedikt Mueller + * @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" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MHD_Response *response; + int ret; + + if (0 == rh->data_size) + rh->data_size = strlen ((const char *) rh->data); + response = MHD_create_response_from_buffer (rh->data_size, + (void *) rh->data, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @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 +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "http://www.git.taler.net/?p=mint.git"); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TMH_RESPONSE_reply_json_pack (connection, + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/* end of taler-mint-httpd_mhd.c */ diff --git a/src/backend/taler-mint-httpd_mhd.h b/src/backend/taler-mint-httpd_mhd.h new file mode 100644 index 00000000..a9f575df --- /dev/null +++ b/src/backend/taler-mint-httpd_mhd.h @@ -0,0 +1,111 @@ +/* + This file is part of TALER + Copyright (C) 2014 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_mhd.h + * @brief helpers for MHD interaction, used to generate simple responses + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_MHD_H +#define TALER_MINT_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @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 +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_MHD_helper_send_json_pack (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/backend/taler-mint-httpd_parsing.c b/src/backend/taler-mint-httpd_parsing.c index 8d9e3f9f..9efd6c23 100644 --- a/src/backend/taler-mint-httpd_parsing.c +++ b/src/backend/taler-mint-httpd_parsing.c @@ -27,6 +27,13 @@ #include "taler-mint-httpd_parsing.h" #include "taler-mint-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_mint_currency_string; /** * Initial size for POST request buffer. */ @@ -139,6 +146,110 @@ buffer_append (struct Buffer *buf, } /** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls) +{ + struct Buffer *r = con_cls; + + if (NULL != r) + { + buffer_deinit (r); + GNUNET_free (r); + } +} + +/** + * Release all memory allocated for the variable-size fields in + * the parser specification. + * + * @param spec specification to free + * @param spec_len number of items in @a spec to look at + */ +static void +release_data (struct TMH_PARSE_FieldSpecification *spec, + unsigned int spec_len) +{ + unsigned int i; + + for (i=0; i < spec_len; i++) + { + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_RET_STRING: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_RET_DATA: + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + if (NULL != spec[i].destination) + { + GNUNET_free (* (void**) spec[i].destination); + *(void**) spec[i].destination = NULL; + *spec[i].destination_size_out = 0; + } + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + json_t *json; + + json = *(json_t **) spec[i].destination; + if (NULL != json) + { + json_decref (json); + *(json_t**) spec[i].destination = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *pk; + + pk = spec[i].destination; + if (NULL != pk->rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (pk->rsa_public_key); + pk->rsa_public_key = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *sig; + + sig = spec[i].destination; + if (NULL != sig->rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (sig->rsa_signature); + sig->rsa_signature = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_AMOUNT: + memset (spec[i].destination, + 0, + sizeof (struct TALER_Amount)); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + break; + case TMH_PARSE_JNC_RET_UINT64: + break; + } + } +} + +/** * Process a POST request containing a JSON object. This function * realizes an MHD POST processor that will (incrementally) process * JSON data uploaded to the HTTP server. It will store the required @@ -239,4 +350,712 @@ 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 + * information is provided. The string will live as long as the containg + * JSON will, and must not be freed by the user + * @param field name of the field + * @param[out] pointer to the string + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_string (const char *field, + char **out) +{ + struct TMH_PARSE_FieldSpecification ret = + {field, (void **) out, 0, NULL, TMH_PARSE_JNC_RET_STRING, 0}; + return ret; +} + +/** + * Generate line in parser specification for 64-bit integer + * given as an integer in JSON. + * + * @param field name of the field + * @param[out] u64 integer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_uint64 (const char *field, + uint64_t *u64) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, (void *) u64, sizeof (uint64_t), NULL, TMH_PARSE_JNC_RET_UINT64, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for JSON object value. + * + * @param field name of the field + * @param[out] jsonp address of pointer to JSON to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_object (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_OBJECT }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for JSON array value. + * + * @param field name of the field + * @param[out] jsonp address of JSON pointer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_array (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an absolute time. + * + * @param field name of the field + * @param[out] atime time to initialize + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_time_abs (const char *field, + struct GNUNET_TIME_Absolute *atime) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, atime, sizeof(struct GNUNET_TIME_Absolute), NULL, TMH_PARSE_JNC_RET_TIME_ABSOLUTE, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param[out] pk key to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_public_key (const char *field, + struct TALER_DenominationPublicKey *pk) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, pk, 0, NULL, TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, 0 }; + pk->rsa_public_key = NULL; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param sig the signature to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_signature (const char *field, + struct TALER_DenominationSignature *sig) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, sig, 0, NULL, TMH_PARSE_JNC_RET_RSA_SIGNATURE, 0 }; + sig->rsa_signature = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an amount. + * + * @param field name of the field + * @param amount a `struct TALER_Amount *` to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_amount (const char *field, + struct TALER_Amount *amount) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, amount, sizeof(struct TALER_Amount), NULL, TMH_PARSE_JNC_RET_AMOUNT, 0 }; + memset (amount, 0, sizeof (struct TALER_Amount)); + return ret; +} + + +/** + * Generate line in parser specification for variable-size value. + * + * @param field name of the field + * @param[out] ptr pointer to initialize + * @param[out] ptr_size size to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_variable (const char *field, + void **ptr, + size_t *ptr_size) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, ptr, 0, ptr_size, TMH_PARSE_JNC_RET_DATA_VAR, 0 }; + *ptr = NULL; + return ret; +} + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see + * `enum TMH_PARSE_JsonNavigationCommand`) + * @return + * #GNUNET_YES if navigation was successful + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error (no response was generated, + * connection must be closed) + */ +int +TMH_PARSE_navigate_json (struct MHD_Connection *connection, + const json_t *root, + ...) +{ + va_list argp; + int ret; + json_t *path; /* what's our current path from 'root'? */ + + path = json_array (); + va_start (argp, root); + ret = 2; /* just not any of the valid return values */ + while (2 == ret) + { + enum TMH_PARSE_JsonNavigationCommand command + = va_arg (argp, + enum TMH_PARSE_JsonNavigationCommand); + + switch (command) + { + case TMH_PARSE_JNC_FIELD: + { + const char *fname = va_arg(argp, const char *); + + json_array_append_new (path, + json_string (fname)); + root = json_object_get (root, + fname); + if (NULL == root) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "missing field in JSON", + "field", fname, + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_INDEX: + { + int fnum = va_arg(argp, int); + + json_array_append_new (path, + json_integer (fnum)); + root = json_array_get (root, + fnum); + if (NULL == root) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "missing index in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_RET_DATA: + { + void *where = va_arg (argp, void *); + size_t len = va_arg (argp, size_t); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + where, len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_STRING: + { + void **where = va_arg (argp, void **); + *where = (void*) json_string_value (root); + ret = GNUNET_OK; + } + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + { + void **where = va_arg (argp, void **); + size_t *len = va_arg (argp, size_t *); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "json_string_value() failed")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *len = (strlen (str) * 5) / 8; + if (NULL != where) + { + *where = GNUNET_malloc (*len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + *where, + *len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (*where); + *where = NULL; + *len = 0; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + int typ = va_arg (argp, int); + const json_t **r_json = va_arg (argp, const json_t **); + + if ( (NULL == root) || + ( (-1 != typ) && + (json_typeof (root) != typ)) ) + { + GNUNET_break_op (0); + *r_json = NULL; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:i, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", typ, + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_json = root; + json_incref ((json_t *) root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_UINT64: + { + uint64_t *r_u64 = va_arg (argp, uint64_t *); + + if (json_typeof (root) != JSON_INTEGER) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", "integer", + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_u64 = (uint64_t) json_integer_value (root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationPublicKey *); + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_public_key) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA public key in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationSignature *); + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_signature) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA signature in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_AMOUNT: + { + struct TALER_Amount *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_amount ((json_t *) root, + where)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "Bad format", + "path", path)) + ? GNUNET_SYSERR : GNUNET_NO; + break; + } + if (0 != strcmp (where->currency, + TMH_mint_currency_string)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O, s:s}", + "error", "Currency not supported", + "path", path, + "currency", where->currency)) + ? GNUNET_SYSERR : GNUNET_NO; + memset (where, 0, sizeof (struct TALER_Amount)); + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + { + struct GNUNET_TIME_Absolute *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_abs ((json_t *) root, + where)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "Bad format", + "hint", "expected absolute time", + "path", path)) + ? GNUNET_SYSERR : GNUNET_NO; + break; + } + ret = GNUNET_OK; + break; + } + + default: + GNUNET_break (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "unhandled value in switch")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + va_end (argp); + json_decref (path); + return ret; +} + + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #TMH_PARSE_release_data() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct TMH_PARSE_FieldSpecification *spec) +{ + unsigned int i; + int ret; + + ret = GNUNET_YES; + for (i=0; NULL != spec[i].field_name; i++) + { + if (GNUNET_YES != ret) + break; + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_RET_DATA: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA, + spec[i].destination, + spec[i].destination_size_in); + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA_VAR, + (void **) spec[i].destination, + spec[i].destination_size_out); + break; + case TMH_PARSE_JNC_RET_STRING: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_STRING, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TYPED_JSON, + spec[i].type, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_SIGNATURE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_AMOUNT: + GNUNET_assert (sizeof (struct TALER_Amount) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_AMOUNT, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + GNUNET_assert (sizeof (struct GNUNET_TIME_Absolute) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TIME_ABSOLUTE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_UINT64: + GNUNET_assert (sizeof (uint64_t) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_UINT64, + spec[i].destination); + break; + } + } + if (GNUNET_YES != ret) + release_data (spec, + i - 1); + return ret; +} + + /* end of taler-mint-httpd_parsing.c */ diff --git a/src/backend/taler-mint-httpd_parsing.h b/src/backend/taler-mint-httpd_parsing.h index d6a2b4ea..dae65092 100644 --- a/src/backend/taler-mint-httpd_parsing.h +++ b/src/backend/taler-mint-httpd_parsing.h @@ -139,7 +139,13 @@ enum TMH_PARSE_JsonNavigationCommand * encoded as a JSON integer. * Param: uint64_t * */ - TMH_PARSE_JNC_RET_UINT64 + TMH_PARSE_JNC_RET_UINT64, + /** + * Return a 'char *' as returned from 'json_string_value ()'. + * So it will live as long as the containg JSON is not freed, + * and must not be freed by the user + */ + TMH_PARSE_JNC_RET_STRING }; @@ -265,7 +271,18 @@ TMH_PARSE_member_variable (const char *field, void **ptr, size_t *ptr_size); - +/** + * Generate line in parser specification for string. The returned + * string is already nul-terminated internally by JSON, so no length + * information is provided. The string will live as long as the containg + * JSON will, and must not be freed by the user + * @param field name of the field + * @param[out] pointer to the string + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_string (const char *field, + char **out); /** * Generate line in parser specification for 64-bit integer * given as an integer in JSON. diff --git a/src/backend/taler-mint-httpd_responses.c b/src/backend/taler-mint-httpd_responses.c index aa8f0bf8..00a4d25f 100644 --- a/src/backend/taler-mint-httpd_responses.c +++ b/src/backend/taler-mint-httpd_responses.c @@ -165,4 +165,42 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) "invalid json"); } +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response) +{ + int TMH_mint_connection_close; + TMH_mint_connection_close = 0; + + /* this test is taken verbatim from the mint's code, + so there is no particular need to do that for a merchant */ + if (TMH_mint_connection_close) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close"); +} + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + const char *hint) +{ + return TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{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-mint-httpd_responses.h index b1a49d42..f947bd57 100644 --- a/src/backend/taler-mint-httpd_responses.h +++ b/src/backend/taler-mint-httpd_responses.h @@ -81,6 +81,16 @@ int TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, const char *hint); /** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, + const char *hint); +/** * Send a response indicating that the request was too big. * * @param connection the MHD connection to use @@ -89,4 +99,14 @@ TMH_RESPONSE_reply_internal_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 + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response); + #endif diff --git a/src/frontend/checkout.php b/src/frontend/checkout.php index 72e78944..ab1c2c28 100644 --- a/src/frontend/checkout.php +++ b/src/frontend/checkout.php @@ -76,9 +76,7 @@ pass it to the extension */ function handle_contract(json_contract) { - var cEvent = new CustomEvent('taler-contract', - { detail: json_contract, - target: "/taler/pay"}); + var cEvent = new CustomEvent('taler-contract', { detail: json_contract }); document.body.dispatchEvent(cEvent); }; @@ -89,7 +87,7 @@ function handle_contract(json_contract) function taler_pay(form) { var contract_request = new XMLHttpRequest(); - contract_request.open("POST", "/generate_taler_contract.php", true); + contract_request.open("GET", "/generate_taler_contract.php", true); contract_request.onload = function (e) { if (contract_request.readyState == 4) diff --git a/src/frontend/generate_taler_contract.php b/src/frontend/generate_taler_contract.php index 33cb2a47..9849dc82 100644 --- a/src/frontend/generate_taler_contract.php +++ b/src/frontend/generate_taler_contract.php @@ -23,8 +23,26 @@ 2. generate the JSON to forward to the backend 3. forward the response with the contract from the backend to to the wallet -*/ + + To test this feature from the command line, issue: + + - $ curl http://merchant_url/generate_taler_contract.php?cli_debug=yes + if the whole "journey" to the backend is begin tested + - $ curl http://merchant_url/generate_taler_contract.php?backend_test=no + if just the frontend job is being tested +*/ + $cli_debug = false; +$backend_test = true; + +if ($_GET['cli_debug'] == 'yes') + $cli_debug = true; + +if ($_GET['backend_test'] == 'no') +{ + $cli_debug = true; + $backend_test = false; +} // 1) recover the session information session_start(); @@ -48,6 +66,7 @@ else { $receiver = "Test Receiver"; $amount = 5; + $currency = "KUDOS"; } @@ -71,6 +90,9 @@ $teatax = array ('value' => 1, 'fraction' => 0, 'currency' => $currency); +// Take a timestamp +$now = new DateTime('now'); + // pack the JSON for the contract // --- FIXME: exact format needs review! $json = json_encode (array ('amount' => array ('value' => $value, @@ -79,7 +101,7 @@ $json = json_encode (array ('amount' => array ('value' => $value, 'max_fee' => array ('value' => 3, 'fraction' => 01010, 'currency' => $currency), - 'trans_id' => $transaction_id, + 'transaction_id' => $transaction_id, 'products' => array ( array ('description' => $desc, 'quantity' => 1, @@ -90,6 +112,10 @@ $json = json_encode (array ('amount' => array ('value' => $value, 'taxes' => array (array ('teatax' => $teatax)), 'delivery_date' => "Some Date Format", 'delivery_location' => 'LNAME1')), + 'timestamp' => "/Date(" . $now->getTimestamp() . ")/", + 'pay_url' => "/taler/pay", + 'expiry' => "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/", + 'refund_deadline' => "/Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")/", 'merchant' => array ('address' => 'LNAME2', 'name' => 'test merchant', 'jurisdiction' => 'LNAME3'), @@ -115,9 +141,8 @@ $json = json_encode (array ('amount' => array ('value' => $value, 'state' => 'Test State', 'region' => 'Test Region', 'province' => 'Test Province', - 'ZIP code' => 4908)))); //, JSON_PRETTY_PRINT); - -if ($cli_debug && TRUE) + 'ZIP code' => 4908))), JSON_PRETTY_PRINT); +if ($cli_debug && !$backend_test) { echo $json . "\n"; exit; diff --git a/src/frontend/index.html b/src/frontend/index.html index 8f0c57f9..19b7a21c 100644 --- a/src/frontend/index.html +++ b/src/frontend/index.html @@ -70,39 +70,9 @@ obtain Taler coins. This is typically done using a wire transfer. However, as this is just a demonstrator, we will allow you to send the mint KUDOS coins using a simple - form on this website instead.</p> - <p>You begin by clicking on the Taler icon and selecting - "Create reserve". The extension will then display some - hexadecimal reserve public key, which you should copy to the - clipboard. After that, you can paste it into the form - below. When dealing with real currency, you would do - the same, except that you would have to copy the string - into the subject area for your wire transfer instead of - into this form. + form on the <a href="http://demo.taler.net/transfer" target="_blank">mint's website</a> + (new tab).</p> </p> - <form id="reserve-form" name="tform" action="/fake_wire_transfer.php" method="POST"> - <div class="participation" id="fake-wire"> - <br> - Paste your reserve public key here (right-click, "paste"): - <input type="text" name="reserve_pk" /> - <select id="mint" name="mint_url"> - <option value="demo.taler.net">mint @taler.net</option> - <option value="localmint">localmint (**)</option> - </select> - <br> - Amount to credit to your reserve: - <select id="amount" name="kudos_amount"> - <option value="1">1 KUDOS</option> - <option value="2">2 KUDOS</option> - <option value="5">5 KUDOS</option> - <option value="10">10 KUDOS</option> - <option value="1000">1000 KUDOS</option> - </select> - <br> - <input type="submit" value="Submit"/> - <br> - </div> - </form> </div> <div class="explanation" id="payment" role="article" style="display:none;"> <h2>Step 3: Select project to donate KUDOS to!</h2> diff --git a/src/frontend/pay.php b/src/frontend/pay.php index c09f83a0..33f3b712 100644 --- a/src/frontend/pay.php +++ b/src/frontend/pay.php @@ -1,13 +1,6 @@ -<!DOCTYPE html> -<html> -<head> -<title>Fullfillment page</title> -</head> -<body> - <?php /* - This file is part of TALER + This file is part of GNU TALER. Copyright (C) 2014, 2015 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the @@ -23,21 +16,83 @@ */ - /* -// recover the session - session_start(); - if(!isset($_SESSION['contract'])){ -// http_response_code(404); - echo "Sorry.."; - } - else echo "Paid"; +/* + This serving module adds the 'max_fee' field to the object which + sends to the backend, and optionally the field 'edate' (indicating + to the mint the tollerated deadline to receive funds for this payment) + NOTE: 'max_fee' must be consistent with the same value indicated within + the contract; thus, a "real" merchant must implement such a mapping - session_destroy(); +*/ +$cli_debug = false; +$backend_test = true; -*/ -?> -Payment successful, thanks! +if ($_GET['cli_debug'] == 'yes') + $cli_debug = true; + +if ($_GET['backend_test'] == 'no') +{ + $cli_debug = true; + $backend_test = false; +} + +$post_body = file_get_contents('php://input'); + +$now = new DateTime('now'); +$edate = array ('edate' => + "/Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")/"); + +$deposit_permission = json_decode ($post_body, true); +$max_fee = array ('max_fee' => array ('value' => 3, + 'fraction' => 8, + 'currency' => "KUDOS")); + +$new_deposit_permission = array_merge ($deposit_permission, $max_fee); +$new_deposit_permission_edate = array_merge ($new_deposit_permission, $edate); -</body> -</html> +/* Craft the HTTP request, note that the backend + could be on an entirely different machine if + desired. */ + +if ($cli_debug && !$backend_test) +{ + + /* DO NOTE the newline at the end of 'echo's argument */ + //echo json_encode ($new_deposit_permission_edate, JSON_PRETTY_PRINT) + echo json_encode ($new_deposit_permission, JSON_PRETTY_PRINT) + . "\n"; + exit; +} + +$req = new http\Client\Request ("POST", + "http://" . $_SERVER["SERVER_NAME"] . "/backend/pay", + array ("Content-Type" => "application/json")); +$req->getBody()->append (json_encode ($new_deposit_permission)); + +// Execute the HTTP request +$client = new http\Client; +$client->enqueue($req)->send (); + +// Fetch the response +$resp = $client->getResponse (); +$status_code = $resp->getResponseCode (); + +// Our response code is the same we got from the backend: +http_response_code ($status_code); + +// Now generate our body +if ($status_code != 200) +{ + /* error: just forwarding to the wallet what + gotten from the backend (which is forwarding 'as is' + the error gotten from the mint) */ + echo $resp->body->toString (); + +} +else +{ + echo "Payment succedeed!\n"; +} + +?> diff --git a/src/include/merchant.h b/src/include/merchant.h index a5273507..fd7e0e20 100644 --- a/src/include/merchant.h +++ b/src/include/merchant.h @@ -25,6 +25,8 @@ #include <gnunet/gnunet_common.h> #include <gnunet/gnunet_crypto_lib.h> +#include <taler/taler_mint_service.h> +#include "merchant.h" /** * Macro to round microseconds to seconds in GNUNET_TIME_* structs. @@ -40,26 +42,99 @@ } while (0) /** - * A mint + * Outcome of a /deposit request for a coin */ -struct MERCHANT_MintInfo { +struct MERCHANT_DepositConfirmation +{ + /** + * How many coins this request is made of + */ + unsigned int coins_cnt; + /** + * 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; + +}; + +struct MERCHANT_DepositConfirmationCls +{ + /** + * Offset of this coin into the array of all coins outcomes + */ + unsigned int index; + + /** + * Pointer to the global (malloc'd) array of all coins outcomes + */ + struct MERCHANT_DepositConfirmation *dc; + +}; + +/** + * Mint + */ +struct MERCHANT_Mint +{ /** * Hostname */ char *hostname; /** - * The public key of the mint + * Flag which indicates whether some HTTP transfer between + * this merchant and the mint is still ongoing */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; + int pending; /** - * The port where the mint's service is running + * A connection to this mint */ - uint16_t port; + struct TALER_MINT_Handle *conn; }; +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. @@ -72,8 +147,20 @@ struct MERCHANT_MintInfo { */ int TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_MintInfo **mints); + 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 diff --git a/src/tests/Makefile.am b/src/tests/Makefile.am index e73ff6ef..30b1e04b 100644 --- a/src/tests/Makefile.am +++ b/src/tests/Makefile.am @@ -9,7 +9,7 @@ bin_PROGRAMS = \ test_contract_SOURCES = \ test_contract.c \ - merchant.c \ + ../backend/merchant.c \ ../backend-lib/merchant_db.c ../backend-lib/merchant_db.h test_contract_LDADD = \ diff --git a/src/tests/deposit_permission.sample b/src/tests/deposit_permission.sample new file mode 100644 index 00000000..c1f2d704 --- /dev/null +++ b/src/tests/deposit_permission.sample @@ -0,0 +1,31 @@ +{ + "transaction_id" : 1, + "timestamp": "\/Date(1447334003)\/", + "H_wire": "V1F0F5AWSNX60E6TXZEYDZRE84J8HMBGXEM09AEVV3N97MM75P6JQRSWR5KQVC1RBBF2SRXY7P10H0ZM0VETWPFAJJRBVJSXNMDWTYR", + "merchant_pub": "BQ0TMTBV2XEJ8G2PXA9KRDD5WDT5EV29KPVS6J9RBQJCS8BCPQ70", + "mint": "localmint", + "coins": [ + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "AH86D5WV9G6RT0A3CHYJW598BNMH8848GAKYTEH1JX6X2BKZWTX0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26C9J8D1MCHHJ84S34H1K8H1M6HHN60V3GH9S84R3GH9K6MVM6GSM70RKCDT188VM4CHM74RM8GSN6GSKEHHS6CT44H9Q8CWKCD1P68SK2G9S8GR34D1P60T4CCJ67523EDJ28S1KJD9N8H330CT58RS38CSM8H346GT58N13CGJ16GS34CHQ6GT42CSS6CTM4C9H8N1KCG9G74T36C1K8GT44E1N8RRK0D218GV46G9R6CT4CHA4890M6H1H8GV34E1Q8N244DHM8CRK2H1R8CV3ADA37113JDHR88W36E9R61242GT5752KEE9R84WK8H9G8GT34GT26S2MAE1P70RMAGSK74W30C1J60S32GSK64WKAC218MWM6GHM8S332DSN70SM2DHR6H146E228CTM8D135452081918G2J2G0", + "coin_sig": "MFTHC54GFYHA3CGKHC8SSDSTYX8YMEJDNQA7AEY5M7JBK3WPDQ9NCJ54ZSPQRZ4QCJC0CPREP0XRWH9JQ509ENSEXWKNNM5FEVRG238" + }, + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "MQY067A8AGG90TSH11C8JNRW8P1R669JAAQX4V2HGFHGJ2WY4ZA0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26H1J60R4AHHG6N1KEC268N23ECT664VKAGSJ8GRK4CA56H2KAH1H8CS4AHA188SKJE266RR3EDA58N148DSS74TM6CA58N24AC9K6X1M4C9Q6RW48CT674SMCE2264V42CSH8D336GSQ84SM6CT564TKECSR8MRMAGHG8GW3JEA56WT32D9S8H334DJ564SK8GSS60WM6E1P8S346H238GRM8DSN6MR36C256GVM8H9M6WV30C246X0MCH9R84TK2D1M70V44CA260VKJGHN6RR4ACT488R3GCA46MW3GDHK6GWMAHHP6S242H9G6D134D9N8RV32DSJ8GSKGC1J6RTK2GHQ8H0K2DHH6WT38GSH74WM4EA574RKEE1M6D0M4DHK8MW4ACT68CT4CE236H236GH35452081918G2J2G0", + "coin_sig": "K9S5273GT4QKF5Y9FYJ62BV710WGEFE1DSXV75A37X272ADWBCV0ERZV9TF2VYTCSH1837R3F7A39R5QEPCC0NYW3JQ5S70X8MJG008" + } + ] +} diff --git a/src/tests/deposit_permission_backend.sample b/src/tests/deposit_permission_backend.sample new file mode 100644 index 00000000..3cd920ed --- /dev/null +++ b/src/tests/deposit_permission_backend.sample @@ -0,0 +1,36 @@ +{ + "transaction_id" : 1, + "timestamp": "\/Date(1447334003)\/", + "H_wire": "V1F0F5AWSNX60E6TXZEYDZRE84J8HMBGXEM09AEVV3N97MM75P6JQRSWR5KQVC1RBBF2SRXY7P10H0ZM0VETWPFAJJRBVJSXNMDWTYR", + "merchant_pub": "BQ0TMTBV2XEJ8G2PXA9KRDD5WDT5EV29KPVS6J9RBQJCS8BCPQ70", + "mint": "demo.taler.net", + "coins": [ + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "AH86D5WV9G6RT0A3CHYJW598BNMH8848GAKYTEH1JX6X2BKZWTX0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26C9J8D1MCHHJ84S34H1K8H1M6HHN60V3GH9S84R3GH9K6MVM6GSM70RKCDT188VM4CHM74RM8GSN6GSKEHHS6CT44H9Q8CWKCD1P68SK2G9S8GR34D1P60T4CCJ67523EDJ28S1KJD9N8H330CT58RS38CSM8H346GT58N13CGJ16GS34CHQ6GT42CSS6CTM4C9H8N1KCG9G74T36C1K8GT44E1N8RRK0D218GV46G9R6CT4CHA4890M6H1H8GV34E1Q8N244DHM8CRK2H1R8CV3ADA37113JDHR88W36E9R61242GT5752KEE9R84WK8H9G8GT34GT26S2MAE1P70RMAGSK74W30C1J60S32GSK64WKAC218MWM6GHM8S332DSN70SM2DHR6H146E228CTM8D135452081918G2J2G0", + "coin_sig": "MFTHC54GFYHA3CGKHC8SSDSTYX8YMEJDNQA7AEY5M7JBK3WPDQ9NCJ54ZSPQRZ4QCJC0CPREP0XRWH9JQ509ENSEXWKNNM5FEVRG238" + }, + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "MQY067A8AGG90TSH11C8JNRW8P1R669JAAQX4V2HGFHGJ2WY4ZA0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26H1J60R4AHHG6N1KEC268N23ECT664VKAGSJ8GRK4CA56H2KAH1H8CS4AHA188SKJE266RR3EDA58N148DSS74TM6CA58N24AC9K6X1M4C9Q6RW48CT674SMCE2264V42CSH8D336GSQ84SM6CT564TKECSR8MRMAGHG8GW3JEA56WT32D9S8H334DJ564SK8GSS60WM6E1P8S346H238GRM8DSN6MR36C256GVM8H9M6WV30C246X0MCH9R84TK2D1M70V44CA260VKJGHN6RR4ACT488R3GCA46MW3GDHK6GWMAHHP6S242H9G6D134D9N8RV32DSJ8GSKGC1J6RTK2GHQ8H0K2DHH6WT38GSH74WM4EA574RKEE1M6D0M4DHK8MW4ACT68CT4CE236H236GH35452081918G2J2G0", + "coin_sig": "K9S5273GT4QKF5Y9FYJ62BV710WGEFE1DSXV75A37X272ADWBCV0ERZV9TF2VYTCSH1837R3F7A39R5QEPCC0NYW3JQ5S70X8MJG008" + } + ], + "max_fee": { + "value": 3, + "fraction": 8, + "currency": "KUDOS" + } +} diff --git a/src/tests/deposit_permission_edate_backend.sample b/src/tests/deposit_permission_edate_backend.sample new file mode 100644 index 00000000..259a62bc --- /dev/null +++ b/src/tests/deposit_permission_edate_backend.sample @@ -0,0 +1,37 @@ +{ + "transaction_id" : 1, + "timestamp": "\/Date(1447334003)\/", + "edate": "\/Date(1447334003)\/", + "H_wire": "V1F0F5AWSNX60E6TXZEYDZRE84J8HMBGXEM09AEVV3N97MM75P6JQRSWR5KQVC1RBBF2SRXY7P10H0ZM0VETWPFAJJRBVJSXNMDWTYR", + "merchant_pub": "BQ0TMTBV2XEJ8G2PXA9KRDD5WDT5EV29KPVS6J9RBQJCS8BCPQ70", + "mint": "demo.taler.net", + "coins": [ + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "AH86D5WV9G6RT0A3CHYJW598BNMH8848GAKYTEH1JX6X2BKZWTX0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26C9J8D1MCHHJ84S34H1K8H1M6HHN60V3GH9S84R3GH9K6MVM6GSM70RKCDT188VM4CHM74RM8GSN6GSKEHHS6CT44H9Q8CWKCD1P68SK2G9S8GR34D1P60T4CCJ67523EDJ28S1KJD9N8H330CT58RS38CSM8H346GT58N13CGJ16GS34CHQ6GT42CSS6CTM4C9H8N1KCG9G74T36C1K8GT44E1N8RRK0D218GV46G9R6CT4CHA4890M6H1H8GV34E1Q8N244DHM8CRK2H1R8CV3ADA37113JDHR88W36E9R61242GT5752KEE9R84WK8H9G8GT34GT26S2MAE1P70RMAGSK74W30C1J60S32GSK64WKAC218MWM6GHM8S332DSN70SM2DHR6H146E228CTM8D135452081918G2J2G0", + "coin_sig": "MFTHC54GFYHA3CGKHC8SSDSTYX8YMEJDNQA7AEY5M7JBK3WPDQ9NCJ54ZSPQRZ4QCJC0CPREP0XRWH9JQ509ENSEXWKNNM5FEVRG238" + }, + { + "f": { + "value": 0, + "fraction": 100000, + "currency": "KUDOS" + }, + "coin_pub": "MQY067A8AGG90TSH11C8JNRW8P1R669JAAQX4V2HGFHGJ2WY4ZA0", + "denom_pub": "51R7ARKCD5HJTTV5F4G0M818E9SP280A40G2GVH04CR30HHG74SM8GSM6D33EGSR84R46E246H23GCHK690M4G9R8GS4CD9S8GRMCDSP64T34GA56GV34DA3851M6H9S6MWK8H9K8D13CD1M85344GSP61244GT28N132HA56N1MCHA56513AD2160RK2CSN6D23AGJ56X33AGSM68W4CH1S88WK4D1H68SMACHJ88TM2CA28MRM4D248GS4ADHR8GSM4C2364V38E1N88S30HA564TKJE1N750KGC9R6RTKJE2370R4AC268D0MACHN6N242D1N68TKAC1M65234DJ26CRK4D9M8MSK6C9J64R32D1R70VKEE25852MCG9N84W32GJ68914CD1J691M6GA360S3GCHP8GWK8HHH68TK8CSK70S4AE9N712K0H9K6X34AC9354520818CMG26C1H60R30C935452081918G2J2G0", + "ub_sig": "51SPJSSDESGPR80A40M74WV140520818ECG26H1J60R4AHHG6N1KEC268N23ECT664VKAGSJ8GRK4CA56H2KAH1H8CS4AHA188SKJE266RR3EDA58N148DSS74TM6CA58N24AC9K6X1M4C9Q6RW48CT674SMCE2264V42CSH8D336GSQ84SM6CT564TKECSR8MRMAGHG8GW3JEA56WT32D9S8H334DJ564SK8GSS60WM6E1P8S346H238GRM8DSN6MR36C256GVM8H9M6WV30C246X0MCH9R84TK2D1M70V44CA260VKJGHN6RR4ACT488R3GCA46MW3GDHK6GWMAHHP6S242H9G6D134D9N8RV32DSJ8GSKGC1J6RTK2GHQ8H0K2DHH6WT38GSH74WM4EA574RKEE1M6D0M4DHK8MW4ACT68CT4CE236H236GH35452081918G2J2G0", + "coin_sig": "K9S5273GT4QKF5Y9FYJ62BV710WGEFE1DSXV75A37X272ADWBCV0ERZV9TF2VYTCSH1837R3F7A39R5QEPCC0NYW3JQ5S70X8MJG008" + } + ], + "max_fee": { + "value": 3, + "fraction": 8, + "currency": "KUDOS" + } +} diff --git a/src/tests/merchant.c b/src/tests/merchant.c deleted file mode 100644 index f124a030..00000000 --- a/src/tests/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/tests/test_contract.c b/src/tests/test_contract.c index 47b3b0e6..147ea4f3 100644 --- a/src/tests/test_contract.c +++ b/src/tests/test_contract.c @@ -23,7 +23,7 @@ #include "platform.h" #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> #include "merchant.h" #include "merchant_db.h" #include <taler_merchant_lib.h> @@ -80,7 +80,6 @@ run (void *cls, char *const *args, const char *cfgfile, { json_t *j_fake_contract; - json_t *j_wire; json_t *j_details; json_t *j_mints; json_t *j_item; @@ -98,22 +97,17 @@ run (void *cls, char *const *args, const char *cfgfile, json_t *j_merchant_zipcode; json_t *j_lnames; json_t *j_deldate; - char *contract_tmp_str; char *desc; struct TALER_Amount amount; int64_t t_id; int64_t p_id; - #ifdef OBSOLETE - struct ContractNBO contract; - #else struct Contract contract; - #endif struct GNUNET_TIME_Absolute edate; struct GNUNET_TIME_Absolute now; uint64_t nounce; struct GNUNET_HashCode h_contract_str; char *aa; - char *fancy_time; + const char *fancy_time; uint32_t ret; db_conn = NULL; @@ -278,8 +272,6 @@ run (void *cls, char *const *args, const char *cfgfile, nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - j_wire = MERCHANT_get_wire_json (wire, nounce, now); - ret = MERCHANT_handle_contract (j_fake_contract, db_conn, &contract, diff --git a/src/tests/test_contract_README b/src/tests/test_contract_README new file mode 100644 index 00000000..9fd337cf --- /dev/null +++ b/src/tests/test_contract_README @@ -0,0 +1,10 @@ +- In order to test the "/contract" facility of the merchant, + just issue + + - curl http://merchant-url/generate_taler_contract.php?cli_debug=yes + (this form tests the whole communication between frontend and backend) + + - curl http://merchant-url/generate_taler_contract.php?backend_test=no + (this form test only the contract proposition generation of the frontend, + so it doesn't further connect to the backend to get the proposition JSON + enhanced) diff --git a/src/tests/test_pay_README b/src/tests/test_pay_README new file mode 100644 index 00000000..1704d6b7 --- /dev/null +++ b/src/tests/test_pay_README @@ -0,0 +1,26 @@ +The merchant's "/pay" facility can be tested either directly by querying the +backend or by querying the frontend (which in turn will query the backend). +Each file *.sample is sample JSON to POST to both components (with the '_backend' +named version being dedicated to just the backend). + +Note: the sample 'deposit_permission_edate_backend.sample' contains the optional +'edate' field to the deposit permission (that field indicates the tollerated delay +by this merchant to receive funds relative to this payment) + +The frontend test admits two versions of queries, as in the following examples: + + * curl -H 'Content-type: application/json' -d@deposit_permission.sample \ + http://merchant-url/pay.php?cli_debug=yes + + (this form tests the whole communication between frontend and backend) + + * curl -d@deposit_permission.sample http://merchant-url/pay.php?backend_test=no + + (this form tests only the deposit permission enhancement made by the + frontend, so it doesn't further connect to the backend to actually POST + that data. By default, the frontend doesn't add the 'edate' field, so it + is necessary to uncomment the related line of code to test this feature) + +Finally, to POST to the backend, issue + * curl -H 'Content-type: application/json' -d@*_backend.sample http://backend-url/pay + |