diff options
Diffstat (limited to 'src/backend')
22 files changed, 2036 insertions, 1119 deletions
diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am index 08601781..a99fab99 100644 --- a/src/backend/Makefile.am +++ b/src/backend/Makefile.am @@ -6,28 +6,18 @@ bin_PROGRAMS = \ taler_merchant_httpd_SOURCES = \ taler-merchant-httpd.c taler-merchant-httpd.h \ - merchant.c merchant.h \ - ../backend-lib/merchant_db.c ../backend-lib/merchant_db.h \ - taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ - taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ - taler-mint-httpd.h \ - taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ - taler-merchant-httpd_contract.c \ - taler-merchant-httpd_contract.h \ - taler-merchant-httpd_pay.c \ - taler-merchant-httpd_pay.h - + taler-merchant-httpd_parsing.c taler-merchant-httpd_parsing.h \ + taler-merchant-httpd_responses.c taler-merchant-httpd_responses.h \ + taler-merchant-httpd_mhd.c taler-merchant-httpd_mhd.h \ + taler-merchant-httpd_auditors.c taler-merchant-httpd_auditors.h \ + taler-merchant-httpd_mints.c taler-merchant-httpd_mints.h \ + taler-merchant-httpd_contract.c taler-merchant-httpd_contract.h \ + taler-merchant-httpd_pay.c taler-merchant-httpd_pay.h taler_merchant_httpd_LDADD = \ - $(LIBGCRYPT_LIBS) \ + $(top_srcdir)/src/backenddb/libtalermerchantdb.la \ + -ltalermint \ -ltalerutil \ -lmicrohttpd \ -ljansson \ - -lcurl \ - -lgnunetutil \ - $(top_srcdir)/src/backend-lib/libtalermerchant.la \ - -ltalermint \ - -ltalerpq \ - -lgnunetpostgres \ - -lpq \ - -lpthread + -lgnunetutil diff --git a/src/backend/QUESTIONS b/src/backend/QUESTIONS deleted file mode 100644 index b992e96f..00000000 --- a/src/backend/QUESTIONS +++ /dev/null @@ -1,6 +0,0 @@ -1. why does the merchant daemon appears to be some executable under some -.libs directory even though it gets launched through an executable located -elsewhere? - -2. why does the httpd prints three times the URL corresponding to GET /some/url -? diff --git a/src/backend/README b/src/backend/README deleted file mode 100644 index 999dd9e6..00000000 --- a/src/backend/README +++ /dev/null @@ -1,7 +0,0 @@ -Here are the files implementing the backend which is in charge of doing -cryptographic calls, binary manipulations and some HTTP/JSON communication. - -NOTE: - -Makefile.am contains some hardcoded paths that need to be tuned to pick -the right files. diff --git a/src/backend/merchant.c b/src/backend/merchant.c deleted file mode 100644 index 02b37fb8..00000000 --- a/src/backend/merchant.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along with - TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> -*/ - -/** - * @file merchant/merchant.c - * @brief Common utility functions for merchant - * @author Sree Harsha Totakura <sreeharsha@totakura.in> - */ - -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include "merchant.h" - - -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - - -/** - * Parses mints from the configuration. - * - * @param cfg the configuration - * @param mints the array of mints upon successful parsing. Will be NULL upon - * error. - * @return the number of mints in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Mint **mints) -{ - char *mints_str; - char *token_nf; /* do no free (nf) */ - char *mint_section; - char *mint_hostname; - struct MERCHANT_Mint *r_mints; - struct MERCHANT_Mint mint; - unsigned int cnt; - int OK; - - OK = 0; - mints_str = NULL; - token_nf = NULL; - mint_section = NULL; - mint_hostname = NULL; - r_mints = NULL; - cnt = 0; - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "TRUSTED_MINTS", - &mints_str)); - for (token_nf = strtok (mints_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&mint_section, - "mint-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "HOSTNAME", - &mint_hostname)); - mint.hostname = mint_hostname; - GNUNET_array_append (r_mints, cnt, mint); - mint_hostname = NULL; - GNUNET_free (mint_section); - mint_section = NULL; - } - OK = 1; - - EXITIF_exit: - GNUNET_free_non_null (mints_str); - GNUNET_free_non_null (mint_section); - GNUNET_free_non_null (mint_hostname); - if (!OK) - { - GNUNET_free_non_null (r_mints); - return GNUNET_SYSERR; - } - - *mints = r_mints; - return cnt; -} - -/** - * Parses auditors from the configuration. - * - * @param cfg the configuration - * @param mints the array of auditors upon successful parsing. Will be NULL upon - * error. - * @return the number of auditors in the above array; GNUNET_SYSERR upon error in - * parsing. - */ -int -TALER_MERCHANT_parse_auditors (const struct GNUNET_CONFIGURATION_Handle *cfg, - struct MERCHANT_Auditor **auditors) -{ - char *auditors_str; - char *token_nf; /* do no free (nf) */ - char *auditor_section; - char *auditor_name; - struct MERCHANT_Auditor *r_auditors; - struct MERCHANT_Auditor auditor; - unsigned int cnt; - int OK; - - OK = 0; - auditors_str = NULL; - token_nf = NULL; - auditor_section = NULL; - auditor_name = NULL; - r_auditors = NULL; - cnt = 0; - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - "merchant", - "AUDITORS", - &auditors_str)); - for (token_nf = strtok (auditors_str, " "); - NULL != token_nf; - token_nf = strtok (NULL, " ")) - { - GNUNET_assert (0 < GNUNET_asprintf (&auditor_section, - "auditor-%s", token_nf)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - auditor_section, - "NAME", - &auditor_name)); - auditor.name = auditor_name; - GNUNET_array_append (r_auditors, cnt, auditor); - auditor_name = NULL; - GNUNET_free (auditor_section); - auditor_section = NULL; - } - OK = 1; - - EXITIF_exit: - GNUNET_free_non_null (auditors_str); - GNUNET_free_non_null (auditor_section); - GNUNET_free_non_null (auditor_name); - if (!OK) - { - GNUNET_free_non_null (r_auditors); - return GNUNET_SYSERR; - } - - *auditors = r_auditors; - return cnt; -} - - -/** - * Parse the SEPA information from the configuration. If any of the required - * fileds is missing return NULL. - * - * @param cfg the configuration - * @return Sepa details as a structure; NULL upon error - */ -struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg) -{ - struct MERCHANT_WIREFORMAT_Sepa *wf; - - wf = GNUNET_new (struct MERCHANT_WIREFORMAT_Sepa); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "IBAN", - &wf->iban)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "NAME", - &wf->name)); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, - "wire-sepa", - "BIC", - &wf->bic)); - return wf; - - EXITIF_exit: - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); - return NULL; - -} - - -/** - * Destroy and free resouces occupied by the wireformat structure - * - * @param wf the wireformat structure - */ -void -TALER_MERCHANT_destroy_wireformat_sepa (struct MERCHANT_WIREFORMAT_Sepa *wf) -{ - GNUNET_free_non_null (wf->iban); - GNUNET_free_non_null (wf->name); - GNUNET_free_non_null (wf->bic); - GNUNET_free (wf); -} - -/* end of merchant.c */ diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf index 4515fd41..80a7b412 100644 --- a/src/backend/merchant.conf +++ b/src/backend/merchant.conf @@ -1,21 +1,52 @@ +# Sample configuration file for a merchant. [merchant] + +# Which port do we run the backend on? (HTTP server) PORT = 9966 + +# FIXME: is this one used? HOSTNAME = localhost -TRUSTED_MINTS = taler + +# Where is our private key? KEYFILE = merchant.priv + +# What currency does this backend accept? CURRENCY = KUDOS + +# FIXME: to be revised +TRUSTED_MINTS = taler + +# How quickly do we want the mint to send us our money? +# Used only if the frontend does not specify a value. +# FIXME: EDATE is a bit short, 'execution_delay'? EDATE = 3 week -AUDITORS = france + +# Which plugin (backend) do we use for the DB. +DB = postgres [mint-taler] -HOSTNAME = mint.demo.taler.net +URI = mint.demo.taler.net +MASTER_KEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 + +# Auditors must be in sections "auditor-", the rest of the section +# name could be anything. +[auditor-ezb] +# Informal name of the auditor. Just for the user. +NAME = European Central Bank + +# URI of the auditor (especially for in the future, when the +# auditor offers an automated issue reporting system). +# Not really used today. +URI = http://taler.ezb.eu/ -[auditor-france] -NAME = Charles De Gaulle +# This is the important bit: the signing key of the auditor. +PUBLIC_KEY = 9QXF7XY7E9VPV47B5Z806NDFSX2VJ79SVHHD29QEQ3BG31ANHZ60 -[merchant-db] +# This specifies which database we use. +[merchantdb-postgres] CONFIG = postgres:///talerdemo +# "wire-" sections include wire details, here for SEPA. [wire-sepa] IBAN = DE67830654080004822650 NAME = GNUNET E.V diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index ba359bd1..2cc553e9 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -13,40 +13,40 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** * @file merchant/backend/taler-merchant-httpd.c * @brief HTTP serving layer intended to perform crypto-work and * communication with the mint * @author Marcello Stanisci + * @author Christian Grothoff */ - #include "platform.h" #include <microhttpd.h> #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_util.h> #include <taler/taler_mint_service.h> -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" +#include "taler_merchantdb_lib.h" #include "taler-merchant-httpd.h" -#include "taler-mint-httpd_mhd.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" #include "taler-merchant-httpd_contract.h" #include "taler-merchant-httpd_pay.h" + + /** - * Our hostname + * Our wire format details in JSON format (with salt). */ -static char *hostname; +struct json_t *j_wire; /** - * The port we are running on + * Hash of our wire format details as given in #j_wire. */ -static long long unsigned port; +struct GNUNET_HashCode h_wire; /** * Merchant's private key @@ -54,30 +54,35 @@ static long long unsigned port; struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; /** - * File holding the merchant's private key + * Merchant's public key */ -char *keyfile; +struct TALER_MerchantPublicKeyP pubkey; /** - * This value tells the mint by which date this merchant would like - * to receive the funds for a deposited payment + * Our hostname */ -struct GNUNET_TIME_Relative edate_delay; +static char *hostname; /** - * To make 'TMH_PARSE_navigate_json ()' compile + * The port we are running on */ -char *TMH_mint_currency_string; +static long long unsigned port; /** - * Trusted mints + * File holding the merchant's private key + */ +static char *keyfile; + +/** + * This value tells the mint by which date this merchant would like + * to receive the funds for a deposited payment */ -struct MERCHANT_Mint *mints; +struct GNUNET_TIME_Relative edate_delay; /** - * Active auditors + * Which currency is supported by this merchant? */ -struct MERCHANT_Auditor *auditors; +char *TMH_merchant_currency_string; /** * Shutdown task identifier @@ -90,31 +95,6 @@ static struct GNUNET_SCHEDULER_Task *shutdown_task; static struct GNUNET_SCHEDULER_Task *mhd_task; /** - * Context "poller" identifier - */ -struct GNUNET_SCHEDULER_Task *poller_task; - -/** - * Our wireformat - */ -struct MERCHANT_WIREFORMAT_Sepa *wire; - -/** - * Salt used to hash the wire object - */ -long long salt; - -/** - * The number of accepted mints - */ -unsigned int nmints; - -/** - * The number of active auditors - */ -unsigned int nauditors; - -/** * Should we do a dry run where temporary tables are used for storing the data. */ static int dry; @@ -127,13 +107,14 @@ static int result; /** * Connection handle to the our database */ -PGconn *db_conn; +struct TALER_MERCHANTDB_Plugin *db; /** * The MHD Daemon */ static struct MHD_Daemon *mhd; + /** * A client has requested the given url using the given method * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, @@ -190,15 +171,9 @@ url_handler (void *cls, "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0, &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - /* Further test page */ - { "/hello", MHD_HTTP_METHOD_GET, "text/plain", - "Hello, Customer.\n", 0, - &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - { "/contract", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_contract, MHD_HTTP_OK }, - { "/contract", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, @@ -206,33 +181,24 @@ url_handler (void *cls, { "/pay", MHD_HTTP_METHOD_POST, "application/json", NULL, 0, &MH_handler_pay, MHD_HTTP_OK }, - { "/pay", NULL, "text/plain", "Only POST is allowed", 0, &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - {NULL, NULL, NULL, NULL, 0, 0 } }; - static struct TMH_RequestHandler h404 = { "", NULL, "text/html", "<html><title>404: not found</title></html>", 0, &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND }; - - /* Compiler complains about non returning a value in a non-void - declared function: the FIX is to return what the handler for - a particular URL returns */ - struct TMH_RequestHandler *rh; unsigned int i; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, - "Handling request for URL '%s'\n", + "Handling request for URL `%s'\n", url); - for (i=0;NULL != handlers[i].url;i++) { rh = &handlers[i]; @@ -252,43 +218,6 @@ url_handler (void *cls, con_cls, upload_data, upload_data_size); - -} - -/** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. - * - * @param cls closure, will be 'struct MERCHANT_Mint' so that - * when this function gets called, it will change the flag 'pending' - * to 'false'. Note: 'keys' is automatically saved inside the mint's - * handle, which is contained inside 'struct MERCHANT_Mint', when - * this callback is called. Thus, once 'pending' turns 'false', - * it is safe to call 'TALER_MINT_get_keys()' on the mint's handle, - * in order to get the "good" keys. - * - * @param keys information about the various keys used - * by the mint - */ -static void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) -{ - /* HOT UPDATE: the merchants need the denomination keys! - Because it wants to (firstly) verify the deposit confirmation - sent by the mint, and the signed blob depends (among the - other things) on the coin's deposit fee. That information - is never communicated by the wallet to the merchant. - Again, the merchant needs it because it wants to verify that - the wallet didn't exceede the limit imposed by the merchant - on the total deposit fee for a purchase */ - - if (NULL != keys) - { - ((struct MERCHANT_Mint *) cls)->pending = 0; - } - else - printf ("no keys gotten\n"); - } @@ -302,19 +231,6 @@ keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) static void do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) { - unsigned int cnt; - - for (cnt = 0; cnt < nmints; cnt++) - { - if (NULL != mints[cnt].conn) - TALER_MINT_disconnect (mints[cnt].conn); - - } - if (NULL != poller_task) - { - GNUNET_SCHEDULER_cancel (poller_task); - poller_task = NULL; - } if (NULL != mhd_task) { GNUNET_SCHEDULER_cancel (mhd_task); @@ -325,77 +241,19 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) MHD_stop_daemon (mhd); mhd = NULL; } - if (NULL != db_conn) + if (NULL != db) { - MERCHANT_DB_disconnect (db_conn); - db_conn = NULL; + TALER_MERCHANTDB_plugin_unload (db); + db = NULL; } + TMH_MINTS_done (); + TMH_AUDITORS_done (); if (NULL != keyfile) GNUNET_free (privkey); } /** - * Task that runs the context's event loop using the GNUnet scheduler. - * - * @param cls mint context - * @param tc scheduler context (unused) - */ -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - long timeout; - int max_fd; - fd_set read_fd_set; - fd_set write_fd_set; - fd_set except_fd_set; - struct GNUNET_NETWORK_FDSet *rs; - struct GNUNET_NETWORK_FDSet *ws; - struct GNUNET_TIME_Relative delay; - struct TALER_MINT_Context *ctx; - - ctx = (struct TALER_MINT_Context *) cls; - poller_task = NULL; - TALER_MINT_perform (ctx); - max_fd = -1; - timeout = -1; - FD_ZERO (&read_fd_set); - FD_ZERO (&write_fd_set); - FD_ZERO (&except_fd_set); - TALER_MINT_get_select_info (ctx, - &read_fd_set, - &write_fd_set, - &except_fd_set, - &max_fd, - &timeout); - if (timeout >= 0) - delay = - GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout); - else - delay = GNUNET_TIME_UNIT_FOREVER_REL; - rs = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (rs, - &read_fd_set, - max_fd + 1); - ws = GNUNET_NETWORK_fdset_create (); - GNUNET_NETWORK_fdset_copy_native (ws, - &write_fd_set, - max_fd + 1); - poller_task = - GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, - delay, - rs, - ws, - &context_task, - cls); - GNUNET_NETWORK_fdset_destroy (rs); - GNUNET_NETWORK_fdset_destroy (ws); -} - - -/** * Function called whenever MHD is done with a request. If the * request was a POST, we may have stored a `struct Buffer *` in the * @a con_cls that might still need to be cleaned up. Call the @@ -451,9 +309,12 @@ run_daemon (void *cls, /** * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. */ void -TM_trigger_daemon () +TMH_trigger_daemon () { GNUNET_SCHEDULER_cancel (mhd_task); run_daemon (NULL, NULL); @@ -461,6 +322,100 @@ TM_trigger_daemon () /** + * Parse the SEPA information from the configuration. If any of the + * required fields is missing return an error. + * + * @param cfg the configuration + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + unsigned long long salt; + char *iban; + char *name; + char *bic; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_number (cfg, + "wire-sepa", + "SALT", + &salt)) + { + salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, + UINT64_MAX); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "No SALT option given in `wire-sepa`, using %llu\n", + (unsigned long long) salt); + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "IBAN", + &iban)) + return GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "NAME", + &name)) + { + GNUNET_free (iban); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + "wire-sepa", + "BIC", + &bic)) + { + GNUNET_free (iban); + GNUNET_free (name); + GNUNET_free (bic); + } + j_wire = json_pack ("{s:s, s:s, s:s, s:s, s:o}", + "type", "SEPA", + "IBAN", iban, + "name", name, + "bic", bic, + "r", json_integer (salt)); + GNUNET_free (iban); + GNUNET_free (name); + GNUNET_free (bic); + if (NULL == j_wire) + return GNUNET_SYSERR; + return GNUNET_OK; +} + + +/** + * Verify that #j_wire contains a well-formed wire format, and + * update #h_wire to match it (if successful). + * + * @param allowed which wire format is allowed/expected? + * @return #GNUNET_OK on success, #GNUNET_SYSERR on error + */ +static int +validate_and_hash_wireformat (const char *allowed) +{ + const char *allowed_arr[] = { + allowed, + NULL + }; + + if (GNUNET_YES != + TALER_json_validate_wireformat (allowed_arr, + j_wire)) + return GNUNET_SYSERR; + if (GNUNET_SYSERR == + TALER_hash_json (j_wire, + &h_wire)) + return MHD_NO; + return GNUNET_OK; +} + + +/** * Function that queries MHD's select sets and * starts the task waiting for them. * @@ -510,55 +465,49 @@ prepare_daemon () } - /** * Main function that will be run by the scheduler. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be - * NULL!) + * NULL!) * @param config configuration */ void -run (void *cls, char *const *args, const char *cfgfile, +run (void *cls, + char *const *args, + const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *config) { - - unsigned int cnt; - mints = NULL; - keyfile = NULL; result = GNUNET_SYSERR; shutdown_task = - GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, - NULL); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, "merchant launched\n"); - + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, + NULL); EXITIF (GNUNET_SYSERR == - (nmints = - TALER_MERCHANT_parse_mints (config, - &mints))); + TMH_MINTS_init (config)); EXITIF (GNUNET_SYSERR == - (nauditors = - TALER_MERCHANT_parse_auditors (config, - &auditors))); - EXITIF (NULL == - (wire = - TALER_MERCHANT_parse_wireformat_sepa (config))); + TMH_AUDITORS_init (config)); + /* FIXME: for now, we just support SEPA here: */ + EXITIF (GNUNET_OK != + parse_wireformat_sepa (config)); + EXITIF (GNUNET_OK != + validate_and_hash_wireformat ("SEPA")); EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_filename (config, - "merchant", - "KEYFILE", - &keyfile)); + GNUNET_CONFIGURATION_get_value_filename (config, + "merchant", + "KEYFILE", + &keyfile)); EXITIF (NULL == - (privkey = - GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + (privkey = + GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + GNUNET_CRYPTO_eddsa_key_get_public (privkey, + &pubkey.eddsa_pub); EXITIF (NULL == - (db_conn = MERCHANT_DB_connect (config))); + (db = TALER_MERCHANTDB_plugin_load (config))); EXITIF (GNUNET_OK != - MERCHANT_DB_initialize (db_conn, dry)); + db->initialize (db->cls, dry)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_number (config, "merchant", @@ -573,30 +522,13 @@ run (void *cls, char *const *args, const char *cfgfile, GNUNET_CONFIGURATION_get_value_string (config, "merchant", "CURRENCY", - &TMH_mint_currency_string)); + &TMH_merchant_currency_string)); EXITIF (GNUNET_SYSERR == GNUNET_CONFIGURATION_get_value_time (config, - "merchant", - "EDATE", - &edate_delay)); - - salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, - UINT64_MAX); - - for (cnt = 0; cnt < nmints; cnt++) - { - EXITIF (NULL == (mints[cnt].ctx = TALER_MINT_init ())); - mints[cnt].pending = 1; - mints[cnt].conn = TALER_MINT_connect (mints[cnt].ctx, - mints[cnt].hostname, - &keys_mgmt_cb, - &mints[cnt], - TALER_MINT_OPTION_END); - EXITIF (NULL == mints[cnt].conn); - poller_task = - GNUNET_SCHEDULER_add_now (&context_task, mints[cnt].ctx); - } + "merchant", + "EDATE", + &edate_delay)); mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME, port, @@ -609,15 +541,15 @@ run (void *cls, char *const *args, const char *cfgfile, result = GNUNET_OK; mhd_task = prepare_daemon (); - EXITIF_exit: - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); + EXITIF_exit: + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); GNUNET_free_non_null (keyfile); - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); - + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); } + /** * The main function of the serve tool * @@ -628,21 +560,18 @@ run (void *cls, char *const *args, const char *cfgfile, int main (int argc, char *const *argv) { - - static const struct GNUNET_GETOPT_CommandLineOption options[] = { + static const struct GNUNET_GETOPT_CommandLineOption options[] = { {'t', "temp", NULL, gettext_noop ("Use temporary database tables"), GNUNET_NO, &GNUNET_GETOPT_set_one, &dry}, - GNUNET_GETOPT_OPTION_END - }; - + GNUNET_GETOPT_OPTION_END + }; if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, - "taler-merchant-http", - "Serve merchant's HTTP interface", + "taler-merchant-httpd", + "Taler merchant's HTTP backend interface", options, &run, NULL)) return 3; return (GNUNET_OK == result) ? 0 : 1; - } diff --git a/src/backend/taler-merchant-httpd.h b/src/backend/taler-merchant-httpd.h index b60d91bd..25242617 100644 --- a/src/backend/taler-merchant-httpd.h +++ b/src/backend/taler-merchant-httpd.h @@ -18,16 +18,83 @@ * @brief HTTP serving layer mainly intended to communicate with the frontend * @author Marcello Stanisci */ +#ifndef TALER_MERCHANT_HTTPD_H +#define TALER_MERCHANT_HTTPD_H -#include "merchant_db.h" +#include "platform.h" +#include "taler_merchantdb_lib.h" +#include <microhttpd.h> /** - * Kick MHD to run now, to be called after MHD_resume_connection(). + * Shorthand for exit jumps. */ -void -TM_trigger_daemon (void); +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TMH_RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext; /** @@ -41,30 +108,65 @@ typedef void (*TM_ContextCleanup)(struct TM_HandlerContext *hc); +/** + * Each MHD response handler that sets the "connection_cls" to a + * non-NULL value must use a struct that has this struct as its first + * member. This struct contains a single callback, which will be + * invoked to clean up the memory when the contection is completed. + */ struct TM_HandlerContext { + /** + * Function to execute the handler-specific cleanup of the + * (typically larger) context. + */ TM_ContextCleanup cc; }; -extern struct MERCHANT_Mint *mints; -extern struct MERCHANT_WIREFORMAT_Sepa *wire; +/** + * Our wire format details in JSON format (with salt). + */ +extern json_t *j_wire; + +/** + * Hash of our wire format details as given in #j_wire. + */ +extern struct GNUNET_HashCode h_wire; -extern PGconn *db_conn; +/** + * Our private key (for the merchant to sign contracts). + */ +extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; -extern long long salt; +/** + * Our public key, corresponds to #privkey. + */ +extern struct TALER_MerchantPublicKeyP pubkey; -extern unsigned int nmints; +/** + * Handle to the database backend. + */ +extern struct TALER_MERCHANTDB_Plugin *db; +/** + * If the frontend does NOT specify an execution date, how long should + * we tell the mint to wait to aggregate transactions before + * executing? This delay is added to the current time when we + * generate the advisory execution time for the mint. + */ extern struct GNUNET_TIME_Relative edate_delay; -extern struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; - -extern struct GNUNET_SCHEDULER_Task *poller_task; +/** + * Kick MHD to run now, to be called after MHD_resume_connection(). + * Basically, we need to explicitly resume MHD's event loop whenever + * we made progress serving a request. This function re-schedules + * the task processing MHD's activities to run immediately. + */ +void +TMH_trigger_daemon (void); -void -context_task (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc); +#endif diff --git a/src/backend/taler-merchant-httpd_auditors.c b/src/backend/taler-merchant-httpd_auditors.c new file mode 100644 index 00000000..84558ed0 --- /dev/null +++ b/src/backend/taler-merchant-httpd_auditors.c @@ -0,0 +1,239 @@ +/* + This file is part of TALER + (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_auditors.c + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_auditors.h" + +/** + * Our representation of an auditor. + */ +struct Auditor +{ + /** + * Auditor's legal name. + */ + char *name; + + /** + * Auditor's URI. + */ + char *uri; + + /** + * Public key of the auditor. + */ + struct TALER_AuditorPublicKeyP public_key; + +}; + + +/** + * Array of the auditors this merchant is willing to accept. + */ +static struct Auditor *auditors; + +/** + * The length of the #auditors array. + */ +static unsigned int nauditors; + +/** + * JSON representation of the auditors accepted by this mint. + */ +json_t *j_auditors; + + +/** + * Check if the given @a dk issued by mint @a mh is audited by + * an auditor that is acceptable for this merchant. (And if the + * denomination is not yet expired or something silly like that.) + * + * @param mh mint issuing @a dk + * @param dk a denomination issued by @a mh + * @param mint_trusted #GNUNET_YES if the mint of @a dk is trusted by config + * @return #GNUNET_OK if we accept this denomination + */ +int +TMH_AUDITORS_check_dk (struct TALER_MINT_Handle *mh, + const struct TALER_MINT_DenomPublicKey *dk, + int mint_trusted) +{ + const struct TALER_MINT_Keys *keys; + const struct TALER_MINT_AuditorInformation *ai; + unsigned int i; + unsigned int j; + + if (0 == GNUNET_TIME_absolute_get_remaining (dk->deposit_valid_until).rel_value_us) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key offered by client has expired for deposits\n"); + return GNUNET_SYSERR; /* expired */ + } + if (GNUNET_YES == mint_trusted) + return GNUNET_OK; + keys = TALER_MINT_get_keys (mh); + if (NULL == keys) + { + /* this should never happen, keys should have been successfully + obtained before we even got into this function */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + for (i=0;i<keys->num_auditors;i++) + { + ai = &keys->auditors[i]; + for (j=0;j<ai->num_denom_keys;j++) + if (ai->denom_keys[j] == dk) + return GNUNET_OK; + } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Denomination key %s offered by client not audited by accepted auditor\n", + GNUNET_h2s (&dk->h_key)); + return GNUNET_NO; +} + + +/** + * Function called on each configuration section. Finds sections + * about auditors and parses the entries. + * + * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` + * @param section name of the section + */ +static void +parse_auditors (void *cls, + const char *section) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + char *pks; + struct Auditor auditor; + + if (0 != strncasecmp (section, + "auditor-", + strlen ("auditor-"))) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "NAME", + &auditor.name)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "NAME"); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URI", + &auditor.uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URI"); + GNUNET_free (auditor.name); + return; + } + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "PUBLIC_KEY", + &pks)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "PUBLIC_KEY"); + GNUNET_free (auditor.name); + GNUNET_free (auditor.uri); + return; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_public_key_from_string (pks, + strlen (pks), + &auditor.public_key.eddsa_pub)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "PUBLIC_KEY", + "valid public key"); + GNUNET_free (auditor.name); + GNUNET_free (auditor.uri); + GNUNET_free (pks); + return; + } + GNUNET_free (pks); + GNUNET_array_append (auditors, + nauditors, + auditor); +} + + +/** + * Parses auditor information from the configuration. + * + * @param cfg the configuration + * @return the number of auditors found; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + unsigned int cnt; + + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_auditors, + (void *) cfg); + + /* Generate preferred mint(s) array. */ + j_auditors = json_array (); + for (cnt = 0; cnt < nauditors; cnt++) + json_array_append_new (j_auditors, + json_pack ("{s:s, s:o, s:s}", + "name", auditors[cnt].name, + "auditor_pub", TALER_json_from_data (&auditors[cnt].public_key, + sizeof (struct TALER_AuditorPublicKeyP)), + "uri", auditors[cnt].uri)); + return nauditors; +} + + +/** + * Release auditor information state. + */ +void +TMH_AUDITORS_done () +{ + unsigned int i; + + json_decref (j_auditors); + j_auditors = NULL; + for (i=0;i<nauditors;i++) + { + GNUNET_free (auditors[i].name); + GNUNET_free (auditors[i].uri); + } + GNUNET_free (auditors); + auditors = NULL; + nauditors = 0; +} + +/* end of taler-merchant-httpd_auditors.c */ diff --git a/src/backend/taler-merchant-httpd_auditors.h b/src/backend/taler-merchant-httpd_auditors.h new file mode 100644 index 00000000..1a05a78d --- /dev/null +++ b/src/backend/taler-merchant-httpd_auditors.h @@ -0,0 +1,74 @@ +/* + This file is part of TALER + (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_auditors.h + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_AUDITORS_H +#define TALER_MERCHANT_HTTPD_AUDITORS_H + +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_mint_service.h> +#include "taler-merchant-httpd.h" + + +/** + * JSON representation of the auditors accepted by this mint. + */ +extern json_t *j_auditors; + + +/** + * Parses auditor information from the configuration. + * + * @param cfg the configuration + * @return the number of auditors found; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_AUDITORS_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Check if the given @a dk issued by mint @a mh is audited by + * an auditor that is acceptable for this merchant. (And if the + * denomination is not yet expired or something silly like that.) + * + * @param mh mint issuing @a dk + * @param dk a denomination issued by @a mh + * @param mint_trusted #GNUNET_YES if the mint of @a dk is trusted by config + * @return #GNUNET_OK if we accept this denomination + */ +int +TMH_AUDITORS_check_dk (struct TALER_MINT_Handle *mh, + const struct TALER_MINT_DenomPublicKey *dk, + int mint_trusted); + + +/** + * Release auditor information state. + */ +void +TMH_AUDITORS_done (void); + + + + +#endif diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c index c42a8b37..e44d3b84 100644 --- a/src/backend/taler-merchant-httpd_contract.c +++ b/src/backend/taler-merchant-httpd_contract.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -13,39 +13,30 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** - * @file merchant/backend/taler-merchant-httpd.c + * @file backend/taler-merchant-httpd_contract.c * @brief HTTP serving layer mainly intended to communicate with the frontend * @author Marcello Stanisci */ - #include "platform.h" -#include <microhttpd.h> #include <jansson.h> -#include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_signatures.h> -#include <taler/taler_amount_lib.h> -#include <taler/taler_json_lib.h> -#include <taler/taler_mint_service.h> -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" #include "taler-merchant-httpd.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" +#include "taler-merchant-httpd_responses.h" -extern struct MERCHANT_Auditor *auditors; -extern unsigned int nauditors; /** - * Manage a contract request. In practical terms, it adds the fields 'mints', - * 'merchant_pub', and 'H_wire' to the contract 'proposition' gotten from the - * frontend. Finally, it adds (outside of the contract) a signature of the - * (hashed stringification) of this contract and the hashed stringification - * of this contract to the final bundle sent back to the frontend. + * Manage a contract request. In practical terms, it adds the fields + * 'mints', 'merchant_pub', and 'H_wire' to the contract 'proposition' + * gotten from the frontend. Finally, it adds (outside of the + * contract) a signature of the (hashed stringification) of the + * contract (and the hashed stringification of this contract as well + * to aid diagnostics) to the final bundle, which is then send back to + * the frontend. + * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] connection_cls the connection's closure (can be updated) @@ -61,18 +52,8 @@ MH_handler_contract (struct TMH_RequestHandler *rh, size_t *upload_data_size) { json_t *root; - json_t *trusted_mints; - json_t *j_auditors; - json_t *auditor; - json_t *mint; - json_t *j_wire; - const struct TALER_MINT_Keys *keys; int res; - int cnt; - struct GNUNET_HashCode h_wire; - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - struct MERCHANT_Contract contract; - char *contract_str; + struct TALER_ContractPS contract; struct GNUNET_CRYPTO_EddsaSignature contract_sig; res = TMH_PARSE_post_json (connection, @@ -82,87 +63,43 @@ MH_handler_contract (struct TMH_RequestHandler *rh, &root); if (GNUNET_SYSERR == res) return MHD_NO; - /* the POST's body has to be further fetched */ if ((GNUNET_NO == res) || (NULL == root)) + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) return MHD_YES; - /* Generate preferred mint(s) array. */ - - trusted_mints = json_array (); - for (cnt = 0; cnt < nmints; cnt++) - { - if (!mints[cnt].pending) - { - keys = TALER_MINT_get_keys (mints[cnt].conn); - mint = json_pack ("{s:s, s:o}", - "url", mints[cnt].hostname, - "master_pub", - TALER_json_from_data - (&keys->master_pub.eddsa_pub, - sizeof (keys->master_pub.eddsa_pub))); - json_array_append_new (trusted_mints, mint); - } - } - j_auditors = json_array (); - for (cnt = 0; cnt < nauditors; cnt++) - { - auditor = json_pack ("{s:s}", - "name", auditors[cnt].name); - json_array_append_new (j_auditors, auditor); - } - - /** - * Return badly if no mints are trusted (or no call to /keys has still - * returned the expected data). WARNING: it - * may be possible that a mint trusted by the wallet is good, but - * still pending; that case must be handled with some "polling-style" - * routine, simply ignored, or ended with an invitation to the wallet - * to just retry later - */ - if (!json_array_size (trusted_mints)) - return MHD_NO; - - /** - * Hard error, no action can be taken by a wallet - */ - if (!json_array_size (j_auditors)) - return MHD_NO; - - json_object_set_new (root, "mints", trusted_mints); - json_object_set_new (root, "auditors", j_auditors); - - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - salt))) - return MHD_NO; - - /* hash wire objcet */ - if (GNUNET_SYSERR == - TALER_hash_json (j_wire, &h_wire)) - return MHD_NO; - + /* add fields to the "root" that the backend should provide */ + json_object_set (root, + "mints", + trusted_mints); + json_object_set (root, + "auditors", + j_auditors); json_object_set_new (root, "H_wire", - TALER_json_from_data (&h_wire, sizeof (h_wire))); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pubkey); + TALER_json_from_data (&h_wire, + sizeof (h_wire))); json_object_set_new (root, "merchant_pub", - TALER_json_from_data (&pubkey, sizeof (pubkey))); - - /* Sign */ - contract_str = json_dumps (root, JSON_COMPACT | JSON_SORT_KEYS); - GNUNET_CRYPTO_hash (contract_str, strlen (contract_str), &contract.h_contract); + TALER_json_from_data (&pubkey, + sizeof (pubkey))); + /* create contract signature */ + GNUNET_assert (GNUNET_OK == + TALER_hash_json (root, + &contract.h_contract)); contract.purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); contract.purpose.size = htonl (sizeof (contract)); - GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &contract_sig); - + GNUNET_CRYPTO_eddsa_sign (privkey, + &contract.purpose, + &contract_sig); + /* return final response */ return TMH_RESPONSE_reply_json_pack (connection, MHD_HTTP_OK, "{s:o, s:o, s:o}", "contract", root, - "sig", TALER_json_from_data - (&contract_sig, sizeof (contract_sig)), - "H_contract", TALER_json_from_data - (&contract.h_contract, - sizeof (contract.h_contract))); - + "sig", TALER_json_from_data (&contract_sig, + sizeof (contract_sig)), + "H_contract", TALER_json_from_data (&contract.h_contract, + sizeof (contract.h_contract))); } + +/* end of taler-merchant-httpd_contract.c */ diff --git a/src/backend/taler-merchant-httpd_contract.h b/src/backend/taler-merchant-httpd_contract.h index 5e72c514..44cef909 100644 --- a/src/backend/taler-merchant-httpd_contract.h +++ b/src/backend/taler-merchant-httpd_contract.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -13,17 +13,15 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** - * @file merchant/backend/taler-merchant-httpd_contract.h + * @file backend/taler-merchant-httpd_contract.h * @brief headers for /contract handler * @author Marcello Stanisci */ - #ifndef TALER_MINT_HTTPD_CONTRACT_H #define TALER_MINT_HTTPD_CONTRACT_H #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" /** * Manage a contract request @@ -33,7 +31,6 @@ * @param[in,out] connection_cls the connection's closure (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * * @return MHD result code */ int diff --git a/src/backend/taler-mint-httpd_mhd.c b/src/backend/taler-merchant-httpd_mhd.c index 419c4fb0..6fd18d9c 100644 --- a/src/backend/taler-mint-httpd_mhd.c +++ b/src/backend/taler-merchant-httpd_mhd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014 GNUnet e.V. + Copyright (C) 2014, 2015 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -13,9 +13,8 @@ You should have received a copy of the GNU Affero General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** - * @file taler-mint-httpd_mhd.c + * @file taler-merchant-httpd_mhd.c * @brief helpers for MHD interaction; these are TALER_MINT_handler_ functions * that generate simple MHD replies that do not require any real operations * to be performed (error handling, static pages, etc.) @@ -24,14 +23,9 @@ * @author Christian Grothoff */ #include "platform.h" -#include <gnunet/gnunet_util_lib.h> #include <jansson.h> -#include <microhttpd.h> -#include <pthread.h> -#include "taler-mint-httpd_responses.h" -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_mhd.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_mhd.h" +#include "taler-merchant-httpd_responses.h" /** diff --git a/src/backend/taler-mint-httpd_mhd.h b/src/backend/taler-merchant-httpd_mhd.h index a9f575df..3fe137db 100644 --- a/src/backend/taler-mint-httpd_mhd.h +++ b/src/backend/taler-merchant-httpd_mhd.h @@ -15,7 +15,7 @@ */ /** - * @file taler-mint-httpd_mhd.h + * @file taler-merchant-httpd_mhd.h * @brief helpers for MHD interaction, used to generate simple responses * @author Florian Dold * @author Benedikt Mueller @@ -25,7 +25,7 @@ #define TALER_MINT_HTTPD_MHD_H #include <gnunet/gnunet_util_lib.h> #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" /** diff --git a/src/backend/taler-merchant-httpd_mints.c b/src/backend/taler-merchant-httpd_mints.c new file mode 100644 index 00000000..e93419cd --- /dev/null +++ b/src/backend/taler-merchant-httpd_mints.c @@ -0,0 +1,530 @@ +/* + This file is part of TALER + (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_mints.c + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#include "platform.h" +#include "taler-merchant-httpd_mints.h" + + +/** + * How often do we retry fetching /keys? + */ +#define KEYS_RETRY_FREQ GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 60) + + +/** + * Mint + */ +struct Mint; + + +/** + * Information we keep for a pending #MMH_MINTS_find_mint() operation. + */ +struct TMH_MINTS_FindOperation +{ + + /** + * Kept in a DLL. + */ + struct TMH_MINTS_FindOperation *next; + + /** + * Kept in a DLL. + */ + struct TMH_MINTS_FindOperation *prev; + + /** + * Function to call with the result. + */ + TMH_MINTS_FindContinuation fc; + + /** + * Closure for @e fc. + */ + void *fc_cls; + + /** + * Mint we wait for the /keys for. + */ + struct Mint *my_mint; + + /** + * Task scheduled to asynchrnously return the result. + */ + struct GNUNET_SCHEDULER_Task *at; + +}; + + +/** + * Mint + */ +struct Mint +{ + + /** + * Kept in a DLL. + */ + struct Mint *next; + + /** + * Kept in a DLL. + */ + struct Mint *prev; + + /** + * Head of FOs pending for this mint. + */ + struct TMH_MINTS_FindOperation *fo_head; + + /** + * Tail of FOs pending for this mint. + */ + struct TMH_MINTS_FindOperation *fo_tail; + + /** + * (base) URI of the mint. + */ + char *uri; + + /** + * A connection to this mint + */ + struct TALER_MINT_Handle *conn; + + /** + * Master public key, guaranteed to be set ONLY for + * trusted mints. + */ + struct TALER_MasterPublicKeyP master_pub; + + /** + * At what time should we try to fetch /keys again? + */ + struct GNUNET_TIME_Absolute retry_time; + + /** + * Flag which indicates whether some HTTP transfer between + * this merchant and the mint is still ongoing + */ + int pending; + + /** + * #GNUNET_YES if this mint is from our configuration and + * explicitly trusted, #GNUNET_NO if we need to check each + * key to be sure it is trusted. + */ + int trusted; + +}; + + +/** + * Context for all mint operations (useful to the event loop) + */ +static struct TALER_MINT_Context *ctx; + +/** + * Task we use to drive the interaction with this mint. + */ +static struct GNUNET_SCHEDULER_Task *poller_task; + +/** + * Head of mints we know about. + */ +static struct Mint *mint_head; + +/** + * Tail of mints we know about. + */ +static struct Mint *mint_tail; + +/** + * List of our trusted mints for inclusion in contracts. + */ +json_t *trusted_mints; + + +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure, will be `struct Mint` so that + * when this function gets called, it will change the flag 'pending' + * to 'false'. Note: 'keys' is automatically saved inside the mint's + * handle, which is contained inside 'struct Mint', when + * this callback is called. Thus, once 'pending' turns 'false', + * it is safe to call 'TALER_MINT_get_keys()' on the mint's handle, + * in order to get the "good" keys. + * @param keys information about the various keys used + * by the mint + */ +static void +keys_mgmt_cb (void *cls, + const struct TALER_MINT_Keys *keys) +{ + struct Mint *mint = cls; + struct TMH_MINTS_FindOperation *fo; + + if (NULL != keys) + { + mint->pending = GNUNET_NO; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Failed to fetch /keys from `%s'\n", + mint->uri); + TALER_MINT_disconnect (mint->conn); + mint->conn = NULL; + mint->pending = GNUNET_SYSERR; /* failed hard */ + mint->retry_time = GNUNET_TIME_relative_to_absolute (KEYS_RETRY_FREQ); + } + while (NULL != (fo = mint->fo_head)) + { + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + fo->fc (fo->fc_cls, + (NULL != keys) ? mint->conn : NULL, + mint->trusted); + GNUNET_free (fo); + } +} + + +/** + * Task that runs the mint's event loop using the GNUnet scheduler. + * + * @param cls a `struct Mint *` + * @param tc scheduler context (unused) + */ +static void +context_task (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + long timeout; + int max_fd; + fd_set read_fd_set; + fd_set write_fd_set; + fd_set except_fd_set; + struct GNUNET_NETWORK_FDSet *rs; + struct GNUNET_NETWORK_FDSet *ws; + struct GNUNET_TIME_Relative delay; + + poller_task = NULL; + TALER_MINT_perform (ctx); + max_fd = -1; + timeout = -1; + FD_ZERO (&read_fd_set); + FD_ZERO (&write_fd_set); + FD_ZERO (&except_fd_set); + TALER_MINT_get_select_info (ctx, + &read_fd_set, + &write_fd_set, + &except_fd_set, + &max_fd, + &timeout); + if (timeout >= 0) + delay = + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, + timeout); + else + delay = GNUNET_TIME_UNIT_FOREVER_REL; + rs = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (rs, + &read_fd_set, + max_fd + 1); + ws = GNUNET_NETWORK_fdset_create (); + GNUNET_NETWORK_fdset_copy_native (ws, + &write_fd_set, + max_fd + 1); + poller_task = + GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + delay, + rs, + ws, + &context_task, + NULL); + GNUNET_NETWORK_fdset_destroy (rs); + GNUNET_NETWORK_fdset_destroy (ws); +} + + +/** + * Task to return find operation result asynchronously to caller. + * + * @param cls a `struct TMH_MINTS_FindOperation` + * @param tc unused + */ +static void +return_result (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + struct TMH_MINTS_FindOperation *fo = cls; + struct Mint *mint = fo->my_mint; + + fo->at = NULL; + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + fo->fc (fo->fc_cls, + (GNUNET_SYSERR == mint->pending) ? NULL : mint->conn, + mint->trusted); + GNUNET_free (fo); + GNUNET_SCHEDULER_cancel (poller_task); + GNUNET_SCHEDULER_add_now (&context_task, + NULL); +} + + +/** + * Find a mint that matches @a chosen_mint. If we cannot connect + * to the mint, or if it is not acceptable, @a fc is called with + * NULL for the mint. + * + * @param chosen_mint URI of the mint we would like to talk to + * @param fc function to call with the handles for the mint + * @param fc_cls closure for @a fc + * @return NULL on error + */ +struct TMH_MINTS_FindOperation * +TMH_MINTS_find_mint (const char *chosen_mint, + TMH_MINTS_FindContinuation fc, + void *fc_cls) +{ + struct Mint *mint; + struct TMH_MINTS_FindOperation *fo; + + if (NULL == ctx) + { + GNUNET_break (0); + return NULL; + } + /* Check if the mint is known */ + for (mint = mint_head; NULL != mint; mint = mint->next) + /* test it by checking public key --- FIXME: hostname or public key!? + Should probably be URI, not hostname anyway! */ + if (0 == strcmp (mint->uri, + chosen_mint)) + break; + if (NULL == mint) + { + /* This is a new mint */ + mint = GNUNET_new (struct Mint); + mint->uri = GNUNET_strdup (chosen_mint); + mint->pending = GNUNET_YES; + GNUNET_CONTAINER_DLL_insert (mint_head, + mint_tail, + mint); + } + + /* check if we should resume this mint */ + if ( (GNUNET_SYSERR == mint->pending) && + (0 == GNUNET_TIME_absolute_get_remaining (mint->retry_time).rel_value_us) ) + mint->pending = GNUNET_YES; + + + fo = GNUNET_new (struct TMH_MINTS_FindOperation); + fo->fc = fc; + fo->fc_cls = fc_cls; + fo->my_mint = mint; + GNUNET_CONTAINER_DLL_insert (mint->fo_head, + mint->fo_tail, + fo); + + if (GNUNET_NO == mint->pending) + { + /* We are not currently waiting for a reply, immediately + return result */ + fo->at = GNUNET_SCHEDULER_add_now (&return_result, + fo); + return fo; + } + + /* If new or resumed, retry fetching /keys */ + if ( (NULL == mint->conn) && + (GNUNET_YES == mint->pending) ) + { + mint->conn = TALER_MINT_connect (ctx, + mint->uri, + &keys_mgmt_cb, + mint, + TALER_MINT_OPTION_END); + GNUNET_break (NULL != mint->conn); + } + return fo; +} + + +/** + * Abort pending find operation. + * + * @param fo handle to operation to abort + */ +void +TMH_MINTS_find_mint_cancel (struct TMH_MINTS_FindOperation *fo) +{ + struct Mint *mint = fo->my_mint; + + if (NULL != fo->at) + { + GNUNET_SCHEDULER_cancel (fo->at); + fo->at = NULL; + } + GNUNET_CONTAINER_DLL_remove (mint->fo_head, + mint->fo_tail, + fo); + GNUNET_free (fo); +} + + +/** + * Function called on each configuration section. Finds sections + * about mints and parses the entries. + * + * @param cls closure, with a `const struct GNUNET_CONFIGURATION_Handle *` + * @param section name of the section + */ +static void +parse_mints (void *cls, + const char *section) +{ + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + char *uri; + char *mks; + struct Mint *mint; + + if (0 != strncasecmp (section, + "mint-", + strlen ("mint-"))) + return; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "URI", + &uri)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + section, + "URI"); + return; + } + mint = GNUNET_new (struct Mint); + mint->uri = uri; + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + section, + "MASTER_KEY", + &mks)) + { + if (GNUNET_OK == + GNUNET_CRYPTO_eddsa_public_key_from_string (mks, + strlen (mks), + &mint->master_pub.eddsa_pub)) + { + mint->trusted = GNUNET_YES; + } + else + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + section, + "MASTER_KEY", + _("ill-formed key")); + } + GNUNET_free (mks); + } + GNUNET_CONTAINER_DLL_insert (mint_head, + mint_tail, + mint); + mint->pending = GNUNET_YES; + mint->conn = TALER_MINT_connect (ctx, + mint->uri, + &keys_mgmt_cb, + mint, + TALER_MINT_OPTION_END); + GNUNET_break (NULL != mint->conn); +} + + +/** + * Parses "trusted" mints listed in the configuration. + * + * @param cfg the configuration + * @return #GNUNET_OK on success; #GNUNET_SYSERR upon error in + * parsing. + */ +int +TMH_MINTS_init (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct Mint *mint; + json_t *j_mint; + + ctx = TALER_MINT_init (); + if (NULL == ctx) + return GNUNET_SYSERR; + GNUNET_CONFIGURATION_iterate_sections (cfg, + &parse_mints, + (void *) cfg); + /* build JSON with list of trusted mints */ + trusted_mints = json_array (); + for (mint = mint_head; NULL != mint; mint = mint->next) + { + if (GNUNET_YES != mint->trusted) + continue; + j_mint = json_pack ("{s:s, s:o}", + "url", mint->uri, + "master_pub", TALER_json_from_data (&mint->master_pub, + sizeof (struct TALER_MasterPublicKeyP))); + json_array_append_new (trusted_mints, + j_mint); + } + poller_task = GNUNET_SCHEDULER_add_now (&context_task, + NULL); + return GNUNET_OK; +} + + +/** + * Function called to shutdown the mints subsystem. + */ +void +TMH_MINTS_done () +{ + struct Mint *mint; + + while (NULL != (mint = mint_head)) + { + GNUNET_CONTAINER_DLL_remove (mint_head, + mint_tail, + mint); + if (NULL != mint->conn) + TALER_MINT_disconnect (mint->conn); + GNUNET_free (mint->uri); + GNUNET_free (mint); + } + if (NULL != poller_task) + { + GNUNET_SCHEDULER_cancel (poller_task); + poller_task = NULL; + } + TALER_MINT_fini (ctx); +} diff --git a/src/backend/taler-merchant-httpd_mints.h b/src/backend/taler-merchant-httpd_mints.h new file mode 100644 index 00000000..a4684f59 --- /dev/null +++ b/src/backend/taler-merchant-httpd_mints.h @@ -0,0 +1,105 @@ +/* + This file is part of TALER + (C) 2014, 2015 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file backend/taler-merchant-httpd_mints.h + * @brief logic this HTTPD keeps for each mint we interact with + * @author Marcello Stanisci + * @author Christian Grothoff + */ +#ifndef TALER_MERCHANT_HTTPD_MINTS_H +#define TALER_MERCHANT_HTTPD_MINTS_H + +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <curl/curl.h> +#include <taler/taler_util.h> +#include <taler/taler_mint_service.h> +#include "taler-merchant-httpd.h" + + +/** + * List of our trusted mints in JSON format for inclusion in contracts. + */ +extern json_t *trusted_mints; + + +/** + * Parses "trusted" mints listed in the configuration. + * + * @param cfg the configuration + * @return #GNUNET_OK on success; #GNUNET_SYSERR upon error in + * parsing or initialization. + */ +int +TMH_MINTS_init (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Function called to shutdown the mints subsystem. + */ +void +TMH_MINTS_done (void); + + +/** + * Function called with the result of a #TMH_MINTS_find_mint() + * operation. + * + * @param cls closure + * @param mh handle to the mint context + * @param mint_trusted #GNUNET_YES if this mint is trusted by config + */ +typedef void +(*TMH_MINTS_FindContinuation)(void *cls, + struct TALER_MINT_Handle *mh, + int mint_trusted); + + +/** + * Information we keep for a pending #MMH_MINTS_find_mint() operation. + */ +struct TMH_MINTS_FindOperation; + + +/** + * Find a mint that matches @a chosen_mint. If we cannot connect + * to the mint, or if it is not acceptable, @a fc is called with + * NULL for the mint. + * + * @param chosen_mint URI of the mint we would like to talk to + * @param fc function to call with the handles for the mint + * @param fc_cls closure for @a fc + * + * FIXME: should probably return a value to *cancel* the + * operation in case MHD connection goes down and needs to + * free fc_cls. + */ +struct TMH_MINTS_FindOperation * +TMH_MINTS_find_mint (const char *chosen_mint, + TMH_MINTS_FindContinuation fc, + void *fc_cls); + + +/** + * Abort pending find operation. + * + * @param fo handle to operation to abort + */ +void +TMH_MINTS_find_mint_cancel (struct TMH_MINTS_FindOperation *fo); + + +#endif diff --git a/src/backend/taler-mint-httpd_parsing.c b/src/backend/taler-merchant-httpd_parsing.c index 9efd6c23..14a87ff4 100644 --- a/src/backend/taler-mint-httpd_parsing.c +++ b/src/backend/taler-merchant-httpd_parsing.c @@ -21,19 +21,18 @@ * @author Benedikt Mueller * @author Christian Grothoff */ - #include "platform.h" #include <gnunet/gnunet_util_lib.h> -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" /* Although the following declaration isn't in any case useful to a merchant's activity, it's needed here to make the function 'TMH_PARSE_nagivate_json ()' compile fine; so its value will be kept on some merchant's accepted currency. For multi currencies merchants, that of course would require a patch */ +extern char *TMH_merchant_currency_string; -extern char *TMH_mint_currency_string; /** * Initial size for POST request buffer. */ @@ -145,6 +144,7 @@ buffer_append (struct Buffer *buf, return GNUNET_OK; } + /** * Function called whenever we are done with a request * to clean up our state. @@ -164,6 +164,7 @@ TMH_PARSE_post_cleanup_callback (void *con_cls) } } + /** * Release all memory allocated for the variable-size fields in * the parser specification. @@ -249,6 +250,7 @@ release_data (struct TMH_PARSE_FieldSpecification *spec, } } + /** * Process a POST request containing a JSON object. This function * realizes an MHD POST processor that will (incrementally) process @@ -350,6 +352,7 @@ TMH_PARSE_post_json (struct MHD_Connection *connection, return GNUNET_YES; } + /** * Generate line in parser specification for string. The returned * string is already nul-terminated internally by JSON, so no length @@ -634,7 +637,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, case TMH_PARSE_JNC_RET_STRING: { - void **where = va_arg (argp, void **); + void **where = va_arg (argp, void **); *where = (void*) json_string_value (root); ret = GNUNET_OK; } @@ -876,7 +879,7 @@ TMH_PARSE_navigate_json (struct MHD_Connection *connection, break; } if (0 != strcmp (where->currency, - TMH_mint_currency_string)) + TMH_merchant_currency_string)) { GNUNET_break_op (0); ret = (MHD_YES != diff --git a/src/backend/taler-mint-httpd_parsing.h b/src/backend/taler-merchant-httpd_parsing.h index dae65092..dae65092 100644 --- a/src/backend/taler-mint-httpd_parsing.h +++ b/src/backend/taler-merchant-httpd_parsing.h diff --git a/src/backend/taler-merchant-httpd_pay.c b/src/backend/taler-merchant-httpd_pay.c index 1951d2d0..319a7001 100644 --- a/src/backend/taler-merchant-httpd_pay.c +++ b/src/backend/taler-merchant-httpd_pay.c @@ -14,81 +14,86 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ /** - * @file merchant/backend/taler-merchant-httpd.c - * @brief HTTP serving layer mainly intended to communicate with the frontend + * @file backend/taler-merchant-httpd_pay.c + * @brief handling of /pay requests * @author Marcello Stanisci + * @author Christian Grothoff */ - #include "platform.h" -#include <microhttpd.h> #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <curl/curl.h> #include <taler/taler_signatures.h> -#include <taler/taler_amount_lib.h> #include <taler/taler_json_lib.h> #include <taler/taler_mint_service.h> #include "taler-merchant-httpd.h" -#include "taler-mint-httpd.h" -#include "taler-mint-httpd_parsing.h" -#include "taler-mint-httpd_responses.h" -#include "taler-mint-httpd_mhd.h" -#include "merchant_db.h" -#include "merchant.h" -#include "taler_merchant_lib.h" +#include "taler-merchant-httpd_parsing.h" +#include "taler-merchant-httpd_responses.h" +#include "taler-merchant-httpd_auditors.h" +#include "taler-merchant-httpd_mints.h" +#include "taler_merchantdb_lib.h" + + +/** + * Information we keep for an individual call to the /pay handler. + */ +struct PayContext; /** - * Fetch the deposit fee related to the given coin aggregate. - * @param connection the connection to send an error response to - * @param coin_aggregate a coin "aggregate" is the JSON set of - * values contained in a single cell of the 'coins' array sent - * in a payment - * @param deposit_fee where to store the resulting deposit fee - * @param mint_index the index which points the chosen mint within - * the global 'mints' array - * @return GNUNET_OK if successful, GNUNET_NO if the data supplied - * is invalid (including the case when the key is not found), - * GNUNET_SYSERR upon internal errors + * Information kept during a /pay request for each coin. */ -static int -deposit_fee_from_coin_aggregate (struct MHD_Connection *connection, - json_t *coin_aggregate, - struct TALER_Amount *deposit_fee, - unsigned int mint_index) +struct MERCHANT_DepositConfirmation { - int res; - const struct TALER_MINT_Keys *keys; - const struct TALER_MINT_DenomPublicKey *denom_details; + + /** + * Reference to the main PayContext + */ + struct PayContext *pc; + + /** + * Handle to the deposit operation we are performing for + * this coin, NULL after the operation is done. + */ + struct TALER_MINT_DepositHandle *dh; + + /** + * Denomination of this coin. + */ struct TALER_DenominationPublicKey denom; - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom), - TMH_PARSE_MEMBER_END - }; - - res = TMH_PARSE_json_data (connection, - coin_aggregate, - spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - if (1 == mints[mint_index].pending) - return GNUNET_SYSERR; - keys = TALER_MINT_get_keys (mints[mint_index].conn); - denom_details = TALER_MINT_get_denomination_key (keys, &denom); - if (NULL == denom_details) - { - TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_BAD_REQUEST, - "{s:s, s:o}", - "hint", "unknown denom to mint", - "denom_pub", TALER_json_from_rsa_public_key (denom.rsa_public_key)); - return GNUNET_NO; - } - *deposit_fee = denom_details->fee_deposit; - return GNUNET_OK; -} + /** + * Amount "f" that this coin contributes to the overall payment. + * This amount includes the deposit fee. + */ + struct TALER_Amount percoin_amount; + + /** + * Amount this coin contributes to the total purchase price. + */ + struct TALER_Amount amount_without_fee; + + /** + * Public key of the coin. + */ + struct TALER_CoinSpendPublicKeyP coin_pub; + + /** + * Signature using the @e denom key over the @e coin_pub. + */ + struct TALER_DenominationSignature ub_sig; + + /** + * Signature of the coin's private key over the contract. + */ + struct TALER_CoinSpendSignatureP coin_sig; + + /** + * Offset of this coin into the `dc` array of all coins in the + * @e pc. + */ + unsigned int index; + +}; /** @@ -103,7 +108,7 @@ struct PayContext struct TM_HandlerContext hc; /** - * Pointer to the global (malloc'd) array of all coins outcomes + * Array with @e coins_cnt coins we are despositing. */ struct MERCHANT_DepositConfirmation *dc; @@ -113,146 +118,452 @@ struct PayContext struct MHD_Connection *connection; /** + * Handle to the mint that we are doing the payment with. + * (initially NULL while @e fo is trying to find a mint). + */ + struct TALER_MINT_Handle *mh; + + /** + * Handle for operation to lookup /keys (and auditors) from + * the mint used for this transaction; NULL if no operation is + * pending. + */ + struct TMH_MINTS_FindOperation *fo; + + /** * Placeholder for #TMH_PARSE_post_json() to keep its internal state. */ void *json_parse_context; /** - * Response to return, NULL if we don't have one yet. + * Mint URI given in @e root. */ - struct MHD_Response *response; + char *chosen_mint; /** - * Transaction id + * Transaction ID given in @e root. */ uint64_t transaction_id; /** - * How many coins this paymen is made of. + * Maximum fee the merchant is willing to pay, from @e root. + * Note that IF the total fee of the mint is higher, that is + * acceptable to the merchant if the customer is willing to + * pay the difference (i.e. amount - max_fee <= actual-amount - actual-fee). + */ + struct TALER_Amount max_fee; + + /** + * Amount from @e root. This is the amount the merchant expects + * to make, minus @e max_fee. + */ + struct TALER_Amount amount; + + /** + * Timestamp from @e root. + */ + struct GNUNET_TIME_Absolute timestamp; + + /** + * Refund deadline from @e root. + */ + struct GNUNET_TIME_Absolute refund_deadline; + + /** + * "H_contract" from @e root. + */ + struct GNUNET_HashCode h_contract; + + /** + * Execution date. How soon would the merchant like the + * transaction to be executed? (Can be given by the frontend + * or be determined by our configuration via #edate_delay.) + */ + struct GNUNET_TIME_Absolute edate; + + /** + * Response to return, NULL if we don't have one yet. + */ + struct MHD_Response *response; + + /** + * Number of coins this payment is made of. Length + * of the @e dc array. */ unsigned int coins_cnt; /** + * Number of transactions still pending. Initially set to + * @e coins_cnt, decremented on each transaction that + * successfully finished. + */ + unsigned int pending; + + /** * HTTP status code to use for the reply, i.e 200 for "OK". * Special value UINT_MAX is used to indicate hard errors - * (no reply, return MHD_NO). + * (no reply, return #MHD_NO). */ unsigned int response_code; }; + /** - * Information needed by a single /deposit callback to refer to its - * own coin inside the confirmations array, namely `struct MERCHANT_DepositConfirmation *dc` - * above. Note: this information can NOT be shared between all the callbacks. + * Resume the given pay context and send the given response. + * Stores the response in the @a pc and signals MHD to resume + * the connection. Also ensures MHD runs immediately. + * + * @param pc payment context + * @param response_code response code to use + * @param response response data to send back */ -struct DepositCallbackContext +static void +resume_pay_with_response (struct PayContext *pc, + unsigned int response_code, + struct MHD_Response *response) { + pc->response_code = response_code; + pc->response = response; + MHD_resume_connection (pc->connection); + TMH_trigger_daemon (); /* we resumed, kick MHD */ +} - /** - * Offset of this coin into the array of all coins outcomes - */ - unsigned int index; - /** - * Reference to the main PayContext - */ - struct PayContext *pc; +/** + * Abort all pending /deposit operations. + * + * @param pc pay context to abort + */ +static void +abort_deposit (struct PayContext *pc) +{ + unsigned int i; + + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dci = &pc->dc[i]; + + if (NULL != dci->dh) + { + TALER_MINT_deposit_cancel (dci->dh); + dci->dh = NULL; + } + } +} -}; /** * Callback to handle a deposit permission's response. * - * @param cls see `struct MERCHANT_DepositConfirmationCls` (i.e. a poinetr to the global - * array of confirmations and an index for this call in that array). That way, the last - * executed callback can detect that no other confirmations are on the way, and can pack - * a response for the wallet - * @param http_status HTTP response code, #MHD_HTTP_OK (200) for successful deposit; - * 0 if the mint's reply is bogus (fails to follow the protocol) - * @param proof the received JSON reply, should be kept as proof (and, in case of errors, - * be forwarded to the customer) + * @param cls a `struct MERCHANT_DepositConfirmation` (i.e. a pointer + * into the global array of confirmations and an index for this call + * in that array). That way, the last executed callback can detect + * that no other confirmations are on the way, and can pack a response + * for the wallet + * @param http_status HTTP response code, #MHD_HTTP_OK + * (200) for successful deposit; 0 if the mint's reply is bogus (fails + * to follow the protocol) + * @param proof the received JSON reply, + * should be kept as proof (and, in case of errors, be forwarded to + * the customer) */ static void deposit_cb (void *cls, unsigned int http_status, json_t *proof) { - struct DepositCallbackContext *dcc = cls; - int i; - - /*FIXME the index is the same for every individual cb */ - if (GNUNET_SYSERR == - MERCHANT_DB_update_deposit_permission (db_conn, - dcc->pc->transaction_id, - 0)) - /* TODO */ - printf ("db error\n"); - dcc->pc->dc[dcc->index].ackd = 1; - dcc->pc->dc[dcc->index].exit_status = http_status; - dcc->pc->dc[dcc->index].proof = proof; - - /* loop through the confirmation array and return accordingly */ - for (i = 0; i < dcc->pc->coins_cnt; i++) + struct MERCHANT_DepositConfirmation *dc = cls; + struct PayContext *pc = dc->pc; + + dc->dh = NULL; + pc->pending--; + if (MHD_HTTP_OK != http_status) { - /* just return if there is at least one coin to be still - confirmed */ - if (! dcc->pc->dc[i].ackd) - { - printf ("still vacant coins\n"); - return; - } + /* Transaction failed; stop all other ongoing deposits */ + abort_deposit (pc); + /* Forward error including 'proof' for the body */ + resume_pay_with_response (pc, + http_status, + TMH_RESPONSE_make_json (proof)); + return; } - - printf ("All /deposit(s) ack'd\n"); - - dcc->pc->response = MHD_create_response_from_buffer (strlen ("All coins ack'd by the mint\n"), - "All coins ack'd by the mint\n", - MHD_RESPMEM_MUST_COPY); - dcc->pc->response_code = MHD_HTTP_OK; - /* Clean up what we can already */ - MHD_resume_connection (dcc->pc->connection); - TM_trigger_daemon (); /* we resumed, kick MHD */ + /* store result to DB */ + if (GNUNET_OK != + db->store_payment (db->cls, + &pc->h_contract, + &h_wire, + pc->transaction_id, + pc->timestamp, + pc->refund_deadline, + &dc->amount_without_fee, + &dc->coin_pub, + proof)) + { + /* internal error */ + abort_deposit (pc); + /* Forward error including 'proof' for the body */ + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("Merchant database error")); + return; + } + if (0 != pc->pending) + return; /* still more to do */ + resume_pay_with_response (pc, + MHD_HTTP_OK, + MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT)); } +/** + * Custom cleanup routine for a `struct PayContext`. + * + * @param hc the `struct PayContext` to clean up. + */ static void pay_context_cleanup (struct TM_HandlerContext *hc) { - int i; struct PayContext *pc = (struct PayContext *) hc; - - for (i = 0; i < pc->coins_cnt; i++) - GNUNET_free_non_null (pc->dc[i].dcc); + unsigned int i; TMH_PARSE_post_cleanup_callback (pc->json_parse_context); + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; - #if 0 + if (NULL != dc->dh) + { + TALER_MINT_deposit_cancel (dc->dh); + dc->dh = NULL; + } + if (NULL != dc->denom.rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (dc->denom.rsa_public_key); + dc->denom.rsa_public_key = NULL; + } + if (NULL != dc->ub_sig.rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (dc->ub_sig.rsa_signature); + dc->ub_sig.rsa_signature = NULL; + } + } + GNUNET_free_non_null (pc->dc); + if (NULL != pc->fo) + { + TMH_MINTS_find_mint_cancel (pc->fo); + pc->fo = NULL; + } if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ - MHD_destroy_response (pc->response); pc->response = NULL; } - #endif - - GNUNET_free_non_null (pc->dc); GNUNET_free (pc); } /** + * Function called with the result of our mint lookup. + * + * @param cls the `struct PayContext` + * @param mh NULL if mint was not found to be acceptable + * @param mint_trusted #GNUNET_YES if this mint is trusted by config + */ +static void +process_pay_with_mint (void *cls, + struct TALER_MINT_Handle *mh, + int mint_trusted) +{ + struct PayContext *pc = cls; + struct TALER_Amount acc_fee; + struct TALER_Amount acc_amount; + const struct TALER_MINT_Keys *keys; + unsigned int i; + + pc->fo = NULL; + if (NULL == mh) + { + /* The mint on offer is not in the set of our (trusted) + mints. Reject the payment. */ + GNUNET_break_op (0); + resume_pay_with_response (pc, + MHD_HTTP_PRECONDITION_FAILED, + TMH_RESPONSE_make_external_error ("mint not supported")); + return; + } + pc->mh = mh; + + keys = TALER_MINT_get_keys (mh); + if (NULL == keys) + { + GNUNET_break (0); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("no keys")); + return; + } + + /* Total up the fees and the value of the deposited coins! */ + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + const struct TALER_MINT_DenomPublicKey *denom_details; + + denom_details = TALER_MINT_get_denomination_key (keys, + &dc->denom); + if (NULL == denom_details) + { + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:o}", + "hint", "unknown denom to mint", + "denom_pub", TALER_json_from_rsa_public_key (dc->denom.rsa_public_key))); + return; + } + if (GNUNET_OK != + TMH_AUDITORS_check_dk (mh, + denom_details, + mint_trusted)) + { + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_json_pack ("{s:s, s:o}", + "hint", "no acceptable auditor for denomination", + "denom_pub", TALER_json_from_rsa_public_key (dc->denom.rsa_public_key))); + return; + } + if (0 == i) + { + acc_fee = denom_details->fee_deposit; + acc_amount = dc->percoin_amount; + } + else + { + if ( (GNUNET_OK != + TALER_amount_add (&acc_fee, + &denom_details->fee_deposit, + &acc_fee)) || + (GNUNET_OK != + TALER_amount_add (&acc_amount, + &dc->percoin_amount, + &acc_amount)) ) + { + GNUNET_break_op (0); + /* Overflow in these amounts? Very strange. */ + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_internal_error ("Overflow adding up amounts")); + return; + } + } + if (GNUNET_SYSERR == + TALER_amount_subtract (&dc->amount_without_fee, + &dc->percoin_amount, + &denom_details->fee_deposit)) + { + GNUNET_break_op (0); + /* fee higher than residual coin value, makes no sense. */ + resume_pay_with_response (pc, + MHD_HTTP_BAD_REQUEST, + TMH_RESPONSE_make_internal_error ("Fee higher than coin value")); + return; + } + } + + /* Now check that the customer paid enough for the full contract */ + if (-1 == TALER_amount_cmp (&pc->max_fee, + &acc_fee)) + { + /* acc_fee > max_fee, customer needs to cover difference */ + struct TALER_Amount excess_fee; + struct TALER_Amount total_needed; + + /* compute fee amount to be covered by customer */ + GNUNET_assert (GNUNET_OK == + TALER_amount_subtract (&excess_fee, + &acc_fee, + &pc->max_fee)); + /* add that to the total */ + if (GNUNET_OK != + TALER_amount_add (&total_needed, + &excess_fee, + &pc->amount)) + { + GNUNET_break (0); + resume_pay_with_response (pc, + MHD_HTTP_INTERNAL_SERVER_ERROR, + TMH_RESPONSE_make_internal_error ("overflow")); + return; + } + /* check if total payment sufficies */ + if (-1 == TALER_amount_cmp (&acc_amount, + &total_needed)) + { + resume_pay_with_response (pc, + MHD_HTTP_NOT_ACCEPTABLE, + TMH_RESPONSE_make_external_error ("insufficient funds (including excessive mint fees to be covered by customer)")); + return; + } + } + else + { + /* fees are acceptable, we cover them all; let's check the amount */ + if (-1 == TALER_amount_cmp (&acc_amount, + &pc->amount)) + { + resume_pay_with_response (pc, + MHD_HTTP_NOT_ACCEPTABLE, + TMH_RESPONSE_make_external_error ("insufficient funds")); + return; + } + } + + /* Initiate /deposit operation for all coins */ + for (i=0;i<pc->coins_cnt;i++) + { + struct MERCHANT_DepositConfirmation *dc = &pc->dc[i]; + + dc->dh = TALER_MINT_deposit (mh, + &dc->percoin_amount, + pc->edate, + j_wire, + &pc->h_contract, + &dc->coin_pub, + &dc->ub_sig, + &dc->denom, + pc->timestamp, + pc->transaction_id, + &pubkey, + pc->refund_deadline, + &dc->coin_sig, + &deposit_cb, + dc); + if (NULL == dc->dh) + { + resume_pay_with_response (pc, + MHD_HTTP_SERVICE_UNAVAILABLE, + TMH_RESPONSE_make_json_pack ("{s:s, s:i}", + "mint", pc->chosen_mint, + "transaction_id", pc->transaction_id)); + return; + } + } +} + + +/** * Accomplish this payment. * * @param rh context of the handler * @param connection the MHD connection to handle * @param[in,out] connection_cls the connection's closure - * (can be updated) + * (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a - * upload_data + * upload_data * @return MHD result code */ int @@ -263,53 +574,8 @@ MH_handler_pay (struct TMH_RequestHandler *rh, size_t *upload_data_size) { struct PayContext *pc; - json_t *root; - json_t *coins; - char *chosen_mint; - json_t *coin_aggregate; - json_t *wire_details; - unsigned int mint_index; /*a cell in the global array*/ - unsigned int coins_index; - unsigned int coins_cnt; - uint64_t transaction_id; int res; - struct TALER_MINT_DepositHandle *dh; - struct TALER_Amount max_fee; - struct TALER_Amount acc_fee; - struct TALER_Amount coin_fee; - struct TALER_Amount amount; - struct TALER_Amount percoin_amount; - struct GNUNET_TIME_Absolute edate; - struct GNUNET_TIME_Absolute timestamp; - struct GNUNET_TIME_Absolute refund_deadline; - struct TALER_MerchantPublicKeyP pubkey; - struct TALER_CoinSpendPublicKeyP coin_pub; - struct TALER_DenominationPublicKey denom_pub; - struct TALER_DenominationSignature ub_sig; - struct TALER_CoinSpendSignatureP coin_sig; - struct GNUNET_HashCode h_contract; - struct MERCHANT_DepositConfirmation *dc; - - struct TMH_PARSE_FieldSpecification spec[] = { - TMH_PARSE_member_array ("coins", &coins), - TMH_PARSE_member_string ("mint", &chosen_mint), - TMH_PARSE_member_amount ("max_fee", &max_fee), - TMH_PARSE_member_amount ("amount", &amount), - TMH_PARSE_member_time_abs ("timestamp", ×tamp), - TMH_PARSE_member_time_abs ("refund_deadline", &refund_deadline), - TMH_PARSE_member_uint64 ("transaction_id", &transaction_id), - TMH_PARSE_member_fixed ("H_contract", &h_contract), - TMH_PARSE_MEMBER_END - }; - - struct TMH_PARSE_FieldSpecification coin_aggregate_spec[] = { - TMH_PARSE_member_amount ("f", &percoin_amount), - TMH_PARSE_member_fixed ("coin_pub", &coin_pub.eddsa_pub), - TMH_PARSE_member_denomination_public_key ("denom_pub", &denom_pub), - TMH_PARSE_member_denomination_signature ("ub_sig", &ub_sig), - TMH_PARSE_member_fixed ("coin_sig", &coin_sig.eddsa_signature), - TMH_PARSE_MEMBER_END - }; + json_t *root; if (NULL == *connection_cls) { @@ -325,21 +591,17 @@ MH_handler_pay (struct TMH_RequestHandler *rh, } if (0 != pc->response_code) { + /* We are *done* processing the request, just queue the response (!) */ if (UINT_MAX == pc->response_code) return MHD_NO; /* hard error */ - /* We are *done* processing the request, just queue the response (!) */ res = MHD_queue_response (connection, pc->response_code, pc->response); - #if 0 - if (pc->response != NULL) + if (NULL != pc->response) { - /* undestroyable regardless of the other MHD_destroy_response called - in this source, FIXME */ MHD_destroy_response (pc->response); pc->response = NULL; } - #endif return res; } @@ -349,201 +611,109 @@ MH_handler_pay (struct TMH_RequestHandler *rh, upload_data_size, &root); if (GNUNET_SYSERR == res) - return MHD_NO; - /* the POST's body has to be further fetched */ + return MHD_NO; /* error parsing JSON */ if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - res = TMH_PARSE_json_data (connection, - root, - spec); - - if (GNUNET_YES != res) - return (GNUNET_NO == res) ? MHD_YES : MHD_NO; - - /* 1 Check if the chosen mint is among the merchant's preferred. - - An error in this case could be due to: - - * the wallet indicated a non existent mint - * the wallet indicated a non trusted mint + return MHD_YES; /* the POST's body has to be further fetched */ - NOTE: by preventively checking this, the merchant - avoids getting HTTP response codes from random - websites that may mislead the wallet in the way - of managing the error. Of course, that protect the - merchant from POSTing coins to untrusted mints. - - */ - - for (mint_index = 0; mint_index <= nmints; mint_index++) + /* Got the JSON upload, parse it */ { - /* no mint found in array */ - if (mint_index == nmints) + json_t *coins; + json_t *coin; + unsigned int coins_index; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_array ("coins", &coins), + TMH_PARSE_member_string ("mint", &pc->chosen_mint), + TMH_PARSE_member_uint64 ("transaction_id", &pc->transaction_id), + TMH_PARSE_member_amount ("max_fee", &pc->max_fee), + TMH_PARSE_member_amount ("amount", &pc->amount), + TMH_PARSE_member_time_abs ("timestamp", &pc->timestamp), + TMH_PARSE_member_time_abs ("refund_deadline", &pc->refund_deadline), + TMH_PARSE_member_fixed ("H_contract", &pc->h_contract), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + root, + spec); + if (GNUNET_YES != res) { - mint_index = -1; - break; + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; } - /* test it by checking public key */ - if (0 == strcmp (mints[mint_index].hostname, - chosen_mint)) - break; - - } - - if (-1 == mint_index) - return TMH_RESPONSE_reply_external_error (connection, "unknown mint"); - - /* no 'edate' from frontend. Generate it here; it will be timestamp - + a edate delay supplied in config file */ - if (NULL == json_object_get (root, "edate")) - { - edate = GNUNET_TIME_absolute_add (timestamp, edate_delay); - if (-1 == json_object_set (root, "edate", TALER_json_from_abs (edate))) - return MHD_NO; - } - - coins_cnt = json_array_size (coins); - - if (0 == coins_cnt) - return TMH_RESPONSE_reply_external_error (connection, "no coins given"); - - json_array_foreach (coins, coins_index, coin_aggregate) - { - res = deposit_fee_from_coin_aggregate (connection, - coin_aggregate, - &coin_fee, - mint_index); - if (GNUNET_NO == res) - return MHD_YES; - if (GNUNET_SYSERR == res) - return MHD_NO; - - if (0 == coins_index) - acc_fee = coin_fee; + /* 'edate' is optional, if it is not present, generate it here; it + will be timestamp plus the edate_delay supplied in config + file */ + if (NULL == json_object_get (root, "edate")) + { + pc->edate = GNUNET_TIME_absolute_add (pc->timestamp, + edate_delay); + } else - TALER_amount_add (&acc_fee, - &acc_fee, - &coin_fee); - } - - - if (-1 == TALER_amount_cmp (&max_fee, &acc_fee)) - return MHD_HTTP_NOT_ACCEPTABLE; - - /* cutting off unneeded fields from deposit permission as - gotten from the wallet */ - if (-1 == json_object_del (root, "mint")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'mint' field"); - if (-1 == json_object_del (root, "coins")) - return TMH_RESPONSE_reply_external_error (connection, - "malformed/non-existent 'coins' field"); - - /* adding our public key to deposit permission */ - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pubkey.eddsa_pub); - json_object_set_new (root, - "merchant_pub", - TALER_json_from_data (&pubkey, sizeof (pubkey))); - - wire_details = MERCHANT_get_wire_json (wire, salt); - /* since memory is zero'd out by GNUNET_malloc, any 'ackd' field will be - (implicitly) set to false */ - dc = GNUNET_malloc (coins_cnt * sizeof (struct MERCHANT_DepositConfirmation)); - if (NULL == dc) - return TMH_RESPONSE_reply_internal_error (connection, "memory failure"); - - /* DEBUG CHECKPOINT: return a provisory fullfilment page to the wallet - to test the reception of coins array */ - - #ifdef COINSCHECKPOINT - rh->data = "Coins received\n"; - return TMH_MHD_handler_static_response (rh, - connection, - connection_cls, - upload_data, - upload_data_size); - - #endif - - /* suspend connection until the last coin has been ack'd to the cb. - That last cb will finally resume the connection and send back a response */ - MHD_suspend_connection (connection); - - pc->dc = dc; - pc->coins_cnt = coins_cnt; - pc->transaction_id = transaction_id; + { + struct TMH_PARSE_FieldSpecification espec[] = { + TMH_PARSE_member_time_abs ("edate", &pc->edate), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + root, + espec); + if (GNUNET_YES != res) + { + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + } - json_array_foreach (coins, coins_index, coin_aggregate) - { + pc->coins_cnt = json_array_size (coins); + if (0 == pc->coins_cnt) + { + json_decref (root); + return TMH_RESPONSE_reply_external_error (connection, + "no coins given"); + } + /* note: 1 coin = 1 deposit confirmation expected */ + pc->dc = GNUNET_new_array (pc->coins_cnt, + struct MERCHANT_DepositConfirmation); - /* 3 For each coin in DB - - a. Generate a deposit permission - b. store it in DB - c. POST to the mint (see mint-lib for this) - (retry until getting a persisten state) - */ - - /* a */ - if (-1 == json_object_update (root, coin_aggregate)) - return TMH_RESPONSE_reply_internal_error (connection, - "deposit permission not generated for storing"); - - /* b */ - char *deposit_permission_str = json_dumps (root, JSON_COMPACT); - if (GNUNET_OK != MERCHANT_DB_store_deposit_permission (db_conn, - deposit_permission_str, - transaction_id, - 1, - mints[mint_index].hostname)) - return TMH_RESPONSE_reply_internal_error (connection, "internal DB failure"); - res = TMH_PARSE_json_data (connection, - coin_aggregate, - coin_aggregate_spec); - if (GNUNET_OK != res) - return res; /* may return GNUNET_NO */ - - /* c */ - struct DepositCallbackContext *percoin_dcc = GNUNET_new (struct DepositCallbackContext); - pc->dc[coins_index].dcc = percoin_dcc; - percoin_dcc->index = coins_index; - percoin_dcc->pc = pc; - - dh = TALER_MINT_deposit (mints[mint_index].conn, - &percoin_amount, - edate, - wire_details, - &h_contract, - &coin_pub, - &ub_sig, - &denom_pub, - timestamp, - transaction_id, - &pubkey, - refund_deadline, - &coin_sig, - &deposit_cb, - percoin_dcc); /*FIXME TODO instantiate an individual cls for each - cb: each of them needs an index which points the - array of all the confirmations */ - if (NULL == dh) + json_array_foreach (coins, coins_index, coin) { - MHD_resume_connection (connection); - return TMH_RESPONSE_reply_json_pack (connection, - MHD_HTTP_SERVICE_UNAVAILABLE, - "{s:s, s:i}", - "mint", mints[mint_index].hostname, - "transaction_id", transaction_id); + struct MERCHANT_DepositConfirmation *dc = &pc->dc[coins_index]; + struct TMH_PARSE_FieldSpecification spec[] = { + TMH_PARSE_member_denomination_public_key ("denom_pub", &dc->denom), + TMH_PARSE_member_amount ("f", &dc->percoin_amount), + TMH_PARSE_member_fixed ("coin_pub", &dc->coin_pub), + TMH_PARSE_member_denomination_signature ("ub_sig", &dc->ub_sig), + TMH_PARSE_member_fixed ("coin_sig", &dc->coin_sig), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_json_data (connection, + coin, + spec); + if (GNUNET_YES != res) + { + json_decref (root); + return (GNUNET_NO == res) ? MHD_YES : MHD_NO; + } + dc->index = coins_index; + dc->pc = pc; } } - GNUNET_SCHEDULER_cancel (poller_task); - GNUNET_SCHEDULER_add_now (context_task, mints[mint_index].ctx); - return MHD_YES; + /* Find the responsible mint, this may take a while... */ + pc->pending = pc->coins_cnt; + pc->fo = TMH_MINTS_find_mint (pc->chosen_mint, + &process_pay_with_mint, + pc); - /* 4 Return response code: success, or whatever data the - mint sent back regarding some bad coin */ + /* ... so we suspend connection until the last coin has been ack'd + or until we have encountered a hard error. Eventually, we will + resume the connection and send back a response using + #resume_pay_with_response(). */ + MHD_suspend_connection (connection); + json_decref (root); + return MHD_YES; } diff --git a/src/backend/taler-merchant-httpd_pay.h b/src/backend/taler-merchant-httpd_pay.h index 6a796e06..0836f9ba 100644 --- a/src/backend/taler-merchant-httpd_pay.h +++ b/src/backend/taler-merchant-httpd_pay.h @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014 Christian Grothoff (and other contributing authors) + (C) 2014, 2015 Christian Grothoff (and other contributing authors) TALER is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -13,17 +13,16 @@ You should have received a copy of the GNU General Public License along with TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ - /** - * @file merchant/backend/taler-merchant-httpd_contract.h + * @file backend/taler-merchant-httpd_pay.h * @brief headers for /pay handler * @author Marcello Stanisci */ - #ifndef TALER_MINT_HTTPD_PAY_H #define TALER_MINT_HTTPD_PAY_H #include <microhttpd.h> -#include "taler-mint-httpd.h" +#include "taler-merchant-httpd.h" + /** * Manage a payment @@ -33,7 +32,6 @@ * @param[in,out] connection_cls the connection's closure (can be updated) * @param upload_data upload data * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * * @return MHD result code */ int diff --git a/src/backend/taler-mint-httpd_responses.c b/src/backend/taler-merchant-httpd_responses.c index 00a4d25f..d8ba1170 100644 --- a/src/backend/taler-mint-httpd_responses.c +++ b/src/backend/taler-merchant-httpd_responses.c @@ -14,7 +14,7 @@ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> */ /** - * @file taler-mint-httpd_responses.c + * @file taler-merchant-httpd_responses.c * @brief API for generating the various replies of the mint; these * functions are called TMH_RESPONSE_reply_ and they generate * and queue MHD response objects for a given connection. @@ -23,27 +23,22 @@ * @author Christian Grothoff */ #include "platform.h" -#include "taler-mint-httpd_responses.h" +#include "taler-merchant-httpd_responses.h" #include <taler/taler_util.h> #include <gnunet/gnunet_util_lib.h> /** - * Send JSON object as response. + * Make JSON response object. * - * @param connection the MHD connection * @param json the json object - * @param response_code the http response code - * @return MHD result code + * @return MHD response object */ -int -TMH_RESPONSE_reply_json (struct MHD_Connection *connection, - const json_t *json, - unsigned int response_code) +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json) { struct MHD_Response *resp; char *json_str; - int ret; json_str = json_dumps (json, JSON_INDENT(2)); GNUNET_assert (NULL != json_str); @@ -53,11 +48,34 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, { free (json_str); GNUNET_break (0); - return MHD_NO; + return NULL; } (void) MHD_add_response_header (resp, MHD_HTTP_HEADER_CONTENT_TYPE, "application/json"); + return resp; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TMH_RESPONSE_reply_json (struct MHD_Connection *connection, + const json_t *json, + unsigned int response_code) +{ + struct MHD_Response *resp; + int ret; + + resp = TMH_RESPONSE_make_json (json); + if (NULL == resp) + return MHD_NO; ret = MHD_queue_response (connection, response_code, resp); @@ -67,6 +85,40 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, /** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...) +{ + json_t *json; + va_list argp; + struct MHD_Response *ret; + json_error_t jerror; + + va_start (argp, fmt); + json = json_vpack_ex (&jerror, 0, fmt, argp); + va_end (argp); + if (NULL == json) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to pack JSON with format `%s': %s\n", + fmt, + jerror.text); + GNUNET_break (0); + return MHD_NO; + } + ret = TMH_RESPONSE_make_json (json); + json_decref (json); + return ret; +} + + +/** * Function to call to handle the request by building a JSON * reply from a format string and varargs. * @@ -106,6 +158,22 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, return ret; } + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "internal error", + "hint", hint); +} + + /** * Send a response indicating an internal error. * @@ -124,6 +192,7 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, "hint", hint); } + /** * Send a response indicating that the request was too big. * @@ -186,6 +255,7 @@ TMH_RESPONSE_add_global_headers (struct MHD_Response *response) "close"); } + /** * Send a response indicating an external error. * @@ -203,4 +273,21 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, "error", "client error", "hint", hint); } + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint) +{ + return TMH_RESPONSE_make_json_pack ("{s:s, s:s}", + "error", "client error", + "hint", hint); +} + + /* end of taler-mint-httpd_responses.c */ diff --git a/src/backend/taler-mint-httpd_responses.h b/src/backend/taler-merchant-httpd_responses.h index f947bd57..7240d601 100644 --- a/src/backend/taler-mint-httpd_responses.h +++ b/src/backend/taler-merchant-httpd_responses.h @@ -1,6 +1,6 @@ /* This file is part of TALER - Copyright (C) 2014 GNUnet e.V. + Copyright (C) 2014, 2015 GNUnet e.V. TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software @@ -15,7 +15,7 @@ */ /** - * @file taler-mint-httpd_responses.h + * @file taler-merchant-httpd_responses.h * @brief API for generating the various replies of the mint; these * functions are called TMH_RESPONSE_reply_ and they generate * and queue MHD response objects for a given connection. @@ -31,6 +31,16 @@ #include <pthread.h> /** + * Make JSON response object. + * + * @param json the json object + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json (const json_t *json); + + +/** * Send JSON object as response. * * @param connection the MHD connection @@ -45,6 +55,18 @@ TMH_RESPONSE_reply_json (struct MHD_Connection *connection, /** + * Make JSON response object. + * + * @param fmt format string for pack + * @param ... varargs + * @return MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_json_pack (const char *fmt, + ...); + + +/** * Function to call to handle the request by building a JSON * reply from a format string and varargs. * @@ -70,6 +92,7 @@ TMH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, int TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); + /** * Send a response indicating an internal error. * @@ -80,6 +103,18 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection); int TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an internal error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_internal_error (const char *hint); + + /** * Send a response indicating an external error. * @@ -90,6 +125,18 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, const char *hint); + + +/** + * Create a response indicating an external error. + * + * @param hint hint about the internal error's nature + * @return a MHD response object + */ +struct MHD_Response * +TMH_RESPONSE_make_external_error (const char *hint); + + /** * Send a response indicating that the request was too big. * @@ -99,6 +146,7 @@ TMH_RESPONSE_reply_external_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + /** * Add headers we want to return in every response. * Useful for testing, like if we want to always close diff --git a/src/backend/taler-mint-httpd.h b/src/backend/taler-mint-httpd.h deleted file mode 100644 index ad8702f0..00000000 --- a/src/backend/taler-mint-httpd.h +++ /dev/null @@ -1,85 +0,0 @@ -/* - This file is part of TALER - Copyright (C) 2014, 2015 GNUnet e.V. - - TALER is free software; you can redistribute it and/or modify it under the - terms of the GNU Affero General Public License as published by the Free Software - Foundation; either version 3, or (at your option) any later version. - - TALER is distributed in the hope that it will be useful, but WITHOUT ANY - WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR - A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License along with - TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> -*/ -/** - * @file taler-mint-httpd.h - * @brief Global declarations for the mint - * @author Florian Dold - * @author Benedikt Mueller - * @author Christian Grothoff - * - * FIXME: Consider which of these need to really be globals... - */ -#ifndef TALER_MINT_HTTPD_H -#define TALER_MINT_HTTPD_H - -#include <microhttpd.h> - -/** - * @brief Struct describing an URL and the handler for it. - */ -struct TMH_RequestHandler -{ - - /** - * URL the handler is for. - */ - const char *url; - - /** - * Method the handler is for, NULL for "all". - */ - const char *method; - - /** - * Mime type to use in reply (hint, can be NULL). - */ - const char *mime_type; - - /** - * Raw data for the @e handler - */ - const void *data; - - /** - * Number of bytes in @e data, 0 for 0-terminated. - */ - size_t data_size; - - /** - * Function to call to handle the request. - * - * @param rh this struct - * @param mime_type the @e mime_type for the reply (hint, can be NULL) - * @param connection the MHD connection to handle - * @param[in,out] connection_cls the connection's closure (can be updated) - * @param upload_data upload data - * @param[in,out] upload_data_size number of bytes (left) in @a upload_data - * @return MHD result code - */ - int (*handler)(struct TMH_RequestHandler *rh, - struct MHD_Connection *connection, - void **connection_cls, - const char *upload_data, - size_t *upload_data_size); - - /** - * Default response code. - */ - int response_code; -}; - - -#endif |