/*
This file is part of TALER
(C) 2014-2018 Taler Systems SA
TALER is free software; you can redistribute it and/or modify it under the
terms of the GNU Affero 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, see
*/
/**
* @file merchant/backend/taler-merchant-httpd.c
* @brief HTTP serving layer intended to perform crypto-work and
* communication with the exchange
* @author Marcello Stanisci
* @author Christian Grothoff
* @author Florian Dold
*/
#include "platform.h"
#include
#include
#include
#include
#include
#include
#include
#include "taler-merchant-httpd_responses.h"
#include "taler_merchantdb_lib.h"
#include "taler-merchant-httpd.h"
#include "taler-merchant-httpd_parsing.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_proposal.h"
#include "taler-merchant-httpd_pay.h"
#include "taler-merchant-httpd_track-transaction.h"
#include "taler-merchant-httpd_track-transfer.h"
#include "taler-merchant-httpd_tip-authorize.h"
#include "taler-merchant-httpd_tip-pickup.h"
#include "taler-merchant-httpd_tip-query.h"
#include "taler-merchant-httpd_tip-reserve-helper.h"
#include "taler-merchant-httpd_history.h"
#include "taler-merchant-httpd_refund.h"
#include "taler-merchant-httpd_check-payment.h"
#include "taler-merchant-httpd_trigger-pay.h"
/**
* Backlog for listen operation on unix-domain sockets.
*/
#define UNIX_BACKLOG 500
/**
* Hashmap pointing at merchant instances by 'id'. An 'id' is
* just a string that identifies a merchant instance. When a frontend
* needs to specify an instance to the backend, it does so by 'id'
*/
struct GNUNET_CONTAINER_MultiHashMap *by_id_map;
/**
* Hashmap pointing at merchant instances by public key. This map
* is mainly used to check whether there is more than one instance
* using the same key
*/
struct GNUNET_CONTAINER_MultiHashMap *by_kpub_map;
/**
* The port we are running on
*/
static long long unsigned port;
/**
* This value tells the exchange by which date this merchant would like
* to receive the funds for a deposited payment
*/
struct GNUNET_TIME_Relative wire_transfer_delay;
/**
* Locations from the configuration. Mapping from
* label to location data.
*/
json_t *default_locations;
/**
* If the frontend does NOT specify a payment deadline, how long should
* offers we make be valid by default?
*/
struct GNUNET_TIME_Relative default_pay_deadline;
/**
* Default maximum wire fee to assume, unless stated differently in the proposal
* already.
*/
struct TALER_Amount default_max_wire_fee;
/**
* Default max deposit fee that the merchant is willing to
* pay; if deposit costs more, then the customer will cover
* the difference.
*/
struct TALER_Amount default_max_deposit_fee;
/**
* Default factor for wire fee amortization.
*/
unsigned long long default_wire_fee_amortization;
/**
* Should a "Connection: close" header be added to each HTTP response?
*/
int TMH_merchant_connection_close;
/**
* Which currency do we use?
*/
char *TMH_currency;
/**
* Task running the HTTP server.
*/
static struct GNUNET_SCHEDULER_Task *mhd_task;
/**
* Global return code
*/
static int result;
/**
* Connection handle to the our database
*/
struct TALER_MERCHANTDB_Plugin *db;
/**
* The MHD Daemon
*/
static struct MHD_Daemon *mhd;
/**
* Path for the unix domain-socket
* to run the daemon on.
*/
static char *serve_unixpath;
/**
* File mode for unix-domain socket.
*/
static mode_t unixpath_mode;
/**
* 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)
{
static struct TMH_RequestHandler handlers[] =
{
/* Landing page, tell humans to go away. */
{ "/", MHD_HTTP_METHOD_GET, "text/plain",
"Hello, I'm a merchant's Taler backend. This HTTP server is not for humans.\n", 0,
&TMH_MHD_handler_static_response, MHD_HTTP_OK },
{ "/public/pay", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,
&MH_handler_pay, MHD_HTTP_OK },
{ "/public/pay", NULL, "text/plain",
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
{ "/track/transfer", MHD_HTTP_METHOD_GET, "application/json",
NULL, 0,
&MH_handler_track_transfer, MHD_HTTP_OK},
{ "/track/transfer", NULL, "text/plain",
"Only GET is allowed", 0,
&TMH_MHD_handler_static_response, MHD_HTTP_OK},
{ "/track/transaction", MHD_HTTP_METHOD_GET, "application/json",
NULL, 0,
&MH_handler_track_transaction, MHD_HTTP_OK},
{ "/track/transaction", NULL, "text/plain",
"Only GET is allowed", 0,
&TMH_MHD_handler_static_response, MHD_HTTP_OK},
{ "/history", MHD_HTTP_METHOD_GET, "text/plain",
"Only GET is allowed", 0,
&MH_handler_history, MHD_HTTP_OK},
{ "/order", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,
&MH_handler_proposal_put, MHD_HTTP_OK },
{ "/public/proposal", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_proposal_lookup, MHD_HTTP_OK},
{ "/proposal", NULL, "text/plain",
"Only GET/POST are allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED },
{ "/refund", MHD_HTTP_METHOD_POST, "application/json",
NULL, 0,
&MH_handler_refund_increase, MHD_HTTP_OK},
{ "/public/refund", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_refund_lookup, MHD_HTTP_OK},
{ "/refund", NULL, "application/json",
"Only POST/GET are allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
{ "/tip-authorize", MHD_HTTP_METHOD_POST, "text/plain",
NULL, 0,
&MH_handler_tip_authorize, MHD_HTTP_OK},
{ "/tip-authorize", NULL, "application/json",
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
/* backwards compatibility alias for /public/tip-pickup */
{ "/tip-pickup", MHD_HTTP_METHOD_POST, "text/plain",
NULL, 0,
&MH_handler_tip_pickup, MHD_HTTP_OK},
{ "/public/tip-pickup", MHD_HTTP_METHOD_POST, "text/plain",
NULL, 0,
&MH_handler_tip_pickup, MHD_HTTP_OK},
{ "/tip-pickup", NULL, "application/json",
"Only POST is allowed", 0,
&TMH_MHD_handler_send_json_pack_error, MHD_HTTP_METHOD_NOT_ALLOWED},
{ "/tip-query", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_tip_query, MHD_HTTP_OK},
{ "/check-payment", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_check_payment, MHD_HTTP_OK},
{ "/public/trigger-pay", MHD_HTTP_METHOD_GET, "text/plain",
NULL, 0,
&MH_handler_trigger_pay, MHD_HTTP_OK},
{NULL, NULL, NULL, NULL, 0, 0 }
};
static struct TMH_RequestHandler h404 =
{
"", NULL, "text/html",
"404: not found", 0,
&TMH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling request (%s) for URL `%s'\n",
method,
url);
for (unsigned int i=0;NULL != handlers[i].url;i++)
{
struct TMH_RequestHandler *rh = &handlers[i];
if ( (0 == strcasecmp (url,
rh->url)) &&
( (NULL == rh->method) ||
(0 == strcasecmp (method,
rh->method)) ) )
{
struct TM_HandlerContext *hc;
int ret;
ret = rh->handler (rh,
connection,
con_cls,
upload_data,
upload_data_size);
hc = *con_cls;
if (NULL != hc)
hc->rh = rh;
return ret;
}
}
return TMH_MHD_handler_static_response (&h404,
connection,
con_cls,
upload_data,
upload_data_size);
}
/**
* Callback that frees all the elements in the hashmap
*
* @param cls closure, NULL
* @param key current key
* @param value a `struct MerchantInstance`
*/
static int
hashmap_free (void *cls,
const struct GNUNET_HashCode *key,
void *value)
{
struct MerchantInstance *mi = value;
struct WireMethod *wm;
while (NULL != (wm = (mi->wm_head)))
{
GNUNET_CONTAINER_DLL_remove (mi->wm_head,
mi->wm_tail,
wm);
json_decref (wm->j_wire);
GNUNET_free (wm->wire_method);
GNUNET_free (wm);
}
GNUNET_free (mi->id);
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free_non_null (mi->tip_exchange);
GNUNET_free (mi);
return GNUNET_YES;
}
/**
* Shutdown task (magically invoked when the application is being
* quit)
*
* @param cls NULL
*/
static void
do_shutdown (void *cls)
{
MH_force_pc_resume ();
MH_force_trh_resume ();
if (NULL != mhd_task)
{
GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL;
}
if (NULL != mhd)
{
MHD_stop_daemon (mhd);
mhd = NULL;
}
if (NULL != db)
{
TALER_MERCHANTDB_plugin_unload (db);
db = NULL;
}
TMH_EXCHANGES_done ();
TMH_AUDITORS_done ();
GNUNET_CONTAINER_multihashmap_iterate (by_id_map,
&hashmap_free,
NULL);
if (NULL != by_id_map)
{
GNUNET_CONTAINER_multihashmap_destroy (by_id_map);
by_id_map = NULL;
}
if (NULL != by_kpub_map)
{
GNUNET_CONTAINER_multihashmap_destroy (by_kpub_map);
by_kpub_map = NULL;
}
}
/**
* Function called whenever MHD is done with a request. If the
* request was a POST, we may have stored a `struct Buffer *` in the
* @a con_cls that might still need to be cleaned up. Call the
* respective function to free the memory.
*
* @param cls client-defined closure
* @param connection connection handle
* @param con_cls value as set by the last call to
* the #MHD_AccessHandlerCallback
* @param toe reason for request termination
* @see #MHD_OPTION_NOTIFY_COMPLETED
* @ingroup request
*/
static void
handle_mhd_completion_callback (void *cls,
struct MHD_Connection *connection,
void **con_cls,
enum MHD_RequestTerminationCode toe)
{
struct TM_HandlerContext *hc = *con_cls;
if (NULL == hc)
return;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Finished handling request for `%s' with status %d\n",
hc->rh->url,
(int) toe);
hc->cc (hc);
*con_cls = NULL;
}
/**
* Function that queries MHD's select sets and
* starts the task waiting for them.
*/
static struct GNUNET_SCHEDULER_Task *
prepare_daemon (void);
/**
* Set if we should immediately #MHD_run again.
*/
static int triggered;
/**
* Call MHD to process pending requests and then go back
* and schedule the next run.
*
* @param cls the `struct MHD_Daemon` of the HTTP server to run
*/
static void
run_daemon (void *cls)
{
mhd_task = NULL;
do {
triggered = 0;
GNUNET_assert (MHD_YES == MHD_run (mhd));
} while (0 != triggered);
mhd_task = prepare_daemon ();
}
/**
* Kick MHD to run now, to be called after MHD_resume_connection().
* Basically, we need to explicitly resume MHD's event loop whenever
* we made progress serving a request. This function re-schedules
* the task processing MHD's activities to run immediately.
*/
void
TMH_trigger_daemon ()
{
if (NULL != mhd_task)
{
GNUNET_SCHEDULER_cancel (mhd_task);
mhd_task = NULL;
run_daemon (NULL);
}
else
{
triggered = 1;
}
}
/**
* Function that queries MHD's select sets and
* starts the task waiting for them.
*
* @param daemon_handle HTTP server to prepare to run
*/
static struct GNUNET_SCHEDULER_Task *
prepare_daemon ()
{
struct GNUNET_SCHEDULER_Task * ret;
fd_set rs;
fd_set ws;
fd_set es;
struct GNUNET_NETWORK_FDSet *wrs;
struct GNUNET_NETWORK_FDSet *wws;
int max;
MHD_UNSIGNED_LONG_LONG timeout;
int haveto;
struct GNUNET_TIME_Relative tv;
FD_ZERO (&rs);
FD_ZERO (&ws);
FD_ZERO (&es);
wrs = GNUNET_NETWORK_fdset_create ();
wws = GNUNET_NETWORK_fdset_create ();
max = -1;
GNUNET_assert (MHD_YES ==
MHD_get_fdset (mhd,
&rs,
&ws,
&es,
&max));
haveto = MHD_get_timeout (mhd, &timeout);
if (haveto == MHD_YES)
tv.rel_value_us = (uint64_t) timeout * 1000LL;
else
tv = GNUNET_TIME_UNIT_FOREVER_REL;
GNUNET_NETWORK_fdset_copy_native (wrs, &rs, max + 1);
GNUNET_NETWORK_fdset_copy_native (wws, &ws, max + 1);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Adding run_daemon select task\n");
ret = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_HIGH,
tv,
wrs,
wws,
&run_daemon,
NULL);
GNUNET_NETWORK_fdset_destroy (wrs);
GNUNET_NETWORK_fdset_destroy (wws);
return ret;
}
/**
* Callback that looks for 'merchant-location-*' sections,
* and populates @a default_locations.
*
* @param cls closure
* @section section name this callback gets
*/
static void
locations_iterator_cb (void *cls,
const char *section)
{
static const char *keys[] = {
"country",
"city",
"state",
"region",
"province",
"zip_code",
"street",
"street_number",
NULL,
};
struct GNUNET_CONFIGURATION_Handle *cfg = cls;
const char *prefix = "merchant-location-";
const char *substr = strstr (section, prefix);
const char *locname;
json_t *loc;
if ( (NULL == substr) || (substr != section) )
return;
locname = section + strlen (prefix);
if (0 == strlen (locname))
return;
GNUNET_assert (json_is_object (default_locations));
loc = json_object ();
json_object_set_new (default_locations,
locname,
loc);
for (unsigned int pos = 0; NULL != keys[pos]; pos++)
{
char *val;
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (cfg,
section,
keys[pos],
&val))
{
json_object_set_new (loc,
keys[pos],
json_string (val));
GNUNET_free (val);
}
}
}
/**
* Closure for the #wireformat_iterator_cb().
*/
struct WireFormatIteratorContext
{
/**
* The global iteration context.
*/
struct IterateInstancesCls *iic;
/**
* The merchant instance we are currently building.
*/
struct MerchantInstance *mi;
/**
* Set to #GNUNET_YES if the default instance was found.
*/
int default_instance;
};
/**
* Callback that looks for 'account-*' sections,
* and populates our wire method according to the data
*
* @param cls closure with a `struct WireFormatIteratorContext *`
* @section section name this callback gets
*/
static void
wireformat_iterator_cb (void *cls,
const char *section)
{
struct WireFormatIteratorContext *wfic = cls;
struct MerchantInstance *mi = wfic->mi;
struct IterateInstancesCls *iic = wfic->iic;
char *instance_option;
struct WireMethod *wm;
char *payto;
char *fn;
char *plugin_name;
struct TALER_WIRE_Plugin *plugin;
json_t *j;
enum TALER_ErrorCode ec;
struct GNUNET_HashCode h_wire;
if (0 != strncasecmp (section,
"account-",
strlen ("account-")))
return;
GNUNET_asprintf (&instance_option,
"HONOR_%s",
mi->id);
if (GNUNET_YES !=
GNUNET_CONFIGURATION_get_value_yesno (iic->config,
section,
instance_option))
{
GNUNET_free (instance_option);
return;
}
GNUNET_free (instance_option);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (iic->config,
section,
"URL",
&payto))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"URL");
iic->ret = GNUNET_SYSERR;
return;
}
/* check payto://-URL is well-formed and matches plugin */
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (iic->config,
section,
"PLUGIN",
&plugin_name))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"PLUGIN");
GNUNET_free (payto);
iic->ret = GNUNET_SYSERR;
return;
}
if (NULL ==
(plugin = TALER_WIRE_plugin_load (iic->config,
plugin_name)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to load wire plugin `%s'\n",
plugin_name);
GNUNET_free (plugin_name);
GNUNET_free (payto);
iic->ret = GNUNET_SYSERR;
return;
}
if (TALER_EC_NONE !=
(ec = plugin->wire_validate (plugin->cls,
payto)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"payto:// URL `%s' not supported by plugin `%s'\n",
payto,
plugin_name);
GNUNET_free (plugin_name);
GNUNET_free (payto);
TALER_WIRE_plugin_unload (plugin);
iic->ret = GNUNET_SYSERR;
return;
}
TALER_WIRE_plugin_unload (plugin);
GNUNET_free (plugin_name);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (iic->config,
section,
"WIRE_RESPONSE",
&fn))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"WIRE_RESPONSE");
GNUNET_free (payto);
iic->ret = GNUNET_SYSERR;
return;
}
/* Try loading existing JSON from file */
if (GNUNET_YES ==
GNUNET_DISK_file_test (fn))
{
json_error_t err;
char *url;
if (NULL ==
(j = json_load_file (fn,
JSON_REJECT_DUPLICATES,
&err)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to load JSON from `%s': %s at %d:%d\n",
fn,
err.text,
err.line,
err.column);
GNUNET_free (fn);
GNUNET_free (payto);
iic->ret = GNUNET_SYSERR;
return;
}
url = TALER_JSON_wire_to_payto (j);
if (NULL == url)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"URL missing in `%s', disabling account `%s'\n",
fn,
section);
GNUNET_free (fn);
GNUNET_free (payto);
iic->ret = GNUNET_SYSERR;
return;
}
if (0 != strcasecmp (url,
payto))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"URL `%s' does not match configuration `%s', disabling account `%s'\n",
url,
payto,
section);
GNUNET_free (fn);
GNUNET_free (payto);
GNUNET_free (url);
iic->ret = GNUNET_SYSERR;
return;
}
GNUNET_free (url);
}
else /* need to generate JSON */
{
struct GNUNET_HashCode salt;
char *salt_str;
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
&salt,
sizeof (salt));
salt_str = GNUNET_STRINGS_data_to_string_alloc (&salt,
sizeof (salt));
j = json_pack ("{s:s, s:s}",
"url", payto,
"salt", salt_str);
GNUNET_free (salt_str);
if (0 != json_dump_file (j,
fn,
JSON_COMPACT | JSON_SORT_KEYS))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to write hashed wire details to `%s'\n",
fn);
GNUNET_free (fn);
GNUNET_free (payto);
json_decref (j);
iic->ret = GNUNET_SYSERR;
return;
}
}
GNUNET_free (fn);
if (GNUNET_OK !=
TALER_JSON_merchant_wire_signature_hash (j,
&h_wire))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to hash wire input\n");
GNUNET_free (fn);
GNUNET_free (payto);
json_decref (j);
iic->ret = GNUNET_SYSERR;
return;
}
wm = GNUNET_new (struct WireMethod);
wm->wire_method = TALER_WIRE_payto_get_method (payto);
GNUNET_free (payto);
GNUNET_asprintf (&instance_option,
"ACTIVE_%s",
mi->id);
wm->active = GNUNET_CONFIGURATION_get_value_yesno (iic->config,
section,
instance_option);
GNUNET_free (instance_option);
if (GNUNET_YES == wm->active)
GNUNET_CONTAINER_DLL_insert (mi->wm_head,
mi->wm_tail,
wm);
else
GNUNET_CONTAINER_DLL_insert_tail (mi->wm_head,
mi->wm_tail,
wm);
wm->j_wire = j;
wm->h_wire = h_wire;
}
/**
* Callback that looks for 'instance-*' sections,
* and populates accordingly each instance's data
*
* @param cls closure of type `struct IterateInstancesCls`
* @section section name this callback gets
*/
static void
instances_iterator_cb (void *cls,
const char *section)
{
struct IterateInstancesCls *iic = cls;
char *token;
struct MerchantInstance *mi;
struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
/* used as hashmap keys */
struct GNUNET_HashCode h_pk;
struct GNUNET_HashCode h_id;
if (0 != strncasecmp (section,
"instance-",
strlen ("instance-")))
return;
/** Get id **/
token = strrchr (section, '-');
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Extracted token: %s\n",
token + 1);
mi = GNUNET_new (struct MerchantInstance);
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (iic->config,
section,
"NAME",
&mi->name))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"NAME");
GNUNET_free (mi);
iic->ret = GNUNET_SYSERR;
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (iic->config,
section,
"KEYFILE",
&mi->keyfile))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"KEYFILE");
GNUNET_free (mi->name);
GNUNET_free (mi);
iic->ret = GNUNET_SYSERR;
return;
}
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (iic->config,
section,
"TIP_EXCHANGE",
&mi->tip_exchange))
{
char *tip_reserves;
struct GNUNET_CRYPTO_EddsaPrivateKey *pk;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (iic->config,
section,
"TIP_RESERVE_PRIV_FILENAME",
&tip_reserves))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
section,
"TIP_RESERVE_PRIV_FILENAME");
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free (mi);
iic->ret = GNUNET_SYSERR;
return;
}
pk = GNUNET_CRYPTO_eddsa_key_create_from_file (tip_reserves);
if (NULL == pk)
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
section,
"TIP_RESERVE_PRIV_FILENAME",
"Failed to read private key");
GNUNET_free (tip_reserves);
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free (mi);
iic->ret = GNUNET_SYSERR;
return;
}
mi->tip_reserve.eddsa_priv = *pk;
GNUNET_free (pk);
GNUNET_free (tip_reserves);
}
if (GNUNET_YES !=
GNUNET_DISK_file_test (mi->keyfile))
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Merchant private key `%s' does not exist yet, creating it!\n",
mi->keyfile);
if (NULL ==
(pk = GNUNET_CRYPTO_eddsa_key_create_from_file (mi->keyfile)))
{
GNUNET_break (0);
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free (mi);
iic->ret = GNUNET_SYSERR;
return;
}
mi->privkey.eddsa_priv = *pk;
GNUNET_CRYPTO_eddsa_key_get_public (pk,
&mi->pubkey.eddsa_pub);
GNUNET_free (pk);
mi->id = GNUNET_strdup (token + 1);
if (0 == strcasecmp ("default",
mi->id))
iic->default_instance = GNUNET_YES;
GNUNET_CRYPTO_hash (mi->id,
strlen (mi->id),
&h_id);
if (GNUNET_OK !=
GNUNET_CONTAINER_multihashmap_put (by_id_map,
&h_id,
mi,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to put an entry into the 'by_id' hashmap\n");
iic->ret = GNUNET_SYSERR;
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free (mi);
return;
}
GNUNET_CRYPTO_hash (&mi->pubkey.eddsa_pub,
sizeof (struct GNUNET_CRYPTO_EddsaPublicKey),
&h_pk);
if (GNUNET_OK !=
GNUNET_CONTAINER_multihashmap_put (by_kpub_map,
&h_pk,
mi,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to put an entry into the 'by_kpub_map' hashmap\n");
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_remove (by_id_map,
&h_id,
mi));
iic->ret = GNUNET_SYSERR;
GNUNET_free (mi->keyfile);
GNUNET_free (mi->name);
GNUNET_free (mi);
return;
}
/* Initialize wireformats */
{
struct WireFormatIteratorContext wfic = {
.iic = iic,
.mi = mi
};
GNUNET_CONFIGURATION_iterate_sections (iic->config,
&wireformat_iterator_cb,
&wfic);
}
if (NULL == mi->wm_head)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to load wire formats for instance `%s'\n",
mi->id);
iic->ret = GNUNET_SYSERR;
}
}
/**
* Lookup a merchant instance by its name.
*
* @param name name of the instance to resolve
* @return NULL if that instance is unknown to us
*/
struct MerchantInstance *
TMH_lookup_instance (const char *name)
{
struct GNUNET_HashCode h_instance;
GNUNET_CRYPTO_hash (name,
strlen (name),
&h_instance);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Looking for by-id key %s of '%s' in hashmap\n",
GNUNET_h2s (&h_instance),
name);
/* We're fine if that returns NULL, the calling routine knows how
to handle that */
return GNUNET_CONTAINER_multihashmap_get (by_id_map,
&h_instance);
}
/**
* Extract merchant instance from the given JSON; if not
* 'instance' field was found, then "default" instance is
* returned.
*
* @param json the JSON to inspect; it is not required to
* comply with any particular format. It will only be checked
* if the field "instance" is there.
* @return a pointer to a `struct MerchantInstance`. This will be
* the 'default' merchant if the frontend did not specify any
* "instance" field. The user should not care to free the returned
* value, as it is taken from a global array that will be freed
* by the general shutdown routine. NULL if the frontend specified
* a wrong instance
*/
struct MerchantInstance *
TMH_lookup_instance_json (struct json_t *json)
{
struct json_t *instance;
const char *instance_str;
if (NULL == (instance = json_object_get (json, "instance")))
instance_str = "default";
else
instance_str = json_string_value (instance);
return TMH_lookup_instance (instance_str);
}
/**
* Iterate over locations in config in order to populate
* the location data.
*
* @param config configuration handle
* @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
*/
static void
iterate_locations (const struct GNUNET_CONFIGURATION_Handle *config)
{
GNUNET_assert (NULL == default_locations);
default_locations = json_object ();
GNUNET_CONFIGURATION_iterate_sections (config,
&locations_iterator_cb,
(void *) config);
}
/**
* Iterate over each merchant instance, in order to populate
* each instance's own data
*
* @param config configuration handle
* @return #GNUNET_OK if successful, #GNUNET_SYSERR upon errors
* (for example, if no "default" instance is defined)
*/
static int
iterate_instances (const struct GNUNET_CONFIGURATION_Handle *config)
{
struct IterateInstancesCls iic;
iic.config = config;
iic.default_instance = GNUNET_NO;
iic.ret = GNUNET_OK;
GNUNET_CONFIGURATION_iterate_sections (config,
&instances_iterator_cb,
&iic);
if (GNUNET_NO == iic.default_instance)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"No default merchant instance found\n");
return GNUNET_SYSERR;
}
if (GNUNET_OK != iic.ret)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"At least one instance was not successfully parsed\n");
return GNUNET_SYSERR;
}
return GNUNET_OK;
}
/**
* 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)
{
int fh;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting taler-merchant-httpd\n");
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
GNUNET_assert (GNUNET_OK ==
GNUNET_log_setup ("taler-merchant-httpd",
"WARNING",
NULL));
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_get_value_string (config,
"taler",
"CURRENCY",
&TMH_currency))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"taler",
"CURRENCY");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_SYSERR ==
TMH_EXCHANGES_init (config))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_SYSERR ==
TMH_AUDITORS_init (config))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (NULL ==
(by_id_map = GNUNET_CONTAINER_multihashmap_create (1,
GNUNET_NO)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (NULL ==
(by_kpub_map = GNUNET_CONTAINER_multihashmap_create (1,
GNUNET_NO)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_get_value_time (config,
"merchant",
"WIRE_TRANSFER_DELAY",
&wire_transfer_delay))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"WIRE_TRANSFER_DELAY");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_get_value_time (config,
"merchant",
"DEFAULT_PAY_DEADLINE",
&default_pay_deadline))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"DEFAULT_PAY_DEADLINE");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TALER_config_get_denom (config,
"merchant",
"DEFAULT_MAX_WIRE_FEE",
&default_max_wire_fee))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"DEFAULT_MAX_WIRE_FEE");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TALER_config_get_denom (config,
"merchant",
"DEFAULT_MAX_DEPOSIT_FEE",
&default_max_deposit_fee))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"DEFAULT_MAX_DEPOSIT_FEE");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_number (config,
"merchant",
"DEFAULT_WIRE_FEE_AMORTIZATION",
&default_wire_fee_amortization))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"DEFAULT_WIRE_FEE_AMORTIZATION");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
iterate_instances (config))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
iterate_locations (config);
if (NULL ==
(db = TALER_MERCHANTDB_plugin_load (config)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
db->initialize (db->cls))
{
GNUNET_break (0);
GNUNET_SCHEDULER_shutdown ();
return;
}
{
const char *choices[] = {"tcp", "unix"};
const char *serve_type;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_choice (config,
"merchant",
"serve",
choices,
&serve_type))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"serve",
"serve type required");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (0 == strcmp (serve_type, "unix"))
{
struct sockaddr_un *un;
char *mode;
struct GNUNET_NETWORK_Handle *nh;
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_filename (config,
"merchant",
"unixpath",
&serve_unixpath))
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"unixpath",
"unixpath required");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (strlen (serve_unixpath) >= sizeof (un->sun_path))
{
fprintf (stderr,
"Invalid configuration: unix path too long\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_string (config,
"merchant",
"UNIXPATH_MODE",
&mode))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"UNIXPATH_MODE");
GNUNET_SCHEDULER_shutdown ();
return;
}
errno = 0;
unixpath_mode = (mode_t) strtoul (mode, NULL, 8);
if (0 != errno)
{
GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"UNIXPATH_MODE",
"must be octal number");
GNUNET_free (mode);
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_free (mode);
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Creating listen socket '%s' with mode %o\n",
serve_unixpath, unixpath_mode);
if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (serve_unixpath))
{
GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
"mkdir",
serve_unixpath);
}
un = GNUNET_new (struct sockaddr_un);
un->sun_family = AF_UNIX;
strncpy (un->sun_path,
serve_unixpath,
sizeof (un->sun_path) - 1);
GNUNET_NETWORK_unix_precheck (un);
if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX,
SOCK_STREAM,
0)))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"socket(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (nh,
(void *) un,
sizeof (struct sockaddr_un)))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"bind(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_NETWORK_socket_listen (nh,
UNIX_BACKLOG))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"listen(AF_UNIX)");
GNUNET_SCHEDULER_shutdown ();
return;
}
fh = GNUNET_NETWORK_get_fd (nh);
GNUNET_NETWORK_socket_free_memory_only_ (nh);
if (0 != chmod (serve_unixpath,
unixpath_mode))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"chmod");
GNUNET_SCHEDULER_shutdown ();
return;
}
port = 0;
}
else if (0 == strcmp (serve_type, "tcp"))
{
char *bind_to;
if (GNUNET_SYSERR ==
GNUNET_CONFIGURATION_get_value_number (config,
"merchant",
"PORT",
&port))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"PORT");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK ==
GNUNET_CONFIGURATION_get_value_string (config,
"merchant",
"BIND_TO",
&bind_to))
{
char port_str[6];
struct addrinfo hints;
struct addrinfo *res;
int ec;
struct GNUNET_NETWORK_Handle *nh;
GNUNET_snprintf (port_str,
sizeof (port_str),
"%u",
(uint16_t) port);
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE
#ifdef AI_IDN
| AI_IDN
#endif
;
if (0 !=
(ec = getaddrinfo (bind_to,
port_str,
&hints,
&res)))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to resolve BIND_TO address `%s': %s\n",
bind_to,
gai_strerror (ec));
GNUNET_free (bind_to);
GNUNET_SCHEDULER_shutdown ();
return;
}
GNUNET_free (bind_to);
if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family,
res->ai_socktype,
res->ai_protocol)))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"socket");
freeaddrinfo (res);
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_NETWORK_socket_bind (nh,
res->ai_addr,
res->ai_addrlen))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"bind");
freeaddrinfo (res);
GNUNET_SCHEDULER_shutdown ();
return;
}
freeaddrinfo (res);
if (GNUNET_OK !=
GNUNET_NETWORK_socket_listen (nh,
UNIX_BACKLOG))
{
GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
"listen");
GNUNET_SCHEDULER_shutdown ();
return;
}
fh = GNUNET_NETWORK_get_fd (nh);
GNUNET_NETWORK_socket_free_memory_only_ (nh);
}
else
{
fh = -1;
}
}
else
{
// not reached
GNUNET_assert (0);
}
}
mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
port,
NULL, NULL,
&url_handler, NULL,
MHD_OPTION_LISTEN_SOCKET, fh,
MHD_OPTION_NOTIFY_COMPLETED, &handle_mhd_completion_callback, NULL,
MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 10 /* 10s */,
MHD_OPTION_END);
if (NULL == mhd)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to launch HTTP service, exiting.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
result = GNUNET_OK;
mhd_task = prepare_daemon ();
}
/**
* 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)
{
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag ('C',
"connection-close",
"force HTTP connections to be closed after each request",
&TMH_merchant_connection_close),
GNUNET_GETOPT_OPTION_END
};
if (GNUNET_OK !=
GNUNET_PROGRAM_run (argc, argv,
"taler-merchant-httpd",
"Taler merchant's HTTP backend interface",
options, &run, NULL))
return 3;
return (GNUNET_OK == result) ? 0 : 1;
}