diff options
Diffstat (limited to 'src/merchant/taler_merchant_serve.c')
-rw-r--r-- | src/merchant/taler_merchant_serve.c | 1539 |
1 files changed, 0 insertions, 1539 deletions
diff --git a/src/merchant/taler_merchant_serve.c b/src/merchant/taler_merchant_serve.c deleted file mode 100644 index 0f00a8c2..00000000 --- a/src/merchant/taler_merchant_serve.c +++ /dev/null @@ -1,1539 +0,0 @@ -#include "platform.h" -#include <gnunet/gnunet_util_lib.h> -#include <microhttpd.h> -#include <taler/taler_util.h> -#include "merchant.h" -#include "merchant_db.h" -#include <taler/taler_mint_service.h> -#include <taler/taler_signatures.h> -#include <taler/taler_json_lib.h> - - -/** - * Shorthand for exit jumps. - */ -#define EXITIF(cond) \ - do { \ - if (cond) { GNUNET_break (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Shorthand for exit jumps due to protocol exceptions resulting from client's - * mistakes - */ -#define EXITIF_OP(cond) \ - do { \ - if (cond) { GNUNET_break_op (0); goto EXITIF_exit; } \ - } while (0) - -/** - * Print JSON parsing related error information - */ -#define WARN_JSON(error) \ - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, \ - "JSON parsing failed at %s:%u: %s (%s)", \ - __FILE__, __LINE__, error.text, error.source) - -/** - * Macro to round microseconds to seconds in GNUNET_TIME_* structs. - */ -#define ROUND_TO_SECS(name,us_field) name.us_field -= name.us_field % (1000 * 1000) - - -struct ContractData -{ - char *product; -}; - - -GNUNET_NETWORK_STRUCT_BEGIN - -struct Contract -{ - /** - * The signature of the merchant for this contract - */ - struct GNUNET_CRYPTO_EddsaSignature sig; - - /** - * Purpose header for the signature over contract - */ - struct GNUNET_CRYPTO_EccSignaturePurpose purpose; - - /** - * The transaction identifier - */ - char m[13]; - - /** - * Expiry time - */ - struct GNUNET_TIME_AbsoluteNBO t; - - /** - * The invoice amount - */ - struct TALER_AmountNBO amount; - - /** - * The hash of the preferred wire format + nounce - */ - struct GNUNET_HashCode h_wire; - - /** - * The contract data - */ - char a[]; -}; - -GNUNET_NETWORK_STRUCT_END - -/** - * A download object - */ -struct Download { - struct Download *next; - struct Download *prev; - char *filename; - struct MHD_Response *resp; - unsigned int id; -}; - -/** - * DLL for downloadable objects - */ -struct Download *dwn_head; -struct Download *dwn_tail; - -/** - * MHD response object for listing all products - */ -struct MHD_Response *list_products_resp; - -/** - * Number of files we make available for downloading - */ -static unsigned int ndownloads; - - -/** - * Context information of the mints we trust - */ -struct Mint -{ - /** - * Public key of this mint - */ - struct GNUNET_CRYPTO_EddsaPublicKey pubkey; - - /** - * Connection handle to this mint - */ - struct TALER_MINT_Handle *conn; -}; - -/** - * Hashmap to store the mint context information - */ -static struct GNUNET_CONTAINER_MultiPeerMap *mints_map; - -/** - * Our private key - */ -struct GNUNET_CRYPTO_EddsaPrivateKey *privkey; - -/** - * Connection handle to the our database - */ -PGconn *db_conn; - -/** - * The MHD Daemon - */ -static struct MHD_Daemon *mhd; - -/** - * Our wireformat - */ -static struct MERCHANT_WIREFORMAT_Sepa *wire; - -/** - * Hash of the wireformat - */ -static struct GNUNET_HashCode h_wire; - -/** - * Shutdown task identifier - */ -static struct GNUNET_SCHEDULER_Task *shutdown_task; - -/** - * Task for calling the select on MHD's sockets - */ -static struct GNUNET_SCHEDULER_Task *select_task; - -/** - * The port we are running on - */ -static long long unsigned port; - -/** - * Mint context - */ -static struct TALER_MINT_Context *mctx; - -/** - * Our hostname - */ -static char *hostname; - -/** - * Directory of data items to serve - */ -static char *data_dir; - -/** - * Should we do a dry run where temporary tables are used for storing the data. - */ -static int dry; - -/** - * Global return code - */ -static int result; - - - -/** - * Send JSON object as response. Decreases the reference count of the - * JSON object. - * - * @param connection the MHD connection - * @param json the json object - * @param status_code the http status code - * @return MHD result code - */ -static int -send_response_json (struct MHD_Connection *connection, - json_t *json, - unsigned int status_code) -{ - struct MHD_Response *resp; - char *json_str; - - json_str = json_dumps (json, JSON_INDENT(2)); - json_decref (json); - resp = MHD_create_response_from_buffer (strlen (json_str), json_str, - MHD_RESPMEM_MUST_FREE); - if (NULL == resp) - return MHD_NO; - return MHD_queue_response (connection, status_code, resp); -} - - -/* ************ JSON post-processing logic; FIXME: why do we use JSON here!? ********** */ - - -/** - * Initial size for POST - * request buffer. - */ -#define REQUEST_BUFFER_INITIAL 1024 - -/** - * Maximum POST request size - */ -#define REQUEST_BUFFER_MAX (1024*1024) - - -/** - * Buffer for POST requests. - */ -struct Buffer -{ - /** - * Allocated memory - */ - char *data; - - /** - * Number of valid bytes in buffer. - */ - size_t fill; - - /** - * Number of allocated bytes in buffer. - */ - size_t alloc; -}; - - -/** - * Initialize a buffer. - * - * @param buf the buffer to initialize - * @param data the initial data - * @param data_size size of the initial data - * @param alloc_size size of the buffer - * @param max_size maximum size that the buffer can grow to - * @return a GNUnet result code - */ -static int -buffer_init (struct Buffer *buf, const void *data, size_t data_size, size_t alloc_size, size_t max_size) -{ - if (data_size > max_size || alloc_size > max_size) - return GNUNET_SYSERR; - if (data_size > alloc_size) - alloc_size = data_size; - buf->data = GNUNET_malloc (alloc_size); - memcpy (buf->data, data, data_size); - return GNUNET_OK; -} - - -/** - * Free the data in a buffer. Does *not* free - * the buffer object itself. - * - * @param buf buffer to de-initialize - */ -static void -buffer_deinit (struct Buffer *buf) -{ - GNUNET_free (buf->data); - buf->data = NULL; -} - - -/** - * Append data to a buffer, growing the buffer if necessary. - * - * @param buf the buffer to append to - * @param data the data to append - * @param size the size of @a data - * @param max_size maximum size that the buffer can grow to - * @return GNUNET_OK on success, - * GNUNET_NO if the buffer can't accomodate for the new data - * GNUNET_SYSERR on fatal error (out of memory?) - */ -static int -buffer_append (struct Buffer *buf, const void *data, size_t data_size, size_t max_size) -{ - if (buf->fill + data_size > max_size) - return GNUNET_NO; - if (data_size + buf->fill > buf->alloc) - { - char *new_buf; - size_t new_size = buf->alloc; - while (new_size < buf->fill + data_size) - new_size += 2; - if (new_size > max_size) - return GNUNET_NO; - new_buf = GNUNET_malloc (new_size); - memcpy (new_buf, buf->data, buf->fill); - buf->data = new_buf; - buf->alloc = new_size; - } - memcpy (buf->data + buf->fill, data, data_size); - buf->fill += data_size; - return GNUNET_OK; -} - - - -/** - * Process a POST request containing a JSON object. - * - * @param connection the MHD connection - * @param con_cs the closure (contains a 'struct Buffer *') - * @param upload_data the POST data - * @param upload_data_size the POST data size - * @param json the JSON object for a completed request - * - * @returns - * GNUNET_YES if json object was parsed - * GNUNET_NO is request incomplete or invalid - * GNUNET_SYSERR on internal error - */ -static int -process_post_json (struct MHD_Connection *connection, - void **con_cls, - const char *upload_data, - size_t *upload_data_size, - json_t **json) -{ - struct Buffer *r = *con_cls; - - if (NULL == *con_cls) - { - /* We are seeing a fresh POST request. */ - - r = GNUNET_new (struct Buffer); - if (GNUNET_OK != buffer_init (r, upload_data, *upload_data_size, - REQUEST_BUFFER_INITIAL, REQUEST_BUFFER_MAX)) - { - *con_cls = NULL; - buffer_deinit (r); - GNUNET_free (r); - return GNUNET_SYSERR; - } - *upload_data_size = 0; - *con_cls = r; - return GNUNET_NO; - } - if (0 != *upload_data_size) - { - /* We are seeing an old request with more data available. */ - - if (GNUNET_OK != buffer_append (r, upload_data, *upload_data_size, - REQUEST_BUFFER_MAX)) - { - /* Request too long or we're out of memory. */ - - *con_cls = NULL; - buffer_deinit (r); - GNUNET_free (r); - return GNUNET_SYSERR; - } - *upload_data_size = 0; - return GNUNET_NO; - } - - /* We have seen the whole request. */ - - *json = json_loadb (r->data, r->fill, 0, NULL); - buffer_deinit (r); - GNUNET_free (r); - if (NULL == *json) - { - struct MHD_Response *resp; - int ret; - - GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Can't parse JSON request body\n"); - resp = MHD_create_response_from_buffer (strlen ("parse error"), - "parse error", - MHD_RESPMEM_PERSISTENT); - ret = MHD_queue_response (connection, - MHD_HTTP_BAD_REQUEST, - resp); - MHD_destroy_response (resp); - return ret; - } - *con_cls = NULL; - - return GNUNET_YES; -} - - -/* ************** END of JSON POST processing logic ************ */ - - -static struct GNUNET_HashCode -hash_wireformat (uint64_t nounce) -{ - struct GNUNET_HashContext *hc; - struct GNUNET_HashCode hash; - - hc = GNUNET_CRYPTO_hash_context_start (); - GNUNET_CRYPTO_hash_context_read (hc, wire->iban, strlen (wire->iban)); - GNUNET_CRYPTO_hash_context_read (hc, wire->name, strlen (wire->name)); - GNUNET_CRYPTO_hash_context_read (hc, wire->bic, strlen (wire->bic)); - nounce = GNUNET_htonll (nounce); - GNUNET_CRYPTO_hash_context_read (hc, &nounce, sizeof (nounce)); - GNUNET_CRYPTO_hash_context_finish (hc, &hash); - return hash; -} - - -static json_t * -build_json_contract (struct Contract *contract) -{ - return json_pack ("{s:s, s:o, s:o, s:s, s:o, s:o}", - "transaction_id", contract->m, - "expiry", TALER_JSON_from_abs (GNUNET_TIME_absolute_ntoh (contract->t)), - "amount", TALER_JSON_from_amount (TALER_amount_ntoh (contract->amount)), - "description", contract->a, - "H_wire", TALER_JSON_from_data (&contract->h_wire, sizeof (struct GNUNET_HashCode)), - "msig", TALER_JSON_from_data (&contract->sig, sizeof (struct GNUNET_CRYPTO_EddsaSignature))); -} - -/** - * Cleeanup entries in the peer map. - * - * @param cls closure - * @param key current public key - * @param value value in the hash map - * @return #GNUNET_YES if we should continue to - * iterate, - * #GNUNET_NO if not. - */ -static int -mints_cleanup_iterator (void *cls, - const struct GNUNET_PeerIdentity *key, - void *value) -{ - struct Mint *mint = value; - - if (NULL != mint->conn) - TALER_MINT_disconnect (mint->conn); - GNUNET_CONTAINER_multipeermap_remove (mints_map, key, mint); - GNUNET_free (mint); - return GNUNET_YES; -} - - -/** - * Shutdown task - * - * @param cls NULL - * @param tc scheduler task context - */ -static void -do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct Download *dwn; - - shutdown_task = NULL; - if (NULL != select_task) - { - GNUNET_SCHEDULER_cancel (select_task); - select_task = NULL; - } - if (NULL != list_products_resp) - { - MHD_destroy_response (list_products_resp); - list_products_resp = NULL; - } - if (NULL != mhd) - { - MHD_stop_daemon (mhd); - mhd = NULL; - } - if (NULL != db_conn) - { - MERCHANT_DB_disconnect (db_conn); - db_conn = NULL; - } - if (NULL != mints_map) - { - GNUNET_CONTAINER_multipeermap_iterate (mints_map, - &mints_cleanup_iterator, - NULL); - GNUNET_CONTAINER_multipeermap_destroy (mints_map); - mints_map = NULL; - } - if (NULL != mctx) - { - TALER_MINT_cleanup (mctx); - mctx = NULL; - } - if (NULL != wire) - { - TALER_MERCHANT_destroy_wireformat_sepa (wire); - wire = NULL; - } - while (NULL != (dwn = dwn_head)) - { - GNUNET_CONTAINER_DLL_remove (dwn_head, dwn_tail, dwn); - if (NULL != dwn->resp) - MHD_destroy_response (dwn->resp); - GNUNET_free (dwn->filename); - GNUNET_free (dwn); - } -} - - -/** - * Get the MHD's sockets which are to be called with select() and schedule the - * select task. - * - * @return GNUNET_YES upon success; GNUNET_NO upon error, in this case the - * select task will not be queued. - */ -static int -poll_mhd (); - - -/** - * One of the MHD's sockets are ready. Call MHD_run_from_select (). - * - * @param cls NULL - * @param tc scheduler task context - */ -static void -run_mhd (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - fd_set fd_rs; - fd_set fd_ws; - select_task = NULL; - if (0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason)) - return; - FD_ZERO (&fd_rs); - FD_ZERO (&fd_ws); - if (0 != (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason)) - fd_rs = tc->read_ready->sds; - if (0 != (GNUNET_SCHEDULER_REASON_WRITE_READY & tc->reason)) - fd_ws = tc->write_ready->sds; - EXITIF (MHD_YES != MHD_run_from_select (mhd, - &fd_rs, - &fd_ws, - NULL)); - EXITIF (GNUNET_NO == poll_mhd ()); - return; - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); -} - - -/** - * Get the MHD's sockets which are to be called with select() and schedule the - * select task. - * - * @return GNUNET_YES upon success; GNUNET_NO upon error, in this case the - * select task will not be queued. - */ -static int -poll_mhd () -{ - struct GNUNET_NETWORK_FDSet rs; - struct GNUNET_NETWORK_FDSet ws; - fd_set fd_rs; - fd_set fd_ws; - fd_set fd_es; - struct GNUNET_TIME_Relative delay; - unsigned long long timeout; - int max_fd; - - FD_ZERO (&fd_rs); - FD_ZERO (&fd_ws); - FD_ZERO (&fd_es); - max_fd = 0; - if (MHD_YES != MHD_get_fdset (mhd, - &fd_rs, - &fd_ws, - &fd_es, - &max_fd)) - return GNUNET_SYSERR; - GNUNET_NETWORK_fdset_zero (&rs); - GNUNET_NETWORK_fdset_zero (&ws); - GNUNET_NETWORK_fdset_copy_native (&rs, &fd_rs, max_fd + 1); - GNUNET_NETWORK_fdset_copy_native (&ws, &fd_ws, max_fd + 1); - if (MHD_NO == MHD_get_timeout (mhd, &timeout)) - timeout = 0; - if (0 == timeout) - delay = GNUNET_TIME_UNIT_FOREVER_REL; - else - delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, - timeout); - if (NULL != select_task) - GNUNET_SCHEDULER_cancel (select_task); - select_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_KEEP, - delay, - &rs, - &ws, - &run_mhd, - NULL); - return GNUNET_OK; -} - -static int -failure_resp (struct MHD_Connection *connection, unsigned int status) -{ - static char page_404[]="\ -<!DOCTYPE html> \ -<html><title>Resource not found</title><body><center> \ -<h3>The resource you are looking for is not found.</h3> \ -</center></body></html>"; - static char page_500[]="\ -<!DOCTYPE html> <html><title>Internal Server Error</title><body><center> \ -<h3>The server experienced an internal error and hence cannot serve your \ -request</h3></center></body></html>"; - struct MHD_Response *resp; - char *page; - size_t size; -#define PAGE(number) \ - do {page=page_ ## number; size=sizeof(page_ ## number)-1;} while(0) - - GNUNET_assert (400 <= status); - resp = NULL; - switch (status) - { - case 404: - PAGE(404); - break; - default: - status = 500; - case 500: - PAGE(500); - } -#undef PAGE - - EXITIF (NULL == (resp = MHD_create_response_from_buffer (size, - page, - MHD_RESPMEM_PERSISTENT))); - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - MHD_destroy_response (resp); - return GNUNET_OK; - - EXITIF_exit: - if (NULL != resp) - MHD_destroy_response (resp); - return GNUNET_SYSERR; -} - - -/** - * Iterator over key-value pairs. This iterator - * can be used to iterate over all of the cookies, - * headers, or POST-data fields of a request, and - * also to iterate over the headers that have been - * added to a response. - * - * @param cls closure - * @param kind kind of the header we are looking at - * @param key key for the value, can be an empty string - * @param value corresponding value, can be NULL - * @return #MHD_YES to continue iterating, - * #MHD_NO to abort the iteration - * @ingroup request - */ -static int -get_contract_values_iter (void *cls, - enum MHD_ValueKind kind, - const char *key, const char *value) -{ - unsigned long long id; - uint64_t *product = cls; - -#define STR_PRODUCT "product" - if (0 == strncasecmp (key, STR_PRODUCT, sizeof (STR_PRODUCT) - 1)) - { - if (1 > sscanf (value, "%llu", &id)) - return GNUNET_NO; - *product = (uint64_t) id; - } - return GNUNET_YES; -} - -#if 0 -static const char * -uint64_to_enc (uint64_t i) -{ - static char buf[14]; - i = GNUNET_htonll (i); - GNUNET_break (NULL != - GNUNET_STRINGS_data_to_string (&i, sizeof (i), buf, sizeof (buf))); - buf[13] = '\0'; - return buf; -} - -static uint64_t -enc_to_uint64 (const char *enc) -{ - uint64_t i; - GNUNET_break (GNUNET_OK == - GNUNET_STRINGS_string_to_data (enc, strlen(enc), &i, sizeof - (i))); - return GNUNET_ntohll (i); -} -#endif - -/** - * Prepare a contract, store it in database and send the corresponding JSON. - * - * @param connection MHD connection handle - * @param _resp pointer to hold the result response upon success - * @return the status code 200 when a contract is generated; 404 when the - * product is not found or upon other errors. - */ -static unsigned int -handle_get_contract (struct MHD_Connection *connection, - struct MHD_Response **_resp) -{ - struct MHD_Response *resp; - struct Contract *contract; - struct GNUNET_TIME_Absolute expiry; - struct TALER_Amount amount; - char *template = "A contract from GNUnet e.V thanking you for a" - " donation of the aforementioned amount. As a token of gratitude, upon" - " successful payment, you may download your image at " - "`http://%s:%u/download?ref=[]'"; - char *desc; - json_t *json; - char *json_str; - uint64_t nounce; - uint64_t product; - uint64_t contract_id_nbo; - long long contract_id; - unsigned int ret; - - resp = NULL; - contract = NULL; - desc = NULL; - ret = 400; - product = UINT64_MAX; - MHD_get_connection_values (connection, MHD_GET_ARGUMENT_KIND, - &get_contract_values_iter, &product); - if (UINT64_MAX == product) - goto EXITIF_exit; - - expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (), - GNUNET_TIME_UNIT_DAYS); - ROUND_TO_SECS (expiry, abs_value_us); - amount.value = 1; - amount.fraction = 0; - strcpy (amount.currency, "EUR"); - nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); - - /* Prepare contract */ - (void) GNUNET_asprintf (&desc, - template, - hostname, - port); - contract_id = MERCHANT_DB_contract_create (db_conn, - expiry, - &amount, - desc, - nounce, - product); - EXITIF (-1 == contract_id); - contract_id_nbo = GNUNET_htonll ((uint64_t) contract_id); - contract = GNUNET_malloc (sizeof (struct Contract) + strlen (desc) + 1); - contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT); - contract->purpose.size = htonl (sizeof (struct Contract) - - offsetof (struct Contract, purpose) - + strlen (desc) + 1); - GNUNET_STRINGS_data_to_string (&contract_id_nbo, sizeof (contract_id_nbo), - contract->m, sizeof (contract->m)); - contract->t = GNUNET_TIME_absolute_hton (expiry); - (void) strcpy (contract->a, desc); - contract->h_wire = hash_wireformat (nounce); - contract->amount = TALER_amount_hton (amount); - GNUNET_CRYPTO_eddsa_sign (privkey, &contract->purpose, &contract->sig); - json = build_json_contract (contract); - json_str = json_dumps (json, JSON_INDENT(2)); - json_decref (json); - resp = MHD_create_response_from_buffer (strlen (json_str), json_str, - MHD_RESPMEM_MUST_FREE); - ret = 200; - - EXITIF_exit: - GNUNET_free_non_null (desc); - if (NULL != resp) - *_resp = resp; - if (NULL != contract) - { - GNUNET_free (contract); - } - return ret; -} - -static struct Download * -find_product (unsigned int id) -{ - struct Download *dwn; - - for (dwn = dwn_head; NULL != dwn; dwn = dwn->next) - { - if (dwn->id == id) - return dwn; - } - return NULL; -} - - -static int -get_download_ref (void *cls, - enum MHD_ValueKind kind, - const char *key, const char *value) -{ - char **coin_pub_enc = cls; - - if (0 == strncasecmp (key, "ref", sizeof ("ref"))) - { - *coin_pub_enc = GNUNET_strdup (value); - return MHD_NO; - } - return MHD_YES; -} - -static unsigned int -handle_download (struct MHD_Connection *conn, - struct MHD_Response **_resp) -{ - char *coin_pub_enc; - struct Download *item; - struct GNUNET_DISK_FileHandle *fh; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - long long product_id; - off_t size; - int ret; - - coin_pub_enc = NULL; - ret = MHD_HTTP_NOT_FOUND; - MHD_get_connection_values (conn, MHD_GET_ARGUMENT_KIND, - &get_download_ref, &coin_pub_enc); - LOG_WARNING ("Trying to start downloading with coin: %s\n", coin_pub_enc); - EXITIF (NULL == coin_pub_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_public_key_from_string (coin_pub_enc, - strlen (coin_pub_enc), - &coin_pub)); - product_id = MERCHANT_DB_get_checkout_product (db_conn, - &coin_pub); - EXITIF (-1 == product_id); - EXITIF (NULL == (item = find_product ((unsigned int) product_id))); - if (NULL != item->resp) - { - *_resp = item->resp; - ret = MHD_HTTP_OK; - goto EXITIF_exit; - } - fh = GNUNET_DISK_file_open (item->filename, - GNUNET_DISK_OPEN_READ, - GNUNET_DISK_PERM_USER_READ); - GNUNET_assert (NULL != fh); - GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_handle_size (fh, - &size)); - item->resp = MHD_create_response_from_fd (size, fh->fd); - GNUNET_assert (MHD_NO != MHD_add_response_header (item->resp, - "Content-Type", - "image/jpeg")); - GNUNET_free (fh); - - EXITIF_exit: - GNUNET_free_non_null (coin_pub_enc); - return ret; -} - -struct CheckoutCtx -{ - /* FIXME: Hook into a DLL for cleaner shutdown */ - struct MHD_Connection *conn; - struct TALER_MINT_DepositHandle *dh; - struct Download *product; - char *coin_pub_enc; - uint64_t transaction_id; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - struct GNUNET_CRYPTO_EddsaSignature coin_sig; - struct TALER_Amount amount; - struct GNUNET_SCHEDULER_Task *timeout_task; - -}; - - -/** - * Callbacks of this type are used to serve the result of submitting a deposit - * permission object to a mint - * - * @param cls closure - * @param status 1 for successful deposit, 2 for retry, 0 for failure - * @param obj the received JSON object; can be NULL if it cannot be constructed - * from the reply - * @param emsg in case of unsuccessful deposit, this contains a human readable - * explanation. - */ -static void -checkout_status (void *cls, int status, json_t *obj, char *emsg) -{ - struct CheckoutCtx *ctx = cls; - const char *tmplt_download_page = - "<!DOCTYPE HTML><html>" - "<body>You are being redirected to the product download page<br>" - "If your browser is unable to redirect, you may click " - "<a href=\"%s\">here</a> to download.</body>" - "</html>"; - char *download_page; - char *location; - struct MHD_Response *resp; - int size; - - LOG_DEBUG ("Processing checkout request reply\n"); - GNUNET_SCHEDULER_cancel (ctx->timeout_task); - ctx->timeout_task = NULL; - download_page = NULL; - location = NULL; - switch (status) - { - case 1: - { - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - - GNUNET_assert (GNUNET_SYSERR != - GNUNET_CRYPTO_eddsa_public_key_from_string - (ctx->coin_pub_enc, - strlen (ctx->coin_pub_enc), - &coin_pub)); - /* FIXME: Put the contract into the checkout DB. */ - } - /* redirect with HTTP FOUND 302 to the product download page */ - GNUNET_assert (NULL != obj); - GNUNET_assert (0 < (size = GNUNET_asprintf (&location, - "/download?ref=%s", - ctx->coin_pub_enc))); - GNUNET_assert (0 < (size = GNUNET_asprintf (&download_page, - tmplt_download_page, - location))); - resp = MHD_create_response_from_buffer (size, - download_page, - MHD_RESPMEM_MUST_FREE); - /* IMP: do not free `download_page' */ - GNUNET_assert (NULL != resp); - GNUNET_assert (MHD_NO != MHD_add_response_header (resp, - "Location", - location)); - GNUNET_assert (MHD_YES == MHD_queue_response (ctx->conn, - MHD_HTTP_FOUND, - resp)); - MHD_destroy_response (resp); - GNUNET_free (location); - location = NULL; - resp = NULL; -#if 0 - struct Download *product; - struct GNUNET_DISK_FileHandle *fh; - GNUNET_assert (NULL != (product = ctx->product)); - if (NULL != product->resp) - { - MHD_queue_response (ctx->conn, MHD_HTTP_OK, product->resp); - break; - } - fh = GNUNET_DISK_file_open (product->filename, - GNUNET_DISK_OPEN_READ, - GNUNET_DISK_PERM_USER_READ); - GNUNET_assert (NULL != fh); - GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_handle_size (fh, - &size)); - product->resp = MHD_create_response_from_fd (size, fh->fd); - GNUNET_assert (MHD_NO != MHD_add_response_header (product->resp, - "Content-Type", - "image/jpeg")); - GNUNET_free (fh); - MHD_queue_response (ctx->conn, MHD_HTTP_OK, product->resp); -#endif - - break; - case 2: - send_response_json (ctx->conn, - json_pack ("{s:s}", "status", "pending"), - 200); /* FIXME: Send Image data */ - break; - case 0: - send_response_json (ctx->conn, - json_pack ("{s:s s:s}", - "status", "failed", - "error", (NULL != emsg) ? emsg : "unknown"), - 400); /* FIXME */ - break; - default: - GNUNET_assert (0); /* should never reach */ - } - GNUNET_free (ctx->coin_pub_enc); - GNUNET_free (ctx); - if (GNUNET_SYSERR == poll_mhd ()) - { - GNUNET_break (0); - GNUNET_SCHEDULER_shutdown (); - } -} - -static void -checkout_status_timedout (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) -{ - struct CheckoutCtx *ctx = cls; - - LOG_DEBUG ("Checkout request timed out\n"); - ctx->timeout_task = NULL; - TALER_MINT_deposit_submit_cancel (ctx->dh); - ctx->dh = NULL; - send_response_json (ctx->conn, - json_pack ("{s:s}", "error", "timeout"), - 400); /* FIXME */ - GNUNET_free (ctx->coin_pub_enc); - GNUNET_free (ctx); - EXITIF (GNUNET_SYSERR == poll_mhd ()); - return; - - EXITIF_exit: - GNUNET_SCHEDULER_shutdown (); -} - -static int -handle_checkout (struct MHD_Connection *conn, - json_t *checkout_json) -{ - struct CheckoutCtx *ctx; - const char *pkey_enc; - const char *tid_enc; - const char *emsg; - const char *coin_pub_enc; - const char *coin_sig_enc; - struct Mint *mint; - struct Download *product; - struct GNUNET_CRYPTO_EddsaPublicKey pkey; - struct GNUNET_CRYPTO_EddsaPublicKey coin_pub; - struct GNUNET_CRYPTO_EddsaSignature coin_sig; - uint64_t tid; - uint64_t product_id; - json_error_t jerror; - unsigned int status; - - coin_pub_enc = NULL; - emsg = "Public key of Mint is missing in the request"; - status = MHD_HTTP_BAD_REQUEST; - if (-1 == json_unpack_ex (checkout_json, - &jerror, - 0, - "{s:s s:s s:s s:s}", - "mint_pub", &pkey_enc, - "transaction_id", &tid_enc, - "coin_pub", &coin_pub_enc, - "coin_sig", &coin_sig_enc)) - { - WARN_JSON (jerror); - goto EXITIF_exit; - } - - EXITIF (GNUNET_OK != GNUNET_STRINGS_string_to_data - (tid_enc, strlen (tid_enc), &tid, sizeof (tid))); - tid = GNUNET_ntohll (tid); - - emsg = "Public key of the coin is missing/malformed in the request"; - EXITIF (NULL == coin_pub_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_CRYPTO_eddsa_public_key_from_string (coin_pub_enc, - strlen (coin_pub_enc), - &coin_pub)); - - emsg = "Signature of the coin is missing/malformed in the request"; - EXITIF (NULL == coin_sig_enc); - EXITIF (GNUNET_SYSERR == - GNUNET_STRINGS_string_to_data (coin_sig_enc, strlen (coin_sig_enc), - &coin_sig, sizeof (coin_sig))); - - emsg = "Contract not found"; - status = MHD_HTTP_NOT_FOUND; - LOG_DEBUG ("Looking for product associated with transaction %u\n", tid); - EXITIF (-1 == (product_id = MERCHANT_DB_get_contract_product (db_conn, tid))); - - emsg = "Could not find the downloadable product. Sorry :("; - EXITIF (NULL == (product = find_product (product_id))); - - emsg = "Invalid public key given for a mint"; - EXITIF (52 != strlen (pkey_enc)); - EXITIF (GNUNET_SYSERR == GNUNET_STRINGS_string_to_data (pkey_enc, 52, - &pkey, sizeof (pkey))); - - emsg = "The provided mint is not trusted by us"; - status = MHD_HTTP_FORBIDDEN; - EXITIF (NULL == (mint = - GNUNET_CONTAINER_multipeermap_get (mints_map, - (const struct - GNUNET_PeerIdentity *) - &pkey))); - - LOG_DEBUG ("Creating a new checkout request\n"); - ctx = GNUNET_new (struct CheckoutCtx); - ctx->product = product; - ctx->conn = conn; - ctx->coin_pub_enc = GNUNET_strdup (coin_pub_enc); - ctx->transaction_id = tid; - ctx->coin_pub = coin_pub; - ctx->coin_sig = coin_sig; - /* FIXME: parse amount */ - /* ctx->amount = ?? */ - ctx->dh = TALER_MINT_deposit_submit_json (mint->conn, - checkout_status, - ctx, - checkout_json); - ctx->timeout_task = GNUNET_SCHEDULER_add_delayed - (GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 3), - &checkout_status_timedout, ctx); - return MHD_YES; - - EXITIF_exit: - json_decref (checkout_json); - return send_response_json (conn, - json_pack ("{s:s s:s}", - "status", "failed", - "error", emsg), - status); -} - -/** - * A client has requested the given url using the given method - * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, - * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback - * must call MHD callbacks to provide content to give back to the - * client and return an HTTP status code (i.e. #MHD_HTTP_OK, - * #MHD_HTTP_NOT_FOUND, etc.). - * - * @param cls argument given together with the function - * pointer when the handler was registered with MHD - * @param url the requested url - * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, - * #MHD_HTTP_METHOD_PUT, etc.) - * @param version the HTTP version string (i.e. - * #MHD_HTTP_VERSION_1_1) - * @param upload_data the data being uploaded (excluding HEADERS, - * for a POST that fits into memory and that is encoded - * with a supported encoding, the POST data will NOT be - * given in upload_data and is instead available as - * part of #MHD_get_connection_values; very large POST - * data *will* be made available incrementally in - * @a upload_data) - * @param upload_data_size set initially to the size of the - * @a upload_data provided; the method must update this - * value to the number of bytes NOT processed; - * @param con_cls pointer that the callback can set to some - * address and that will be preserved by MHD for future - * calls for this request; since the access handler may - * be called many times (i.e., for a PUT/POST operation - * with plenty of upload data) this allows the application - * to easily associate some request-specific state. - * If necessary, this state can be cleaned up in the - * global #MHD_RequestCompletedCallback (which - * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). - * Initially, `*con_cls` will be NULL. - * @return #MHD_YES if the connection was handled successfully, - * #MHD_NO if the socket must be closed due to a serios - * error while handling the request - */ -static int -url_handler (void *cls, - struct MHD_Connection *connection, - const char *url, - const char *method, - const char *version, - const char *upload_data, - size_t *upload_data_size, - void **con_cls) -{ -#define URL_PRODUCTS "/products" -#define URL_CONTRACT "/contract" -#define URL_CHECKOUT "/checkout" -#define URL_HTTPTEST "/httptest" -#define URL_DOWNLOAD "/download" -#define STR_404_NOTFOUND "The requested resource is not found" - struct MHD_Response *resp; - int no_destroy; - unsigned int status; - - resp = NULL; - status = 404; - no_destroy = 0; - LOG_DEBUG ("request for URL `%s'\n", url); - - if (0 == strncasecmp (url, URL_PRODUCTS, sizeof (URL_PRODUCTS))) - { - /* parse for /contract */ - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - { - resp = list_products_resp; - no_destroy = 1; - status = 200; - } - else - GNUNET_break (0); /* FIXME: implement for post */ - } - - if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT))) - { - /* parse for /contract */ - if (0 == strcmp (MHD_HTTP_METHOD_GET, method)) - status = handle_get_contract (connection, &resp); - else - GNUNET_break (0); /* FIXME: implement for post */ - } - - if (0 == strncasecmp (url, URL_CHECKOUT, sizeof (URL_CHECKOUT))) - { - json_t *checkout_obj; - int ret; - /* parse for /checkout */ - ret = process_post_json (connection, - con_cls, - upload_data, - upload_data_size, - &checkout_obj); - if (GNUNET_SYSERR == ret) - return MHD_NO; - if (GNUNET_NO == ret) - return MHD_YES; - /* Handle the response in the request handler */ - ret = handle_checkout (connection, checkout_obj); - return ret; - } - - if (0 == strncasecmp (url, URL_HTTPTEST, sizeof (URL_HTTPTEST))) - { - static char page[]="\ -<!DOCTYPE html> \ -<html><title>HTTP Test page</title><body><center><h3>HTTP Test page</h3> \ -</center></body></html>"; - resp = MHD_create_response_from_buffer (sizeof (page) - 1, - page, - MHD_RESPMEM_PERSISTENT); - EXITIF (NULL == resp); - } - - if ((0 == strncasecmp (url, URL_DOWNLOAD, sizeof (URL_DOWNLOAD))) - && (0 == strcmp (MHD_HTTP_METHOD_GET, method))) - { - status = handle_download (connection, &resp); - if (status != MHD_HTTP_OK) - no_destroy = 1; - } - if (NULL != resp) - { - EXITIF (MHD_YES != MHD_queue_response (connection, status, resp)); - if (!no_destroy) - MHD_destroy_response (resp); - } - else - EXITIF (GNUNET_OK != failure_resp (connection, status)); - return MHD_YES; - - EXITIF_exit: - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); - return MHD_NO; -} - - -/** - * Callback for catching serious error conditions from MHD. - * - * @param cls user specified value - * @param file where the error occured - * @param line where the error occured - * @param reason error detail, may be NULL - */ -static void -mhd_panic_cb (void *cls, - const char *file, - unsigned int line, - const char *reason) -{ - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "MHD panicked at %s:%u: %s", - file, line, reason); - result = GNUNET_SYSERR; - GNUNET_SCHEDULER_shutdown (); -} - -/** - * Function called with a filename. - * - * @param cls closure - * @param filename complete filename (absolute path) - * @return #GNUNET_OK to continue to iterate, - * #GNUNET_NO to stop iteration with no error, - * #GNUNET_SYSERR to abort iteration with error! - */ -static int -add_download_file (void *cls, const char *filename) -{ - struct Download *dwn; - - dwn = GNUNET_new (struct Download); - dwn->filename = GNUNET_strdup (filename); - dwn->id = ndownloads++; - GNUNET_CONTAINER_DLL_insert (dwn_head, dwn_tail, dwn); - return GNUNET_OK; -} - - -/** - * Function to build a MHD response object to list products - * - * @return GNUNET_OK upon success; GNUNET_SYSERR otherwise - */ -static int -build_list_product_response () -{ - const char *header = "\ -<!DOCTYPE html> \ -<html><title>Products List</title> \ -<body><center><ol>"; - char **partials; - const char *footer = "</ol></center></body></html>"; - char *page; - struct Download *dwn; - size_t size; - unsigned int cnt; - int psize; - unsigned int header_size; - unsigned int footer_size; - unsigned int *partial_sizes; - int ret; - - ret = GNUNET_SYSERR; - GNUNET_assert (NULL == list_products_resp); - header_size = strlen (header); - footer_size = strlen (footer); - size = header_size; - size += footer_size; - partials = GNUNET_malloc (sizeof (char *) * ndownloads); - partial_sizes = GNUNET_malloc (sizeof (unsigned int) * ndownloads); - EXITIF (0 == ndownloads); - for (cnt = 0, dwn = dwn_head; cnt < ndownloads; cnt++, dwn=dwn->next) - { - EXITIF (NULL == dwn); - psize = GNUNET_asprintf (&partials[cnt], - "<li><a href=\"/contract?product=%u\">%s</a></li>", - cnt, - GNUNET_STRINGS_get_short_name (dwn->filename)); - EXITIF (psize < 0); - size += psize; - partial_sizes [cnt] = psize; - } - page = GNUNET_malloc (size); - size = 0; - (void) memcpy (page, header, header_size); - size += header_size; - for (cnt = 0; cnt < ndownloads; cnt++) - { - (void) memcpy (page + size, partials[cnt], partial_sizes[cnt]); - size += partial_sizes[cnt]; - } - (void) memcpy (page + size, footer, footer_size); - size += footer_size; - list_products_resp = MHD_create_response_from_buffer (size, page, MHD_RESPMEM_MUST_FREE); - ret = GNUNET_OK; - - EXITIF_exit: - for (cnt = 0; cnt < ndownloads; cnt++) - GNUNET_free_non_null (partials[cnt]); - GNUNET_free_non_null (partials); - GNUNET_free_non_null (partial_sizes); - return ret; -} - -/** - * Main function that will be run by the scheduler. - * - * @param cls closure - * @param args remaining command-line arguments - * @param cfgfile name of the configuration file used (for saving, can be NULL!) - * @param config configuration - */ -static void -run (void *cls, char *const *args, const char *cfgfile, - const struct GNUNET_CONFIGURATION_Handle *config) -{ - char *keyfile; - struct MERCHANT_MintInfo *mint_infos; - unsigned int nmints; - unsigned int cnt; - - result = GNUNET_SYSERR; - keyfile = NULL; - mint_infos = NULL; - shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, - &do_shutdown, NULL); - if (NULL == data_dir) - { - LOG_ERROR ("Data directory for download files is missing. It can be given with the `-d' option\n"); - goto EXITIF_exit; - } - EXITIF (GNUNET_SYSERR == (nmints = TALER_MERCHANT_parse_mints (config, - &mint_infos))); - EXITIF (NULL == (wire = TALER_MERCHANT_parse_wireformat_sepa (config))); - EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (config, - "merchant", - "KEYFILE", - &keyfile)); - EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile))); - EXITIF (0 == GNUNET_DISK_directory_scan (data_dir, - &add_download_file, - NULL)); - EXITIF (GNUNET_SYSERR == build_list_product_response ()); - EXITIF (NULL == (db_conn = MERCHANT_DB_connect (config))); - EXITIF (GNUNET_OK != MERCHANT_DB_initialise (db_conn, dry)); - EXITIF (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_number (config, - "merchant", - "port", - &port)); - EXITIF (GNUNET_SYSERR == - GNUNET_CONFIGURATION_get_value_string (config, - "merchant", - "hostname", - &hostname)); - EXITIF (NULL == (mctx = TALER_MINT_init ())); - EXITIF (NULL == (mints_map = GNUNET_CONTAINER_multipeermap_create (nmints, GNUNET_YES))); - for (cnt = 0; cnt < nmints; cnt++) - { - struct Mint *mint; - - mint = GNUNET_new (struct Mint); - mint->pubkey = mint_infos[cnt].pubkey; - mint->conn = TALER_MINT_connect (mctx, - mint_infos[cnt].hostname, - mint_infos[cnt].port, - &mint->pubkey); - EXITIF (NULL == mint->conn); - EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put - (mints_map, - (struct GNUNET_PeerIdentity *) &mint->pubkey, - mint, - GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)); - } - MHD_set_panic_func (&mhd_panic_cb, NULL); - mhd = MHD_start_daemon (MHD_USE_DEBUG, //| MHD_USE_TCP_FASTOPEN, - (unsigned short) port, - NULL, NULL, - &url_handler, NULL, - //MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE, - //(unsigned int) 16, - MHD_OPTION_END); - EXITIF (NULL == mhd); - EXITIF (GNUNET_SYSERR == poll_mhd ()); - GNUNET_CRYPTO_hash (wire, sizeof (*wire), &h_wire); - result = GNUNET_OK; - - EXITIF_exit: - if (NULL != mint_infos) - { - for (cnt = 0; cnt < nmints; cnt++) - GNUNET_free (mint_infos[cnt].hostname); - GNUNET_free (mint_infos); - } - GNUNET_free_non_null (keyfile); - if (GNUNET_OK != result) - GNUNET_SCHEDULER_shutdown (); -} - - -/** - * The main function of the serve tool - * - * @param argc number of arguments from the command line - * @param argv command line arguments - * @return 0 ok, 1 on error - */ -int -main (int argc, char *const *argv) -{ - static const struct GNUNET_GETOPT_CommandLineOption options[] = { - {'t', "temp", NULL, - gettext_noop ("Use temporary database tables"), GNUNET_NO, - &GNUNET_GETOPT_set_one, &dry}, - {'d', "dir", "DIRECTORY", - gettext_noop ("Directory of the data files to serve"), GNUNET_YES, - &GNUNET_GETOPT_set_string, &data_dir}, - GNUNET_GETOPT_OPTION_END - }; - - if (GNUNET_OK != - GNUNET_PROGRAM_run (argc, argv, - "taler-merchant-serve", - "Serve merchant's HTTP interface", - options, &run, NULL)) - return 3; - return (GNUNET_OK == result) ? 0 : 1; -} |