/*
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_mhd.h"
#include "taler-mint-httpd_admin.h"
#include "taler-mint-httpd_deposit.h"
#include "taler-mint-httpd_withdraw.h"
#include "taler-mint-httpd_refresh.h"
#include "taler-mint-httpd_keystate.h"
#include "taler-mint-httpd_responses.h"
#include "merchant.h"
#include "merchant_db.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)
/**
* 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)
/**
* 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;
/**
* The MHD Daemon
*/
static struct MHD_Daemon *mhd;
/**
* Connection handle to the our database
*/
PGconn *db_conn;
/**
* Which currency is used by this mint?
* (verbatim copy from mint's code, just to make this
* merchant's source compile)
*/
char *TMH_mint_currency_string;
/* As above */
struct TALER_MINTDB_Plugin *TMH_plugin;
/**
* As above, though the merchant does need some form of
* configuration
*/
struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* As above
*/
int TMH_test_mode;
/**
* As above
*/
char *TMH_mint_directory;
/**
* As above
*/
struct GNUNET_CRYPTO_EddsaPublicKey TMH_master_public_key;
/**
* As above
*/
char *TMH_expected_wire_format;
/**
* 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;
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
/**
* 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;
/**
* 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;
}
/**
* 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;
}
/**
* 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 ();
}
/**
* 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_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;
}
/**
* Generate the hash containing the information (= a nounce + merchant's IBAN) to
* redeem money from mint in a subsequent /deposit operation
* @param nounce the nounce
* @return the hash to be included in the contract's blob
*
*/
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;
}
/*
* Make a binary blob representing a contract, store it into the DB, sign it
* and return a pointer to it.
* @param a 0-terminated string representing the description of this
* purchase (it should contain a human readable description of the good
* in question)
* @param product some product numerical id. Its indended use is to link the
* good, or service being sold to some entry in the DB managed by the frontend
* @price the cost of this good or service
* @return pointer to the allocated contract (which has a field, 'sig', holding
* its own signature), NULL upon errors
*/
struct Contract *
generate_and_store_contract (const char *a, uint64_t product, struct TALER_Amount *price)
{
struct Contract *contract;
struct GNUNET_TIME_Absolute expiry;
uint64_t nounce;
long long contract_id;
uint64_t contract_id_nbo;
expiry = GNUNET_TIME_absolute_add (GNUNET_TIME_absolute_get (),
GNUNET_TIME_UNIT_DAYS);
ROUND_TO_SECS (expiry, abs_value_us);
nounce = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX);
contract_id = MERCHANT_DB_contract_create (db_conn,
expiry,
price,
a,
nounce,
product);
EXITIF (-1 == contract_id);
contract_id_nbo = GNUNET_htonll ((uint64_t) contract_id);
contract = GNUNET_malloc (sizeof (struct Contract) + strlen (a) + 1);
contract->purpose.purpose = htonl (TALER_SIGNATURE_MERCHANT_CONTRACT);
contract->purpose.size = htonl (sizeof (struct Contract)
- offsetof (struct Contract, purpose)
+ strlen (a) + 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, a);
contract->h_wire = hash_wireformat (nounce);
TALER_amount_hton (&contract->amount, price);
GNUNET_CRYPTO_eddsa_sign (privkey, &contract->purpose, &contract->sig);
return contract;
/* legacy from old merchant */
EXITIF_exit:
if (NULL != contract)
{
GNUNET_free (contract);
}
return NULL;
}
/**
* 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)
{
unsigned int status;
unsigned int no_destroy;
json_int_t prod_id;
struct Contract *contract;
struct MHD_Response *resp;
struct TALER_Amount price;
struct GNUNET_CRYPTO_EddsaPublicKey pub;
json_t *json_price;
json_t *root;
json_t *contract_enc;
json_t *sig_enc;
json_t *eddsa_pub_enc;
json_t *response;
int res;
const char *desc;
#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); //TBD
else
GNUNET_break (0);
}
// to be called by the frontend passing all the product's information
// which are relevant for the contract's generation
if (0 == strncasecmp (url, URL_CONTRACT, sizeof (URL_CONTRACT)))
{
if (0 == strcmp (MHD_HTTP_METHOD_GET, method))
status = generate_message (&resp, "Sorry, only POST is allowed");
else
res = TMH_PARSE_post_json (connection,
connection_cls,
upload_data,
upload_data_size,
&root);
if (GNUNET_SYSERR == res)
{
status = generate_message (&resp, "unable to parse JSON root");
return MHD_NO;
}
if ((GNUNET_NO == res) || (NULL == root))
return MHD_YES;
/* The frontend should supply a JSON in the follwoing format:
{
"desc" : string human-readable describing this deal,
"product" : uint64-like integer referring to the product in some
DB adminstered by the frontend,
"price" : a 'struct TALER_Amount' in the Taler compliant JSON format }
*/
#if 0
/*res = json_typeof (root); <- seg fault*/
json_int_t id;
const char *desc_test;
const char *cur_test;
json_t *id_json;
json_t *desc_json;
json_t *cur_json;
id_json = json_object_get (root, "product");
desc_json = json_object_get (root, "desc");
id = json_integer_value (id_json);
desc_test = json_string_value (desc_json);
json_price = json_object_get (root, "price");
json_typeof (json_price);
cur_json = json_object_get (json_price, "currency");
cur_test = json_string_value (cur_json);
printf ("id is %" JSON_INTEGER_FORMAT "\n", id);
printf ("desc is %s\n", desc_test);
TALER_json_to_amount (json_price, &price);
printf ("cur_test is %s\n", price.currency);
#endif
json_error_t err;
#if 0
if (res = json_unpack_ex (root, &err, JSON_VALIDATE_ONLY, "{s:s, s:I, s:o}",
"desc",
//&desc,
"product",
//&prod_id,
"price"//,
//json_price
))
#else
if (res = json_unpack (root, "{s:s, s:I, s:o}",
"desc",
&desc,
"product",
&prod_id,
"price",
&json_price
))
#endif
/* still not possible to return a taler-compliant error message
since this JSON format is not among the taler officials ones */
{
status = generate_message (&resp, "unable to parse /contract JSON\n");
}
else
{
if (GNUNET_OK != TALER_json_to_amount (json_price, &price))
{/* still not possible to return a taler-compliant error message
since this JSON format is not among the taler officials ones */
status = generate_message (&resp, "unable to parse `price' field in /contract JSON");}
else
{
/* Let's generate this contract! */
if (NULL == (contract = generate_and_store_contract (desc, prod_id, &price)))
{
/* status equals 500, so the user will get a "Internal server error" */
//failure_resp (connection, status);
status = generate_message (&resp, "unable to generate and store this contract");
//return MHD_YES;
}
else
{
json_decref (root);
json_decref (json_price);
printf ("Good contract\n");
/* the contract is good and stored in DB, produce now JSON to return.
As of now, the format is {"contract" : base32contract,
"sig" : contractSignature,
"eddsa_pub" : keyToCheckSignatureAgainst
}
*/
sig_enc = TALER_json_from_eddsa_sig (&contract->purpose, &contract->sig);
GNUNET_CRYPTO_eddsa_key_get_public (privkey, &pub);
eddsa_pub_enc = TALER_json_from_data ((void *) &pub, sizeof (pub));
/* cutting of the signature at the beginning */
contract_enc = TALER_json_from_data (&contract->purpose, sizeof (*contract)
- offsetof (struct Contract, purpose)
+ strlen (desc) +1);
response = json_pack ("{s:o, s:o, s:o}", "contract", contract_enc, "sig", sig_enc,
"eddsa_pub", eddsa_pub_enc);
TMH_RESPONSE_reply_json (connection, response, MHD_HTTP_OK);
printf ("Got something?\n");
return MHD_YES;
/* FRONTIER - CODE ABOVE STILL NOT TESTED */
}
}
}
}
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;
}
/**
* 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;
}
}
/**
* 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?) public 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)
{
char *keyfile;
unsigned int nmints;
unsigned int cnt;
struct MERCHANT_MintInfo *mint_infos;
void *keys_mgmt_cls;
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;
}