diff options
author | Marcello Stanisci <marcello.stanisci@inria.fr> | 2015-10-28 17:40:30 +0100 |
---|---|---|
committer | Marcello Stanisci <marcello.stanisci@inria.fr> | 2015-10-28 17:40:30 +0100 |
commit | 9cec564ddc9bec9ea34ed7b0ca308800651cabd6 (patch) | |
tree | 01cfe65dd9f454d9b6b92abc93a070d17d7b1103 | |
parent | 2fbdccc8dc18157e2df7486e4ecedee343c81d8d (diff) | |
download | merchant-9cec564ddc9bec9ea34ed7b0ca308800651cabd6.tar.gz merchant-9cec564ddc9bec9ea34ed7b0ca308800651cabd6.tar.bz2 merchant-9cec564ddc9bec9ea34ed7b0ca308800651cabd6.zip |
Adding main behaviour to contract mgmt, plus importing additional
features from the mint code base
-rw-r--r-- | src/backend-lib/merchant_api_contract.c | 16 | ||||
-rw-r--r-- | src/backend-lib/taler_merchant_contract_lib.h | 6 | ||||
-rw-r--r-- | src/backend/merchant.c | 21 | ||||
-rw-r--r-- | src/backend/merchant.conf | 12 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 92 | ||||
-rw-r--r-- | src/backend/taler-merchant-httpd_contract.c | 103 | ||||
-rw-r--r-- | src/backend/taler-mint-httpd_parsing.c | 783 | ||||
-rw-r--r-- | src/frontend/generate_taler_contract.php | 1 | ||||
-rw-r--r-- | src/include/merchant.h | 11 |
9 files changed, 963 insertions, 82 deletions
diff --git a/src/backend-lib/merchant_api_contract.c b/src/backend-lib/merchant_api_contract.c index 42e1e064..239e29fb 100644 --- a/src/backend-lib/merchant_api_contract.c +++ b/src/backend-lib/merchant_api_contract.c @@ -32,32 +32,28 @@ * Take the global wire details and return a JSON containing them, * compliantly with the Taler's API. * @param wire the merchant's wire details - * @param nounce the nounce for hashing the wire details with + * @param salt the nounce for hashing the wire details with * @param edate when the beneficiary wants this transfer to take place * @return JSON representation of the wire details, NULL upon errors */ json_t * MERCHANT_get_wire_json (const struct MERCHANT_WIREFORMAT_Sepa *wire, - uint64_t nounce, - const struct GNUNET_TIME_Absolute edate) + uint64_t salt) { json_t *root; - json_t *j_edate; - json_t *j_nounce; + json_t *j_salt; - j_nounce = json_integer (nounce); - j_edate = TALER_json_from_abs (edate); + j_nounce = json_integer (salt); - if (NULL == (root = json_pack ("{s:s, s:s, s:s, s:s, s:o, s:I}", + if (NULL == (root = json_pack ("{s:s, s:s, s:s, s:s, s:I}", "type", "SEPA", "IBAN", wire->iban, "name", wire->name, "bic", wire->bic, - "edate", j_edate, - "r", json_integer_value (j_nounce)))) + "r", json_integer_value (j_salt)))) return NULL; return root; diff --git a/src/backend-lib/taler_merchant_contract_lib.h b/src/backend-lib/taler_merchant_contract_lib.h index ca799ff2..1d2d0eba 100644 --- a/src/backend-lib/taler_merchant_contract_lib.h +++ b/src/backend-lib/taler_merchant_contract_lib.h @@ -20,15 +20,13 @@ struct Contract * Take the global wire details and return a JSON containing them, * compliantly with the Taler's API. * @param wire the merchant's wire details - * @param nounce the nounce for hashing the wire details with - * @param edate when the beneficiary wants this transfer to take place + * @param salt the nounce for hashing the wire details with * @return JSON representation of the wire details, NULL upon errors */ json_t * MERCHANT_get_wire_json (const struct MERCHANT_WIREFORMAT_Sepa *wire, - uint64_t nounce, - struct GNUNET_TIME_Absolute edate); + uint64_t salt); /** * Take from the frontend the (partly) generated contract and fill diff --git a/src/backend/merchant.c b/src/backend/merchant.c index 58005394..4fa66f16 100644 --- a/src/backend/merchant.c +++ b/src/backend/merchant.c @@ -48,10 +48,8 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, char *token_nf; /* do no free (nf) */ char *mint_section; char *mint_hostname; - char *mint_pubkey_enc; struct MERCHANT_Mint *r_mints; struct MERCHANT_Mint mint; - unsigned long long mint_port; unsigned int cnt; int OK; @@ -60,7 +58,6 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, token_nf = NULL; mint_section = NULL; mint_hostname = NULL; - mint_pubkey_enc = NULL; r_mints = NULL; cnt = 0; EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, @@ -78,26 +75,9 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, mint_section, "HOSTNAME", &mint_hostname)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_number (cfg, - mint_section, - "PORT", - &mint_port)); - EXITIF (GNUNET_OK != - GNUNET_CONFIGURATION_get_value_string (cfg, - mint_section, - "PUBKEY", - &mint_pubkey_enc)); - EXITIF (GNUNET_OK != - GNUNET_CRYPTO_eddsa_public_key_from_string (mint_pubkey_enc, - strlen (mint_pubkey_enc), - &mint.pubkey)); mint.hostname = mint_hostname; - mint.port = (uint16_t) mint_port; GNUNET_array_append (r_mints, cnt, mint); mint_hostname = NULL; - GNUNET_free (mint_pubkey_enc); - mint_pubkey_enc = NULL; GNUNET_free (mint_section); mint_section = NULL; } @@ -107,7 +87,6 @@ TALER_MERCHANT_parse_mints (const struct GNUNET_CONFIGURATION_Handle *cfg, GNUNET_free_non_null (mints_str); GNUNET_free_non_null (mint_section); GNUNET_free_non_null (mint_hostname); - GNUNET_free_non_null (mint_pubkey_enc); if (!OK) { GNUNET_free_non_null (r_mints); diff --git a/src/backend/merchant.conf b/src/backend/merchant.conf index 851a6062..f24611a9 100644 --- a/src/backend/merchant.conf +++ b/src/backend/merchant.conf @@ -6,15 +6,9 @@ KEYFILE = merchant.priv [mint-taler] HOSTNAME = demo.taler.net -PORT = 80 -PUBKEY = Q1WVGRGC1F4W7RYC6M23AEGFEXQEHQ730K3GG0B67VPHQSRR75H0 -COUNTRY = France -CITY = Rennes -REGION = Bretagne -PROVINCE = Ile-et-Vilaine -ZIP_CODE = 35510 -STREET = Avenue du General Leclerc -STREET_NUMBER = 21 + +[mint-localmint] +HOSTNAME = localmint [merchant-db] CONFIG = postgres:///talerdemo diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 91a6fdf8..8da6ce21 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -62,6 +62,11 @@ char *keyfile; static struct TALER_MINT_Context *mctx; /** + * To make 'TMH_PARSE_navigate_json ()' compile + */ +char *TMH_mint_currency_string; + +/** * Mints' URL,port,key triples */ struct MERCHANT_Mint *mints; @@ -79,7 +84,12 @@ static struct GNUNET_SCHEDULER_Task *poller_task; /** * Our wireformat */ -static struct MERCHANT_WIREFORMAT_Sepa *wire; +struct MERCHANT_WIREFORMAT_Sepa *wire; + +/** + * Salt used to hash the wire object + */ +long long salt; /** * The number of accepted mints @@ -102,22 +112,6 @@ static int result; PGconn *db_conn; /** - * 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; -}; - -/** * The MHD Daemon */ static struct MHD_Daemon *mhd; @@ -238,16 +232,20 @@ url_handler (void *cls, * Function called with information about who is auditing * a particular mint and what key the mint is using. * - * @param cls closure + * @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) { - /* 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 need the denomination keys! Because it wants to (firstly) verify the deposit confirmation sent by the mint, and the signed blob depends (among the @@ -256,7 +254,8 @@ keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys) 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 */ - printf ("Unregistering connection currently at %p\n", cls); + + ((struct MERCHANT_Mint *) cls)->pending = 0; } @@ -270,7 +269,14 @@ 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); @@ -290,8 +296,6 @@ do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) } if (NULL != keyfile) GNUNET_free (privkey); - - } /** @@ -351,6 +355,31 @@ context_task (void *cls, GNUNET_NETWORK_fdset_destroy (ws); } +/** + * Function called whenever MHD is done with a request. If the + * request was a POST, we may have stored a `struct Buffer *` in the + * @a con_cls that might still need to be cleaned up. Call the + * respective function to free the memory. + * + * @param cls client-defined closure + * @param connection connection handle + * @param con_cls value as set by the last call to + * the #MHD_AccessHandlerCallback + * @param toe reason for request termination + * @see #MHD_OPTION_NOTIFY_COMPLETED + * @ingroup request + */ +static void +handle_mhd_completion_callback (void *cls, + struct MHD_Connection *connection, + void **con_cls, + enum MHD_RequestTerminationCode toe) +{ + if (NULL == *con_cls) + return; + TMH_PARSE_post_cleanup_callback (*con_cls); + *con_cls = NULL; +} /** * Main function that will be run by the scheduler. @@ -367,9 +396,7 @@ run (void *cls, char *const *args, const char *cfgfile, { unsigned int cnt; - void *keys_mgmt_cls; - keys_mgmt_cls = NULL; mints = NULL; keyfile = NULL; result = GNUNET_SYSERR; @@ -406,14 +433,22 @@ run (void *cls, char *const *args, const char *cfgfile, "merchant", "hostname", &hostname)); + EXITIF (GNUNET_SYSERR == + GNUNET_CONFIGURATION_get_value_string (config, + "merchant", + "currency", + &TMH_mint_currency_string)); + + salt = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, + UINT64_MAX); EXITIF (NULL == (mctx = TALER_MINT_init ())); for (cnt = 0; cnt < nmints; cnt++) { + mints[cnt].pending = 1; mints[cnt].conn = TALER_MINT_connect (mctx, mints[cnt].hostname, &keys_mgmt_cb, &mints[cnt]); - printf ("trying to, %p\n", &mints[cnt]); EXITIF (NULL == mints[cnt].conn); poller_task = GNUNET_SCHEDULER_add_now (&context_task, mctx); @@ -423,6 +458,9 @@ run (void *cls, char *const *args, const char *cfgfile, port, NULL, NULL, &url_handler, NULL, + MHD_OPTION_NOTIFY_COMPLETED, + &handle_mhd_completion_callback, NULL, + MHD_OPTION_END); EXITIF (NULL == mhd); diff --git a/src/backend/taler-merchant-httpd_contract.c b/src/backend/taler-merchant-httpd_contract.c index 143746b6..04726839 100644 --- a/src/backend/taler-merchant-httpd_contract.c +++ b/src/backend/taler-merchant-httpd_contract.c @@ -35,6 +35,13 @@ #include "merchant.h" #include "taler_merchant_lib.h" +extern struct MERCHANT_Mint *mints; +extern struct GNUNET_CRYPTO_EddsaPrivateKey privkey; +extern const struct MERCHANT_WIREFORMAT_Sepa *wire; +extern unsigned int nmints; +extern PGconn *db_conn; +extern long long salt; + /** * Manage a contract request * @@ -53,18 +60,108 @@ MH_handler_contract (struct TMH_RequestHandler *rh, const char *upload_data, size_t *upload_data_size) { + json_t *root; + json_t *trusted_mints; + json_t *mint; + json_t *j_wire; + const struct TALER_MINT_Keys *keys; + int res; + int cnt; + uint64_t trans_id; + struct TALER_Amount amount; + struct GNUNET_TIME_Absolute timestamp; + struct GNUNET_TIME_Absolute refund; + struct GNUNET_TIME_Absolute expiry; + struct GNUNET_TIME_Absolute edate; + struct GNUNET_HashCode h_wire; + struct GNUNET_CRYPTO_EddsaPublicKey pubkey; + struct TMH_PARSE_FieldSpecification spec[] = + { + TMH_PARSE_member_time_abs ("timestamp", ×tamp), + TMH_PARSE_member_time_abs ("refund_deadline", &refund), + TMH_PARSE_member_time_abs ("expiry", &expiry), + TMH_PARSE_member_amount ("amount", &amount), + TMH_PARSE_member_uint64 ("trans_id", &trans_id), + TMH_PARSE_MEMBER_END + }; + + res = TMH_PARSE_post_json (connection, + connection_cls, + upload_data, + upload_data_size, + &root); + + if (GNUNET_SYSERR == res) + return MHD_NO; + /* the POST's body has to be further fetched */ + if ((GNUNET_NO == res) || (NULL == root)) + return MHD_YES; + /* 1. Generate preferred mint(s) array. The 'mint' JSON layout is as follows: { "url": "mint_base_url", "master_pub": "base32 mint's master public key" } - */ + 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); + } + } + + /* Return badly if no mints are trusted (or ready). 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; + + json_object_set_new (root, "mints", trusted_mints); - /* To suppress compilation warning */ - return 0; + res = TMH_PARSE_json_data (connection, root, spec); + if (GNUNET_SYSERR == res) + return MHD_NO; /* hard failure */ + if (GNUNET_NO == res) + return MHD_YES; /* failure */ + + /* (indicative) deadline which the merchant wants its wire tranfer + before */ + edate = GNUNET_TIME_absolute_add (timestamp, GNUNET_TIME_UNIT_WEEKS); + TALER_round_abs_time (&edate); + if (NULL == (j_wire = MERCHANT_get_wire_json (wire, + salt))) + return MHD_NO; + + /* hash wire objcet */ + if (GNUNET_SYSERR == + TALER_hash_json (j_wire, &h_wire)) + return MHD_NO; + + json_object_set_new (root, + "H_wire", + TALER_json_from_data (&h_wire, sizeof (h_wire))); + + GNUNET_CRYPTO_eddsa_key_get_public (&privkey, &pubkey); + json_object_set_new (root, + "merchant_pub", + TALER_json_from_data (&pubkey, sizeof (pubkey))); + + /* store relevant values for this contract, in DB */ + //res = MERCHANT_handle_contract (); + } diff --git a/src/backend/taler-mint-httpd_parsing.c b/src/backend/taler-mint-httpd_parsing.c index 8d9e3f9f..a4dc43ab 100644 --- a/src/backend/taler-mint-httpd_parsing.c +++ b/src/backend/taler-mint-httpd_parsing.c @@ -27,6 +27,13 @@ #include "taler-mint-httpd_parsing.h" #include "taler-mint-httpd_responses.h" +/* Although the following declaration isn't in any case useful + to a merchant's activity, it's needed here to make the function + 'TMH_PARSE_nagivate_json ()' compile fine; so its value will be + kept on some merchant's accepted currency. For multi currencies + merchants, that of course would require a patch */ + +extern char *TMH_mint_currency_string; /** * Initial size for POST request buffer. */ @@ -139,6 +146,107 @@ buffer_append (struct Buffer *buf, } /** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + * #TMH_PARSE_post_json(), to be cleaned up + */ +void +TMH_PARSE_post_cleanup_callback (void *con_cls) +{ + struct Buffer *r = con_cls; + + if (NULL != r) + { + buffer_deinit (r); + GNUNET_free (r); + } +} + +/** + * Release all memory allocated for the variable-size fields in + * the parser specification. + * + * @param spec specification to free + * @param spec_len number of items in @a spec to look at + */ +static void +release_data (struct TMH_PARSE_FieldSpecification *spec, + unsigned int spec_len) +{ + unsigned int i; + + for (i=0; i < spec_len; i++) + { + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return; + case TMH_PARSE_JNC_RET_DATA: + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + if (NULL != spec[i].destination) + { + GNUNET_free (* (void**) spec[i].destination); + *(void**) spec[i].destination = NULL; + *spec[i].destination_size_out = 0; + } + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + json_t *json; + + json = *(json_t **) spec[i].destination; + if (NULL != json) + { + json_decref (json); + *(json_t**) spec[i].destination = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *pk; + + pk = spec[i].destination; + if (NULL != pk->rsa_public_key) + { + GNUNET_CRYPTO_rsa_public_key_free (pk->rsa_public_key); + pk->rsa_public_key = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *sig; + + sig = spec[i].destination; + if (NULL != sig->rsa_signature) + { + GNUNET_CRYPTO_rsa_signature_free (sig->rsa_signature); + sig->rsa_signature = NULL; + } + } + break; + case TMH_PARSE_JNC_RET_AMOUNT: + memset (spec[i].destination, + 0, + sizeof (struct TALER_Amount)); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + break; + case TMH_PARSE_JNC_RET_UINT64: + break; + } + } +} + +/** * Process a POST request containing a JSON object. This function * realizes an MHD POST processor that will (incrementally) process * JSON data uploaded to the HTTP server. It will store the required @@ -239,4 +347,679 @@ TMH_PARSE_post_json (struct MHD_Connection *connection, return GNUNET_YES; } + +/** + * Generate line in parser specification for 64-bit integer + * given as an integer in JSON. + * + * @param field name of the field + * @param[out] u64 integer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_uint64 (const char *field, + uint64_t *u64) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, (void *) u64, sizeof (uint64_t), NULL, TMH_PARSE_JNC_RET_UINT64, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for JSON object value. + * + * @param field name of the field + * @param[out] jsonp address of pointer to JSON to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_object (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_OBJECT }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for JSON array value. + * + * @param field name of the field + * @param[out] jsonp address of JSON pointer to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_array (const char *field, + json_t **jsonp) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, jsonp, 0, NULL, TMH_PARSE_JNC_RET_TYPED_JSON, JSON_ARRAY }; + *jsonp = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an absolute time. + * + * @param field name of the field + * @param[out] atime time to initialize + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_time_abs (const char *field, + struct GNUNET_TIME_Absolute *atime) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, atime, sizeof(struct GNUNET_TIME_Absolute), NULL, TMH_PARSE_JNC_RET_TIME_ABSOLUTE, 0 }; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param[out] pk key to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_public_key (const char *field, + struct TALER_DenominationPublicKey *pk) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, pk, 0, NULL, TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, 0 }; + pk->rsa_public_key = NULL; + return ret; +} + + +/** + * Generate line in parser specification for RSA public key. + * + * @param field name of the field + * @param sig the signature to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_denomination_signature (const char *field, + struct TALER_DenominationSignature *sig) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, sig, 0, NULL, TMH_PARSE_JNC_RET_RSA_SIGNATURE, 0 }; + sig->rsa_signature = NULL; + return ret; +} + + +/** + * Generate line in parser specification for an amount. + * + * @param field name of the field + * @param amount a `struct TALER_Amount *` to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_amount (const char *field, + struct TALER_Amount *amount) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, amount, sizeof(struct TALER_Amount), NULL, TMH_PARSE_JNC_RET_AMOUNT, 0 }; + memset (amount, 0, sizeof (struct TALER_Amount)); + return ret; +} + + +/** + * Generate line in parser specification for variable-size value. + * + * @param field name of the field + * @param[out] ptr pointer to initialize + * @param[out] ptr_size size to initialize + * @return corresponding field spec + */ +struct TMH_PARSE_FieldSpecification +TMH_PARSE_member_variable (const char *field, + void **ptr, + size_t *ptr_size) +{ + struct TMH_PARSE_FieldSpecification ret = + { field, ptr, 0, ptr_size, TMH_PARSE_JNC_RET_DATA_VAR, 0 }; + *ptr = NULL; + return ret; +} + +/** + * Navigate through a JSON tree. + * + * Sends an error response if navigation is impossible (i.e. + * the JSON object is invalid) + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param ... navigation specification (see `enum TMH_PARSE_JsonNavigationCommand`) + * @return + * #GNUNET_YES if navigation was successful + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error (no response was generated, + * connection must be closed) + */ +int +TMH_PARSE_navigate_json (struct MHD_Connection *connection, + const json_t *root, + ...) +{ + va_list argp; + int ret; + json_t *path; /* what's our current path from 'root'? */ + + path = json_array (); + va_start (argp, root); + ret = 2; /* just not any of the valid return values */ + while (2 == ret) + { + enum TMH_PARSE_JsonNavigationCommand command + = va_arg (argp, + enum TMH_PARSE_JsonNavigationCommand); + + switch (command) + { + case TMH_PARSE_JNC_FIELD: + { + const char *fname = va_arg(argp, const char *); + + json_array_append_new (path, + json_string (fname)); + root = json_object_get (root, + fname); + if (NULL == root) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "missing field in JSON", + "field", fname, + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_INDEX: + { + int fnum = va_arg(argp, int); + + json_array_append_new (path, + json_integer (fnum)); + root = json_array_get (root, + fnum); + if (NULL == root) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "missing index in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + break; + + case TMH_PARSE_JNC_RET_DATA: + { + void *where = va_arg (argp, void *); + size_t len = va_arg (argp, size_t); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + res = GNUNET_STRINGS_string_to_data (str, strlen (str), + where, len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_DATA_VAR: + { + void **where = va_arg (argp, void **); + size_t *len = va_arg (argp, size_t *); + const char *str; + int res; + + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "json_string_value() failed")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *len = (strlen (str) * 5) / 8; + if (NULL != where) + { + *where = GNUNET_malloc (*len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + *where, + *len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (*where); + *where = NULL; + *len = 0; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_TYPED_JSON: + { + int typ = va_arg (argp, int); + const json_t **r_json = va_arg (argp, const json_t **); + + if ( (NULL == root) || + ( (-1 != typ) && + (json_typeof (root) != typ)) ) + { + GNUNET_break_op (0); + *r_json = NULL; + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:i, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", typ, + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_json = root; + json_incref ((json_t *) root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_UINT64: + { + uint64_t *r_u64 = va_arg (argp, uint64_t *); + + if (json_typeof (root) != JSON_INTEGER) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:i, s:O}", + "error", "wrong JSON field type", + "type_expected", "integer", + "type_actual", json_typeof (root), + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + *r_u64 = (uint64_t) json_integer_value (root); + ret = GNUNET_OK; + } + break; + + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + { + struct TALER_DenominationPublicKey *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationPublicKey *); + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_public_key = GNUNET_CRYPTO_rsa_public_key_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_public_key) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA public key in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + { + struct TALER_DenominationSignature *where; + size_t len; + const char *str; + int res; + void *buf; + + where = va_arg (argp, + struct TALER_DenominationSignature *); + str = json_string_value (root); + if (NULL == str) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "string expected", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + len = (strlen (str) * 5) / 8; + buf = GNUNET_malloc (len); + res = GNUNET_STRINGS_string_to_data (str, + strlen (str), + buf, + len); + if (GNUNET_OK != res) + { + GNUNET_break_op (0); + GNUNET_free (buf); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed binary data in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + where->rsa_signature = GNUNET_CRYPTO_rsa_signature_decode (buf, + len); + GNUNET_free (buf); + if (NULL == where->rsa_signature) + { + GNUNET_break_op (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "malformed RSA signature in JSON", + "path", path)) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_AMOUNT: + { + struct TALER_Amount *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_amount ((json_t *) root, + where)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O}", + "error", "Bad format", + "path", path)) + ? GNUNET_SYSERR : GNUNET_NO; + break; + } + if (0 != strcmp (where->currency, + TMH_mint_currency_string)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:O, s:s}", + "error", "Currency not supported", + "path", path, + "currency", where->currency)) + ? GNUNET_SYSERR : GNUNET_NO; + memset (where, 0, sizeof (struct TALER_Amount)); + break; + } + ret = GNUNET_OK; + break; + } + + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + { + struct GNUNET_TIME_Absolute *where = va_arg (argp, void *); + + if (GNUNET_OK != + TALER_json_to_abs ((json_t *) root, + where)) + { + GNUNET_break_op (0); + ret = (MHD_YES != + TMH_RESPONSE_reply_json_pack (connection, + MHD_HTTP_BAD_REQUEST, + "{s:s, s:s, s:O}", + "error", "Bad format", + "hint", "expected absolute time", + "path", path)) + ? GNUNET_SYSERR : GNUNET_NO; + break; + } + ret = GNUNET_OK; + break; + } + + default: + GNUNET_break (0); + ret = (MHD_YES == + TMH_RESPONSE_reply_internal_error (connection, + "unhandled value in switch")) + ? GNUNET_NO : GNUNET_SYSERR; + break; + } + } + va_end (argp); + json_decref (path); + return ret; +} + + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + * #GNUNET_YES if navigation was successful (caller is responsible + * for freeing allocated variable-size data using + * #TMH_PARSE_release_data() when done) + * #GNUNET_NO if json is malformed, error response was generated + * #GNUNET_SYSERR on internal error + */ +int +TMH_PARSE_json_data (struct MHD_Connection *connection, + const json_t *root, + struct TMH_PARSE_FieldSpecification *spec) +{ + unsigned int i; + int ret; + + ret = GNUNET_YES; + for (i=0; NULL != spec[i].field_name; i++) + { + if (GNUNET_YES != ret) + break; + switch (spec[i].command) + { + case TMH_PARSE_JNC_FIELD: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_INDEX: + GNUNET_break (0); + return GNUNET_SYSERR; + case TMH_PARSE_JNC_RET_DATA: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA, + spec[i].destination, + spec[i].destination_size_in); + break; + case TMH_PARSE_JNC_RET_DATA_VAR: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_DATA_VAR, + (void **) spec[i].destination, + spec[i].destination_size_out); + break; + case TMH_PARSE_JNC_RET_TYPED_JSON: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TYPED_JSON, + spec[i].type, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_PUBLIC_KEY, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_RSA_SIGNATURE: + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_RSA_SIGNATURE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_AMOUNT: + GNUNET_assert (sizeof (struct TALER_Amount) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_AMOUNT, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_TIME_ABSOLUTE: + GNUNET_assert (sizeof (struct GNUNET_TIME_Absolute) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_TIME_ABSOLUTE, + spec[i].destination); + break; + case TMH_PARSE_JNC_RET_UINT64: + GNUNET_assert (sizeof (uint64_t) == + spec[i].destination_size_in); + ret = TMH_PARSE_navigate_json (connection, + root, + TMH_PARSE_JNC_FIELD, + spec[i].field_name, + TMH_PARSE_JNC_RET_UINT64, + spec[i].destination); + break; + } + } + if (GNUNET_YES != ret) + release_data (spec, + i - 1); + return ret; +} + + /* end of taler-mint-httpd_parsing.c */ diff --git a/src/frontend/generate_taler_contract.php b/src/frontend/generate_taler_contract.php index bcfeaeb9..52db0d22 100644 --- a/src/frontend/generate_taler_contract.php +++ b/src/frontend/generate_taler_contract.php @@ -95,6 +95,7 @@ $json = json_encode (array ('amount' => array ('value' => $value, 'delivery_date' => "Some Date Format", 'delivery_location' => 'LNAME1')), 'timestamp' => "Date(" . $now->getTimestamp() . ")", + 'expiry' => "Date(" . $now->add(new DateInterval('P2W'))->getTimestamp() . ")", 'refund_deadline' => "Date(" . $now->add(new DateInterval('P3M'))->getTimestamp() . ")", 'merchant' => array ('address' => 'LNAME2', 'name' => 'test merchant', diff --git a/src/include/merchant.h b/src/include/merchant.h index e207b4e3..1368c575 100644 --- a/src/include/merchant.h +++ b/src/include/merchant.h @@ -51,14 +51,10 @@ struct MERCHANT_Mint char *hostname; /** - * The public key of the mint + * Flag which indicates whether some HTTP transfer between + * this merchant and the mint is still ongoing */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - - /** - * The port where the mint's service is running - */ - uint16_t port; + int pending; /** * A connection to this mint @@ -67,7 +63,6 @@ struct MERCHANT_Mint }; - /** * Parses mints from the configuration. * |