/*
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
*/
/**
* @file merchant/taler_merchant_serve.c
* @brief Reference implementation of the merchant's HTTP interface
* @author Sree Harsha Totakura
*/
#include "platform.h"
#include
#include
#include
#include "merchant.h"
#include "merchant_db.h"
#include
#include
#include
/**
* 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 TALER_HashContext hc;
struct GNUNET_HashCode hash;
TALER_hash_context_start (&hc);
TALER_hash_context_read (&hc, wire->iban, strlen (wire->iban));
TALER_hash_context_read (&hc, wire->name, strlen (wire->name));
TALER_hash_context_read (&hc, wire->bic, strlen (wire->bic));
nounce = GNUNET_htonll (nounce);
TALER_hash_context_read (&hc, &nounce, sizeof (nounce));
TALER_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[]="\
\
Resource not found \
The resource you are looking for is not found.
\
";
static char page_500[]="\
Internal Server Error \
The server experienced an internal error and hence cannot serve your \
request
";
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 */
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 =
""
"You are being redirected to the product download page
"
"If your browser is unable to redirect, you may click "
"here to download."
"";
char *download_page;
char *location;
struct MHD_Response *resp;
ssize_t 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[]="\
\
HTTP Test pageHTTP Test page
\
";
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 = "\
\
Products List \
";
char **partials;
const char *footer = "
";
char *page;
struct Download *dwn;
size_t size;
unsigned int cnt;
unsigned 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],
"%s",
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;
}