/*
This file is part of TALER
(C) 2014-2022 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 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 "taler-merchant-httpd_auditors.h"
#include "taler-merchant-httpd_config.h"
#include "taler-merchant-httpd_exchanges.h"
#include "taler-merchant-httpd_get-orders-ID.h"
#include "taler-merchant-httpd_get-tips-ID.h"
#include "taler-merchant-httpd_mhd.h"
#include "taler-merchant-httpd_private-delete-instances-ID.h"
#include "taler-merchant-httpd_private-delete-products-ID.h"
#include "taler-merchant-httpd_private-delete-orders-ID.h"
#include "taler-merchant-httpd_private-delete-reserves-ID.h"
#include "taler-merchant-httpd_private-delete-transfers-ID.h"
#include "taler-merchant-httpd_private-get-instances.h"
#include "taler-merchant-httpd_private-get-instances-ID.h"
#include "taler-merchant-httpd_private-get-instances-ID-kyc.h"
#include "taler-merchant-httpd_private-get-products.h"
#include "taler-merchant-httpd_private-get-products-ID.h"
#include "taler-merchant-httpd_private-get-orders.h"
#include "taler-merchant-httpd_private-get-orders-ID.h"
#include "taler-merchant-httpd_private-get-reserves.h"
#include "taler-merchant-httpd_private-get-reserves-ID.h"
#include "taler-merchant-httpd_private-get-tips-ID.h"
#include "taler-merchant-httpd_private-get-tips.h"
#include "taler-merchant-httpd_private-get-transfers.h"
#include "taler-merchant-httpd_private-patch-instances-ID.h"
#include "taler-merchant-httpd_private-patch-orders-ID-forget.h"
#include "taler-merchant-httpd_private-patch-products-ID.h"
#include "taler-merchant-httpd_private-post-instances.h"
#include "taler-merchant-httpd_private-post-instances-ID-auth.h"
#include "taler-merchant-httpd_private-post-orders.h"
#include "taler-merchant-httpd_private-post-orders-ID-refund.h"
#include "taler-merchant-httpd_private-post-products.h"
#include "taler-merchant-httpd_private-post-products-ID-lock.h"
#include "taler-merchant-httpd_private-post-reserves.h"
#include "taler-merchant-httpd_private-post-reserves-ID-authorize-tip.h"
#include "taler-merchant-httpd_private-post-transfers.h"
#include "taler-merchant-httpd_post-orders-ID-abort.h"
#include "taler-merchant-httpd_post-orders-ID-claim.h"
#include "taler-merchant-httpd_post-orders-ID-paid.h"
#include "taler-merchant-httpd_post-orders-ID-pay.h"
#include "taler-merchant-httpd_post-orders-ID-refund.h"
#include "taler-merchant-httpd_post-tips-ID-pickup.h"
#include "taler-merchant-httpd_reserves.h"
#include "taler-merchant-httpd_spa.h"
#include "taler-merchant-httpd_statics.h"
#include "taler-merchant-httpd_templating.h"
/**
* Fixme: document.
*/
#define INSTANCE_STALENESS GNUNET_TIME_UNIT_MINUTES
/**
* Backlog for listen operation on unix-domain sockets.
*/
#define UNIX_BACKLOG 500
/**
* Default maximum upload size permitted. Can be overridden
* per handler.
*/
#define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024)
/**
* Which currency do we use?
*/
char *TMH_currency;
/**
* Inform the auditor for all deposit confirmations (global option)
*/
int TMH_force_audit;
/**
* Connection handle to the our database
*/
struct TALER_MERCHANTDB_Plugin *TMH_db;
/**
* Event handler for instance settings changes.
*/
static struct GNUNET_DB_EventHandler *instance_eh;
/**
* 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 *TMH_by_id_map;
/**
* How long do we need to keep information on paid contracts on file for tax
* or other legal reasons? Used to block deletions for younger transaction
* data.
*/
struct GNUNET_TIME_Relative TMH_legal_expiration;
/**
* The port we are running on
*/
static uint16_t port;
/**
* Should a "Connection: close" header be added to each HTTP response?
*/
static int merchant_connection_close;
/**
* Global return code
*/
static int result;
/**
* Our configuration.
*/
static const struct GNUNET_CONFIGURATION_Handle *cfg;
/**
* Initial authorization token.
*/
char *TMH_default_auth;
enum GNUNET_GenericReturnValue
TMH_check_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
struct TALER_MerchantAuthenticationHashP *hash)
{
struct GNUNET_HashCode val;
char *dec;
size_t dec_len;
if (GNUNET_is_zero (hash))
return GNUNET_OK;
if (NULL == token)
return GNUNET_SYSERR;
dec_len = GNUNET_STRINGS_urldecode (token,
strlen (token),
&dec);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Checking against token with salt %s\n",
TALER_B2S (salt));
GNUNET_assert (GNUNET_YES ==
GNUNET_CRYPTO_kdf (&val,
sizeof (val),
salt,
sizeof (*salt),
dec,
dec_len,
"merchant-instance-auth",
strlen ("merchant-instance-auth"),
NULL,
0));
GNUNET_free (dec);
return (0 == GNUNET_memcmp (&val,
&hash->hash))
? GNUNET_OK
: GNUNET_SYSERR;
}
void
TMH_compute_auth (const char *token,
struct TALER_MerchantAuthenticationSaltP *salt,
struct TALER_MerchantAuthenticationHashP *hash)
{
GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
salt,
sizeof (*salt));
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Computing initial auth using token with salt %s\n",
TALER_B2S (salt));
GNUNET_assert (GNUNET_YES ==
GNUNET_CRYPTO_kdf (hash,
sizeof (*hash),
salt,
sizeof (*salt),
token,
strlen (token),
"merchant-instance-auth",
strlen ("merchant-instance-auth"),
NULL,
0));
}
void
TMH_instance_decref (struct TMH_MerchantInstance *mi)
{
struct TMH_WireMethod *wm;
mi->rc--;
if (0 != mi->rc)
return;
TMH_force_get_orders_resume (mi);
while (NULL != (wm = (mi->wm_head)))
{
GNUNET_CONTAINER_DLL_remove (mi->wm_head,
mi->wm_tail,
wm);
GNUNET_free (wm->payto_uri);
GNUNET_free (wm->wire_method);
GNUNET_free (wm);
}
GNUNET_free (mi->settings.id);
GNUNET_free (mi->settings.name);
GNUNET_free (mi->settings.email);
GNUNET_free (mi->settings.website);
GNUNET_free (mi->settings.logo);
json_decref (mi->settings.address);
json_decref (mi->settings.jurisdiction);
GNUNET_free (mi);
}
/**
* Callback that frees an instances removing
* it from the global hashmap.
*
* @param cls closure, NULL
* @param key current key
* @param value a `struct TMH_MerchantInstance`
*/
int
TMH_instance_free_cb (void *cls,
const struct GNUNET_HashCode *key,
void *value)
{
struct TMH_MerchantInstance *mi = value;
(void) cls;
(void) key;
GNUNET_assert (GNUNET_OK ==
GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map,
&mi->h_instance,
mi));
TMH_instance_decref (mi);
return GNUNET_YES;
}
/**
* Shutdown task (magically invoked when the application is being
* quit)
*
* @param cls NULL
*/
static void
do_shutdown (void *cls)
{
(void) cls;
TMH_force_ac_resume ();
TMH_force_pc_resume ();
TMH_force_kyc_resume ();
TMH_force_rc_resume ();
TMH_force_gorc_resume ();
TMH_force_post_transfers_resume ();
TMH_force_tip_pickup_resume ();
TMH_force_wallet_get_order_resume ();
TMH_force_wallet_refund_order_resume ();
{
struct MHD_Daemon *mhd;
mhd = TALER_MHD_daemon_stop ();
if (NULL != mhd)
MHD_stop_daemon (mhd);
}
TMH_RESERVES_done ();
if (NULL != instance_eh)
{
TMH_db->event_listen_cancel (instance_eh);
instance_eh = NULL;
}
if (NULL != TMH_db)
{
TALER_MERCHANTDB_plugin_unload (TMH_db);
TMH_db = NULL;
}
TMH_EXCHANGES_done ();
TMH_AUDITORS_done ();
if (NULL != TMH_by_id_map)
{
GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map,
&TMH_instance_free_cb,
NULL);
GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map);
TMH_by_id_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 TMH_HandlerContext *hc = *con_cls;
if (NULL == hc)
return;
GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
{
#if MHD_VERSION >= 0x00097304
const union MHD_ConnectionInfo *ci;
unsigned int http_status = 0;
ci = MHD_get_connection_info (connection,
MHD_CONNECTION_INFO_HTTP_STATUS);
if (NULL != ci)
http_status = ci->http_status;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Request for `%s' completed with HTTP status %u (%d)\n",
hc->url,
http_status,
toe);
#else
(void) connection;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Finished handling request for `%s' with MHD termination code %d\n",
hc->url,
(int) toe);
#endif
}
if (NULL != hc->cc)
hc->cc (hc->ctx);
TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context);
GNUNET_free (hc->infix);
if (NULL != hc->request_body)
json_decref (hc->request_body);
if (NULL != hc->instance)
TMH_instance_decref (hc->instance);
GNUNET_free (hc);
*con_cls = NULL;
}
struct TMH_MerchantInstance *
TMH_lookup_instance (const char *instance_id)
{
struct GNUNET_HashCode h_instance;
if (NULL == instance_id)
instance_id = "default";
GNUNET_CRYPTO_hash (instance_id,
strlen (instance_id),
&h_instance);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Looking for by-id key %s of '%s' in hashmap\n",
GNUNET_h2s (&h_instance),
instance_id);
/* We're fine if that returns NULL, the calling routine knows how
to handle that */
return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map,
&h_instance);
}
/**
* Add instance definition to our active set of instances.
*
* @param[in,out] mi merchant instance details to define
* @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already
*/
enum GNUNET_GenericReturnValue
TMH_add_instance (struct TMH_MerchantInstance *mi)
{
const char *id;
int ret;
id = mi->settings.id;
if (NULL == id)
id = "default";
GNUNET_CRYPTO_hash (id,
strlen (id),
&mi->h_instance);
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Looking for by-id key %s of `%s' in hashmap\n",
GNUNET_h2s (&mi->h_instance),
id);
ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map,
&mi->h_instance,
mi,
GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY);
if (GNUNET_OK == ret)
{
GNUNET_assert (mi->rc < UINT_MAX);
mi->rc++;
}
return ret;
}
/**
* Handle a OPTIONS "*" request.
*
* @param rh context of the handler
* @param connection the MHD connection to handle
* @param[in,out] hc context with further information about the request
* @return MHD result code
*/
static MHD_RESULT
handle_server_options (const struct TMH_RequestHandler *rh,
struct MHD_Connection *connection,
struct TMH_HandlerContext *hc)
{
return TALER_MHD_reply_cors_preflight (connection);
}
/**
* Extract the token from authorization header value @a auth.
*
* @param auth pointer to authorization header value,
* will be updated to point to the start of the token
* or set to NULL if header value is invalid
*/
static void
extract_token (const char **auth)
{
const char *bearer = "Bearer ";
const char *tok = *auth;
if (0 != strncmp (tok, bearer, strlen (bearer)))
{
*auth = NULL;
return;
}
tok = tok + strlen (bearer);
while (' ' == *tok)
tok++;
if (0 != strncasecmp (tok,
RFC_8959_PREFIX,
strlen (RFC_8959_PREFIX)))
{
*auth = NULL;
return;
}
*auth = tok;
}
/**
* Checks if the @a rh matches the given (parsed) URL.
*
* @param rh handler to compare against
* @param url the main URL (without "/private/" prefix, if any)
* @param prefix_strlen length of the prefix, i.e. 8 for '/orders/' or 7 for '/config'
* @param infix_url infix text, i.e. "$ORDER_ID".
* @param infix_strlen length of the string in @a infix_url
* @param suffix_url suffix, i.e. "/refund", including the "/"
* @param suffix_strlen number of characters in @a suffix_url
* @return true if @a rh matches this request
*/
static bool
prefix_match (const struct TMH_RequestHandler *rh,
const char *url,
size_t prefix_strlen,
const char *infix_url,
size_t infix_strlen,
const char *suffix_url,
size_t suffix_strlen)
{
if ( (prefix_strlen != strlen (rh->url_prefix)) ||
(0 != memcmp (url,
rh->url_prefix,
prefix_strlen)) )
return false;
if (! rh->have_id_segment)
{
/* Require /$PREFIX/$SUFFIX or /$PREFIX */
if (NULL != suffix_url)
return false; /* too many segments to match */
if ( (NULL == infix_url) /* either or */
^ (NULL == rh->url_suffix) )
return false; /* suffix existence mismatch */
/* If /$PREFIX/$SUFFIX, check $SUFFIX matches */
if ( (NULL != infix_url) &&
( (infix_strlen != strlen (rh->url_suffix)) ||
(0 != memcmp (infix_url,
rh->url_suffix,
infix_strlen)) ) )
return false; /* cannot use infix as suffix: content mismatch */
}
else
{
/* Require /$PREFIX/$ID or /$PREFIX/$ID/$SUFFIX */
if (NULL == infix_url)
return false; /* infix existence mismatch */
if ( ( (NULL == suffix_url)
^ (NULL == rh->url_suffix) ) )
return false; /* suffix existence mismatch */
if ( (NULL != suffix_url) &&
( (suffix_strlen != strlen (rh->url_suffix)) ||
(0 != memcmp (suffix_url,
rh->url_suffix,
suffix_strlen)) ) )
return false; /* suffix content mismatch */
}
return true;
}
/**
* 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 connection the MHD connection to handle
* @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 serious
* error while handling the request
*/
static MHD_RESULT
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 management_handlers[] = {
/* GET /instances */
{
.url_prefix = "/instances",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = true,
.default_only = true,
.handler = &TMH_private_get_instances
},
/* POST /instances */
{
.url_prefix = "/instances",
.method = MHD_HTTP_METHOD_POST,
.skip_instance = true,
.default_only = true,
.handler = &TMH_private_post_instances,
/* allow instance data of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* GET /instances/$ID/ */
{
.url_prefix = "/instances/",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = true,
.default_only = true,
.have_id_segment = true,
.handler = &TMH_private_get_instances_default_ID
},
/* DELETE /instances/$ID */
{
.url_prefix = "/instances/",
.method = MHD_HTTP_METHOD_DELETE,
.skip_instance = true,
.default_only = true,
.have_id_segment = true,
.handler = &TMH_private_delete_instances_default_ID
},
/* PATCH /instances/$ID */
{
.url_prefix = "/instances/",
.method = MHD_HTTP_METHOD_PATCH,
.skip_instance = true,
.default_only = true,
.have_id_segment = true,
.handler = &TMH_private_patch_instances_default_ID,
/* allow instance data of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* POST /auth: */
{
.url_prefix = "/instances/",
.url_suffix = "auth",
.method = MHD_HTTP_METHOD_POST,
.skip_instance = true,
.default_only = true,
.have_id_segment = true,
.handler = &TMH_private_post_instances_default_ID_auth,
/* Body should be pretty small. */
.max_upload = 1024 * 1024
},
/* POST /kyc: */
{
.url_prefix = "/instances/",
.url_suffix = "kyc",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = true,
.default_only = true,
.have_id_segment = true,
.handler = &TMH_private_get_instances_default_ID_kyc,
},
{
NULL
}
};
static struct TMH_RequestHandler private_handlers[] = {
/* GET /instances/$ID/: */
{
.url_prefix = "/",
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_instances_ID
},
/* DELETE /instances/$ID/: */
{
.url_prefix = "/",
.method = MHD_HTTP_METHOD_DELETE,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_instances_ID
},
/* PATCH /instances/$ID/: */
{
.url_prefix = "/",
.method = MHD_HTTP_METHOD_PATCH,
.handler = &TMH_private_patch_instances_ID,
.allow_deleted_instance = true,
/* allow instance data of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* POST /auth: */
{
.url_prefix = "/auth",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_instances_ID_auth,
/* Body should be pretty small. */
.max_upload = 1024 * 1024,
},
/* GET /kyc: */
{
.url_prefix = "/kyc",
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_instances_ID_kyc,
},
/* GET /products: */
{
.url_prefix = "/products",
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_products
},
/* POST /products: */
{
.url_prefix = "/products",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_products,
/* allow product data of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* GET /products/$ID/: */
{
.url_prefix = "/products/",
.method = MHD_HTTP_METHOD_GET,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_get_products_ID
},
/* DELETE /products/$ID/: */
{
.url_prefix = "/products/",
.method = MHD_HTTP_METHOD_DELETE,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_products_ID
},
/* PATCH /products/$ID/: */
{
.url_prefix = "/products/",
.method = MHD_HTTP_METHOD_PATCH,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_patch_products_ID,
/* allow product data of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* POST /products/$ID/lock: */
{
.url_prefix = "/products/",
.url_suffix = "lock",
.method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
.handler = &TMH_private_post_products_ID_lock,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /orders: */
{
.url_prefix = "/orders",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_orders,
/* allow contracts of up to 8 MB, that should be plenty;
note that exceeding #GNUNET_MAX_MALLOC_CHECKED (40 MB)
would require further changes to the allocation logic
in the code... */
.max_upload = 1024 * 1024 * 8
},
/* GET /orders/$ID: */
{
.url_prefix = "/orders/",
.method = MHD_HTTP_METHOD_GET,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_get_orders_ID
},
/* GET /orders: */
{
.url_prefix = "/orders",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
.handler = &TMH_private_get_orders
},
/* POST /orders/$ID/refund: */
{
.url_prefix = "/orders/",
.url_suffix = "refund",
.method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
.handler = &TMH_private_post_orders_ID_refund,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* PATCH /orders/$ID/forget: */
{
.url_prefix = "/orders/",
.url_suffix = "forget",
.method = MHD_HTTP_METHOD_PATCH,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_patch_orders_ID_forget,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* DELETE /orders/$ID: */
{
.url_prefix = "/orders/",
.method = MHD_HTTP_METHOD_DELETE,
.have_id_segment = true,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_orders_ID
},
/* POST /reserves: */
{
.url_prefix = "/reserves",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_reserves,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* DELETE /reserves/$ID: */
{
.url_prefix = "/reserves/",
.have_id_segment = true,
.allow_deleted_instance = true,
.method = MHD_HTTP_METHOD_DELETE,
.handler = &TMH_private_delete_reserves_ID
},
/* POST /reserves/$ID/authorize-tip: */
{
.url_prefix = "/reserves/",
.url_suffix = "authorize-tip",
.have_id_segment = true,
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_reserves_ID_authorize_tip,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /tips: */
{
.url_prefix = "/tips",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_private_post_tips,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* GET /tips: */
{
.url_prefix = "/tips",
.allow_deleted_instance = true,
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_tips
},
/* GET /tips/$ID: */
{
.url_prefix = "/tips/",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
.have_id_segment = true,
.handler = &TMH_private_get_tips_ID
},
/* GET /reserves: */
{
.url_prefix = "/reserves",
.allow_deleted_instance = true,
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_reserves
},
/* GET /reserves/$ID: */
{
.url_prefix = "/reserves/",
.allow_deleted_instance = true,
.have_id_segment = true,
.method = MHD_HTTP_METHOD_GET,
.handler = &TMH_private_get_reserves_ID
},
/* POST /transfers: */
{
.url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_POST,
.allow_deleted_instance = true,
.handler = &TMH_private_post_transfers,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* DELETE /transfers/$ID: */
{
.url_prefix = "/transfers/",
.method = MHD_HTTP_METHOD_DELETE,
.allow_deleted_instance = true,
.handler = &TMH_private_delete_transfers_ID,
.have_id_segment = true,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* GET /transfers: */
{
.url_prefix = "/transfers",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
.handler = &TMH_private_get_transfers
},
{
NULL
}
};
static struct TMH_RequestHandler public_handlers[] = {
{
.url_prefix = "/",
.method = MHD_HTTP_METHOD_GET,
.mime_type = "text/html",
.skip_instance = true,
.handler = &TMH_return_spa,
.response_code = MHD_HTTP_OK
},
{
.url_prefix = "/agpl",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = true,
.handler = &TMH_MHD_handler_agpl_redirect
},
{
.url_prefix = "/config",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = true,
.default_only = true,
.handler = &MH_handler_config
},
/* Also serve the same /config per instance */
{
.url_prefix = "/config",
.method = MHD_HTTP_METHOD_GET,
.skip_instance = false,
.allow_deleted_instance = true,
.handler = &MH_handler_config
},
/* POST /orders/$ID/abort: */
{
.url_prefix = "/orders/",
.have_id_segment = true,
.url_suffix = "abort",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_post_orders_ID_abort,
/* wallet may give us many coins to sign, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /orders/$ID/claim: */
{
.url_prefix = "/orders/",
.have_id_segment = true,
.url_suffix = "claim",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_post_orders_ID_claim,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /orders/$ID/pay: */
{
.url_prefix = "/orders/",
.have_id_segment = true,
.url_suffix = "pay",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_post_orders_ID_pay,
/* wallet may give us many coins to sign, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /orders/$ID/paid: */
{
.url_prefix = "/orders/",
.have_id_segment = true,
.allow_deleted_instance = true,
.url_suffix = "paid",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_post_orders_ID_paid,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* POST /orders/$ID/refund: */
{
.url_prefix = "/orders/",
.have_id_segment = true,
.allow_deleted_instance = true,
.url_suffix = "refund",
.method = MHD_HTTP_METHOD_POST,
.handler = &TMH_post_orders_ID_refund,
/* the body should be pretty small, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* GET /orders/$ID: */
{
.url_prefix = "/orders/",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
.have_id_segment = true,
.handler = &TMH_get_orders_ID
},
/* GET /tips/$ID: */
{
.url_prefix = "/tips/",
.method = MHD_HTTP_METHOD_GET,
.allow_deleted_instance = true,
.have_id_segment = true,
.handler = &TMH_get_tips_ID
},
/* POST /tips/$ID/pickup: */
{
.url_prefix = "/tips/",
.method = MHD_HTTP_METHOD_POST,
.have_id_segment = true,
.allow_deleted_instance = true,
.url_suffix = "pickup",
.handler = &TMH_post_tips_ID_pickup,
/* wallet may give us many coins to sign, allow 1 MB of upload
to set a conservative bound for sane wallets */
.max_upload = 1024 * 1024
},
/* GET /static/ *: */
{
.url_prefix = "/static/",
.method = MHD_HTTP_METHOD_GET,
.have_id_segment = true,
.handler = &TMH_return_static
},
{
.url_prefix = "*",
.method = MHD_HTTP_METHOD_OPTIONS,
.handler = &handle_server_options
},
{
NULL
}
};
struct TMH_HandlerContext *hc = *con_cls;
struct TMH_RequestHandler *handlers;
bool use_default = false;
(void) cls;
(void) version;
if (NULL != hc)
{
/* MHD calls us again for a request, for first call
see 'else' case below */
GNUNET_assert (NULL != hc->rh);
GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
if ( (hc->has_body) &&
(NULL == hc->request_body) )
{
size_t mul = hc->rh->max_upload;
enum GNUNET_GenericReturnValue res;
if (0 == mul)
mul = DEFAULT_MAX_UPLOAD_SIZE;
if ( (hc->total_upload + *upload_data_size < hc->total_upload) ||
(hc->total_upload + *upload_data_size > mul) )
{
/* Client exceeds upload limit. Should _usually_ be checked earlier
when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with
chunked encoding an uploader MAY have omitted this, and thus
not permitted us to check on time. In this case, we just close
the connection once it exceeds our limit (instead of waiting
for the upload to complete and then fail). This could theoretically
cause some clients to retry, alas broken or malicious clients
are likely to retry anyway, so little we can do about it, and
failing earlier seems the best option here. */
GNUNET_break_op (0);
return MHD_NO;
}
hc->total_upload += *upload_data_size;
res = TALER_MHD_parse_post_json (connection,
&hc->json_parse_context,
upload_data,
upload_data_size,
&hc->request_body);
if (GNUNET_SYSERR == res)
return MHD_NO;
/* A error response was already generated */
if ( (GNUNET_NO == res) ||
/* or, need more data to accomplish parsing */
(NULL == hc->request_body) )
return MHD_YES; /* let MHD call us *again* */
}
/* Upload complete (if any), call handler to generate reply */
return hc->rh->handler (hc->rh,
connection,
hc);
}
hc = GNUNET_new (struct TMH_HandlerContext);
*con_cls = hc;
GNUNET_async_scope_fresh (&hc->async_scope_id);
GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
hc->url = url;
{
const char *correlation_id;
correlation_id = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
"Taler-Correlation-Id");
if ( (NULL != correlation_id) &&
(GNUNET_YES !=
GNUNET_CURL_is_valid_scope_id (correlation_id)) )
{
GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
"Illegal incoming correlation ID\n");
correlation_id = NULL;
}
if (NULL != correlation_id)
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling request for (%s) URL '%s', correlation_id=%s\n",
method,
url,
correlation_id);
else
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Handling request (%s) for URL '%s'\n",
method,
url);
}
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_HEAD))
method = MHD_HTTP_METHOD_GET; /* MHD will deal with the rest */
/* Find out the merchant backend instance for the request.
* If there is an instance, remove the instance specification
* from the beginning of the request URL. */
{
const char *instance_prefix = "/instances/";
if (0 == strncmp (url,
instance_prefix,
strlen (instance_prefix)))
{
/* url starts with "/instances/" */
const char *istart = url + strlen (instance_prefix);
const char *slash = strchr (istart, '/');
char *instance_id;
if (NULL == slash)
instance_id = GNUNET_strdup (istart);
else
instance_id = GNUNET_strndup (istart,
slash - istart);
hc->instance = TMH_lookup_instance (instance_id);
GNUNET_free (instance_id);
if (NULL == slash)
url = "";
else
url = slash;
}
else
{
/* use 'default' */
use_default = true;
hc->instance = TMH_lookup_instance (NULL);
if ( (NULL != TMH_default_auth) &&
(NULL != hc->instance) )
{
/* Override default instance access control */
GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
"Overriding access control\n");
TMH_compute_auth (TMH_default_auth,
&hc->instance->auth.auth_salt,
&hc->instance->auth.auth_hash);
hc->instance->auth_override = true;
GNUNET_free (TMH_default_auth);
}
}
if (NULL != hc->instance)
{
GNUNET_assert (hc->instance->rc < UINT_MAX);
hc->instance->rc++;
}
}
{
const char *management_prefix = "/management/";
const char *private_prefix = "/private/";
if ( (0 == strncmp (url,
management_prefix,
strlen (management_prefix))) )
{
handlers = management_handlers;
url += strlen (management_prefix) - 1;
}
else if ( (0 == strncmp (url,
private_prefix,
strlen (private_prefix))) ||
(0 == strcmp (url,
"/private")) )
{
handlers = private_handlers;
url += strlen (private_prefix) - 1;
}
else
{
handlers = public_handlers;
}
}
if (0 == strcmp (url,
""))
url = "/"; /* code below does not like empty string */
{
/* Matching URL found, but maybe method doesn't match */
size_t prefix_strlen; /* i.e. 8 for "/orders/", or 7 for "/config" */
const char *infix_url = NULL; /* i.e. "$ORDER_ID", no '/'-es */
size_t infix_strlen = 0; /* number of characters in infix_url */
const char *suffix_url = NULL; /* i.e. "refund", excludes '/' at the beginning */
size_t suffix_strlen = 0; /* number of characters in suffix_url */
/* parse the URL into the three different components */
{
const char *slash;
slash = strchr (&url[1], '/');
if (NULL == slash)
{
/* the prefix was everything */
prefix_strlen = strlen (url);
}
else
{
prefix_strlen = slash - url + 1; /* includes both '/'-es if present! */
infix_url = slash + 1;
slash = strchr (&infix_url[1], '/');
if (NULL == slash)
{
/* the infix was the rest */
infix_strlen = strlen (infix_url);
}
else
{
infix_strlen = slash - infix_url; /* excludes both '/'-es */
suffix_url = slash + 1; /* skip the '/' */
suffix_strlen = strlen (suffix_url);
}
hc->infix = GNUNET_strndup (infix_url,
infix_strlen);
}
}
/* find matching handler */
{
bool url_found = false;
for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
{
struct TMH_RequestHandler *rh = &handlers[i];
if (rh->default_only && (! use_default))
continue;
if (! prefix_match (rh,
url,
prefix_strlen,
infix_url,
infix_strlen,
suffix_url,
suffix_strlen))
continue;
url_found = true;
if (0 == strcasecmp (method,
MHD_HTTP_METHOD_OPTIONS))
{
return TALER_MHD_reply_cors_preflight (connection);
}
if ( (rh->method != NULL) &&
(0 != strcasecmp (method,
rh->method)) )
continue;
hc->rh = rh;
break;
}
/* Handle HTTP 405: METHOD NOT ALLOWED case */
if ( (NULL == hc->rh) &&
(url_found) )
{
struct MHD_Response *reply;
MHD_RESULT ret;
char *allowed = NULL;
GNUNET_break_op (0);
/* compute 'Allowed:' header (required by HTTP spec for 405 replies) */
for (unsigned int i = 0; NULL != handlers[i].url_prefix; i++)
{
struct TMH_RequestHandler *rh = &handlers[i];
if (rh->default_only && (! use_default))
continue;
if (! prefix_match (rh,
url,
prefix_strlen,
infix_url,
infix_strlen,
suffix_url,
suffix_strlen))
continue;
if (NULL == allowed)
{
allowed = GNUNET_strdup (rh->method);
}
else
{
char *tmp;
GNUNET_asprintf (&tmp,
"%s, %s",
allowed,
rh->method);
GNUNET_free (allowed);
allowed = tmp;
}
if (0 == strcasecmp (rh->method,
MHD_HTTP_METHOD_GET))
{
char *tmp;
GNUNET_asprintf (&tmp,
"%s, %s",
allowed,
MHD_HTTP_METHOD_HEAD);
GNUNET_free (allowed);
allowed = tmp;
}
}
reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
method);
GNUNET_break (MHD_YES ==
MHD_add_response_header (reply,
MHD_HTTP_HEADER_ALLOW,
allowed));
GNUNET_free (allowed);
ret = MHD_queue_response (connection,
MHD_HTTP_METHOD_NOT_ALLOWED,
reply);
MHD_destroy_response (reply);
return ret;
}
if (NULL == hc->rh)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
hc->url);
}
}
/* At this point, we must have found a handler */
GNUNET_assert (NULL != hc->rh);
/* If an instance should be there, check one exists */
if ( (NULL == hc->instance) &&
(! hc->rh->skip_instance) )
{
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
hc->infix);
}
/* Access control for non-public handlers */
if (public_handlers != handlers)
{
const char *auth;
bool auth_ok;
bool auth_malformed = false;
/* PATCHing an instance can alternatively be checked against
the default instance */
auth = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_AUTHORIZATION);
if (NULL != auth)
{
/* We _only_ complain about malformed auth headers if
authorization was truly required (#6737). This helps
in case authorization was disabled in the backend
because some reverse proxy is already doing it, and
then that reverse proxy may forward malformed auth
headers to the backend. */
extract_token (&auth);
if (NULL == auth)
auth_malformed = true;
hc->auth_token = auth;
}
/* If we have zero configured instances (not even ones that have been
purged) AND no override credentials, THEN we accept anything (no access
control), as we then also have no data to protect. */
auth_ok = ( (0 ==
GNUNET_CONTAINER_multihashmap_size (TMH_by_id_map)) &&
(NULL == TMH_default_auth) );
/* Check against selected instance, if we have one */
if (NULL != hc->instance)
auth_ok |= (GNUNET_OK ==
TMH_check_auth (auth,
&hc->instance->auth.auth_salt,
&hc->instance->auth.auth_hash));
else /* Are the credentials provided OK for CLI override? */
auth_ok |= ( (use_default ||
/* also allow default credentials if
accessed via /instances/default/ */
(0 == strcmp ("default",
hc->instance->settings.id))) &&
(NULL != TMH_default_auth) &&
(NULL != auth) &&
(! auth_malformed) &&
(0 == strcmp (auth,
TMH_default_auth)) );
if (! auth_ok)
{
if (auth_malformed)
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_UNAUTHORIZED,
TALER_EC_GENERIC_PARAMETER_MALFORMED,
"'" RFC_8959_PREFIX
"' prefix or 'Bearer' missing in 'Authorization' header");
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_UNAUTHORIZED,
TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
"Check 'Authorization' header");
}
} /* if (use_private) */
if ( (NULL == hc->instance) &&
(! hc->rh->skip_instance) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN,
url);
if ( (NULL != hc->instance) && /* make static analysis happy */
(! hc->rh->skip_instance) &&
(hc->instance->deleted) &&
(! hc->rh->allow_deleted_instance) )
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_NOT_FOUND,
TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED,
hc->instance->settings.id);
/* parse request body */
hc->has_body = ( (0 == strcasecmp (method,
MHD_HTTP_METHOD_POST)) ||
/* PUT is not yet used */
(0 == strcasecmp (method,
MHD_HTTP_METHOD_PATCH)) );
if (hc->has_body)
{
const char *cl;
/* Maybe check for maximum upload size
and refuse requests if they are just too big. */
cl = MHD_lookup_connection_value (connection,
MHD_HEADER_KIND,
MHD_HTTP_HEADER_CONTENT_LENGTH);
if (NULL != cl)
{
unsigned long long cv;
size_t mul = hc->rh->max_upload;
char dummy;
if (0 == mul)
mul = DEFAULT_MAX_UPLOAD_SIZE;
if (1 != sscanf (cl,
"%llu%c",
&cv,
&dummy))
{
/* Not valid HTTP request, just close connection. */
GNUNET_break_op (0);
return MHD_NO;
}
if (cv > mul)
{
GNUNET_break_op (0);
return TALER_MHD_reply_with_error (connection,
MHD_HTTP_PAYLOAD_TOO_LARGE,
TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT,
cl);
}
}
GNUNET_break (NULL == hc->request_body); /* can't have it already */
}
return MHD_YES; /* wait for MHD to call us again */
}
/**
* Function called during startup to add all known instances to our
* hash map in memory for faster lookups when we receive requests.
*
* @param cls closure, NULL, unused
* @param merchant_pub public key of the instance
* @param merchant_priv private key of the instance, NULL if not available
* @param is detailed configuration settings for the instance
* @param ias authentication settings for the instance
* @param accounts_length length of the @a accounts array
* @param accounts list of accounts of the merchant
*/
static void
add_instance_cb (void *cls,
const struct TALER_MerchantPublicKeyP *merchant_pub,
const struct TALER_MerchantPrivateKeyP *merchant_priv,
const struct TALER_MERCHANTDB_InstanceSettings *is,
const struct TALER_MERCHANTDB_InstanceAuthSettings *ias,
unsigned int accounts_length,
const struct TALER_MERCHANTDB_AccountDetails accounts[])
{
struct TMH_MerchantInstance *mi;
(void) cls;
mi = TMH_lookup_instance (is->id);
if (NULL != mi)
{
/* (outdated) entry exists, remove old entry */
(void) TMH_instance_free_cb (NULL,
&mi->h_instance,
mi);
}
mi = GNUNET_new (struct TMH_MerchantInstance);
mi->settings = *is;
mi->auth = *ias;
mi->settings.id = GNUNET_strdup (mi->settings.id);
mi->settings.name = GNUNET_strdup (mi->settings.name);
if (NULL != mi->settings.email)
mi->settings.email = GNUNET_strdup (mi->settings.email);
if (NULL != mi->settings.website)
mi->settings.website = GNUNET_strdup (mi->settings.website);
if (NULL != mi->settings.logo)
mi->settings.logo = GNUNET_strdup (mi->settings.logo);
mi->settings.address = json_incref (mi->settings.address);
mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
if (NULL != merchant_priv)
mi->merchant_priv = *merchant_priv;
else
mi->deleted = true;
mi->merchant_pub = *merchant_pub;
for (unsigned int i = 0; ih_wire = acc->h_wire;
wm->payto_uri = GNUNET_strdup (acc->payto_uri);
wm->wire_salt = acc->salt;
wm->wire_method = TALER_payto_get_method (acc->payto_uri);
wm->active = acc->active;
GNUNET_CONTAINER_DLL_insert (mi->wm_head,
mi->wm_tail,
wm);
}
GNUNET_assert (GNUNET_OK ==
TMH_add_instance (mi));
}
/**
* Trigger (re)loading of instance settings from DB.
*
* @param cls NULL
* @param extra ID of the instance that changed, NULL
* to load all instances (will not handle purges!)
* @param extra_len number of bytes in @a extra
*/
static void
load_instances (void *cls,
const void *extra,
size_t extra_len)
{
enum GNUNET_DB_QueryStatus qs;
const char *id = extra;
(void) cls;
(void) extra;
(void) extra_len;
if ( (NULL != extra) &&
( (0 == extra_len) ||
('\0' != id[extra_len - 1]) ) )
{
GNUNET_break (0 == extra_len);
extra = NULL;
}
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Received instance settings notification: reload `%s'\n",
id);
if (NULL == extra)
{
qs = TMH_db->lookup_instances (TMH_db->cls,
false,
&add_instance_cb,
NULL);
}
else
{
struct TMH_MerchantInstance *mi;
/* This must be done here to handle instance
purging, as for purged instances, the DB
lookup below will otherwise do nothing */
mi = TMH_lookup_instance (id);
if (NULL != mi)
{
(void) TMH_instance_free_cb (NULL,
&mi->h_instance,
mi);
}
qs = TMH_db->lookup_instance (TMH_db->cls,
id,
false,
&add_instance_cb,
NULL);
}
if (0 > qs)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed initialization. Check database setup.\n");
result = EXIT_FAILURE;
GNUNET_SCHEDULER_shutdown ();
return;
}
}
/**
* A transaction modified an instance setting
* (or created/deleted/purged one). Notify all
* backends about the change.
*
* @param id ID of the instance that changed
*/
void
TMH_reload_instances (const char *id)
{
struct GNUNET_DB_EventHeaderP es = {
.size = ntohs (sizeof (es)),
.type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
};
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Generating instance settings notification: reload `%s'\n",
id);
TMH_db->event_notify (TMH_db->cls,
&es,
id,
(NULL == id)
? 0
: strlen (id) + 1);
}
/**
* 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;
enum TALER_MHD_GlobalOptions go;
int elen;
int alen;
const char *tok;
(void) cls;
(void) args;
(void) cfgfile;
tok = getenv ("TALER_MERCHANT_TOKEN");
if ( (NULL != tok) &&
(NULL == TMH_default_auth) )
TMH_default_auth = GNUNET_strdup (tok);
if ( (NULL != TMH_default_auth) &&
(0 != strncmp (TMH_default_auth,
RFC_8959_PREFIX,
strlen (RFC_8959_PREFIX))) )
{
char *tmp;
GNUNET_asprintf (&tmp,
"%s%s",
RFC_8959_PREFIX,
TMH_default_auth);
GNUNET_free (TMH_default_auth);
TMH_default_auth = tmp;
}
cfg = config;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
"Starting taler-merchant-httpd\n");
go = TALER_MHD_GO_NONE;
if (merchant_connection_close)
go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
TALER_MHD_setup (go);
result = GNUNET_SYSERR;
GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
NULL);
if (GNUNET_OK !=
TALER_config_get_currency (cfg,
&TMH_currency))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
GNUNET_CONFIGURATION_get_value_time (cfg,
"merchant",
"LEGAL_PRESERVATION",
&TMH_legal_expiration))
{
GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
"merchant",
"LEGAL_PRESERVATION");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_YES ==
GNUNET_CONFIGURATION_get_value_yesno (cfg,
"merchant",
"FORCE_AUDIT"))
TMH_force_audit = GNUNET_YES;
TMH_templating_init ();
if (GNUNET_OK !=
TMH_spa_init ())
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to load single page app\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
TMH_statics_init ();
elen = TMH_EXCHANGES_init (config);
if (GNUNET_SYSERR == elen)
{
GNUNET_SCHEDULER_shutdown ();
return;
}
alen = TMH_AUDITORS_init (config);
if (GNUNET_SYSERR == alen)
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (0 == elen + alen)
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Fatal: no trusted exchanges and no trusted auditors configured. Exiting.\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
if (NULL ==
(TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4,
GNUNET_YES)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (NULL ==
(TMH_db = TALER_MERCHANTDB_plugin_load (cfg)))
{
GNUNET_SCHEDULER_shutdown ();
return;
}
if (GNUNET_OK !=
TMH_db->connect (TMH_db->cls))
{
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
"Failed to initialize database connection\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
{
struct GNUNET_DB_EventHeaderP es = {
.size = ntohs (sizeof (es)),
.type = ntohs (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS)
};
instance_eh = TMH_db->event_listen (TMH_db->cls,
&es,
GNUNET_TIME_UNIT_FOREVER_REL,
&load_instances,
NULL);
}
load_instances (NULL,
NULL,
0);
/* start watching reserves */
TMH_RESERVES_init ();
fh = TALER_MHD_bind (cfg,
"merchant",
&port);
if ( (0 == port) &&
(-1 == fh) )
{
GNUNET_SCHEDULER_shutdown ();
return;
}
{
struct MHD_Daemon *mhd;
mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK
| MHD_USE_AUTO,
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. Is the port in use?\n");
GNUNET_SCHEDULER_shutdown ();
return;
}
result = GNUNET_OK;
TALER_MHD_daemon_start (mhd);
}
}
/**
* The main function of the serve tool
*
* @param argc number of arguments from the command line
* @param argv command line arguments
* @return 0 ok, non-zero on error
*/
int
main (int argc,
char *const *argv)
{
enum GNUNET_GenericReturnValue res;
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag ('C',
"connection-close",
"force HTTP connections to be closed after each request",
&merchant_connection_close),
GNUNET_GETOPT_option_timetravel ('T',
"timetravel"),
GNUNET_GETOPT_option_string ('a',
"auth",
"TOKEN",
"use TOKEN to initially authenticate access to the default instance (you can also set the TALER_MERCHANT_TOKEN environment variable instead)",
&TMH_default_auth),
GNUNET_GETOPT_option_version (PACKAGE_VERSION "-" VCS_VERSION),
GNUNET_GETOPT_OPTION_END
};
TALER_OS_init ();
res = GNUNET_PROGRAM_run (argc, argv,
"taler-merchant-httpd",
"Taler merchant's HTTP backend interface",
options,
&run, NULL);
if (GNUNET_SYSERR == res)
return EXIT_INVALIDARGUMENT;
if (GNUNET_NO == res)
return EXIT_SUCCESS;
return (GNUNET_OK == result) ? EXIT_SUCCESS : 1;
}