diff options
Diffstat (limited to 'src/backend/taler-merchant-httpd.c')
-rw-r--r-- | src/backend/taler-merchant-httpd.c | 739 |
1 files changed, 521 insertions, 218 deletions
diff --git a/src/backend/taler-merchant-httpd.c b/src/backend/taler-merchant-httpd.c index 8e1a0fc0..7384bfc9 100644 --- a/src/backend/taler-merchant-httpd.c +++ b/src/backend/taler-merchant-httpd.c @@ -1,6 +1,6 @@ /* This file is part of TALER - (C) 2014-2022 Taler Systems SA + (C) 2014-2024 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 @@ -28,23 +28,23 @@ #include <taler/taler_mhd_lib.h> #include <taler/taler_templating_lib.h> #include <taler/taler_exchange_service.h> -#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_get-templates-ID.h" #include "taler-merchant-httpd_mhd.h" -#include "taler-merchant-httpd_private-delete-webhooks-ID.h" -#include "taler-merchant-httpd_private-delete-templates-ID.h" +#include "taler-merchant-httpd_private-delete-account-ID.h" #include "taler-merchant-httpd_private-delete-instances-ID.h" +#include "taler-merchant-httpd_private-delete-instances-ID-token.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-otp-devices-ID.h" +#include "taler-merchant-httpd_private-delete-templates-ID.h" +#include "taler-merchant-httpd_private-delete-token-families-SLUG.h" #include "taler-merchant-httpd_private-delete-transfers-ID.h" -#include "taler-merchant-httpd_private-get-webhooks.h" -#include "taler-merchant-httpd_private-get-webhooks-ID.h" -#include "taler-merchant-httpd_private-get-templates.h" -#include "taler-merchant-httpd_private-get-templates-ID.h" +#include "taler-merchant-httpd_private-delete-webhooks-ID.h" +#include "taler-merchant-httpd_private-get-accounts.h" +#include "taler-merchant-httpd_private-get-accounts-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" @@ -52,35 +52,42 @@ #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-otp-devices.h" +#include "taler-merchant-httpd_private-get-otp-devices-ID.h" +#include "taler-merchant-httpd_private-get-templates.h" +#include "taler-merchant-httpd_private-get-templates-ID.h" +#include "taler-merchant-httpd_private-get-token-families.h" +#include "taler-merchant-httpd_private-get-token-families-SLUG.h" #include "taler-merchant-httpd_private-get-transfers.h" -#include "taler-merchant-httpd_private-patch-webhooks-ID.h" -#include "taler-merchant-httpd_private-patch-templates-ID.h" +#include "taler-merchant-httpd_private-get-webhooks.h" +#include "taler-merchant-httpd_private-get-webhooks-ID.h" +#include "taler-merchant-httpd_private-patch-accounts-ID.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-otp-devices-ID.h" #include "taler-merchant-httpd_private-patch-products-ID.h" -#include "taler-merchant-httpd_private-post-webhooks.h" -#include "taler-merchant-httpd_private-post-templates.h" +#include "taler-merchant-httpd_private-patch-templates-ID.h" +#include "taler-merchant-httpd_private-patch-token-families-SLUG.h" +#include "taler-merchant-httpd_private-patch-webhooks-ID.h" +#include "taler-merchant-httpd_private-post-account.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-instances-ID-token.h" +#include "taler-merchant-httpd_private-post-otp-devices.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-templates.h" +#include "taler-merchant-httpd_private-post-token-families.h" #include "taler-merchant-httpd_private-post-transfers.h" +#include "taler-merchant-httpd_private-post-webhooks.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-using-templates.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" @@ -107,6 +114,13 @@ char *TMH_currency; /** + * What is the base URL for this merchant backend? NULL if it is not + * configured and is to be determined from HTTP headers (X-Forwarded-Host and + * X-Forwarded-Port and X-Forwarded-Prefix) of the reverse proxy. + */ +char *TMH_base_url; + +/** * Inform the auditor for all deposit confirmations (global option) */ int TMH_force_audit; @@ -136,6 +150,16 @@ struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map; struct GNUNET_TIME_Relative TMH_legal_expiration; /** + * Length of the TMH_cspecs array. + */ +unsigned int TMH_num_cspecs; + +/** + * Rendering specs for currencies. + */ +struct TALER_CurrencySpecification *TMH_cspecs; + +/** * The port we are running on */ static uint16_t port; @@ -146,9 +170,20 @@ static uint16_t port; static int merchant_connection_close; /** + * Context for all exchange operations (useful to the event loop). + */ +struct GNUNET_CURL_Context *TMH_curl_ctx; + +/** + * Context for integrating #TMH_curl_ctx with the + * GNUnet event loop. + */ +static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc; + +/** * Global return code */ -static int result; +static int global_ret; /** * Our configuration. @@ -161,6 +196,75 @@ static const struct GNUNET_CONFIGURATION_Handle *cfg; char *TMH_default_auth; +/** + * Check validity of login @a token for the given @a instance_id. + * + * @param token the login token given in the request + * @param instance_id the instance the login is to be checked against + * @param[out] as set to scope of the token if it is valid + * @return TALER_EC_NONE on success + */ +static enum TALER_ErrorCode +TMH_check_token (const char *token, + const char *instance_id, + enum TMH_AuthScope *as) +{ + enum TMH_AuthScope scope; + struct GNUNET_TIME_Timestamp expiration; + enum GNUNET_DB_QueryStatus qs; + struct TALER_MERCHANTDB_LoginTokenP btoken; + + if (NULL == token) + { + *as = TMH_AS_NONE; + return TALER_EC_NONE; + } + /* This was presumably checked before... */ + GNUNET_assert (0 == strncasecmp (token, + RFC_8959_PREFIX, + strlen (RFC_8959_PREFIX))); + token += strlen (RFC_8959_PREFIX); + if (GNUNET_OK != + GNUNET_STRINGS_string_to_data (token, + strlen (token), + &btoken, + sizeof (btoken))) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Given authorization token `%s' is malformed\n", + token); + GNUNET_break_op (0); + return TALER_EC_GENERIC_TOKEN_MALFORMED; + } + qs = TMH_db->select_login_token (TMH_db->cls, + instance_id, + &btoken, + &expiration, + &scope); + if (qs < 0) + { + GNUNET_break (0); + return TALER_EC_GENERIC_DB_FETCH_FAILED; + } + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Authorization token `%s' unknown\n", + token); + return TALER_EC_GENERIC_TOKEN_UNKNOWN; + } + if (GNUNET_TIME_absolute_is_past (expiration.abs_time)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Authorization token `%s' expired\n", + token); + return TALER_EC_GENERIC_TOKEN_EXPIRED; + } + *as = scope; + return TALER_EC_NONE; +} + + enum GNUNET_GenericReturnValue TMH_check_auth (const char *token, struct TALER_MerchantAuthenticationSaltP *salt, @@ -225,6 +329,17 @@ TMH_compute_auth (const char *token, void +TMH_wire_method_free (struct TMH_WireMethod *wm) +{ + GNUNET_free (wm->payto_uri); + GNUNET_free (wm->wire_method); + GNUNET_free (wm->credit_facade_url); + json_decref (wm->credit_facade_credentials); + GNUNET_free (wm); +} + + +void TMH_instance_decref (struct TMH_MerchantInstance *mi) { struct TMH_WireMethod *wm; @@ -238,9 +353,7 @@ TMH_instance_decref (struct TMH_MerchantInstance *mi) GNUNET_CONTAINER_DLL_remove (mi->wm_head, mi->wm_tail, wm); - GNUNET_free (wm->payto_uri); - GNUNET_free (wm->wire_method); - GNUNET_free (wm); + TMH_wire_method_free (wm); } GNUNET_free (mi->settings.id); @@ -282,13 +395,11 @@ static void do_shutdown (void *cls) { (void) cls; + TMH_force_orders_resume (); 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 (); { @@ -298,19 +409,17 @@ do_shutdown (void *cls) if (NULL != mhd) MHD_stop_daemon (mhd); } - TMH_RESERVES_done (); if (NULL != instance_eh) { TMH_db->event_listen_cancel (instance_eh); instance_eh = NULL; } + TMH_EXCHANGES_done (); 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, @@ -320,6 +429,16 @@ do_shutdown (void *cls) TMH_by_id_map = NULL; } TALER_TEMPLATING_done (); + if (NULL != TMH_curl_ctx) + { + GNUNET_CURL_fini (TMH_curl_ctx); + TMH_curl_ctx = NULL; + } + if (NULL != merchant_curl_rc) + { + GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc); + merchant_curl_rc = NULL; + } } @@ -379,6 +498,7 @@ handle_mhd_completion_callback (void *cls, json_decref (hc->request_body); if (NULL != hc->instance) TMH_instance_decref (hc->instance); + GNUNET_free (hc->full_url); GNUNET_free (hc); *con_cls = NULL; } @@ -459,6 +579,15 @@ handle_server_options (const struct TMH_RequestHandler *rh, } +/** + * Generates the response for "/", redirecting the + * client to the "/webui/" from where we serve the SPA. + * + * @param rh request handler + * @param connection MHD connection + * @param hc handler context + * @return MHD result code + */ static MHD_RESULT spa_redirect (const struct TMH_RequestHandler *rh, struct MHD_Connection *connection, @@ -466,6 +595,7 @@ spa_redirect (const struct TMH_RequestHandler *rh, { const char *text = "Redirecting to /webui/"; struct MHD_Response *response; + char *dst; response = MHD_create_response_from_buffer (strlen (text), (void *) text, @@ -480,15 +610,25 @@ spa_redirect (const struct TMH_RequestHandler *rh, MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/plain")); + if ( (NULL == hc->instance) || + (0 == strcmp ("default", + hc->instance->settings.id)) ) + dst = GNUNET_strdup ("/webui/"); + else + GNUNET_asprintf (&dst, + "/instances/%s/webui/", + hc->instance->settings.id); if (MHD_NO == MHD_add_response_header (response, MHD_HTTP_HEADER_LOCATION, - "/webui/")) + dst)) { GNUNET_break (0); MHD_destroy_response (response); + GNUNET_free (dst); return MHD_NO; } + GNUNET_free (dst); { MHD_RESULT ret; @@ -515,12 +655,14 @@ extract_token (const char **auth) const char *bearer = "Bearer "; const char *tok = *auth; - if (0 != strncmp (tok, bearer, strlen (bearer))) + if (0 != strncmp (tok, + bearer, + strlen (bearer))) { *auth = NULL; return; } - tok = tok + strlen (bearer); + tok += strlen (bearer); while (' ' == *tok) tok++; if (0 != strncasecmp (tok, @@ -596,6 +738,29 @@ prefix_match (const struct TMH_RequestHandler *rh, /** + * Function called first by MHD with the full URL. + * + * @param cls NULL + * @param full_url the full URL + * @param con MHD connection object + * @return our handler context + */ +static void * +full_url_track_callback (void *cls, + const char *full_url, + struct MHD_Connection *con) +{ + struct TMH_HandlerContext *hc; + + hc = GNUNET_new (struct TMH_HandlerContext); + GNUNET_async_scope_fresh (&hc->async_scope_id); + GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); + hc->full_url = GNUNET_strdup (full_url); + return hc; +} + + +/** * 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 @@ -630,7 +795,8 @@ prefix_match (const struct TMH_RequestHandler *rh, * 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. + * Initially, `*con_cls` will be set up by the + * full_url_track_callback(). * @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 @@ -711,7 +877,7 @@ url_handler (void *cls, /* Body should be pretty small. */ .max_upload = 1024 * 1024 }, - /* POST /kyc: */ + /* GET /kyc: */ { .url_prefix = "/instances/", .url_suffix = "kyc", @@ -880,100 +1046,66 @@ url_handler (void *cls, .allow_deleted_instance = true, .handler = &TMH_private_delete_orders_ID }, - /* POST /reserves: */ + /* POST /transfers: */ { - .url_prefix = "/reserves", + .url_prefix = "/transfers", .method = MHD_HTTP_METHOD_POST, - .handler = &TMH_private_post_reserves, + .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 /reserves/$ID: */ + /* DELETE /transfers/$ID: */ { - .url_prefix = "/reserves/", - .have_id_segment = true, - .allow_deleted_instance = true, + .url_prefix = "/transfers/", .method = MHD_HTTP_METHOD_DELETE, - .handler = &TMH_private_delete_reserves_ID - }, - /* POST /reserves/$ID/authorize-tip: */ - { - .url_prefix = "/reserves/", - .url_suffix = "authorize-tip", + .allow_deleted_instance = true, + .handler = &TMH_private_delete_transfers_ID, .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: */ + /* GET /transfers: */ { - .url_prefix = "/tips/", + .url_prefix = "/transfers", .method = MHD_HTTP_METHOD_GET, .allow_deleted_instance = true, - .have_id_segment = true, - .handler = &TMH_private_get_tips_ID + .handler = &TMH_private_get_transfers }, - /* GET /reserves: */ + /* POST /otp-devices: */ { - .url_prefix = "/reserves", - .allow_deleted_instance = true, - .method = MHD_HTTP_METHOD_GET, - .handler = &TMH_private_get_reserves + .url_prefix = "/otp-devices", + .method = MHD_HTTP_METHOD_POST, + .handler = &TMH_private_post_otp_devices }, - /* GET /reserves/$ID: */ + /* GET /otp-devices: */ { - .url_prefix = "/reserves/", - .allow_deleted_instance = true, - .have_id_segment = true, + .url_prefix = "/otp-devices", .method = MHD_HTTP_METHOD_GET, - .handler = &TMH_private_get_reserves_ID + .handler = &TMH_private_get_otp_devices }, - /* POST /transfers: */ + /* GET /otp-devices/$ID/: */ { - .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 + .url_prefix = "/otp-devices/", + .method = MHD_HTTP_METHOD_GET, + .have_id_segment = true, + .handler = &TMH_private_get_otp_devices_ID }, - /* DELETE /transfers/$ID: */ + /* DELETE /otp-devices/$ID/: */ { - .url_prefix = "/transfers/", + .url_prefix = "/otp-devices/", .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 + .handler = &TMH_private_delete_otp_devices_ID }, - /* GET /transfers: */ + /* PATCH /otp-devices/$ID/: */ { - .url_prefix = "/transfers", - .method = MHD_HTTP_METHOD_GET, - .allow_deleted_instance = true, - .handler = &TMH_private_get_transfers + .url_prefix = "/otp-devices/", + .method = MHD_HTTP_METHOD_PATCH, + .have_id_segment = true, + .handler = &TMH_private_patch_otp_devices_ID }, /* POST /templates: */ { @@ -1067,16 +1199,114 @@ url_handler (void *cls, in the code... */ .max_upload = 1024 * 1024 * 8 }, + /* POST /accounts: */ + { + .url_prefix = "/accounts", + .method = MHD_HTTP_METHOD_POST, + .handler = &TMH_private_post_account, + /* allow account details of up to 8 kb, that should be plenty */ + .max_upload = 1024 * 8 + }, + /* PATCH /accounts/$H_WIRE: */ + { + .url_prefix = "/accounts/", + .method = MHD_HTTP_METHOD_PATCH, + .handler = &TMH_private_patch_accounts_ID, + .have_id_segment = true, + /* allow account details of up to 8 kb, that should be plenty */ + .max_upload = 1024 * 8 + }, + /* GET /accounts: */ + { + .url_prefix = "/accounts", + .method = MHD_HTTP_METHOD_GET, + .handler = &TMH_private_get_accounts + }, + /* GET /accounts/$H_WIRE: */ + { + .url_prefix = "/accounts/", + .method = MHD_HTTP_METHOD_GET, + .have_id_segment = true, + .handler = &TMH_private_get_accounts_ID + }, + /* DELETE /accounts/$H_WIRE: */ + { + .url_prefix = "/accounts/", + .method = MHD_HTTP_METHOD_DELETE, + .handler = &TMH_private_delete_account_ID, + .have_id_segment = true + }, + /* POST /token: */ + { + .url_prefix = "/token", + .auth_scope = TMH_AS_REFRESHABLE, + .method = MHD_HTTP_METHOD_POST, + .handler = &TMH_private_post_instances_ID_token, + /* Body should be tiny. */ + .max_upload = 1024 + }, + /* DELETE /token: */ + { + .url_prefix = "/token", + .auth_scope = TMH_AS_READ_ONLY, + .method = MHD_HTTP_METHOD_DELETE, + .handler = &TMH_private_delete_instances_ID_token, + }, + /* GET /tokenfamilies: */ + { + .url_prefix = "/tokenfamilies", + .method = MHD_HTTP_METHOD_GET, + .handler = &TMH_private_get_tokenfamilies + }, + /* POST /tokenfamilies: */ + { + .url_prefix = "/tokenfamilies", + .method = MHD_HTTP_METHOD_POST, + .handler = &TMH_private_post_token_families + }, + /* GET /tokenfamilies/$SLUG/: */ + { + .url_prefix = "/tokenfamilies/", + .method = MHD_HTTP_METHOD_GET, + .have_id_segment = true, + .handler = &TMH_private_get_tokenfamilies_SLUG + }, + /* DELETE /tokenfamilies/$SLUG/: */ + { + .url_prefix = "/tokenfamilies/", + .method = MHD_HTTP_METHOD_DELETE, + .have_id_segment = true, + .handler = &TMH_private_delete_token_families_SLUG + }, + /* PATCH /tokenfamilies/$SLUG/: */ + { + .url_prefix = "/tokenfamilies/", + .method = MHD_HTTP_METHOD_PATCH, + .have_id_segment = true, + .handler = &TMH_private_patch_token_family_SLUG, + }, { .url_prefix = NULL } }; static struct TMH_RequestHandler public_handlers[] = { { + /* for "default" instance, it does not even + have to exist before we give the WebUI */ .url_prefix = "/", .method = MHD_HTTP_METHOD_GET, .mime_type = "text/html", .skip_instance = true, + .default_only = true, + .handler = &spa_redirect, + .response_code = MHD_HTTP_FOUND + }, + { + /* for "normal" instance,s they must exist + before we give the WebUI */ + .url_prefix = "/", + .method = MHD_HTTP_METHOD_GET, + .mime_type = "text/html", .handler = &spa_redirect, .response_code = MHD_HTTP_FOUND }, @@ -1175,39 +1405,25 @@ url_handler (void *cls, .have_id_segment = true, .handler = &TMH_get_orders_ID }, - /* GET /tips/$ID: */ + /* GET /static/ *: */ { - .url_prefix = "/tips/", + .url_prefix = "/static/", .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 + .handler = &TMH_return_static }, - /* GET /static/ *: */ + /* GET /templates/$ID/: */ { - .url_prefix = "/static/", + .url_prefix = "/templates/", .method = MHD_HTTP_METHOD_GET, .have_id_segment = true, - .handler = &TMH_return_static + .handler = &TMH_get_templates_ID }, /* POST /templates/$ID: */ { .url_prefix = "/templates/", .method = MHD_HTTP_METHOD_POST, .have_id_segment = true, - .allow_deleted_instance = true, .handler = &TMH_post_using_templates_ID, .max_upload = 1024 * 1024 }, @@ -1226,7 +1442,7 @@ url_handler (void *cls, (void) cls; (void) version; - if (NULL != hc) + if (NULL != hc->url) { /* MHD calls us again for a request, for first call see 'else' case below */ @@ -1274,10 +1490,6 @@ url_handler (void *cls, 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; @@ -1331,6 +1543,37 @@ url_handler (void *cls, else instance_id = GNUNET_strndup (istart, slash - istart); + if (0 == strcmp (instance_id, + "default")) + { + MHD_RESULT ret; + struct MHD_Response *response; + const char *rstart = hc->full_url + strlen (instance_prefix); + const char *rslash = strchr (rstart, '/'); + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Client used deprecated '/instances/default/' path. Redirecting to modern path\n"); + + response + = MHD_create_response_from_buffer (0, + NULL, + MHD_RESPMEM_PERSISTENT); + TALER_MHD_add_global_headers (response); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + rslash)) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + ret = MHD_queue_response (connection, + MHD_HTTP_PERMANENT_REDIRECT, + response); + MHD_destroy_response (response); + return ret; + } hc->instance = TMH_lookup_instance (instance_id); if ( (NULL == hc->instance) && (0 == strcmp ("default", @@ -1399,7 +1642,11 @@ url_handler (void *cls, "/private")) ) { handlers = private_handlers; - url += strlen (private_prefix) - 1; + if (0 == strcmp (url, + "/private")) + url = "/"; + else + url += strlen (private_prefix) - 1; } else { @@ -1566,8 +1813,8 @@ url_handler (void *cls, (! hc->rh->skip_instance) ) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, - "Instance `%s' not known\n", - hc->infix); + "Instance for `%s' not known\n", + hc->url); return TALER_MHD_reply_with_error (connection, MHD_HTTP_NOT_FOUND, TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, @@ -1619,9 +1866,40 @@ url_handler (void *cls, (! auth_malformed) && (0 == strcmp (auth, TMH_default_auth)) ); - if (! auth_ok) + if (auth_ok) { - if (auth_malformed) + hc->auth_scope = TMH_AS_ALL; + } + else + { + if (NULL != hc->instance) + { + enum TALER_ErrorCode ec; + + ec = TMH_check_token (auth, + hc->instance->settings.id, + &hc->auth_scope); + if (TALER_EC_NONE != ec) + return TALER_MHD_reply_with_ec (connection, + ec, + NULL); + } + else + hc->auth_scope = TMH_AS_NONE; + } + /* We grant access if: + - scope is 'all' + - rh has an explicit non-NONE scope that matches + - scope is 'read only' and we have a GET request */ + if (! ( (TMH_AS_ALL == hc->auth_scope) || + ( (TMH_AS_NONE != hc->rh->auth_scope) && + (hc->rh->auth_scope == (hc->rh->auth_scope & hc->auth_scope)) ) || + ( (TMH_AS_READ_ONLY == (hc->auth_scope & TMH_AS_READ_ONLY)) && + (0 == strcmp (MHD_HTTP_METHOD_GET, + method)) ) ) ) + { + if (auth_malformed && + (TMH_AS_NONE == hc->auth_scope) ) return TALER_MHD_reply_with_error (connection, MHD_HTTP_UNAUTHORIZED, TALER_EC_GENERIC_PARAMETER_MALFORMED, @@ -1667,39 +1945,10 @@ url_handler (void *cls, 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); - } - } + TALER_MHD_check_content_length (connection, + 0 == hc->rh->max_upload + ? DEFAULT_MAX_UPLOAD_SIZE + : hc->rh->max_upload); GNUNET_break (NULL == hc->request_body); /* can't have it already */ } return MHD_YES; /* wait for MHD to call us again */ @@ -1707,6 +1956,31 @@ url_handler (void *cls, /** + * Callback invoked with information about a bank account. + * + * @param cls closure with a `struct TMH_MerchantInstance *` + * @param acc details about the account + */ +static void +add_account_cb (void *cls, + const struct TALER_MERCHANTDB_AccountDetails *acc) +{ + struct TMH_MerchantInstance *mi = cls; + struct TMH_WireMethod *wm; + + wm = GNUNET_new (struct TMH_WireMethod); + wm->h_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); +} + + +/** * Function called during startup to add all known instances to our * hash map in memory for faster lookups when we receive requests. * @@ -1715,19 +1989,16 @@ url_handler (void *cls, * @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[]) + const struct TALER_MERCHANTDB_InstanceAuthSettings *ias) { struct TMH_MerchantInstance *mi; + enum GNUNET_DB_QueryStatus qs; (void) cls; mi = TMH_lookup_instance (is->id); @@ -1757,20 +2028,15 @@ add_instance_cb (void *cls, else mi->deleted = true; mi->merchant_pub = *merchant_pub; - for (unsigned int i = 0; i<accounts_length; i++) + qs = TMH_db->select_accounts (TMH_db->cls, + mi->settings.id, + &add_account_cb, + mi); + if (0 > qs) { - const struct TALER_MERCHANTDB_AccountDetails *acc = &accounts[i]; - struct TMH_WireMethod *wm; - - wm = GNUNET_new (struct TMH_WireMethod); - wm->h_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_log (GNUNET_ERROR_TYPE_ERROR, + "Error loading accounts of `%s' from database\n", + mi->settings.id); } GNUNET_assert (GNUNET_OK == TMH_add_instance (mi)); @@ -1794,8 +2060,6 @@ load_instances (void *cls, const char *id = extra; (void) cls; - (void) extra; - (void) extra_len; if ( (NULL != extra) && ( (0 == extra_len) || ('\0' != id[extra_len - 1]) ) ) @@ -1837,7 +2101,7 @@ load_instances (void *cls, { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed initialization. Check database setup.\n"); - result = EXIT_FAILURE; + global_ret = EXIT_NOPERMISSION; GNUNET_SCHEDULER_shutdown (); return; } @@ -1845,9 +2109,8 @@ load_instances (void *cls, /** - * A transaction modified an instance setting - * (or created/deleted/purged one). Notify all - * backends about the change. + * 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 */ @@ -1889,7 +2152,6 @@ run (void *cls, int fh; enum TALER_MHD_GlobalOptions go; int elen; - int alen; const char *tok; (void) cls; @@ -1904,10 +2166,10 @@ run (void *cls, RFC_8959_PREFIX, strlen (RFC_8959_PREFIX))) ) { - fprintf (stderr, - "Authentication token does not start with `%s' prefix\n", - RFC_8959_PREFIX); - result = GNUNET_SYSERR; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Authentication token does not start with `%s' prefix\n", + RFC_8959_PREFIX); + global_ret = EXIT_NOTCONFIGURED; GNUNET_SCHEDULER_shutdown (); return; } @@ -1919,16 +2181,46 @@ run (void *cls, go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; TALER_MHD_setup (go); - result = GNUNET_SYSERR; + global_ret = EXIT_SUCCESS; GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); - if (GNUNET_OK != + + TMH_curl_ctx + = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, + &merchant_curl_rc); + if (NULL == TMH_curl_ctx) + { + GNUNET_break (0); + global_ret = EXIT_NO_RESTART; + GNUNET_SCHEDULER_shutdown (); + return; + } + merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx); + /* Disable 100 continue processing */ + GNUNET_break (GNUNET_OK == + GNUNET_CURL_append_header (TMH_curl_ctx, + MHD_HTTP_HEADER_EXPECT ":")); + GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx, + "Taler-Correlation-Id"); + + if (GNUNET_SYSERR == TALER_config_get_currency (cfg, &TMH_currency)) { + + GNUNET_SCHEDULER_shutdown (); + return; + } + + if (GNUNET_OK != + TALER_CONFIG_parse_currencies (cfg, + &TMH_num_cspecs, + &TMH_cspecs)) + { GNUNET_SCHEDULER_shutdown (); return; } + if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_time (cfg, "merchant", @@ -1941,6 +2233,22 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } + if (GNUNET_OK == + GNUNET_CONFIGURATION_get_value_string (cfg, + "merchant", + "BASE_URL", + &TMH_base_url)) + { + if (! TALER_is_web_url (TMH_base_url)) + { + GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, + "merchant", + "BASE_URL", + "Needs to start with 'http://' or 'https://'"); + GNUNET_SCHEDULER_shutdown (); + return; + } + } if (GNUNET_YES == GNUNET_CONFIGURATION_get_value_yesno (cfg, "merchant", @@ -1957,46 +2265,41 @@ run (void *cls, } /* /static/ is currently not used */ /* (void) TMH_statics_init (); */ - elen = TMH_EXCHANGES_init (config); - if (GNUNET_SYSERR == elen) + if (NULL == + (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4, + GNUNET_YES))) { GNUNET_SCHEDULER_shutdown (); return; } - alen = TMH_AUDITORS_init (config); - if (GNUNET_SYSERR == alen) + if (NULL == + (TMH_db = TALER_MERCHANTDB_plugin_load (cfg))) { GNUNET_SCHEDULER_shutdown (); return; } - if (0 == elen + alen) + if (GNUNET_OK != + TMH_db->connect (TMH_db->cls)) { 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))) - { + "Failed to initialize database connection\n"); GNUNET_SCHEDULER_shutdown (); return; } - if (NULL == - (TMH_db = TALER_MERCHANTDB_plugin_load (cfg))) + elen = TMH_EXCHANGES_init (config); + if (GNUNET_SYSERR == elen) { GNUNET_SCHEDULER_shutdown (); return; } - if (GNUNET_OK != - TMH_db->connect (TMH_db->cls)) + if (0 == elen) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - "Failed to initialize database connection\n"); + "Fatal: no trusted exchanges configured. Exiting.\n"); GNUNET_SCHEDULER_shutdown (); return; } + { struct GNUNET_DB_EventHeaderP es = { .size = ntohs (sizeof (es)), @@ -2012,8 +2315,6 @@ run (void *cls, load_instances (NULL, NULL, 0); - /* start watching reserves */ - TMH_RESERVES_init (); fh = TALER_MHD_bind (cfg, "merchant", &port); @@ -2033,6 +2334,8 @@ run (void *cls, NULL, NULL, &url_handler, NULL, MHD_OPTION_LISTEN_SOCKET, fh, + MHD_OPTION_URI_LOG_CALLBACK, + &full_url_track_callback, NULL, MHD_OPTION_NOTIFY_COMPLETED, &handle_mhd_completion_callback, NULL, MHD_OPTION_CONNECTION_TIMEOUT, @@ -2045,7 +2348,7 @@ run (void *cls, GNUNET_SCHEDULER_shutdown (); return; } - result = GNUNET_OK; + global_ret = EXIT_SUCCESS; TALER_MHD_daemon_start (mhd); } } @@ -2089,5 +2392,5 @@ main (int argc, return EXIT_INVALIDARGUMENT; if (GNUNET_NO == res) return EXIT_SUCCESS; - return (GNUNET_OK == result) ? EXIT_SUCCESS : 1; + return global_ret; } |