merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

commit b0097e0389376fe5b1fc78684c53c2b0de647582
parent 1ca15d8f10a8c4b6860eeccf30899252eb6c3b79
Author: Marcello Stanisci <marcello.stanisci@inria.fr>
Date:   Fri, 23 Oct 2015 19:56:07 +0200

Restructuring merchant:

- Calling URLs handlers as the mint does
- Moving URLs handlers into dedicated functions
  (moved inside dedicated source files)
- Some file renaming
- Fixing some warnings about 'const' variables used
  in test_contract

Diffstat:
Msrc/backend-lib/Makefile.am | 6++++--
Rsrc/backend-lib/taler-merchant-httpd_contract.c -> src/backend-lib/merchant_api_contract.c | 0
Rsrc/backend-lib/taler-merchant-httpd_deposit.c -> src/backend-lib/merchant_api_deposit.c | 0
Msrc/backend-lib/merchant_db.c | 3---
Msrc/backend/Makefile.am | 2++
Msrc/backend/taler-merchant-httpd.c | 744+++++++++++--------------------------------------------------------------------
Asrc/backend/taler-merchant-httpd_contract.c | 74++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-merchant-httpd_obsolete.c | 985+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-mint-httpd.h | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-mint-httpd_mhd.c | 154+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/backend/taler-mint-httpd_mhd.h | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/backend/taler-mint-httpd_responses.c | 21+++++++++++++++++++++
Msrc/backend/taler-mint-httpd_responses.h | 10++++++++++
Msrc/tests/test_contract.c | 8++------
14 files changed, 1547 insertions(+), 656 deletions(-)

