/*
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/backend/taler-merchant-httpd.c
* @brief HTTP serving layer mainly intended to communicate with the frontend
* @author Marcello Stanisci
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include "taler-mint-httpd_parsing.h"
#include "taler-mint-httpd_responses.h"
#include "merchant_db.h"
#include "merchant.h"
#include "taler_merchant_lib.h"
extern struct MERCHANT_WIREFORMAT_Sepa *
TALER_MERCHANT_parse_wireformat_sepa (const struct GNUNET_CONFIGURATION_Handle *cfg);
/**
* Shorthand for exit jumps.
*/
#define EXITIF(cond) \
do { \
if (cond) { GNUNET_break (0); goto EXITIF_exit; } \
} while (0)
/**
* Our hostname
*/
static char *hostname;
/**
* The port we are running on
*/
static long long unsigned port;
/**
* Merchant's private key
*/
struct GNUNET_CRYPTO_EddsaPrivateKey *privkey;
/**
* File holding the merchant's private key
*/
char *keyfile;
/**
* The MHD Daemon
*/
static struct MHD_Daemon *mhd;
/**
* Connection handle to the our database
*/
PGconn *db_conn;
/**
* merchant's conf handle
*/
struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Shutdown task identifier
*/
static struct GNUNET_SCHEDULER_Task *shutdown_task;
/**
* Our wireformat
*/
static struct MERCHANT_WIREFORMAT_Sepa *wire;
/**
* Hash of the wireformat
*/
static struct GNUNET_HashCode h_wire;
/**
* Should we do a dry run where temporary tables are used for storing the data.
*/
static int dry;
/**
* Global return code
*/
static int result;
/**
* Mint context
*/
static struct TALER_MINT_Context *mctx;
/**
* Context information of the mints we trust
*/
struct Mint
{
/**
* Public key of this mint
*/
struct GNUNET_CRYPTO_EddsaPublicKey pubkey;
/**
* Connection handle to this mint
*/
struct TALER_MINT_Handle *conn;
};
/**
* Hashmap to store the mint context information
*/
static struct GNUNET_CONTAINER_MultiPeerMap *mints_map;
/**
* Mints' URL,port,key triples
*/
struct MERCHANT_MintInfo *mint_infos;
/**
* The number of accepted mints
*/
unsigned int nmints;
#if FUTURE_USE
/**
* Return the given message to the other end of connection
* @msg (0-terminated) message to show
* @param connection a MHD connection
* @param resp where to store the response for the calling function
* @return HTTP status code reflecting the operation outcome
*
*/
static unsigned int
generate_message (struct MHD_Response **resp, const char *msg) // this parameter was preceded by a '_' in its original file. Why?
{
unsigned int ret;
*resp = MHD_create_response_from_buffer (strlen (msg), (void *) msg,
MHD_RESPMEM_PERSISTENT);
ret = 200;
return ret;
}
#endif
/**
* Generate the 'hello world' response
* @param connection a MHD connection
* @param resp where to store the response for the calling function
* @return HTTP status code reflecting the operation outcome
*
*/
static unsigned int
generate_hello (struct MHD_Response **resp) // this parameter was preceded by a '_' in its original file. Why?
{
const char *hello = "Hello customer\n";
unsigned int ret;
*resp = MHD_create_response_from_buffer (strlen (hello), (void *) hello,
MHD_RESPMEM_PERSISTENT);
ret = 200;
return ret;
}
#ifdef PANIC_MGMT
/**
* Callback for catching serious error conditions from MHD.
*
* @param cls user specified value
* @param file where the error occured
* @param line where the error occured
* @param reason error detail, may be NULL
*/
static void
mhd_panic_cb (void *cls,
const char *file,
unsigned int line,
const char *reason)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"MHD panicked at %s:%u: %s",
file, line, reason);
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
}
#endif
/**
* Manage a non 200 HTTP status. I.e. it shows a 'failure' page to
* the client
* @param connection the channel thorugh which send the message
* @status the HTTP status to examine
* @return GNUNET_OK on successful message sending, GNUNET_SYSERR upon error
*
*/
static int
failure_resp (struct MHD_Connection *connection, unsigned int status)
{
static char page_MHD_HTTP_NOT_FOUND[]="\
\
Resource not found \
The resource you are looking for is not found.
\
";
static char page_MHD_HTTP_BAD_REQUEST[]="\
\
Bad request \
Malformed POSTed JSON.
\
";
static char page_MHD_HTTP_METHOD_NOT_ALLOWED[]="\
\
Method NOT allowed \
ONLY POSTs are allowed.
\
";
static char page_MHD_HTTP_INTERNAL_SERVER_ERROR[]="\
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 (MHD_HTTP_BAD_REQUEST <= status);
resp = NULL;
switch (status)
{
case MHD_HTTP_NOT_FOUND :
PAGE(MHD_HTTP_NOT_FOUND);
break;
case MHD_HTTP_BAD_REQUEST:
PAGE(MHD_HTTP_BAD_REQUEST);
break;
case MHD_HTTP_METHOD_NOT_ALLOWED:
PAGE(MHD_HTTP_METHOD_NOT_ALLOWED);
break;
default:
status = MHD_HTTP_INTERNAL_SERVER_ERROR;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
PAGE(MHD_HTTP_INTERNAL_SERVER_ERROR);
}
#undef PAGE
EXITIF (NULL == (resp = MHD_create_response_from_buffer (size,
page,
MHD_RESPMEM_PERSISTENT)));
EXITIF (MHD_YES != MHD_queue_response (connection, status, resp));
MHD_destroy_response (resp);
return GNUNET_OK;
EXITIF_exit:
if (NULL != resp)
MHD_destroy_response (resp);
return GNUNET_SYSERR;
}
/**
* A client has requested the given url using the given method
* (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
* #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback
* must call MHD callbacks to provide content to give back to the
* client and return an HTTP status code (i.e. #MHD_HTTP_OK,
* #MHD_HTTP_NOT_FOUND, etc.).
*
* @param cls argument given together with the function
* pointer when the handler was registered with MHD
* @param url the requested url
* @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
* #MHD_HTTP_METHOD_PUT, etc.)
* @param version the HTTP version string (i.e.
* #MHD_HTTP_VERSION_1_1)
* @param upload_data the data being uploaded (excluding HEADERS,
* for a POST that fits into memory and that is encoded
* with a supported encoding, the POST data will NOT be
* given in upload_data and is instead available as
* part of #MHD_get_connection_values; very large POST
* data *will* be made available incrementally in
* @a upload_data)
* @param upload_data_size set initially to the size of the
* @a upload_data provided; the method must update this
* value to the number of bytes NOT processed;
* @param con_cls pointer that the callback can set to some
* address and that will be preserved by MHD for future
* calls for this request; since the access handler may
* be called many times (i.e., for a PUT/POST operation
* with plenty of upload data) this allows the application
* to easily associate some request-specific state.
* If necessary, this state can be cleaned up in the
* global #MHD_RequestCompletedCallback (which
* can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
* Initially, `*con_cls` will be NULL.
* @return #MHD_YES if the connection was handled successfully,
* #MHD_NO if the socket must be closed due to a serios
* error while handling the request
*/
static int
url_handler (void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **connection_cls)
{
printf ("%s\n", url);
unsigned int status;
unsigned int no_destroy;
struct GNUNET_CRYPTO_EddsaSignature c_sig;
struct GNUNET_CRYPTO_EddsaPublicKey pub;
struct ContractNBO contract;
struct MHD_Response *resp;
json_t *j_contract_complete;
json_t *root;
json_t *j_sig_enc;
json_t *eddsa_pub_enc;
json_t *response;
json_t *j_mints;
json_t *j_mint;
int cnt; /* loop counter */
#if 0
json_t *root_tmp;
json_t *j_amount_tmp;
json_t *j_details_tmp;
json_int_t *j_trans_id_tmp;
#endif
int res = GNUNET_SYSERR;
#define URL_HELLO "/hello"
#define URL_CONTRACT "/contract"
no_destroy = 0;
resp = NULL;
status = MHD_HTTP_INTERNAL_SERVER_ERROR;
if (0 == strncasecmp (url, URL_HELLO, sizeof (URL_HELLO)))
{
if (0 == strcmp (MHD_HTTP_METHOD_GET, method))
status = generate_hello (&resp);
else
{
status = MHD_HTTP_METHOD_NOT_ALLOWED;
}
}
/*
* To be called by the frontend passing the contract with some "holes"
* which will be completed, stored in DB, signed, and returned
*
*/
if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT)))
{
if (0 == strcmp (MHD_HTTP_METHOD_GET, method))
{
status = MHD_HTTP_METHOD_NOT_ALLOWED;
goto end;
}
else
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&root);
if (GNUNET_SYSERR == res)
{
status = MHD_HTTP_BAD_REQUEST;
goto end;
}
/* the POST's body has to be fetched furthermore */
if ((GNUNET_NO == res) || (NULL == root))
return MHD_YES;
/* The frontend should supply a JSON in the format described in
http://link-to-specs : */
/* TODO Specifying the accepted mints, in the contract */
j_mints = json_array ();
for (cnt = 0; cnt < nmints; cnt++)
{
j_mint = json_pack ("{s:s}",
mint_infos[cnt].hostname,
GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey));
json_array_append_new (j_mints, j_mint);
#ifdef DEBUG
printf ("mint(s): url %s, key %s", mint_infos[cnt].hostname,
GNUNET_CRYPTO_eddsa_public_key_to_string (&mint_infos[cnt].pubkey));
#endif
}
json_object_set (root, "mints", j_mints);
TMH_RESPONSE_reply_json (connection, root, MHD_HTTP_OK);
return MHD_YES;
#if 0
root_tmp = json_unpack ("{s:o, s:I, s:o}",
"amount", &j_amount_tmp,
"trans_id", &j_trans_id_tmp,
"details", &j_details_tmp);
#endif
if (NULL == (j_contract_complete = MERCHANT_handle_contract (root,
db_conn,
wire,
&contract)))
{
status = MHD_HTTP_INTERNAL_SERVER_ERROR;
goto end;
}
GNUNET_CRYPTO_eddsa_sign (privkey, &contract.purpose, &c_sig);
/**
*
* As of now, the format is
*
* {"contract" : {the contract in "plain" JSON},
* "sig" : base32 encoding of the signed 'struct ContractNBO',
* "eddsa_pub" : base32 encoding of merchant's public key}
*
*/
j_sig_enc = TALER_json_from_eddsa_sig (&contract.purpose, &c_sig);
GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub);
eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub));
response = json_pack ("{s:o, s:o, s:o}",
"contract", j_contract_complete,
"sig", j_sig_enc,
"eddsa_pub", eddsa_pub_enc);
TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK);
return MHD_YES;
}
end:
if (NULL != resp)
{
EXITIF (MHD_YES != MHD_queue_response (connection, status, resp));
return MHD_YES;
if (!no_destroy)
MHD_destroy_response (resp);
}
else
{
EXITIF (GNUNET_OK != failure_resp (connection, status));
return MHD_YES;
}
EXITIF_exit:
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_shutdown ();
return MHD_NO;
}
/**
* Shutdown task (magically invoked when the application is being
* quit)
*
* @param cls NULL
* @param tc scheduler task context
*/
static void
do_shutdown (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
{
if (NULL != mhd)
{
MHD_stop_daemon (mhd);
mhd = NULL;
}
if (NULL != db_conn)
{
MERCHANT_DB_disconnect (db_conn);
db_conn = NULL;
}
if (keyfile != NULL)
GNUNET_free (privkey);
}
/**
* Function called with information about who is auditing
* a particular mint and what key the mint is using.
*
* @param cls closure
* @param keys information about the various keys used
* by the mint
*/
void
keys_mgmt_cb (void *cls, const struct TALER_MINT_Keys *keys)
{
/* which kind of mint's keys a merchant should need? Sign
keys? It has already the mint's master key from the conf file */
return;
}
/**
* Main function that will be run by the scheduler.
*
* @param cls closure
* @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)
{
unsigned int cnt;
void *keys_mgmt_cls;
keys_mgmt_cls = NULL;
mint_infos = NULL;
keyfile = NULL;
result = GNUNET_SYSERR;
shutdown_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL,
&do_shutdown, NULL);
EXITIF (GNUNET_SYSERR == (nmints = TALER_MERCHANT_parse_mints (config,
&mint_infos)));
EXITIF (NULL == (wire = TALER_MERCHANT_parse_wireformat_sepa (config)));
EXITIF (GNUNET_OK != GNUNET_CONFIGURATION_get_value_filename (config,
"merchant",
"KEYFILE",
&keyfile));
EXITIF (NULL == (privkey = GNUNET_CRYPTO_eddsa_key_create_from_file (keyfile)));
EXITIF (NULL == (db_conn = MERCHANT_DB_connect (config)));
EXITIF (GNUNET_OK != MERCHANT_DB_initialize (db_conn, GNUNET_NO));
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;
/* port this to the new API */
mint->conn = TALER_MINT_connect (mctx,
mint_infos[cnt].hostname,
&keys_mgmt_cb,
keys_mgmt_cls); /*<- safe?segfault friendly?*/
/* NOTE: the keys mgmt callback should roughly do what the following lines do */
EXITIF (NULL == mint->conn);
EXITIF (GNUNET_SYSERR == GNUNET_CONTAINER_multipeermap_put
(mints_map,
(struct GNUNET_PeerIdentity *) /* to retrieve now from cb's args -> */&mint->pubkey,
mint,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST));
}
mhd = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY,
port,
NULL, NULL,
&url_handler, NULL,
MHD_OPTION_END);
EXITIF (NULL == mhd);
/* WARNING: a 'poll_mhd ()' call is here in the original merchant. Is that
mandatory ? */
GNUNET_CRYPTO_hash (wire, sizeof (*wire), &h_wire);
result = GNUNET_OK;
EXITIF_exit:
if (GNUNET_OK != result)
GNUNET_SCHEDULER_shutdown ();
GNUNET_free_non_null (keyfile);
if (GNUNET_OK != result)
GNUNET_SCHEDULER_shutdown ();
}
/**
* The main function of the serve tool
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, 1 on error
*/
int
main (int argc, char *const *argv)
{
static const struct GNUNET_GETOPT_CommandLineOption options[] = {
{'t', "temp", NULL,
gettext_noop ("Use temporary database tables"), GNUNET_NO,
&GNUNET_GETOPT_set_one, &dry},
GNUNET_GETOPT_OPTION_END
};
if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc, argv,
"taler-merchant-serve",
"Serve merchant's HTTP interface",
options, &run, NULL))
return 3;
return (GNUNET_OK == result) ? 0 : 1;
}