taler-merchant-httpd.c (56288B)
1 /* 2 This file is part of TALER 3 (C) 2014-2025 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file src/backend/taler-merchant-httpd.c 18 * @brief HTTP serving layer intended to perform crypto-work and 19 * communication with the exchange 20 * @author Marcello Stanisci 21 * @author Christian Grothoff 22 * @author Florian Dold 23 * @author Priscilla HUANG 24 */ 25 #include "platform.h" 26 #include <taler/taler_dbevents.h> 27 #include <taler/taler_bank_service.h> 28 #include <taler/taler_mhd_lib.h> 29 #include <taler/taler_templating_lib.h> 30 #include <taler/taler_exchange_service.h> 31 #include <donau/donau_service.h> 32 #include "taler/taler_merchant_util.h" 33 #include "taler-merchant-httpd_auth.h" 34 #include "taler-merchant-httpd_dispatcher.h" 35 #include "taler-merchant-httpd_exchanges.h" 36 #include "taler-merchant-httpd_helper.h" 37 #include "taler-merchant-httpd_mhd.h" 38 #include "taler-merchant-httpd_mfa.h" 39 #include "taler-merchant-httpd_post-private-orders.h" 40 #include "taler-merchant-httpd_post-orders-ORDER_ID-abort.h" 41 #include "taler-merchant-httpd_post-challenge-ID.h" 42 #include "taler-merchant-httpd_get-orders-ORDER_ID.h" 43 #include "taler-merchant-httpd_get-sessions-SESSION_ID.h" 44 #include "taler-merchant-httpd_get-templates-TEMPLATE_ID.h" 45 #include "taler-merchant-httpd_get-exchanges.h" 46 #include "taler-merchant-httpd_get-webui.h" 47 #include "taler-merchant-httpd_get-terms.h" 48 #include "taler-merchant-httpd_get-private-kyc.h" 49 #include "taler-merchant-httpd_get-private-statistics-report-transactions.h" 50 #include "taler-merchant-httpd_post-private-donau.h" 51 #include "taler-merchant-httpd_get-private-orders-ORDER_ID.h" 52 #include "taler-merchant-httpd_get-private-orders.h" 53 #include "taler-merchant-httpd_post-orders-ORDER_ID-pay.h" 54 #include "taler-merchant-httpd_post-orders-ORDER_ID-refund.h" 55 #include "taler-merchant-httpd_post-private-accounts-H_WIRE-kycauth.h" 56 #include "merchant-database/iterate_instances.h" 57 #include "merchant-database/set_instance.h" 58 #include "merchant-database/iterate_accounts_by_instance.h" 59 #include "merchant-database/event_listen.h" 60 #include "merchant-database/preflight.h" 61 #include "merchant-database/event_notify.h" 62 63 /** 64 * Backlog for listen operation on unix-domain sockets. 65 */ 66 #define UNIX_BACKLOG 500 67 68 /** 69 * Default maximum upload size permitted. Can be overridden 70 * per handler. 71 */ 72 #define DEFAULT_MAX_UPLOAD_SIZE (16 * 1024) 73 74 char *TMH_currency; 75 76 char *TMH_base_url; 77 78 char *TMH_spa_dir; 79 80 char *TMH_helper_email; 81 82 char *TMH_helper_sms; 83 84 char *TMH_phone_regex; 85 86 regex_t TMH_phone_rx; 87 88 char *TMH_allowed_payment_targets; 89 90 char *TMH_default_persona; 91 92 char *TMH_payment_target_regex; 93 94 regex_t TMH_payment_target_re; 95 96 int TMH_force_audit; 97 98 struct TALER_MERCHANTDB_PostgresContext *TMH_db; 99 100 struct GNUNET_CONTAINER_MultiHashMap *TMH_by_id_map; 101 102 struct GNUNET_TIME_Relative TMH_default_pay_delay; 103 104 struct GNUNET_TIME_Relative TMH_default_refund_delay; 105 106 struct GNUNET_TIME_Relative TMH_default_wire_transfer_delay; 107 108 enum GNUNET_TIME_RounderInterval TMH_default_wire_transfer_rounding_interval; 109 110 int TMH_strict_v19; 111 112 int TMH_auth_disabled; 113 114 int TMH_have_self_provisioning; 115 116 int TMH_password_change_mfa; 117 118 enum TEH_TanChannelSet TEH_mandatory_tan_channels; 119 120 struct GNUNET_TIME_Relative TMH_legal_expiration; 121 122 unsigned int TMH_num_cspecs; 123 124 json_t *TMH_global_spa_config_data; 125 126 struct TALER_CurrencySpecification *TMH_cspecs; 127 128 struct GNUNET_CURL_Context *TMH_curl_ctx; 129 130 /** 131 * Event handler for instance settings changes. 132 */ 133 static struct GNUNET_DB_EventHandler *instance_eh; 134 135 /** 136 * True if we started any HTTP daemon. 137 */ 138 static bool have_daemons; 139 140 /** 141 * Should a "Connection: close" header be added to each HTTP response? 142 */ 143 static int merchant_connection_close; 144 145 /** 146 * Should we enable HTTP/2 and HTTP/3 when talking to the exchange 147 * (and donau)? Those are not expected to be terribly beneficial for 148 * a server with stable connections to an exchange, but they could 149 * cause stability issues with libcurl. Per default, we *enforce* 150 * HTTP/1.x-only, as that is the conservative and most tested code 151 * path. 152 */ 153 static int enable_h3; 154 155 /** 156 * Context for integrating #TMH_curl_ctx with the 157 * GNUnet event loop. 158 */ 159 static struct GNUNET_CURL_RescheduleContext *merchant_curl_rc; 160 161 /** 162 * Global return code 163 */ 164 static int global_ret; 165 166 /** 167 * Our configuration. 168 */ 169 const struct GNUNET_CONFIGURATION_Handle *TMH_cfg; 170 171 172 void 173 TMH_wire_method_free (struct TMH_WireMethod *wm) 174 { 175 GNUNET_free (wm->payto_uri.full_payto); 176 GNUNET_free (wm->wire_method); 177 GNUNET_free (wm->extra_wire_subject_metadata); 178 GNUNET_free (wm->credit_facade_url); 179 json_decref (wm->credit_facade_credentials); 180 GNUNET_free (wm); 181 } 182 183 184 void 185 TMH_instance_decref (struct TMH_MerchantInstance *mi) 186 { 187 struct TMH_WireMethod *wm; 188 189 mi->rc--; 190 if (0 != mi->rc) 191 return; 192 TMH_force_get_orders_resume (mi); 193 while (NULL != (wm = (mi->wm_head))) 194 { 195 GNUNET_CONTAINER_DLL_remove (mi->wm_head, 196 mi->wm_tail, 197 wm); 198 TMH_wire_method_free (wm); 199 } 200 201 GNUNET_free (mi->settings.id); 202 GNUNET_free (mi->settings.name); 203 GNUNET_free (mi->settings.email); 204 GNUNET_free (mi->settings.phone); 205 GNUNET_free (mi->settings.website); 206 GNUNET_free (mi->settings.logo); 207 json_decref (mi->settings.address); 208 json_decref (mi->settings.jurisdiction); 209 GNUNET_free (mi); 210 } 211 212 213 enum GNUNET_GenericReturnValue 214 TMH_instance_free_cb (void *cls, 215 const struct GNUNET_HashCode *key, 216 void *value) 217 { 218 struct TMH_MerchantInstance *mi = value; 219 220 (void) cls; 221 (void) key; 222 TMH_force_get_orders_resume (mi); 223 GNUNET_assert (GNUNET_OK == 224 GNUNET_CONTAINER_multihashmap_remove (TMH_by_id_map, 225 &mi->h_instance, 226 mi)); 227 TMH_instance_decref (mi); 228 return GNUNET_YES; 229 } 230 231 232 /** 233 * Shutdown task (invoked when the application is being 234 * terminated for any reason) 235 * 236 * @param cls NULL 237 */ 238 static void 239 do_shutdown (void *cls) 240 { 241 (void) cls; 242 TALER_MHD_daemons_halt (); 243 TMH_handler_statistic_report_transactions_cleanup (); 244 TMH_force_kac_resume (); 245 TMH_force_orders_resume (); 246 TMH_force_get_sessions_ID_resume (); 247 TMH_force_get_templates_ID_resume (); 248 TMH_force_get_orders_resume_typst (); 249 TMH_force_ac_resume (); 250 TMH_force_pc_resume (); 251 TMH_force_kyc_resume (); 252 TMH_force_gorc_resume (); 253 TMH_force_wallet_get_order_resume (); 254 TMH_force_wallet_refund_order_resume (); 255 TMH_challenge_done (); 256 if (NULL != instance_eh) 257 { 258 TALER_MERCHANTDB_event_listen_cancel (instance_eh); 259 instance_eh = NULL; 260 } 261 if (NULL != TMH_by_id_map) 262 { 263 GNUNET_CONTAINER_multihashmap_iterate (TMH_by_id_map, 264 &TMH_instance_free_cb, 265 NULL); 266 GNUNET_CONTAINER_multihashmap_destroy (TMH_by_id_map); 267 TMH_by_id_map = NULL; 268 } 269 TALER_MHD_daemons_destroy (); 270 TMH_EXCHANGES_done (); 271 if (NULL != TMH_db) 272 { 273 TALER_MERCHANTDB_disconnect (TMH_db); 274 TMH_db = NULL; 275 } 276 TALER_TEMPLATING_done (); 277 if (NULL != TMH_curl_ctx) 278 { 279 GNUNET_CURL_fini (TMH_curl_ctx); 280 TMH_curl_ctx = NULL; 281 } 282 if (NULL != merchant_curl_rc) 283 { 284 GNUNET_CURL_gnunet_rc_destroy (merchant_curl_rc); 285 merchant_curl_rc = NULL; 286 } 287 if (NULL != TMH_payment_target_regex) 288 { 289 regfree (&TMH_payment_target_re); 290 GNUNET_free (TMH_payment_target_regex); 291 } 292 } 293 294 295 /** 296 * Function called whenever MHD is done with a request. If the 297 * request was a POST, we may have stored a `struct Buffer *` in the 298 * @a con_cls that might still need to be cleaned up. Call the 299 * respective function to free the memory. 300 * 301 * @param cls client-defined closure 302 * @param connection connection handle 303 * @param con_cls value as set by the last call to 304 * the #MHD_AccessHandlerCallback 305 * @param toe reason for request termination 306 * @see #MHD_OPTION_NOTIFY_COMPLETED 307 * @ingroup request 308 */ 309 static void 310 handle_mhd_completion_callback (void *cls, 311 struct MHD_Connection *connection, 312 void **con_cls, 313 enum MHD_RequestTerminationCode toe) 314 { 315 struct TMH_HandlerContext *hc = *con_cls; 316 317 (void) cls; 318 if (NULL == hc) 319 return; 320 GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); 321 { 322 #if MHD_VERSION >= 0x00097304 323 const union MHD_ConnectionInfo *ci; 324 unsigned int http_status = 0; 325 326 ci = MHD_get_connection_info (connection, 327 MHD_CONNECTION_INFO_HTTP_STATUS); 328 if (NULL != ci) 329 http_status = ci->http_status; 330 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 331 "Request for `%s' completed with HTTP status %u (%d)\n", 332 hc->url, 333 http_status, 334 toe); 335 #else 336 (void) connection; 337 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 338 "Finished handling request for `%s' with MHD termination code %d\n", 339 hc->url, 340 (int) toe); 341 #endif 342 } 343 if (NULL != hc->cc) 344 hc->cc (hc->ctx); 345 TALER_MHD_parse_post_cleanup_callback (hc->json_parse_context); 346 GNUNET_free (hc->infix); 347 GNUNET_free (hc->rewritten_url); 348 if (NULL != hc->request_body) 349 json_decref (hc->request_body); 350 if (NULL != hc->instance) 351 TMH_instance_decref (hc->instance); 352 TALER_MERCHANTDB_preflight (TMH_db); 353 GNUNET_free (hc->full_url); 354 GNUNET_free (hc); 355 *con_cls = NULL; 356 } 357 358 359 struct TMH_MerchantInstance * 360 TMH_lookup_instance (const char *instance_id) 361 { 362 struct GNUNET_HashCode h_instance; 363 char *id; 364 365 if (NULL == instance_id) 366 id = GNUNET_strdup ("admin"); 367 else 368 id = GNUNET_STRINGS_utf8_tolower (instance_id); 369 GNUNET_CRYPTO_hash (id, 370 strlen (id), 371 &h_instance); 372 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 373 "Looking for by-id key %s of '%s' in hashmap\n", 374 GNUNET_h2s (&h_instance), 375 id); 376 GNUNET_free (id); 377 /* We're fine if that returns NULL, the calling routine knows how 378 to handle that */ 379 return GNUNET_CONTAINER_multihashmap_get (TMH_by_id_map, 380 &h_instance); 381 } 382 383 384 /** 385 * Add instance definition to our active set of instances. 386 * 387 * @param[in,out] mi merchant instance details to define 388 * @return #GNUNET_OK on success, #GNUNET_NO if the same ID is in use already 389 */ 390 enum GNUNET_GenericReturnValue 391 TMH_add_instance (struct TMH_MerchantInstance *mi) 392 { 393 const char *id; 394 enum GNUNET_GenericReturnValue ret; 395 396 id = mi->settings.id; 397 if (NULL == id) 398 id = "admin"; 399 GNUNET_CRYPTO_hash (id, 400 strlen (id), 401 &mi->h_instance); 402 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, 403 "Looking for by-id key %s of `%s' in hashmap\n", 404 GNUNET_h2s (&mi->h_instance), 405 id); 406 ret = GNUNET_CONTAINER_multihashmap_put (TMH_by_id_map, 407 &mi->h_instance, 408 mi, 409 GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY); 410 if (GNUNET_OK == ret) 411 { 412 GNUNET_assert (mi->rc < UINT_MAX); 413 mi->rc++; 414 } 415 return ret; 416 } 417 418 419 /** 420 * Function called first by MHD with the full URL. 421 * 422 * @param cls NULL 423 * @param full_url the full URL 424 * @param con MHD connection object 425 * @return our handler context 426 */ 427 static void * 428 full_url_track_callback (void *cls, 429 const char *full_url, 430 struct MHD_Connection *con) 431 { 432 struct TMH_HandlerContext *hc; 433 434 hc = GNUNET_new (struct TMH_HandlerContext); 435 hc->connection = con; 436 GNUNET_async_scope_fresh (&hc->async_scope_id); 437 GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); 438 hc->full_url = GNUNET_strdup (full_url); 439 return hc; 440 } 441 442 443 /** 444 * The callback was called again by MHD, continue processing 445 * the request with the already identified handler. 446 * 447 * @param hc the handler context 448 * @param upload_data the data being uploaded (excluding HEADERS, 449 * for a POST that fits into memory and that is encoded 450 * with a supported encoding, the POST data will NOT be 451 * given in upload_data and is instead available as 452 * part of #MHD_get_connection_values; very large POST 453 * data *will* be made available incrementally in 454 * @a upload_data) 455 * @param upload_data_size set initially to the size of the 456 * @a upload_data provided; the method must update this 457 * value to the number of bytes NOT processed; 458 * @return #MHD_YES if the connection was handled successfully, 459 * #MHD_NO if the socket must be closed due to a serious 460 * error while handling the request 461 */ 462 static enum MHD_Result 463 process_upload_with_handler (struct TMH_HandlerContext *hc, 464 const char *upload_data, 465 size_t *upload_data_size) 466 { 467 GNUNET_assert (NULL != hc->rh); 468 GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id); 469 if ( (hc->has_body) && 470 (NULL == hc->request_body) ) 471 { 472 size_t mul = hc->rh->max_upload; 473 enum GNUNET_GenericReturnValue res; 474 475 if (0 == mul) 476 mul = DEFAULT_MAX_UPLOAD_SIZE; 477 if ( (hc->total_upload + *upload_data_size < hc->total_upload) || 478 (hc->total_upload + *upload_data_size > mul) ) 479 { 480 /* Client exceeds upload limit. Should _usually_ be checked earlier 481 when we look at the MHD_HTTP_HEADER_CONTENT_LENGTH, alas with 482 chunked encoding an uploader MAY have omitted this, and thus 483 not permitted us to check on time. In this case, we just close 484 the connection once it exceeds our limit (instead of waiting 485 for the upload to complete and then fail). This could theoretically 486 cause some clients to retry, alas broken or malicious clients 487 are likely to retry anyway, so little we can do about it, and 488 failing earlier seems the best option here. */ 489 GNUNET_break_op (0); 490 return MHD_NO; 491 } 492 hc->total_upload += *upload_data_size; 493 res = TALER_MHD_parse_post_json (hc->connection, 494 &hc->json_parse_context, 495 upload_data, 496 upload_data_size, 497 &hc->request_body); 498 if (GNUNET_SYSERR == res) 499 return MHD_NO; 500 /* A error response was already generated */ 501 if ( (GNUNET_NO == res) || 502 /* or, need more data to accomplish parsing */ 503 (NULL == hc->request_body) ) 504 return MHD_YES; /* let MHD call us *again* */ 505 } 506 /* Upload complete (if any), call handler to generate reply */ 507 return hc->rh->handler (hc->rh, 508 hc->connection, 509 hc); 510 } 511 512 513 /** 514 * Log information about the request being handled. 515 * 516 * @param hc handler context 517 * @param method HTTP method of the request 518 */ 519 static void 520 log_request (const struct TMH_HandlerContext *hc, 521 const char *method) 522 { 523 const char *correlation_id; 524 525 correlation_id = MHD_lookup_connection_value (hc->connection, 526 MHD_HEADER_KIND, 527 "Taler-Correlation-Id"); 528 if ( (NULL != correlation_id) && 529 (GNUNET_YES != 530 GNUNET_CURL_is_valid_scope_id (correlation_id)) ) 531 { 532 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 533 "Illegal incoming correlation ID\n"); 534 correlation_id = NULL; 535 } 536 if (NULL != correlation_id) 537 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 538 "Handling request for (%s) URL '%s', correlation_id=%s\n", 539 method, 540 hc->url, 541 correlation_id); 542 else 543 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 544 "Handling request (%s) for URL '%s'\n", 545 method, 546 hc->url); 547 } 548 549 550 /** 551 * Identify the instance of the request from the URL. 552 * 553 * @param[in,out] hc handler context 554 * @param[in,out] urlp URL path of the request, updated to point to the rest 555 * @param[out] use_admin set to true if we are using the admin instance 556 * @return #GNUNET_OK on success, 557 * #GNUNET_NO if an error was queued (return #MHD_YES) 558 * #GNUNET_SYSERR to close the connection (return #MHD_NO) 559 */ 560 static enum GNUNET_GenericReturnValue 561 identify_instance (struct TMH_HandlerContext *hc, 562 const char **urlp, 563 bool *use_admin) 564 { 565 const char *url = *urlp; 566 const char *instance_prefix = "/instances/"; 567 568 if (0 == strncmp (url, 569 instance_prefix, 570 strlen (instance_prefix))) 571 { 572 /* url starts with "/instances/" */ 573 const char *istart = url + strlen (instance_prefix); 574 const char *slash = strchr (istart, '/'); 575 char *raw_id; 576 char *instance_id; 577 578 if (NULL == slash) 579 raw_id = GNUNET_strdup (istart); 580 else 581 raw_id = GNUNET_strndup (istart, 582 slash - istart); 583 /* Instance IDs are case-insensitive, so fold the segment before comparing 584 it against "admin" below. Without this, '/instances/Admin/' misses the 585 redirect to the modern path, leaving use_admin false, and every 586 'default_only' handler that '/instances/admin/' reaches (all of 587 /management/) then replies 404. */ 588 instance_id = GNUNET_STRINGS_utf8_tolower (raw_id); 589 GNUNET_free (raw_id); 590 if (0 == strcmp (instance_id, 591 "admin")) 592 { 593 enum MHD_Result ret; 594 struct MHD_Response *response; 595 const char *rstart = hc->full_url + strlen (instance_prefix); 596 const char *rslash = strchr (rstart, '/'); 597 598 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 599 "Client used deprecated '/instances/admin/' path. Redirecting to modern path\n"); 600 601 response 602 = MHD_create_response_from_buffer (0, 603 NULL, 604 MHD_RESPMEM_PERSISTENT); 605 TALER_MHD_add_global_headers (response, 606 true); 607 if (MHD_NO == 608 MHD_add_response_header (response, 609 MHD_HTTP_HEADER_LOCATION, 610 NULL == rslash 611 ? "/" 612 : rslash)) 613 { 614 GNUNET_break (0); 615 MHD_destroy_response (response); 616 GNUNET_free (instance_id); 617 return GNUNET_SYSERR; 618 } 619 ret = MHD_queue_response (hc->connection, 620 MHD_HTTP_PERMANENT_REDIRECT, 621 response); 622 MHD_destroy_response (response); 623 GNUNET_free (instance_id); 624 return (MHD_YES == ret) ? GNUNET_NO : GNUNET_SYSERR; 625 } 626 hc->instance = TMH_lookup_instance (instance_id); 627 if ( (NULL == hc->instance) && 628 (0 == strcmp ("admin", 629 instance_id)) ) 630 hc->instance = TMH_lookup_instance (NULL); 631 GNUNET_free (instance_id); 632 if (NULL == slash) 633 *urlp = ""; 634 else 635 *urlp = slash; 636 } 637 else 638 { 639 /* use 'default' */ 640 *use_admin = true; 641 hc->instance = TMH_lookup_instance (NULL); 642 } 643 if (NULL != hc->instance) 644 { 645 GNUNET_assert (hc->instance->rc < UINT_MAX); 646 hc->instance->rc++; 647 } 648 return GNUNET_OK; 649 } 650 651 652 /** 653 * A client has requested the given url using the given method 654 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 655 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback 656 * must call MHD callbacks to provide content to give back to the 657 * client and return an HTTP status code (i.e. #MHD_HTTP_OK, 658 * #MHD_HTTP_NOT_FOUND, etc.). 659 * 660 * @param cls argument given together with the function 661 * pointer when the handler was registered with MHD 662 * @param connection the MHD connection to handle 663 * @param url the requested url 664 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 665 * #MHD_HTTP_METHOD_PUT, etc.) 666 * @param version the HTTP version string (i.e. 667 * #MHD_HTTP_VERSION_1_1) 668 * @param upload_data the data being uploaded (excluding HEADERS, 669 * for a POST that fits into memory and that is encoded 670 * with a supported encoding, the POST data will NOT be 671 * given in upload_data and is instead available as 672 * part of #MHD_get_connection_values; very large POST 673 * data *will* be made available incrementally in 674 * @a upload_data) 675 * @param upload_data_size set initially to the size of the 676 * @a upload_data provided; the method must update this 677 * value to the number of bytes NOT processed; 678 * @param con_cls pointer that the callback can set to some 679 * address and that will be preserved by MHD for future 680 * calls for this request; since the access handler may 681 * be called many times (i.e., for a PUT/POST operation 682 * with plenty of upload data) this allows the application 683 * to easily associate some request-specific state. 684 * If necessary, this state can be cleaned up in the 685 * global #MHD_RequestCompletedCallback (which 686 * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). 687 * Initially, `*con_cls` will be set up by the 688 * full_url_track_callback(). 689 * @return #MHD_YES if the connection was handled successfully, 690 * #MHD_NO if the socket must be closed due to a serious 691 * error while handling the request 692 */ 693 static enum MHD_Result 694 url_handler (void *cls, 695 struct MHD_Connection *connection, 696 const char *url, 697 const char *method, 698 const char *version, 699 const char *upload_data, 700 size_t *upload_data_size, 701 void **con_cls) 702 { 703 struct TMH_HandlerContext *hc = *con_cls; 704 bool use_admin = false; 705 bool is_public = false; 706 707 (void) cls; 708 (void) version; 709 if (NULL == hc->url) 710 { 711 /* First time. 712 * Find out the merchant backend instance for the request. 713 * If there is an instance, remove the instance specification 714 * from the beginning of the request URL. */ 715 enum GNUNET_GenericReturnValue ret; 716 717 hc->url = url; 718 log_request (hc, 719 method); 720 ret = identify_instance (hc, 721 &url, 722 &use_admin); 723 if (GNUNET_OK != ret) 724 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 725 } 726 727 if (NULL != hc->instance) 728 { 729 /* Narrow DB interaction to selected instance */ 730 enum GNUNET_DB_QueryStatus qs; 731 732 qs = TALER_MERCHANTDB_set_instance (TMH_db, 733 hc->instance->settings.id); 734 switch (qs) 735 { 736 case GNUNET_DB_STATUS_HARD_ERROR: 737 GNUNET_break (0); 738 return TALER_MHD_reply_with_error ( 739 connection, 740 MHD_HTTP_INTERNAL_SERVER_ERROR, 741 TALER_EC_GENERIC_DB_SETUP_FAILED, 742 "set_instance"); 743 case GNUNET_DB_STATUS_SOFT_ERROR: 744 GNUNET_break (0); 745 return TALER_MHD_reply_with_error ( 746 connection, 747 MHD_HTTP_INTERNAL_SERVER_ERROR, 748 TALER_EC_GENERIC_DB_SETUP_FAILED, 749 "set_instance"); 750 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 751 return TALER_MHD_reply_with_error ( 752 connection, 753 MHD_HTTP_NOT_FOUND, 754 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 755 hc->url); 756 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 757 break; 758 } 759 } 760 761 if (NULL != hc->rh) 762 { 763 enum MHD_Result res; 764 765 /* MHD calls us again for a request, we already identified 766 the handler, just continue processing with the handler */ 767 res = process_upload_with_handler (hc, 768 upload_data, 769 upload_data_size); 770 if (NULL != hc->instance) 771 { 772 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 773 TALER_MERCHANTDB_set_instance (TMH_db, 774 NULL)); 775 } 776 return res; 777 } 778 779 /* First time, let's figure out the handler */ 780 { 781 enum GNUNET_GenericReturnValue ret; 782 783 ret = TMH_dispatch_request (hc, 784 url, 785 method, 786 use_admin, 787 &is_public); 788 if (GNUNET_OK != ret) 789 { 790 if (NULL != hc->instance) 791 { 792 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 793 TALER_MERCHANTDB_set_instance (TMH_db, 794 NULL)); 795 } 796 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 797 } 798 } 799 800 /* At this point, we must have found a handler */ 801 GNUNET_assert (NULL != hc->rh); 802 803 /* If an instance must be there, check one exists */ 804 if ( (NULL == hc->instance) && 805 (! hc->rh->skip_instance) ) 806 { 807 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 808 "Instance for `%s' not known\n", 809 hc->url); 810 return TALER_MHD_reply_with_error (connection, 811 MHD_HTTP_NOT_FOUND, 812 TALER_EC_MERCHANT_GENERIC_INSTANCE_UNKNOWN, 813 hc->url); 814 } 815 816 /* Perform access control for non-public handlers */ 817 if (! is_public) 818 { 819 enum GNUNET_GenericReturnValue ret; 820 821 ret = TMH_perform_access_control (hc); 822 if (GNUNET_OK != ret) 823 { 824 if (NULL != hc->instance) 825 { 826 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 827 TALER_MERCHANTDB_set_instance (TMH_db, 828 NULL)); 829 } 830 return (GNUNET_NO == ret) ? MHD_YES : MHD_NO; 831 } 832 } 833 if (NULL != hc->instance) 834 { 835 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 836 TALER_MERCHANTDB_set_instance (TMH_db, 837 NULL)); 838 } 839 840 if ( (NULL != hc->instance) && /* make static analysis happy */ 841 (! hc->rh->skip_instance) && 842 (hc->instance->deleted) && 843 (! hc->rh->allow_deleted_instance) ) 844 { 845 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 846 "Instance `%s' was deleted\n", 847 hc->instance->settings.id); 848 return TALER_MHD_reply_with_error (connection, 849 MHD_HTTP_NOT_FOUND, 850 TALER_EC_MERCHANT_GENERIC_INSTANCE_DELETED, 851 hc->instance->settings.id); 852 } 853 854 /* Check upload constraints */ 855 hc->has_body = ( (0 == strcasecmp (method, 856 MHD_HTTP_METHOD_POST)) || 857 /* PUT is not yet used */ 858 (0 == strcasecmp (method, 859 MHD_HTTP_METHOD_PATCH)) ); 860 if (hc->has_body) 861 { 862 /* This is a macro: it will queue an error response and return 863 from this function if the upload would be too large. */ 864 TALER_MHD_check_content_length (connection, 865 0 == hc->rh->max_upload 866 ? DEFAULT_MAX_UPLOAD_SIZE 867 : hc->rh->max_upload); 868 GNUNET_break (NULL == hc->request_body); /* can't have it already */ 869 } 870 /* wait for MHD to call us again, this time hc->url will be non-NULL 871 and we should jump straight into process_upload_with_handler(). */ 872 return MHD_YES; 873 } 874 875 876 /** 877 * Callback invoked with information about a bank account. 878 * 879 * @param cls closure with a `struct TMH_MerchantInstance *` 880 * @param merchant_priv private key of the merchant instance 881 * @param acc details about the account 882 */ 883 static void 884 add_account_cb (void *cls, 885 const struct TALER_MerchantPrivateKeyP *merchant_priv, 886 const struct TALER_MERCHANTDB_AccountDetails *acc) 887 { 888 struct TMH_MerchantInstance *mi = cls; 889 struct TMH_WireMethod *wm; 890 891 (void) merchant_priv; 892 wm = GNUNET_new (struct TMH_WireMethod); 893 wm->h_wire = acc->h_wire; 894 wm->payto_uri.full_payto 895 = GNUNET_strdup (acc->payto_uri.full_payto); 896 if (NULL != acc->extra_wire_subject_metadata) 897 wm->extra_wire_subject_metadata 898 = GNUNET_strdup (acc->extra_wire_subject_metadata); 899 wm->wire_salt = acc->salt; 900 wm->wire_method 901 = TALER_payto_get_method (acc->payto_uri.full_payto); 902 wm->active = acc->active; 903 GNUNET_CONTAINER_DLL_insert (mi->wm_head, 904 mi->wm_tail, 905 wm); 906 } 907 908 909 /** 910 * Function called during startup to add all known instances to our 911 * hash map in memory for faster lookups when we receive requests. 912 * 913 * @param cls closure, NULL, unused 914 * @param merchant_pub public key of the instance 915 * @param merchant_priv private key of the instance, NULL if not available 916 * @param is detailed configuration settings for the instance 917 * @param ias authentication settings for the instance 918 */ 919 static void 920 add_instance_cb (void *cls, 921 const struct TALER_MerchantPublicKeyP *merchant_pub, 922 const struct TALER_MerchantPrivateKeyP *merchant_priv, 923 const struct TALER_MERCHANTDB_InstanceSettings *is, 924 const struct TALER_MERCHANTDB_InstanceAuthSettings *ias) 925 { 926 struct TMH_MerchantInstance *mi; 927 enum GNUNET_DB_QueryStatus qs; 928 929 (void) cls; 930 mi = TMH_lookup_instance (is->id); 931 if (NULL != mi) 932 { 933 /* (outdated) entry exists, remove old entry */ 934 (void) TMH_instance_free_cb (NULL, 935 &mi->h_instance, 936 mi); 937 } 938 mi = GNUNET_new (struct TMH_MerchantInstance); 939 mi->settings = *is; 940 mi->auth = *ias; 941 mi->settings.id = GNUNET_STRINGS_utf8_tolower (mi->settings.id); 942 mi->settings.name = GNUNET_strdup (mi->settings.name); 943 if (NULL != mi->settings.email) 944 mi->settings.email = GNUNET_strdup (mi->settings.email); 945 if (NULL != mi->settings.phone) 946 mi->settings.phone = GNUNET_strdup (mi->settings.phone); 947 if (NULL != mi->settings.website) 948 mi->settings.website = GNUNET_strdup (mi->settings.website); 949 if (NULL != mi->settings.logo) 950 mi->settings.logo = GNUNET_strdup (mi->settings.logo); 951 mi->settings.address = json_incref (mi->settings.address); 952 mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction); 953 if (NULL != merchant_priv) 954 mi->merchant_priv = *merchant_priv; 955 else 956 mi->deleted = true; 957 mi->merchant_pub = *merchant_pub; 958 qs = TALER_MERCHANTDB_set_instance (TMH_db, 959 mi->settings.id); 960 if (0 > qs) 961 { 962 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 963 "Error setting instance `%s'\n", 964 mi->settings.id); 965 GNUNET_SCHEDULER_shutdown (); 966 return; 967 } 968 qs = TALER_MERCHANTDB_iterate_accounts_by_instance ( 969 TMH_db, 970 mi->settings.id, 971 &add_account_cb, 972 mi); 973 if (0 > qs) 974 { 975 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 976 "Error loading accounts of `%s' from database\n", 977 mi->settings.id); 978 GNUNET_SCHEDULER_shutdown (); 979 return; 980 } 981 GNUNET_assert (GNUNET_OK == 982 TMH_add_instance (mi)); 983 GNUNET_break (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT == 984 TALER_MERCHANTDB_set_instance (TMH_db, 985 NULL)); 986 } 987 988 989 /** 990 * Trigger (re)loading of instance settings from DB. 991 * 992 * @param cls NULL 993 * @param extra ID of the instance that changed, NULL 994 * to load all instances (will not handle purges!) 995 * @param extra_len number of bytes in @a extra 996 */ 997 static void 998 load_instances (void *cls, 999 const void *extra, 1000 size_t extra_len) 1001 { 1002 enum GNUNET_DB_QueryStatus qs; 1003 const char *id = extra; 1004 1005 (void) cls; 1006 if ( (NULL != extra) && 1007 ( (0 == extra_len) || 1008 ('\0' != id[extra_len - 1]) ) ) 1009 { 1010 GNUNET_break (0 == extra_len); 1011 extra = NULL; 1012 } 1013 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1014 "Received instance settings notification: reload `%s'\n", 1015 id); 1016 if (NULL == extra) 1017 { 1018 qs = TALER_MERCHANTDB_iterate_instances (TMH_db, 1019 false, 1020 &add_instance_cb, 1021 NULL); 1022 } 1023 else 1024 { 1025 struct TMH_MerchantInstance *mi; 1026 1027 /* This must be done here to handle instance 1028 purging, as for purged instances, the DB 1029 lookup below will otherwise do nothing */ 1030 mi = TMH_lookup_instance (id); 1031 if (NULL != mi) 1032 { 1033 (void) TMH_instance_free_cb (NULL, 1034 &mi->h_instance, 1035 mi); 1036 } 1037 qs = TALER_MERCHANTDB_iterate_instances_by_id (TMH_db, 1038 id, 1039 false, 1040 &add_instance_cb, 1041 NULL); 1042 } 1043 if (0 > qs) 1044 { 1045 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1046 "Failed initialization. Check database setup.\n"); 1047 global_ret = EXIT_FAILURE; 1048 GNUNET_SCHEDULER_shutdown (); 1049 return; 1050 } 1051 } 1052 1053 1054 /** 1055 * A transaction modified an instance setting (or created/deleted/purged 1056 * one). Notify all backends about the change. 1057 * 1058 * @param id ID of the instance that changed 1059 */ 1060 void 1061 TMH_reload_instances (const char *id) 1062 { 1063 struct GNUNET_DB_EventHeaderP es = { 1064 .size = htons (sizeof (es)), 1065 .type = htons (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS) 1066 }; 1067 1068 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1069 "Generating instance settings notification: reload `%s'\n", 1070 id); 1071 TALER_MERCHANTDB_event_notify (TMH_db, 1072 &es, 1073 id, 1074 (NULL == id) 1075 ? 0 1076 : strlen (id) + 1); 1077 } 1078 1079 1080 /** 1081 * Callback invoked on every listen socket to start the 1082 * respective MHD HTTP daemon. 1083 * 1084 * @param cls unused 1085 * @param lsock the listen socket 1086 */ 1087 static void 1088 start_daemon (void *cls, 1089 int lsock) 1090 { 1091 struct MHD_Daemon *mhd; 1092 1093 (void) cls; 1094 GNUNET_assert (-1 != lsock); 1095 mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK 1096 | MHD_USE_AUTO, 1097 0 /* port */, 1098 NULL, NULL, 1099 &url_handler, NULL, 1100 MHD_OPTION_LISTEN_SOCKET, lsock, 1101 MHD_OPTION_URI_LOG_CALLBACK, 1102 &full_url_track_callback, NULL, 1103 MHD_OPTION_NOTIFY_COMPLETED, 1104 &handle_mhd_completion_callback, NULL, 1105 MHD_OPTION_CONNECTION_TIMEOUT, 1106 (unsigned int) 10 /* 10s */, 1107 MHD_OPTION_END); 1108 if (NULL == mhd) 1109 { 1110 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1111 "Failed to launch HTTP service.\n"); 1112 GNUNET_SCHEDULER_shutdown (); 1113 return; 1114 } 1115 have_daemons = true; 1116 TALER_MHD_daemon_start (mhd); 1117 } 1118 1119 1120 /** 1121 * Main function that will be run by the scheduler. 1122 * 1123 * @param cls closure 1124 * @param args remaining command-line arguments 1125 * @param cfgfile name of the configuration file used (for saving, can be 1126 * NULL!) 1127 * @param config configuration 1128 */ 1129 static void 1130 run (void *cls, 1131 char *const *args, 1132 const char *cfgfile, 1133 const struct GNUNET_CONFIGURATION_Handle *config) 1134 { 1135 enum TALER_MHD_GlobalOptions go; 1136 int elen; 1137 1138 (void) cls; 1139 (void) args; 1140 (void) cfgfile; 1141 TMH_cfg = config; 1142 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1143 "Starting taler-merchant-httpd\n"); 1144 go = TALER_MHD_GO_NONE; 1145 if (merchant_connection_close) 1146 go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; 1147 TALER_MHD_setup (go); 1148 TALER_EXCHANGE_setup (enable_h3 1149 ? TALER_EXCHANGE_GO_ENABLE_HTTP3 1150 : TALER_EXCHANGE_GO_FORCE_HTTP1_1); 1151 DONAU_setup (enable_h3 1152 ? DONAU_GO_ENABLE_HTTP3 1153 : DONAU_GO_FORCE_HTTP1_1); 1154 1155 global_ret = EXIT_SUCCESS; 1156 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 1157 NULL); 1158 1159 TMH_curl_ctx 1160 = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 1161 &merchant_curl_rc); 1162 if (NULL == TMH_curl_ctx) 1163 { 1164 GNUNET_break (0); 1165 global_ret = EXIT_FAILURE; 1166 GNUNET_SCHEDULER_shutdown (); 1167 return; 1168 } 1169 merchant_curl_rc = GNUNET_CURL_gnunet_rc_create (TMH_curl_ctx); 1170 /* Disable 100 continue processing */ 1171 GNUNET_break (GNUNET_OK == 1172 GNUNET_CURL_append_header (TMH_curl_ctx, 1173 MHD_HTTP_HEADER_EXPECT ":")); 1174 GNUNET_CURL_enable_async_scope_header (TMH_curl_ctx, 1175 "Taler-Correlation-Id"); 1176 1177 if (GNUNET_SYSERR == 1178 TALER_config_get_currency (TMH_cfg, 1179 "merchant", 1180 &TMH_currency)) 1181 { 1182 global_ret = EXIT_NOTCONFIGURED; 1183 GNUNET_SCHEDULER_shutdown (); 1184 return; 1185 } 1186 if (GNUNET_OK != 1187 TALER_CONFIG_parse_currencies (TMH_cfg, 1188 TMH_currency, 1189 &TMH_num_cspecs, 1190 &TMH_cspecs)) 1191 { 1192 global_ret = EXIT_NOTCONFIGURED; 1193 GNUNET_SCHEDULER_shutdown (); 1194 return; 1195 } 1196 1197 { 1198 char *spa_data; 1199 1200 if (GNUNET_OK == 1201 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1202 "merchant", 1203 "GLOBAL_SPA_CONFIG_DATA", 1204 &spa_data)) 1205 { 1206 json_error_t err; 1207 1208 TMH_global_spa_config_data = json_loads (spa_data, 1209 JSON_REJECT_DUPLICATES, 1210 &err); 1211 GNUNET_free (spa_data); 1212 if (NULL == TMH_global_spa_config_data) 1213 { 1214 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1215 "merchant", 1216 "GLOBAL_SPA_CONFIG_DATA", 1217 err.text); 1218 global_ret = EXIT_NOTCONFIGURED; 1219 GNUNET_SCHEDULER_shutdown (); 1220 return; 1221 } 1222 } 1223 } 1224 1225 1226 if (GNUNET_SYSERR == 1227 (TMH_strict_v19 1228 = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg, 1229 "merchant", 1230 "STRICT_PROTOCOL_V19"))) 1231 { 1232 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1233 "merchant", 1234 "STRICT_PROTOCOL_V19"); 1235 TMH_strict_v19 = GNUNET_NO; 1236 } 1237 if (GNUNET_SYSERR == 1238 (TMH_auth_disabled = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg, 1239 "merchant", 1240 "DISABLE_AUTHENTICATION"))) 1241 { 1242 TMH_auth_disabled = GNUNET_NO; 1243 } 1244 if (GNUNET_YES == TMH_auth_disabled) 1245 { 1246 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1247 "DANGEROUS: Endpoint Authentication disabled!"); 1248 } 1249 1250 if (GNUNET_SYSERR == 1251 (TMH_have_self_provisioning 1252 = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg, 1253 "merchant", 1254 "ENABLE_SELF_PROVISIONING"))) 1255 { 1256 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1257 "merchant", 1258 "ENABLE_SELF_PROVISIONING"); 1259 TMH_have_self_provisioning = GNUNET_NO; 1260 } 1261 1262 if (GNUNET_SYSERR == 1263 (TMH_password_change_mfa 1264 = GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg, 1265 "merchant", 1266 "PASSWORD_CHANGE_MFA"))) 1267 { 1268 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1269 "merchant", 1270 "PASSWORD_CHANGE_MFA"); 1271 TMH_password_change_mfa = GNUNET_NO; 1272 } 1273 1274 if (GNUNET_OK != 1275 GNUNET_CONFIGURATION_get_value_time (TMH_cfg, 1276 "merchant", 1277 "LEGAL_PRESERVATION", 1278 &TMH_legal_expiration)) 1279 { 1280 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 1281 "merchant", 1282 "LEGAL_PRESERVATION"); 1283 global_ret = EXIT_NOTCONFIGURED; 1284 GNUNET_SCHEDULER_shutdown (); 1285 return; 1286 } 1287 1288 if (GNUNET_OK != 1289 GNUNET_CONFIGURATION_get_value_time (TMH_cfg, 1290 "merchant", 1291 "DEFAULT_PAY_DELAY", 1292 &TMH_default_pay_delay)) 1293 { 1294 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1295 "merchant", 1296 "DEFAULT_PAY_DELAY"); 1297 TMH_default_pay_delay = GNUNET_TIME_UNIT_DAYS; 1298 } 1299 if (GNUNET_TIME_relative_is_forever (TMH_default_pay_delay)) 1300 { 1301 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO, 1302 "merchant", 1303 "DEFAULT_PAY_DELAY", 1304 "forever is not allowed"); 1305 global_ret = EXIT_NOTCONFIGURED; 1306 GNUNET_SCHEDULER_shutdown (); 1307 return; 1308 } 1309 if (GNUNET_OK != 1310 GNUNET_CONFIGURATION_get_value_time (TMH_cfg, 1311 "merchant", 1312 "DEFAULT_REFUND_DELAY", 1313 &TMH_default_refund_delay)) 1314 { 1315 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1316 "merchant", 1317 "DEFAULT_REFUND_DELAY"); 1318 TMH_default_refund_delay = GNUNET_TIME_relative_multiply ( 1319 GNUNET_TIME_UNIT_DAYS, 1320 15); 1321 } 1322 if (GNUNET_TIME_relative_is_forever (TMH_default_refund_delay)) 1323 { 1324 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO, 1325 "merchant", 1326 "DEFAULT_REFUND_DELAY", 1327 "forever is not allowed"); 1328 global_ret = EXIT_NOTCONFIGURED; 1329 GNUNET_SCHEDULER_shutdown (); 1330 return; 1331 } 1332 1333 if (GNUNET_OK != 1334 GNUNET_CONFIGURATION_get_value_time (TMH_cfg, 1335 "merchant", 1336 "DEFAULT_WIRE_TRANSFER_DELAY", 1337 &TMH_default_wire_transfer_delay)) 1338 { 1339 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1340 "merchant", 1341 "DEFAULT_WIRE_TRANSFER_DELAY"); 1342 TMH_default_wire_transfer_delay = GNUNET_TIME_UNIT_MONTHS; 1343 } 1344 if (GNUNET_TIME_relative_is_forever (TMH_default_wire_transfer_delay)) 1345 { 1346 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_INFO, 1347 "merchant", 1348 "DEFAULT_WIRE_TRANSFER_DELAY", 1349 "forever is not allowed"); 1350 global_ret = EXIT_NOTCONFIGURED; 1351 GNUNET_SCHEDULER_shutdown (); 1352 return; 1353 } 1354 1355 { 1356 char *dwtri; 1357 1358 if (GNUNET_OK != 1359 GNUNET_CONFIGURATION_get_value_string ( 1360 TMH_cfg, 1361 "merchant", 1362 "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL", 1363 &dwtri)) 1364 { 1365 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_INFO, 1366 "merchant", 1367 "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL"); 1368 TMH_default_wire_transfer_rounding_interval = GNUNET_TIME_RI_NONE; 1369 } 1370 else 1371 { 1372 if (GNUNET_OK != 1373 GNUNET_TIME_string_to_round_interval ( 1374 dwtri, 1375 &TMH_default_wire_transfer_rounding_interval)) 1376 { 1377 GNUNET_log_config_invalid ( 1378 GNUNET_ERROR_TYPE_ERROR, 1379 "merchant", 1380 "DEFAULT_WIRE_TRANSFER_ROUNDING_INTERVAL", 1381 "invalid time rounding interval"); 1382 global_ret = EXIT_NOTCONFIGURED; 1383 GNUNET_free (dwtri); 1384 GNUNET_SCHEDULER_shutdown (); 1385 return; 1386 } 1387 GNUNET_free (dwtri); 1388 } 1389 } 1390 1391 TMH_load_terms (TMH_cfg); 1392 1393 if (GNUNET_OK != 1394 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1395 "merchant", 1396 "PAYMENT_TARGET_TYPES", 1397 &TMH_allowed_payment_targets)) 1398 { 1399 TMH_allowed_payment_targets = GNUNET_strdup ("*"); 1400 } 1401 1402 if (GNUNET_OK != 1403 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1404 "merchant", 1405 "DEFAULT_PERSONA", 1406 &TMH_default_persona)) 1407 { 1408 TMH_default_persona = GNUNET_strdup ("expert"); 1409 } 1410 1411 if (GNUNET_OK != 1412 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1413 "merchant", 1414 "PAYMENT_TARGET_REGEX", 1415 &TMH_payment_target_regex)) 1416 { 1417 TMH_payment_target_regex = NULL; 1418 } 1419 else 1420 { 1421 if (0 == strlen (TMH_payment_target_regex)) 1422 { 1423 GNUNET_free (TMH_payment_target_regex); 1424 } 1425 else 1426 { 1427 if (0 != regcomp (&TMH_payment_target_re, 1428 TMH_payment_target_regex, 1429 REG_NOSUB | REG_EXTENDED)) 1430 { 1431 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1432 "merchant", 1433 "PAYMENT_TARGET_REGEX", 1434 "malformed regular expression"); 1435 global_ret = EXIT_NOTCONFIGURED; 1436 GNUNET_free (TMH_payment_target_regex); 1437 GNUNET_SCHEDULER_shutdown (); 1438 return; 1439 } 1440 } 1441 } 1442 if (GNUNET_OK != 1443 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1444 "merchant", 1445 "PHONE_REGEX", 1446 &TMH_phone_regex)) 1447 { 1448 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, 1449 "merchant", 1450 "PHONE_REGEX", 1451 "no restrictions on phone number specified"); 1452 } 1453 else 1454 { 1455 if (0 != regcomp (&TMH_phone_rx, 1456 TMH_phone_regex, 1457 REG_EXTENDED)) 1458 { 1459 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1460 "merchant", 1461 "PHONE_REGEX", 1462 "Invalid regex specified"); 1463 global_ret = EXIT_NOTCONFIGURED; 1464 GNUNET_SCHEDULER_shutdown (); 1465 return; 1466 } 1467 } 1468 1469 if (GNUNET_OK != 1470 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1471 "merchant", 1472 "HELPER_SMS", 1473 &TMH_helper_sms)) 1474 { 1475 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, 1476 "merchant", 1477 "HELPER_SMS", 1478 "no helper specified"); 1479 } 1480 1481 if (GNUNET_OK != 1482 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1483 "merchant", 1484 "HELPER_EMAIL", 1485 &TMH_helper_email)) 1486 { 1487 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_WARNING, 1488 "merchant", 1489 "HELPER_EMAIL", 1490 "no helper specified"); 1491 } 1492 1493 { 1494 char *tan_channels; 1495 1496 if (GNUNET_OK == 1497 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1498 "merchant", 1499 "MANDATORY_TAN_CHANNELS", 1500 &tan_channels)) 1501 { 1502 for (char *tok = strtok (tan_channels, 1503 " "); 1504 NULL != tok; 1505 tok = strtok (NULL, 1506 " ")) 1507 { 1508 if (0 == strcasecmp (tok, 1509 "sms")) 1510 { 1511 if (NULL == TMH_helper_sms) 1512 { 1513 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1514 "merchant", 1515 "MANDATORY_TAN_CHANNELS", 1516 "SMS mandatory, but no HELPER_SMS configured"); 1517 global_ret = EXIT_NOTCONFIGURED; 1518 GNUNET_SCHEDULER_shutdown (); 1519 GNUNET_free (tan_channels); 1520 return; 1521 } 1522 TEH_mandatory_tan_channels |= TMH_TCS_SMS; 1523 } 1524 else if (0 == strcasecmp (tok, 1525 "email")) 1526 { 1527 if (NULL == TMH_helper_email) 1528 { 1529 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1530 "merchant", 1531 "MANDATORY_TAN_CHANNELS", 1532 "EMAIL mandatory, but no HELPER_EMAIL configured"); 1533 global_ret = EXIT_NOTCONFIGURED; 1534 GNUNET_SCHEDULER_shutdown (); 1535 GNUNET_free (tan_channels); 1536 return; 1537 } 1538 TEH_mandatory_tan_channels |= TMH_TCS_EMAIL; 1539 } 1540 else 1541 { 1542 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1543 "merchant", 1544 "MANDATORY_TAN_CHANNELS", 1545 tok); 1546 global_ret = EXIT_NOTCONFIGURED; 1547 GNUNET_SCHEDULER_shutdown (); 1548 GNUNET_free (tan_channels); 1549 return; 1550 } 1551 } 1552 GNUNET_free (tan_channels); 1553 } 1554 } 1555 1556 if ( (GNUNET_YES == TMH_password_change_mfa) && 1557 (TMH_TCS_NONE == TEH_mandatory_tan_channels) ) 1558 { 1559 GNUNET_log_config_invalid ( 1560 GNUNET_ERROR_TYPE_ERROR, 1561 "merchant", 1562 "PASSWORD_CHANGE_MFA", 1563 "requires at least one MANDATORY_TAN_CHANNELS entry"); 1564 global_ret = EXIT_NOTCONFIGURED; 1565 GNUNET_SCHEDULER_shutdown (); 1566 return; 1567 } 1568 1569 if (GNUNET_OK == 1570 GNUNET_CONFIGURATION_get_value_string (TMH_cfg, 1571 "merchant", 1572 "BASE_URL", 1573 &TMH_base_url)) 1574 { 1575 if (! TALER_is_web_url (TMH_base_url)) 1576 { 1577 GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, 1578 "merchant", 1579 "BASE_URL", 1580 "Needs to start with 'http://' or 'https://'"); 1581 global_ret = EXIT_NOTCONFIGURED; 1582 GNUNET_SCHEDULER_shutdown (); 1583 return; 1584 } 1585 } 1586 if (GNUNET_OK == 1587 GNUNET_CONFIGURATION_get_value_filename (TMH_cfg, 1588 "merchant", 1589 "BACKOFFICE_SPA_DIR", 1590 &TMH_spa_dir)) 1591 { 1592 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1593 "Loading merchant SPA from %s\n", 1594 TMH_spa_dir); 1595 } 1596 else 1597 { 1598 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1599 "Loading merchant SPA from default location\n"); 1600 } 1601 1602 if (GNUNET_YES == 1603 GNUNET_CONFIGURATION_get_value_yesno (TMH_cfg, 1604 "merchant", 1605 "FORCE_AUDIT")) 1606 TMH_force_audit = GNUNET_YES; 1607 if (GNUNET_OK != 1608 TALER_TEMPLATING_init (TALER_MERCHANT_project_data ())) 1609 { 1610 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1611 "Failed to setup templates\n"); 1612 global_ret = EXIT_NO_RESTART; 1613 GNUNET_SCHEDULER_shutdown (); 1614 return; 1615 } 1616 if (GNUNET_OK != 1617 TMH_spa_init (TMH_spa_dir)) 1618 { 1619 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1620 "Failed to load single page app\n"); 1621 global_ret = EXIT_NO_RESTART; 1622 GNUNET_SCHEDULER_shutdown (); 1623 return; 1624 } 1625 /* /static/ is currently not used */ 1626 /* (void) TMH_statics_init (); */ 1627 if (NULL == 1628 (TMH_by_id_map = GNUNET_CONTAINER_multihashmap_create (4, 1629 GNUNET_YES))) 1630 { 1631 global_ret = EXIT_FAILURE; 1632 GNUNET_SCHEDULER_shutdown (); 1633 return; 1634 } 1635 if (NULL == 1636 (TMH_db = TALER_MERCHANTDB_connect (TMH_cfg))) 1637 { 1638 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1639 "Failed to connect to database. Consider running taler-merchant-dbinit!\n"); 1640 global_ret = EXIT_FAILURE; 1641 GNUNET_SCHEDULER_shutdown (); 1642 return; 1643 } 1644 elen = TMH_EXCHANGES_init (config); 1645 if (GNUNET_SYSERR == elen) 1646 { 1647 global_ret = EXIT_NOTCONFIGURED; 1648 GNUNET_SCHEDULER_shutdown (); 1649 return; 1650 } 1651 if (0 == elen) 1652 { 1653 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 1654 "Fatal: no trusted exchanges configured. Exiting.\n"); 1655 global_ret = EXIT_NOTCONFIGURED; 1656 GNUNET_SCHEDULER_shutdown (); 1657 return; 1658 } 1659 1660 { 1661 struct GNUNET_DB_EventHeaderP es = { 1662 .size = htons (sizeof (es)), 1663 .type = htons (TALER_DBEVENT_MERCHANT_INSTANCE_SETTINGS) 1664 }; 1665 1666 instance_eh = TALER_MERCHANTDB_event_listen (TMH_db, 1667 &es, 1668 GNUNET_TIME_UNIT_FOREVER_REL, 1669 &load_instances, 1670 NULL); 1671 } 1672 load_instances (NULL, 1673 NULL, 1674 0); 1675 { 1676 enum GNUNET_GenericReturnValue ret; 1677 1678 ret = TALER_MHD_listen_bind (TMH_cfg, 1679 "merchant", 1680 &start_daemon, 1681 NULL); 1682 switch (ret) 1683 { 1684 case GNUNET_SYSERR: 1685 global_ret = EXIT_NOTCONFIGURED; 1686 GNUNET_SCHEDULER_shutdown (); 1687 return; 1688 case GNUNET_NO: 1689 if (! have_daemons) 1690 { 1691 global_ret = EXIT_FAILURE; 1692 GNUNET_SCHEDULER_shutdown (); 1693 return; 1694 } 1695 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 1696 "Could not open all configured listen sockets\n"); 1697 break; 1698 case GNUNET_OK: 1699 break; 1700 } 1701 } 1702 global_ret = EXIT_SUCCESS; 1703 } 1704 1705 1706 /** 1707 * The main function of the serve tool 1708 * 1709 * @param argc number of arguments from the command line 1710 * @param argv command line arguments 1711 * @return 0 ok, non-zero on error 1712 */ 1713 int 1714 main (int argc, 1715 char *const *argv) 1716 { 1717 enum GNUNET_GenericReturnValue res; 1718 struct GNUNET_GETOPT_CommandLineOption options[] = { 1719 GNUNET_GETOPT_option_flag ('C', 1720 "connection-close", 1721 "force HTTP connections to be closed after each request", 1722 &merchant_connection_close), 1723 GNUNET_GETOPT_option_flag ('3', 1724 "http3", 1725 "enable support for HTTP/2 and HTTP/3", 1726 &enable_h3), 1727 GNUNET_GETOPT_option_timetravel ('T', 1728 "timetravel"), 1729 GNUNET_GETOPT_option_version (PACKAGE_VERSION), 1730 GNUNET_GETOPT_OPTION_END 1731 }; 1732 1733 res = GNUNET_PROGRAM_run ( 1734 TALER_MERCHANT_project_data (), 1735 argc, argv, 1736 "taler-merchant-httpd", 1737 "Taler merchant's HTTP backend interface", 1738 options, 1739 &run, NULL); 1740 if (GNUNET_SYSERR == res) 1741 return EXIT_NOTCONFIGURED; 1742 if (GNUNET_NO == res) 1743 return EXIT_SUCCESS; 1744 return global_ret; 1745 }