diff --git a/src/backend-lib/Makefile.am b/src/backend-lib/Makefile.am @@ -7,11 +7,13 @@ lib_LTLIBRARIES = \ include_HEADERS = \ taler_merchant_lib.h \ taler_merchant_contract_lib.h + taler_merchant_deposit_lib.h libtalermerchant_la_SOURCES = \ - taler-merchant-httpd_contract.c \ - taler-merchant-httpd_deposit.c \ + merchant_api_contract.c \ + merchant_api_deposit.c \ taler_merchant_contract_lib.h \ + taler_merchant_deposit_lib.h \ merchant_db.c merchant_db.h \ merchant.h diff --git a/src/backend-lib/taler-merchant-httpd_contract.c b/src/backend-lib/merchant_api_contract.c diff --git a/src/backend-lib/taler-merchant-httpd_deposit.c b/src/backend-lib/merchant_api_deposit.c diff --git a/src/backend-lib/merchant_db.c b/src/backend-lib/merchant_db.c @@ -314,9 +314,6 @@ MERCHANT_DB_contract_create (PGconn *conn, PQclear (res); return GNUNET_OK; - EXITIF_exit: - PQclear (res); - return GNUNET_SYSERR; } long long diff --git a/src/backend/Makefile.am b/src/backend/Makefile.am @@ -10,6 +10,8 @@ taler_merchant_httpd_SOURCES = \ ../backend-lib/merchant_db.c ../backend-lib/merchant_db.h \ taler-mint-httpd_parsing.c taler-mint-httpd_parsing.h \ taler-mint-httpd_responses.c taler-mint-httpd_responses.h \ + taler-mint-httpd.h \ + taler-mint-httpd_mhd.c taler-mint-httpd_mhd.h \ ../backend-lib/taler-merchant-httpd_contract.h taler_merchant_httpd_LDADD = \ diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c @@ -25,24 +25,14 @@ #include <jansson.h> #include <gnunet/gnunet_util_lib.h> #include <curl/curl.h> -#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> #include <taler/taler_mint_service.h> #include "taler-mint-httpd_parsing.h" #include "taler-mint-httpd_responses.h" #include "merchant_db.h" #include "merchant.h" #include "taler_merchant_lib.h" - -extern struct MERCHANT_WIREFORMAT_Sepa * -TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) +#include "taler-mint-httpd_mhd.h" /** * Our hostname @@ -65,24 +55,24 @@ struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; char *keyfile; /** - * The MHD Daemon + * Mint context */ -static struct MHD_Daemon *mhd; +static struct TALER_MINT_Context *mctx; /** - * Connection handle to the our database + * Mints' URL,port,key triples */ -PGconn *db_conn; +struct MERCHANT_MintInfo *mint_infos; /** - * merchant's conf handle + * Shutdown task identifier */ -struct GNUNET_CONFIGURATION_Handle *cfg; +static struct GNUNET_SCHEDULER_Task *shutdown_task; /** - * Shutdown task identifier + * Hashmap to store the mint context information */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; +static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; /** * Our wireformat @@ -90,6 +80,11 @@ static struct GNUNET_SCHEDULER_Task *shutdown_task; static struct MERCHANT_WIREFORMAT_Sepa *wire; /** + * The number of accepted mints + */ +unsigned int nmints; + +/** * Should we do a dry run where temporary tables are used for storing the data. */ static int dry; @@ -100,9 +95,15 @@ static int dry; static int result; /** - * Mint context + * Connection handle to the our database */ -static struct TALER_MINT_Context *mctx; +PGconn *db_conn; + +/** + * Hashmap (with 'big entries') to make a mint's base URL + * to point to some mint-describing structure + */ +static struct GNUNET_CONTAINER_MultiHashMap *mints_hashmap; /** * Context information of the mints we trust @@ -121,214 +122,9 @@ struct Mint }; /** - * Hashmap to store the mint context information - */ -static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; - -/** - * Hashmap (with 'big entries') to make a mint's base URL - * to point to some mint-describing structure - */ -static struct GNUNET_CONTAINER_MultiHashMap *mints_hashmap; - - - -/** - * Mints' URL,port,key triples - */ -struct MERCHANT_MintInfo *mint_infos; - -/** - * The number of accepted mints - */ -unsigned int nmints; - -struct Mint_Response -{ - char *ptr; - size_t size; - -}; - - -/** - * Generate the 'hello world' response - * @param connection a MHD connection - * @param resp where to store the response for the calling function. - * Note that in its original implementation this parameter was preceeded - * by a '_'. Still not clear why. - * @return HTTP status code reflecting the operation outcome - * - */ -static unsigned int -generate_hello (struct MHD_Response **resp) -{ - - const char *hello = "Hello customer\n"; - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, - MHD_RESPMEM_PERSISTENT); - ret = 200; - return ret; -} - -/** - * Return the given message to the other end of connection - * @msg (0-terminated) message to show - * @param connection a MHD connection - * @param resp where to store the response for the calling function - * @return HTTP status code reflecting the operation outcome - * - */ -static unsigned int -generate_message (struct MHD_Response **resp, const char *msg) -{ - - unsigned int ret; - - *resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg, - MHD_RESPMEM_MUST_FREE); - ret = 200; - return ret; -} - -/** - * Callback to pass to curl used to store a HTTP response - * in a custom memory location. - * See http://curl.haxx.se/libcurl/c/getinmemory.html for a - * detailed example - * - * @param contents the data gotten so far from the server - * @param size symbolic (arbitrarily chosen by libcurl) unit - * of bytes - * @param nmemb factor to multiply by @a size to get the real - * size of @a contents - * @param userdata a pointer to a memory location which remains - * the same across all the calls to this callback (i.e. it has - * to be grown at each invocation of this callback) - * @return number of written bytes - * See http://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html - * for official documentation - * - */ -size_t -get_response_in_memory (char *contents, - size_t size, - size_t nmemb, - void *userdata) -{ - struct Mint_Response *mr; - size_t realsize; - - realsize = size * nmemb; - mr = userdata; - mr->ptr = realloc (mr->ptr, mr->size + realsize + 1); - - if (mr->ptr == NULL) { - printf ("Out of memory, could not get in memory mint's" - "response"); - return 0; - } - memcpy(&(mr->ptr[mr->size]), contents, realsize); - mr->size += realsize; - mr->ptr[mr->size] = 0; - - return realsize; - -} - -#ifdef PANIC_MGMT -/** - * Callback for catching serious error conditions from MHD. - * - * @param cls user specified value - * @param file where the error occured - * @param line where the error occured - * @param reason error detail, may be NULL - */ -static void -mhd_panic_cb (void *cls, - const char *file, - unsigned int line, - const char *reason) -{ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "MHD panicked at %s:%u: %s", - file, line, reason); - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); -} -#endif - -/** - * Manage a non 200 HTTP status. I.e. it shows a 'failure' page to - * the client - * @param connection the channel thorugh which send the message - * @status the HTTP status to examine - * @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error + * The MHD Daemon */ -static int -failure_resp (struct MHD_Connection *connection, unsigned int status) -{ - static char page_MHD_HTTP_NOT_FOUND[]="\ -<!DOCTYPE html> \ -<html><title>Resource not found</title><body><center> \ -<h3>The resource you are looking for is not found.</h3> \ -</center></body></html>"; - static char page_MHD_HTTP_BAD_REQUEST[]="\ -<!DOCTYPE html> \ -<html><title>Bad request</title><body><center> \ -<h3>Malformed POSTed JSON.</h3> \ -</center></body></html>"; -static char page_MHD_HTTP_METHOD_NOT_ALLOWED[]="\ -<!DOCTYPE html> \ -<html><title>Method NOT allowed</title><body><center> \ -<h3>ONLY POSTs are allowed.</h3> \ -</center></body></html>"; - static char page_MHD_HTTP_INTERNAL_SERVER_ERROR[]="\ -<!DOCTYPE html> <html><title>Internal Server Error</title><body><center> \ -<h3>The server experienced an internal error and hence cannot serve your \ -request</h3></center></body></html>"; - struct MHD_Response *resp; - char *page; - size_t size; -#define PAGE(number) \ - do {page=page_ ## number; size=sizeof(page_ ## number)-1;} while(0) - - GNUNET_assert (MHD_HTTP_BAD_REQUEST <= status); - resp = NULL; - switch (status) - { - case MHD_HTTP_NOT_FOUND : - PAGE(MHD_HTTP_NOT_FOUND); - break; - case MHD_HTTP_BAD_REQUEST: - PAGE(MHD_HTTP_BAD_REQUEST); - break; - case MHD_HTTP_METHOD_NOT_ALLOWED: - PAGE(MHD_HTTP_METHOD_NOT_ALLOWED); - break; - default: - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - case MHD_HTTP_INTERNAL_SERVER_ERROR: - PAGE(MHD_HTTP_INTERNAL_SERVER_ERROR); - } -#undef PAGE - - EXITIF (NULL == (resp = MHD_create_response_from_buffer (size, - page, - MHD_RESPMEM_PERSISTENT))); - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - MHD_destroy_response (resp); - return GNUNET_OK; - - EXITIF_exit: - if (NULL != resp) - MHD_destroy_response (resp); - return GNUNET_SYSERR; -} - +static struct MHD_Daemon *mhd; /** * A client has requested the given url using the given method @@ -377,423 +173,100 @@ url_handler (void *cls, const char *version, const char *upload_data, size_t *upload_data_size, - void **connection_cls) + void **con_cls) { - - /*printf ("%s\n", url);*/ - unsigned long status; - unsigned int no_destroy; - struct GNUNET_CRYPTO_EddsaSignature c_sig; - struct GNUNET_CRYPTO_EddsaSignature deposit_confirm_sig; - struct GNUNET_CRYPTO_EddsaPublicKey pub; - #ifdef OBSOLETE - struct ContractNBO contract; - #else - struct Contract contract; - #endif - struct MHD_Response *resp; - json_t *root; - json_t *j_sig_enc; - json_t *j_h_contract; - json_t *j_tmp; - json_t *eddsa_pub_enc; - json_t *response; - json_t *j_mints; - json_t *j_mint; - json_t *j_wire; - int cnt; /* loop counter */ - char *deposit_body; - json_t *j_contract_add; - struct GNUNET_TIME_Absolute now; - struct GNUNET_TIME_Absolute expiry; - struct GNUNET_TIME_Absolute edate; - struct GNUNET_TIME_Absolute refund; - struct GNUNET_HashCode h_json_wire; - json_t *j_h_json_wire; - struct curl_slist *slist; - char *contract_str; - struct GNUNET_HashCode h_contract_str; - struct MERCHANT_contract_handle ch; - struct TALER_MintPublicKeyP mint_pub; - uint64_t nounce; - - CURL *curl; - CURLcode curl_res; - - uint32_t res = GNUNET_SYSERR; - - #define URL_HELLO "/hello" - #define URL_CONTRACT "/contract" - #define URL_PAY "/pay" - no_destroy = 0; - resp = NULL; - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - - if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = generate_hello (&resp); - else + static struct TMH_RequestHandler handlers[] = { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - } - } + /* Landing page, tell humans to go away. */ + { "/", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - if (0 == strncasecmp (url, URL_PAY, sizeof (URL_PAY))) - { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - goto end; + /* Further test page */ + { "/hello", MHD_HTTP_METHOD_GET, "text/plain", + "Hello, Customer.\n", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_OK }, - } - else - res = TMH_PARSE_post_json (connection, - connection_cls, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_BAD_REQUEST; - goto end; - } - - /* the POST's body has to be further fetched */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - /* Firstly, check if the wallet is paying against an approved - mint */ - json_t *j_chosen_mint; - j_chosen_mint = json_object_get (root, "mint"); - struct GNUNET_HashCode hash_key; - char *chosen_mint; - - chosen_mint = json_string_value (j_chosen_mint); - GNUNET_CRYPTO_hash (chosen_mint, strlen (chosen_mint), &hash_key); - - if (NULL == - GNUNET_CONTAINER_multihashmap_get (mints_hashmap, &hash_key)) - { - printf ("Untrusted mint\n"); - status = MHD_HTTP_FORBIDDEN; - goto end; - - } - - /* NOTE: from now on, the mint's base URL is pointed by 'chosen_mint' */ - - /* The merchant will only add its 'wire' object to the JSON - it got from the wallet */ + { "/contract", MHD_HTTP_METHOD_GET, "application/json", + NULL, 0, + &MH_handler_contract, MHD_HTTP_OK }, - /* Get this dep. perm.'s H_contract */ - - if (NULL == (j_h_contract = json_object_get (root, "H_contract"))) - { - printf ("H_contract field missing\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - TALER_json_to_data (j_h_contract, &h_contract_str, sizeof (struct GNUNET_HashCode)); + { "/contract", NULL, "text/plain", + "Only POST is allowed", 0, + &TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED }, - nounce = 0; - edate.abs_value_us = 0; - - if (GNUNET_SYSERR == - MERCHANT_DB_get_contract_values (db_conn, - &h_contract_str, - &nounce, - &edate)) - { - printf ("not existing contract\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - /* Reproducing the wire object */ - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - nounce, - edate))) - - { - printf ("wire object not reproduced\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - if (-1 == json_object_set (root, "wire", j_wire)) - { - printf ("depperm not augmented\n"); - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - /* POST to mint's "/deposit" */ - curl = curl_easy_init (); - - struct Mint_Response mr; - mr.ptr = malloc(1); - mr.size = 0; + {NULL, NULL, NULL, NULL, 0, 0 } + }; - if (curl) + static struct TMH_RequestHandler h404 = { - - char deposit_url[strlen (chosen_mint) + strlen ("http://") + strlen ("/deposit") + 1]; - sprintf (deposit_url, "http://%s/deposit", chosen_mint); - slist = curl_slist_append (slist, "Content-type: application/json"); - curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist); - - curl_easy_setopt (curl, CURLOPT_URL, deposit_url); - curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_response_in_memory); - curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *) &mr); - - /* NOTE: hopefully, this string won't need any URL-encoding, since as for the - Jansson specs, any space and-or newline are not in place using JSON_COMPACT - flag */ - deposit_body = json_dumps (root, JSON_COMPACT | JSON_PRESERVE_ORDER); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, deposit_body); - - curl_res = curl_easy_perform (curl); - - curl_slist_free_all(slist); - if(curl_res != CURLE_OK) - { - printf ("deposit not sent\n"); - goto end; - } - else - printf ("\ndeposit request issued\n"); - - curl_easy_cleanup(curl); - - curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); - - /* If the request was successful, the deposit confirmation - has to be verified*/ - - if (MHD_HTTP_OK != result) - /* Jump here to error mgmt, see issue #3800 (TODO) */ - - if (GNUNET_SYSERR == - MERCHANT_DB_get_contract_handle (db_conn, &h_contract_str, &ch)) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - root = json_loads (mr.ptr, 0, NULL); - j_tmp = json_object_get (root, "sig"); - TALER_json_to_data (j_tmp, - &deposit_confirm_sig, - sizeof (struct GNUNET_CRYPTO_EddsaSignature)); - j_tmp = json_object_get (root, "pub"); - - TALER_json_to_data (j_tmp, - &mint_pub.eddsa_pub, - sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); - - - /* The following check could be done once the merchant is able to - retrieve the deposit fee for each coin it gets a payment with. - That happens because the signature made on a deposit confirmation - uses, among its values of interest, the subtraction of the deposit - fee from the used coin. That fee is never communicated by the wallet - to the merchant, so the only way for the merchant to get this - information is to fetch all the mint's keys, namely it needs to - invoke "/keys", and store what gotten in its DB */ - - #ifdef DENOMMGMT - if (GNUNET_NO == - MERCHANT_verify_confirmation (&h_contract_str, - &h_json_wire, - ch.timestamp, - ch.refund_deadline, - ch.contract_id, - amount_minus_fee, /* MISSING */ - coin_pub, /* MISSING */ - &pub, - &deposit_confirm_sig, - &mint_pub)) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - - } - - #endif - - /* Place here the successful message to issue to the frontend */ - status = MHD_HTTP_OK; - generate_message (&resp, "fullfillment page here"); - GNUNET_free (mr.ptr); - - } + "", NULL, "text/html", + "<html><title>404: not found</title></html>", 0, + &TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND + }; + /* Compiler complains about non returning a value in a non-void + declared function: the FIX is to return what the handler for + a particular URL returns */ - } + struct TMH_RequestHandler *rh; + unsigned int i; - /* - * To be called by the frontend passing the contract with some "holes" - * which will be completed, stored in DB, signed, and returned - * - */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Handling request for URL '%s'\n", + url); - if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) + for (i=0;NULL != handlers[i].url;i++) { - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - status = MHD_HTTP_METHOD_NOT_ALLOWED; - goto end; - - } - else - res = TMH_PARSE_post_json (connection, - connection_cls, - upload_data, - upload_data_size, - &root); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_BAD_REQUEST; - goto end; - } - - - /* the POST's body has to be fetched furthermore */ - if ((GNUNET_NO == res) || (NULL == root)) - return MHD_YES; - - j_mints = json_array (); - for (cnt = 0; cnt < nmints; cnt++) - { - j_mint = json_pack ("{s:s}", - mint_infos[cnt].hostname, - GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey)); - json_array_append_new (j_mints, j_mint); - - } - - /* timestamp */ - now = GNUNET_TIME_absolute_get (); - /* expiry */ - expiry = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - /* edate, note: this value must be generated now (and not when the - wallet sends back a deposit permission because the hashed 'wire' object, - which carries this values in it, has to be included in the signed bundle - by the wallet) */ - edate = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - refund = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); - - TALER_round_abs_time (&now); - TALER_round_abs_time (&expiry); - TALER_round_abs_time (&edate); - TALER_round_abs_time (&refund); - - /* getting the SEPA-aware JSON */ - /* nounce for hashing the wire object */ - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - - /* get wire object */ - - if (NULL == (j_wire = MERCHANT_get_wire_json (wire, - nounce, - edate))) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - - /* hash wire objcet */ - if (GNUNET_SYSERR == TALER_hash_json (j_wire, &h_json_wire)) - goto end; - - j_h_json_wire = TALER_json_from_data ((void *) &h_json_wire, sizeof (struct GNUNET_HashCode)); - - GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); - eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); - - if (NULL == (j_contract_add = json_pack ("{s:s, s:s, s:o, s:o, s:o}", - "merchant_pub", json_string_value (eddsa_pub_enc), - "H_wire", json_string_value (j_h_json_wire), - "timestamp", TALER_json_from_abs (now), - "refund", TALER_json_from_abs (refund), - "mints", j_mints))) - { - printf ("BAD contract enhancement\n"); - goto end; - } - - /* melt to what received from the wallet */ - if (-1 == json_object_update (root, j_contract_add)) - { - printf ("depperm response not built\n"); - goto end; - } - - res = MERCHANT_handle_contract (root, - db_conn, - &contract, - now, - expiry, - edate, - refund, - &contract_str, - nounce); - if (GNUNET_SYSERR == res) - { - status = MHD_HTTP_INTERNAL_SERVER_ERROR; - goto end; - } - if (GNUNET_NO == res) - { - status = MHD_HTTP_METHOD_NOT_ACCEPTABLE; - goto end; - } - - GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &c_sig); - GNUNET_CRYPTO_hash (contract_str, strlen (contract_str) + 1, &h_contract_str); - - j_sig_enc = TALER_json_from_eddsa_sig (&contract.purpose, &c_sig); - - response = json_pack ("{s:o, s:o, s:o}", - "contract", root, - "sig", j_sig_enc, - "h_contract", - TALER_json_from_data ((void *) &h_contract_str, sizeof (struct GNUNET_HashCode))); + rh = &handlers[i]; + if ( (0 == strcasecmp (url, + rh->url)) && + ( (NULL == rh->method) || + (0 == strcasecmp (method, + rh->method)) ) ) + return rh->handler (rh, + connection, + con_cls, + upload_data, + upload_data_size); + } + return TMH_MHD_handler_static_response (&h404, + connection, + con_cls, + upload_data, + upload_data_size); - GNUNET_free (contract_str); +} - TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); - return MHD_YES; +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure + * @param keys information about the various keys used + * by the mint + */ +static void +keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) +{ + /* which kind of mint's keys a merchant should need? Sign + keys? It has already the mint's master key from the conf file */ + + /* HOT UPDATE: the merchants needs the denomination keys! + Because it wants to (firstly) verify the deposit confirmation + sent by the mint, and the signed blob depends (among the + other things) on the coin's deposit fee. That information + is never communicated by the wallet to the merchant. + Again, the merchant needs it because it wants to verify that + the wallet didn't exceede the limit imposed by the merchant + on the total deposit fee for a purchase */ - } - end: + return; - if (NULL != resp) - { - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - return MHD_YES; - if (!no_destroy) - MHD_destroy_response (resp); - } - else - { - - EXITIF (GNUNET_OK != failure_resp (connection, status)); - return MHD_YES; - - } - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); - return MHD_NO; } + /** * Shutdown task (magically invoked when the application is being * quit) @@ -823,25 +296,6 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) } /** - * Function called with information about who is auditing - * a particular mint and what key the mint is using. - * - * @param cls closure - * @param keys information about the various keys used - * by the mint - */ -static void -keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) -{ - /* which kind of mint's keys a merchant should need? Sign - keys? It has already the mint's master key from the conf file */ - return; - -} - - - -/** * Main function that will be run by the scheduler. * * @param cls closure diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c @@ -0,0 +1,74 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file merchant/backend/taler-merchant-httpd.c + * @brief HTTP serving layer mainly intended to communicate with the frontend + * @author Marcello Stanisci + */ + +#include "platform.h" +#include <microhttpd.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <curl/curl.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mint_service.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" +#include "merchant_db.h" +#include "merchant.h" +#include "taler_merchant_lib.h" + +/** + * Manage a contract request + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * + * @return MHD result code + */ +int +MH_handler_contract (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + /* 1. Generate preferred mint(s) array. + + a. Add the configuration lines to specify the mint's business + address. + b. Add this address to the 'locations' object, that should be + already present in the 'proposition' gotten from the frontend. + c. Point the 'address' field's label to the one added to 'locations' + + The 'mint' JSON layout is as follows: + + { "address": "address_label", + "url": "mint_base_url", + "master_pub": "base32 mint's master public key" } + + */ + + + + + +} diff --git a/src/backend/taler-merchant-httpd_obsolete.c b/src/backend/taler-merchant-httpd_obsolete.c @@ -0,0 +1,985 @@ +/* + This file is part of TALER + (C) 2014 Christian Grothoff (and other contributing authors) + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file merchant/backend/taler-merchant-httpd.c + * @brief HTTP serving layer mainly intended to communicate with the frontend + * @author Marcello Stanisci + */ + +#include "platform.h" +#include <microhttpd.h> +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <curl/curl.h> +#include <taler/taler_json_lib.h> +#include <taler/taler_mint_service.h> +#include "taler-mint-httpd_parsing.h" +#include "taler-mint-httpd_responses.h" +#include "merchant_db.h" +#include "merchant.h" +#include "taler_merchant_lib.h" + +extern struct MERCHANT_WIREFORMAT_Sepa * +TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg); + +/** + * Shorthand for exit jumps. + */ +#define EXITIF(cond) \ + do { \ + if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ + } while (0) + +/** + * Our hostname + */ +static char *hostname; + +/** + * The port we are running on + */ +static long long unsigned port; + +/** + * Merchant's private key + */ +struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; + +/** + * File holding the merchant's private key + */ +char *keyfile; + +/** + * The MHD Daemon + */ +static struct MHD_Daemon *mhd; + +/** + * Connection handle to the our database + */ +PGconn *db_conn; + +/** + * merchant's conf handle + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Shutdown task identifier + */ +static struct GNUNET_SCHEDULER_Task *shutdown_task; + +/** + * Our wireformat + */ +static struct MERCHANT_WIREFORMAT_Sepa *wire; + +/** + * Should we do a dry run where temporary tables are used for storing the data. + */ +static int dry; + +/** + * Global return code + */ +static int result; + +/** + * Mint context + */ +static struct TALER_MINT_Context *mctx; + +/** + * Context information of the mints we trust + */ +struct Mint +{ + /** + * Public key of this mint + */ + struct GNUNET_CRYPTO_EddsaPublicKey pubkey; + + /** + * Connection handle to this mint + */ + struct TALER_MINT_Handle *conn; +}; + +/** + * Hashmap to store the mint context information + */ +static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; + +/** + * Hashmap (with 'big entries') to make a mint's base URL + * to point to some mint-describing structure + */ +static struct GNUNET_CONTAINER_MultiHashMap *mints_hashmap; + +/** + * Mints' URL,port,key triples + */ +struct MERCHANT_MintInfo *mint_infos; + +/** + * The number of accepted mints + */ +unsigned int nmints; + +struct Mint_Response +{ + char *ptr; + size_t size; + +}; + + +/** + * Generate the 'hello world' response + * @param connection a MHD connection + * @param resp where to store the response for the calling function. + * Note that in its original implementation this parameter was preceeded + * by a '_'. Still not clear why. + * @return HTTP status code reflecting the operation outcome + * + */ +static unsigned int +generate_hello (struct MHD_Response **resp) +{ + + const char *hello = "Hello customer\n"; + unsigned int ret; + + *resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello, + MHD_RESPMEM_PERSISTENT); + ret = 200; + return ret; +} + +/** + * Return the given message to the other end of connection + * @msg (0-terminated) message to show + * @param connection a MHD connection + * @param resp where to store the response for the calling function + * @return HTTP status code reflecting the operation outcome + * + */ +static unsigned int +generate_message (struct MHD_Response **resp, const char *msg) +{ + + unsigned int ret; + + *resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg, + MHD_RESPMEM_MUST_FREE); + ret = 200; + return ret; +} + +/** + * Callback to pass to curl used to store a HTTP response + * in a custom memory location. + * See http://curl.haxx.se/libcurl/c/getinmemory.html for a + * detailed example + * + * @param contents the data gotten so far from the server + * @param size symbolic (arbitrarily chosen by libcurl) unit + * of bytes + * @param nmemb factor to multiply by @a size to get the real + * size of @a contents + * @param userdata a pointer to a memory location which remains + * the same across all the calls to this callback (i.e. it has + * to be grown at each invocation of this callback) + * @return number of written bytes + * See http://curl.haxx.se/libcurl/c/CURLOPT_WRITEFUNCTION.html + * for official documentation + * + */ +size_t +get_response_in_memory (char *contents, + size_t size, + size_t nmemb, + void *userdata) +{ + struct Mint_Response *mr; + size_t realsize; + + realsize = size * nmemb; + mr = userdata; + mr->ptr = realloc (mr->ptr, mr->size + realsize + 1); + + if (mr->ptr == NULL) { + printf ("Out of memory, could not get in memory mint's" + "response"); + return 0; + } + memcpy(&(mr->ptr[mr->size]), contents, realsize); + mr->size += realsize; + mr->ptr[mr->size] = 0; + + return realsize; + +} + +#ifdef PANIC_MGMT +/** + * Callback for catching serious error conditions from MHD. + * + * @param cls user specified value + * @param file where the error occured + * @param line where the error occured + * @param reason error detail, may be NULL + */ +static void +mhd_panic_cb (void *cls, + const char *file, + unsigned int line, + const char *reason) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "MHD panicked at %s:%u: %s", + file, line, reason); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} +#endif + +/** + * Manage a non 200 HTTP status. I.e. it shows a 'failure' page to + * the client + * @param connection the channel thorugh which send the message + * @status the HTTP status to examine + * @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error + */ +static int +failure_resp (struct MHD_Connection *connection, unsigned int status) +{ + static char page_MHD_HTTP_NOT_FOUND[]="\ +<!DOCTYPE html> \ +<html><title>Resource not found</title><body><center> \ +<h3>The resource you are looking for is not found.</h3> \ +</center></body></html>"; + static char page_MHD_HTTP_BAD_REQUEST[]="\ +<!DOCTYPE html> \ +<html><title>Bad request</title><body><center> \ +<h3>Malformed POSTed JSON.</h3> \ +</center></body></html>"; +static char page_MHD_HTTP_METHOD_NOT_ALLOWED[]="\ +<!DOCTYPE html> \ +<html><title>Method NOT allowed</title><body><center> \ +<h3>ONLY POSTs are allowed.</h3> \ +</center></body></html>"; + static char page_MHD_HTTP_INTERNAL_SERVER_ERROR[]="\ +<!DOCTYPE html> <html><title>Internal Server Error</title><body><center> \ +<h3>The server experienced an internal error and hence cannot serve your \ +request</h3></center></body></html>"; + struct MHD_Response *resp; + char *page; + size_t size; +#define PAGE(number) \ + do {page=page_ ## number; size=sizeof(page_ ## number)-1;} while(0) + + GNUNET_assert (MHD_HTTP_BAD_REQUEST <= status); + resp = NULL; + switch (status) + { + case MHD_HTTP_NOT_FOUND : + PAGE(MHD_HTTP_NOT_FOUND); + break; + case MHD_HTTP_BAD_REQUEST: + PAGE(MHD_HTTP_BAD_REQUEST); + break; + case MHD_HTTP_METHOD_NOT_ALLOWED: + PAGE(MHD_HTTP_METHOD_NOT_ALLOWED); + break; + default: + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + case MHD_HTTP_INTERNAL_SERVER_ERROR: + PAGE(MHD_HTTP_INTERNAL_SERVER_ERROR); + } +#undef PAGE + + EXITIF (NULL == (resp = MHD_create_response_from_buffer (size, + page, + MHD_RESPMEM_PERSISTENT))); + EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); + MHD_destroy_response (resp); + return GNUNET_OK; + + EXITIF_exit: + if (NULL != resp) + MHD_destroy_response (resp); + return GNUNET_SYSERR; +} + + +/** + * A client has requested the given url using the given method + * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, + * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback + * must call MHD callbacks to provide content to give back to the + * client and return an HTTP status code (i.e. #MHD_HTTP_OK, + * #MHD_HTTP_NOT_FOUND, etc.). + * + * @param cls argument given together with the function + * pointer when the handler was registered with MHD + * @param url the requested url + * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, + * #MHD_HTTP_METHOD_PUT, etc.) + * @param version the HTTP version string (i.e. + * #MHD_HTTP_VERSION_1_1) + * @param upload_data the data being uploaded (excluding HEADERS, + * for a POST that fits into memory and that is encoded + * with a supported encoding, the POST data will NOT be + * given in upload_data and is instead available as + * part of #MHD_get_connection_values; very large POST + * data *will* be made available incrementally in + * @a upload_data) + * @param upload_data_size set initially to the size of the + * @a upload_data provided; the method must update this + * value to the number of bytes NOT processed; + * @param con_cls pointer that the callback can set to some + * address and that will be preserved by MHD for future + * calls for this request; since the access handler may + * be called many times (i.e., for a PUT/POST operation + * with plenty of upload data) this allows the application + * to easily associate some request-specific state. + * If necessary, this state can be cleaned up in the + * global #MHD_RequestCompletedCallback (which + * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). + * Initially, `*con_cls` will be NULL. + * @return #MHD_YES if the connection was handled successfully, + * #MHD_NO if the socket must be closed due to a serios + * error while handling the request + */ +static int +url_handler (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **connection_cls) +{ + + /*printf ("%s\n", url);*/ + unsigned long status; + unsigned int no_destroy; + struct GNUNET_CRYPTO_EddsaSignature c_sig; + struct GNUNET_CRYPTO_EddsaSignature deposit_confirm_sig; + struct GNUNET_CRYPTO_EddsaPublicKey pub; + #ifdef OBSOLETE + struct ContractNBO contract; + #else + struct Contract contract; + #endif + struct MHD_Response *resp; + json_t *root; + json_t *j_sig_enc; + json_t *j_h_contract; + json_t *j_tmp; + json_t *eddsa_pub_enc; + json_t *response; + json_t *j_mints; + json_t *j_mint; + json_t *j_wire; + int cnt; /* loop counter */ + char *deposit_body; + json_t *j_contract_add; + struct GNUNET_TIME_Absolute now; + struct GNUNET_TIME_Absolute expiry; + struct GNUNET_TIME_Absolute edate; + struct GNUNET_TIME_Absolute refund; + struct GNUNET_HashCode h_json_wire; + json_t *j_h_json_wire; + struct curl_slist *slist; + char *contract_str; + struct GNUNET_HashCode h_contract_str; + struct MERCHANT_contract_handle ch; + struct TALER_MintPublicKeyP mint_pub; + uint64_t nounce; + + CURL *curl; + CURLcode curl_res; + + uint32_t res = GNUNET_SYSERR; + + #define URL_HELLO "/hello" + #define URL_CONTRACT "/contract" + #define URL_PAY "/pay" + no_destroy = 0; + resp = NULL; + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + + if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + status = generate_hello (&resp); + else + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + } + } + + if (0 == strncasecmp (url, URL_PAY, sizeof (URL_PAY))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + goto end; + + } + else + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_BAD_REQUEST; + goto end; + } + + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + /* Firstly, check if the wallet is paying against an approved + mint */ + json_t *j_chosen_mint; + j_chosen_mint = json_object_get (root, "mint"); + struct GNUNET_HashCode hash_key; + char *chosen_mint; + + chosen_mint = json_string_value (j_chosen_mint); + GNUNET_CRYPTO_hash (chosen_mint, strlen (chosen_mint), &hash_key); + + if (NULL == + GNUNET_CONTAINER_multihashmap_get (mints_hashmap, &hash_key)) + { + printf ("Untrusted mint\n"); + status = MHD_HTTP_FORBIDDEN; + goto end; + + } + + /* NOTE: from now on, the mint's base URL is pointed by 'chosen_mint' */ + + /* The merchant will only add its 'wire' object to the JSON + it got from the wallet */ + + /* Get this dep. perm.'s H_contract */ + + if (NULL == (j_h_contract = json_object_get (root, "H_contract"))) + { + printf ("H_contract field missing\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + TALER_json_to_data (j_h_contract, &h_contract_str, sizeof (struct GNUNET_HashCode)); + + nounce = 0; + edate.abs_value_us = 0; + + if (GNUNET_SYSERR == + MERCHANT_DB_get_contract_values (db_conn, + &h_contract_str, + &nounce, + &edate)) + { + printf ("not existing contract\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* Reproducing the wire object */ + if (NULL == (j_wire = MERCHANT_get_wire_json (wire, + nounce, + edate))) + + { + printf ("wire object not reproduced\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + if (-1 == json_object_set (root, "wire", j_wire)) + { + printf ("depperm not augmented\n"); + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* POST to mint's "/deposit" */ + curl = curl_easy_init (); + + struct Mint_Response mr; + mr.ptr = malloc(1); + mr.size = 0; + + if (curl) + { + + char deposit_url[strlen (chosen_mint) + strlen ("http://") + strlen ("/deposit") + 1]; + sprintf (deposit_url, "http://%s/deposit", chosen_mint); + slist = curl_slist_append (slist, "Content-type: application/json"); + curl_easy_setopt (curl, CURLOPT_HTTPHEADER, slist); + + curl_easy_setopt (curl, CURLOPT_URL, deposit_url); + curl_easy_setopt (curl, CURLOPT_WRITEFUNCTION, get_response_in_memory); + curl_easy_setopt (curl, CURLOPT_WRITEDATA, (void *) &mr); + + /* NOTE: hopefully, this string won't need any URL-encoding, since as for the + Jansson specs, any space and-or newline are not in place using JSON_COMPACT + flag */ + deposit_body = json_dumps (root, JSON_COMPACT | JSON_PRESERVE_ORDER); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, deposit_body); + + curl_res = curl_easy_perform (curl); + + curl_slist_free_all(slist); + if(curl_res != CURLE_OK) + { + printf ("deposit not sent\n"); + goto end; + } + else + printf ("\ndeposit request issued\n"); + + curl_easy_cleanup(curl); + + curl_easy_getinfo (curl, CURLINFO_RESPONSE_CODE, &status); + + /* If the request was successful, the deposit confirmation + has to be verified*/ + + if (MHD_HTTP_OK != result) + /* Jump here to error mgmt, see issue #3800 (TODO) */ + + if (GNUNET_SYSERR == + MERCHANT_DB_get_contract_handle (db_conn, &h_contract_str, &ch)) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + root = json_loads (mr.ptr, 0, NULL); + j_tmp = json_object_get (root, "sig"); + TALER_json_to_data (j_tmp, + &deposit_confirm_sig, + sizeof (struct GNUNET_CRYPTO_EddsaSignature)); + j_tmp = json_object_get (root, "pub"); + + TALER_json_to_data (j_tmp, + &mint_pub.eddsa_pub, + sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + + GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); + + + /* The following check could be done once the merchant is able to + retrieve the deposit fee for each coin it gets a payment with. + That happens because the signature made on a deposit confirmation + uses, among its values of interest, the subtraction of the deposit + fee from the used coin. That fee is never communicated by the wallet + to the merchant, so the only way for the merchant to get this + information is to fetch all the mint's keys, namely it needs to + invoke "/keys", and store what gotten in its DB */ + + #ifdef DENOMMGMT + if (GNUNET_NO == + MERCHANT_verify_confirmation (&h_contract_str, + &h_json_wire, + ch.timestamp, + ch.refund_deadline, + ch.contract_id, + amount_minus_fee, /* MISSING */ + coin_pub, /* MISSING */ + &pub, + &deposit_confirm_sig, + &mint_pub)) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + + } + + #endif + + /* Place here the successful message to issue to the frontend */ + status = MHD_HTTP_OK; + generate_message (&resp, "fullfillment page here"); + GNUNET_free (mr.ptr); + + } + + + } + + /* + * To be called by the frontend passing the contract with some "holes" + * which will be completed, stored in DB, signed, and returned + * + */ + + if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) + { + if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) + { + status = MHD_HTTP_METHOD_NOT_ALLOWED; + goto end; + + } + else + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_BAD_REQUEST; + goto end; + } + + + /* the POST's body has to be fetched furthermore */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + + j_mints = json_array (); + for (cnt = 0; cnt < nmints; cnt++) + { + j_mint = json_pack ("{s:s}", + mint_infos[cnt].hostname, + GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey)); + json_array_append_new (j_mints, j_mint); + + } + + /* timestamp */ + now = GNUNET_TIME_absolute_get (); + /* expiry */ + expiry = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + /* edate, note: this value must be generated now (and not when the + wallet sends back a deposit permission because the hashed 'wire' object, + which carries this values in it, has to be included in the signed bundle + by the wallet) */ + edate = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + refund = GNUNET_TIME_absolute_add (now, GNUNET_TIME_UNIT_WEEKS); + + TALER_round_abs_time (&now); + TALER_round_abs_time (&expiry); + TALER_round_abs_time (&edate); + TALER_round_abs_time (&refund); + + /* getting the SEPA-aware JSON */ + /* nounce for hashing the wire object */ + nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); + + /* get wire object */ + + if (NULL == (j_wire = MERCHANT_get_wire_json (wire, + nounce, + edate))) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + + /* hash wire objcet */ + if (GNUNET_SYSERR == TALER_hash_json (j_wire, &h_json_wire)) + goto end; + + j_h_json_wire = TALER_json_from_data ((void *) &h_json_wire, sizeof (struct GNUNET_HashCode)); + + GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub); + eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub)); + + if (NULL == (j_contract_add = json_pack ("{s:s, s:s, s:o, s:o, s:o}", + "merchant_pub", json_string_value (eddsa_pub_enc), + "H_wire", json_string_value (j_h_json_wire), + "timestamp", TALER_json_from_abs (now), + "refund", TALER_json_from_abs (refund), + "mints", j_mints))) + { + printf ("BAD contract enhancement\n"); + goto end; + } + + /* melt to what received from the wallet */ + if (-1 == json_object_update (root, j_contract_add)) + { + printf ("depperm response not built\n"); + goto end; + } + + res = MERCHANT_handle_contract (root, + db_conn, + &contract, + now, + expiry, + edate, + refund, + &contract_str, + nounce); + if (GNUNET_SYSERR == res) + { + status = MHD_HTTP_INTERNAL_SERVER_ERROR; + goto end; + } + if (GNUNET_NO == res) + { + status = MHD_HTTP_METHOD_NOT_ACCEPTABLE; + goto end; + } + + GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &c_sig); + GNUNET_CRYPTO_hash (contract_str, strlen (contract_str) + 1, &h_contract_str); + + j_sig_enc = TALER_json_from_eddsa_sig (&contract.purpose, &c_sig); + + response = json_pack ("{s:o, s:o, s:o}", + "contract", root, + "sig", j_sig_enc, + "h_contract", + TALER_json_from_data ((void *) &h_contract_str, sizeof (struct GNUNET_HashCode))); + + GNUNET_free (contract_str); + + TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK); + return MHD_YES; + + } + + end: + + if (NULL != resp) + { + EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); + return MHD_YES; + if (!no_destroy) + MHD_destroy_response (resp); + } + else + { + + EXITIF (GNUNET_OK != failure_resp (connection, status)); + return MHD_YES; + + } + + EXITIF_exit: + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + return MHD_NO; +} + +/** + * Shutdown task (magically invoked when the application is being + * quit) + * + * @param cls NULL + * @param tc scheduler task context + */ +static void +do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) +{ + + if (NULL != mhd) + { + MHD_stop_daemon (mhd); + mhd = NULL; + } + + if (NULL != db_conn) + { + MERCHANT_DB_disconnect (db_conn); + db_conn = NULL; + } + if (keyfile != NULL) + GNUNET_free (privkey); + + +} + +/** + * Function called with information about who is auditing + * a particular mint and what key the mint is using. + * + * @param cls closure + * @param keys information about the various keys used + * by the mint + */ +static void +keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) +{ + /* which kind of mint's keys a merchant should need? Sign + keys? It has already the mint's master key from the conf file */ + + /* HOT UPDATE: the merchants needs the denomination keys! + Because it wants to (firstly) verify the deposit confirmation + sent by the mint, and the signed blob depends (among the + other things) on the coin's deposit fee. That information + is never communicated by the wallet to the merchant. + Again, the merchant needs it because it wants to verify that + the wallet didn't exceede the limit imposed by the merchant + on the total deposit fee for a purchase */ + + + return; + +} + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param config configuration + */ +void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *config) +{ + + unsigned int cnt; + void *keys_mgmt_cls; + + keys_mgmt_cls = NULL; + mint_infos = NULL; + keyfile = NULL; + result = GNUNET_SYSERR; + shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, + &do_shutdown, NULL); + EXITIF (GNUNET_SYSERR == (nmints = TALER_MERCHANT_parse_mints (config, + &mint_infos))); + EXITIF (NULL == (wire = TALER_MERCHANT_parse_wireformat_sepa (config))); + EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (config, + "merchant", + "KEYFILE", + &keyfile)); + EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); + EXITIF (NULL == (db_conn = MERCHANT_DB_connect (config))); + EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_YES)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_number (config, + "merchant", + "port", + &port)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (config, + "merchant", + "hostname", + &hostname)); + + EXITIF (NULL == (mctx = TALER_MINT_init ())); + /* Still not used */ + EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); + /* Used when the wallet points out which mint it want to deal with. + That indication is made through the mint's base URL, which will be + the hash-key for this table */ + EXITIF (NULL == (mints_hashmap = GNUNET_CONTAINER_multihashmap_create (nmints, GNUNET_NO))); + + for (cnt = 0; cnt < nmints; cnt++) + { + struct Mint *mint; + struct GNUNET_HashCode mint_key; + + mint = GNUNET_new (struct Mint); + mint->pubkey = mint_infos[cnt].pubkey; + /* port this to the new API */ + mint->conn = TALER_MINT_connect (mctx, + mint_infos[cnt].hostname, + &keys_mgmt_cb, + keys_mgmt_cls); /*<- safe?segfault friendly?*/ + + /* NOTE: the keys mgmt callback should roughly do what the following lines do */ + EXITIF (NULL == mint->conn); + + EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put + (mints_map, + (struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey, + mint, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); + + /* 1 create hash key + 2 create big entry + 3 put + */ + GNUNET_CRYPTO_hash (mint_infos[cnt].hostname, + strlen (mint_infos[cnt].hostname), + &mint_key); + GNUNET_CONTAINER_multihashmap_put (mints_hashmap, + &mint_key, + &mint_infos[cnt], + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); + } + + mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY, + port, + NULL, NULL, + &url_handler, NULL, + MHD_OPTION_END); + + EXITIF (NULL == mhd); + + /* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that + mandatory ? */ + result = GNUNET_OK; + + EXITIF_exit: + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); + GNUNET_free_non_null (keyfile); + if (GNUNET_OK != result) + GNUNET_SCHEDULER_shutdown (); + +} + +/** + * The main function of the serve tool + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + {'t', "temp", NULL, + gettext_noop ("Use temporary database tables"), GNUNET_NO, + &GNUNET_GETOPT_set_one, &dry}, + GNUNET_GETOPT_OPTION_END + }; + + + if (GNUNET_OK != + GNUNET_PROGRAM_run (argc, argv, + "taler-merchant-serve", + "Serve merchant's HTTP interface", + options, &run, NULL)) + return 3; + return (GNUNET_OK == result) ? 0 : 1; + +} diff --git a/src/backend/taler-mint-httpd.h b/src/backend/taler-mint-httpd.h @@ -0,0 +1,85 @@ +/* + This file is part of TALER + Copyright (C) 2014, 2015 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-mint-httpd.h + * @brief Global declarations for the mint + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + * + * FIXME: Consider which of these need to really be globals... + */ +#ifndef TALER_MINT_HTTPD_H +#define TALER_MINT_HTTPD_H + +#include <microhttpd.h> + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TMH_RequestHandler +{ + + /** + * URL the handler is for. + */ + const char *url; + + /** + * Method the handler is for, NULL for "all". + */ + const char *method; + + /** + * Mime type to use in reply (hint, can be NULL). + */ + const char *mime_type; + + /** + * Raw data for the @e handler + */ + const void *data; + + /** + * Number of bytes in @e data, 0 for 0-terminated. + */ + size_t data_size; + + /** + * Function to call to handle the request. + * + * @param rh this struct + * @param mime_type the @e mime_type for the reply (hint, can be NULL) + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ + int (*handler)(struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + /** + * Default response code. + */ + int response_code; +}; + + +#endif diff --git a/src/backend/taler-mint-httpd_mhd.c b/src/backend/taler-mint-httpd_mhd.c @@ -0,0 +1,154 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.c + * @brief helpers for MHD interaction; these are TALER_MINT_handler_ functions + * that generate simple MHD replies that do not require any real operations + * to be performed (error handling, static pages, etc.) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler-mint-httpd_responses.h" +#include "taler-mint-httpd.h" +#include "taler-mint-httpd_mhd.h" +#include "taler-mint-httpd_responses.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + struct MHD_Response *response; + int ret; + + if (0 == rh->data_size) + rh->data_size = strlen ((const char *) rh->data); + response = MHD_create_response_from_buffer (rh->data_size, + (void *) rh->data, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + const char *agpl = + "This server is licensed under the Affero GPL. You will now be redirected to the source code."; + struct MHD_Response *response; + int ret; + + response = MHD_create_response_from_buffer (strlen (agpl), + (void *) agpl, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TMH_RESPONSE_add_global_headers (response); + if (NULL != rh->mime_type) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + rh->mime_type); + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "http://www.git.taler.net/?p=mint.git"); + ret = MHD_queue_response (connection, + rh->response_code, + response); + MHD_destroy_response (response); + return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size) +{ + return TMH_RESPONSE_reply_json_pack (connection, + rh->response_code, + "{s:s}", + "error", + rh->data); +} + + +/* end of taler-mint-httpd_mhd.c */ diff --git a/src/backend/taler-mint-httpd_mhd.h b/src/backend/taler-mint-httpd_mhd.h @@ -0,0 +1,111 @@ +/* + This file is part of TALER + Copyright (C) 2014 GNUnet e.V. + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-mint-httpd_mhd.h + * @brief helpers for MHD interaction, used to generate simple responses + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_MINT_HTTPD_MHD_H +#define TALER_MINT_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-mint-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_static_response (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_agpl_redirect (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TMH_MHD_helper_send_json_pack (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void *connection_cls, + int response_code, + int do_cache, + const char *fmt, + ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TMH_MHD_handler_send_json_pack_error (struct TMH_RequestHandler *rh, + struct MHD_Connection *connection, + void **connection_cls, + const char *upload_data, + size_t *upload_data_size); + + +#endif diff --git a/src/backend/taler-mint-httpd_responses.c b/src/backend/taler-mint-httpd_responses.c @@ -165,4 +165,25 @@ TMH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) "invalid json"); } +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response) +{ + int TMH_mint_connection_close; + TMH_mint_connection_close = 0; + + /* this test is taken verbatim from the mint's code, + so there is no particular need to do that for a merchant */ + if (TMH_mint_connection_close) + (void) MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "close"); +} + /* end of taler-mint-httpd_responses.c */ diff --git a/src/backend/taler-mint-httpd_responses.h b/src/backend/taler-mint-httpd_responses.h @@ -89,4 +89,14 @@ TMH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, int TMH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TMH_RESPONSE_add_global_headers (struct MHD_Response *response); + #endif diff --git a/src/tests/test_contract.c b/src/tests/test_contract.c @@ -23,7 +23,7 @@ #include "platform.h" #include <jansson.h> #include <gnunet/gnunet_util_lib.h> -#include <taler/taler_json_lib.h> +#include <taler/taler_util.h> #include "merchant.h" #include "merchant_db.h" #include <taler_merchant_lib.h> @@ -80,7 +80,6 @@ run (void *cls, char *const *args, const char *cfgfile, { json_t *j_fake_contract; - json_t *j_wire; json_t *j_details; json_t *j_mints; json_t *j_item; @@ -98,7 +97,6 @@ run (void *cls, char *const *args, const char *cfgfile, json_t *j_merchant_zipcode; json_t *j_lnames; json_t *j_deldate; - char *contract_tmp_str; char *desc; struct TALER_Amount amount; int64_t t_id; @@ -113,7 +111,7 @@ run (void *cls, char *const *args, const char *cfgfile, uint64_t nounce; struct GNUNET_HashCode h_contract_str; char *aa; - char *fancy_time; + const char *fancy_time; uint32_t ret; db_conn = NULL; @@ -278,8 +276,6 @@ run (void *cls, char *const *args, const char *cfgfile, nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - j_wire = MERCHANT_get_wire_json (wire, nounce, now); - ret = MERCHANT_handle_contract (j_fake_contract, db_conn, &contract,