sync-httpd2.c (17057B)
1 /* 2 This file is part of TALER 3 (C) 2019--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 sync/sync-httpd.c 18 * @brief HTTP serving layer intended to provide basic backup operations 19 * @author Christian Grothoff 20 */ 21 #include "platform.h" 22 #include <gnunet/gnunet_util_lib.h> 23 #include "sync/sync_util.h" 24 #include "sync-httpd2.h" 25 #include "sync/sync_database_lib.h" 26 #include "sync-httpd2_backup.h" 27 #include "sync-httpd2_config.h" 28 29 30 /** 31 * Should a "Connection: close" header be added to each HTTP response? 32 */ 33 static int SH_sync_connection_close; 34 35 /** 36 * Upload limit to the service, in megabytes. 37 */ 38 unsigned long long int SH_upload_limit_mb; 39 40 /** 41 * Annual fee for the backup account. 42 */ 43 struct TALER_Amount SH_annual_fee; 44 45 /** 46 * Our Taler backend to process payments. 47 */ 48 char *SH_backend_url; 49 50 /** 51 * Our fulfillment URL. 52 */ 53 char *SH_fulfillment_url; 54 55 /** 56 * Our context for making HTTP requests. 57 */ 58 struct GNUNET_CURL_Context *SH_ctx; 59 60 /** 61 * Reschedule context for #SH_ctx. 62 */ 63 static struct GNUNET_CURL_RescheduleContext *rc; 64 65 /** 66 * Global return code 67 */ 68 static int global_ret; 69 70 /** 71 * Set to true if we have started an MHD daemons. 72 */ 73 static bool have_daemons; 74 75 /** 76 * Username and password to use for client authentication 77 * (optional). 78 */ 79 static char *userpass; 80 81 /** 82 * Type of the client's TLS certificate (optional). 83 */ 84 static char *certtype; 85 86 /** 87 * File with the client's TLS certificate (optional). 88 */ 89 static char *certfile; 90 91 /** 92 * File with the client's TLS private key (optional). 93 */ 94 static char *keyfile; 95 96 /** 97 * This value goes in the Authorization:-header. 98 */ 99 static char *apikey; 100 101 /** 102 * Passphrase to decrypt client's TLS private key file (optional). 103 */ 104 static char *keypass; 105 106 /** 107 * Amount of insurance. 108 */ 109 struct TALER_Amount SH_insurance; 110 111 112 /** 113 * Function to respond to GET requests on '/'. 114 * 115 * @param request the MHD request to handle 116 * @param upload_size number of bytes uploaded 117 * @return MHD action 118 */ 119 static const struct MHD_Action * 120 respond_root (struct MHD_Request *request, 121 uint_fast64_t upload_size) 122 { 123 const char *msg = "Hello, I'm sync. This HTTP server is not for humans.\n"; 124 struct MHD_Response *resp; 125 126 GNUNET_break_op (0 == upload_size); 127 resp = MHD_response_from_buffer_static ( 128 MHD_HTTP_STATUS_OK, 129 strlen (msg), 130 msg); 131 GNUNET_break (MHD_SC_OK == 132 MHD_response_add_header (resp, 133 MHD_HTTP_HEADER_CONTENT_TYPE, 134 "text/plain")); 135 return MHD_action_from_response (request, 136 resp); 137 } 138 139 140 /** 141 * Function to respond to GET requests on unknown URLs. 142 * 143 * @param request the MHD request to handle 144 * @param upload_size number of bytes uploaded 145 * @return MHD action 146 */ 147 static const struct MHD_Action * 148 respond_404 (struct MHD_Request *request, 149 uint_fast64_t upload_size) 150 { 151 const char *msg = "<html><title>404: not found</title></html>"; 152 struct MHD_Response *resp; 153 154 GNUNET_break_op (0 == upload_size); 155 resp = MHD_response_from_buffer_static ( 156 MHD_HTTP_STATUS_NOT_FOUND, 157 strlen (msg), 158 msg); 159 GNUNET_break (MHD_SC_OK == 160 MHD_response_add_header (resp, 161 MHD_HTTP_HEADER_CONTENT_TYPE, 162 "text/html")); 163 return MHD_action_from_response (request, 164 resp); 165 } 166 167 168 /** 169 * Function to respond to GET requests on '/agpl'. 170 * 171 * @param request the MHD request to handle 172 * @param upload_size number of bytes uploaded 173 * @return MHD action 174 */ 175 static const struct MHD_Action * 176 respond_agpl (struct MHD_Request *request, 177 uint_fast64_t upload_size) 178 { 179 GNUNET_break_op (0 == upload_size); 180 return TALER_MHD2_reply_agpl (request, 181 "https://git.taler.net/sync.git/"); 182 } 183 184 185 /** 186 * A client has requested the given url using the given method 187 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 188 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback 189 * must call MHD callbacks to provide content to give back to the 190 * client. 191 * 192 * @param cls argument given together with the function 193 * pointer when the handler was registered with MHD 194 * @param request request the request to handle 195 * @param path the requested uri (without arguments after "?") 196 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 197 * #MHD_HTTP_METHOD_PUT, etc.) 198 * @param upload_size the size of the message upload content payload, 199 * #MHD_SIZE_UNKNOWN for chunked uploads (if the 200 * final chunk has not been processed yet) 201 * @return next action 202 */ 203 static const struct MHD_Action * 204 url_handler (void *cls, 205 struct MHD_Request *request, 206 const struct MHD_String *path, 207 enum MHD_HTTP_Method method, 208 uint_fast64_t upload_size) 209 { 210 static struct SH_RequestHandler handlers[] = { 211 /* Landing page, tell humans to go away. */ 212 { 213 .url = "/", 214 .method = MHD_HTTP_METHOD_GET, 215 .handler = &respond_root 216 }, 217 { 218 .url = "/agpl", 219 .method = MHD_HTTP_METHOD_GET, 220 .handler = &respond_agpl, 221 }, 222 { 223 .url = "/config", 224 .method = MHD_HTTP_METHOD_GET, 225 .handler = &SH_handler_config, 226 }, 227 { 228 .url = NULL 229 } 230 }; 231 struct SYNC_AccountPublicKeyP account_pub; 232 233 (void) cls; 234 #if BUG 235 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 236 "Handling %s request for URL '%s'\n", 237 MHD_http_method_to_string (method)->cstr, 238 path->cstr); 239 #endif 240 if (0 == strncmp (path->cstr, 241 "/backups/", 242 strlen ("/backups/"))) 243 { 244 const char *ac = &path->cstr[strlen ("/backups/")]; 245 246 if (GNUNET_OK != 247 GNUNET_CRYPTO_eddsa_public_key_from_string (ac, 248 strlen (ac), 249 &account_pub.eddsa_pub)) 250 { 251 GNUNET_break_op (0); 252 return TALER_MHD2_reply_with_error (request, 253 MHD_HTTP_STATUS_BAD_REQUEST, 254 TALER_EC_GENERIC_PARAMETER_MALFORMED, 255 ac); 256 } 257 if (MHD_HTTP_METHOD_OPTIONS == method) 258 { 259 return TALER_MHD2_reply_cors_preflight (request); 260 } 261 if (MHD_HTTP_METHOD_GET == method) 262 { 263 return SH_backup_get (request, 264 &account_pub); 265 } 266 if (MHD_HTTP_METHOD_POST == method) 267 { 268 return SH_backup_post (request, 269 &account_pub, 270 upload_size); 271 } 272 return TALER_MHD2_reply_with_error ( 273 request, 274 MHD_HTTP_STATUS_METHOD_NOT_ALLOWED, 275 TALER_EC_GENERIC_METHOD_INVALID, 276 MHD_http_method_to_string (method)->cstr); 277 } 278 for (unsigned int i = 0; NULL != handlers[i].url; i++) 279 { 280 struct SH_RequestHandler *rh = &handlers[i]; 281 282 if (0 == strcmp (path->cstr, 283 rh->url)) 284 { 285 if (MHD_HTTP_METHOD_OPTIONS == method) 286 { 287 return TALER_MHD2_reply_cors_preflight (request); 288 } 289 if (rh->method == method) 290 { 291 return rh->handler (request, 292 upload_size); 293 } 294 } 295 } 296 return respond_404 (request, 297 upload_size); 298 } 299 300 301 /** 302 * Shutdown task. Invoked when the application is being terminated. 303 * 304 * @param cls NULL 305 */ 306 static void 307 do_shutdown (void *cls) 308 { 309 (void) cls; 310 TALER_MHD2_daemons_halt (); 311 SH_resume_all_bc (); 312 if (NULL != SH_ctx) 313 { 314 GNUNET_CURL_fini (SH_ctx); 315 SH_ctx = NULL; 316 } 317 if (NULL != rc) 318 { 319 GNUNET_CURL_gnunet_rc_destroy (rc); 320 rc = NULL; 321 } 322 TALER_MHD2_daemons_destroy (); 323 SYNCDB_fini (); 324 } 325 326 327 /** 328 * Kick MHD to run now, to be called after MHD_request_resume(). 329 * Basically, we need to explicitly resume MHD's event loop whenever 330 * we made progress serving a request. This function re-schedules 331 * the task processing MHD's activities to run immediately. 332 */ 333 // FIXME: replace with direct call... 334 void 335 SH_trigger_daemon () 336 { 337 TALER_MHD2_daemons_trigger (); 338 } 339 340 341 /** 342 * Kick GNUnet Curl scheduler to begin curl interactions. 343 */ 344 void 345 SH_trigger_curl () 346 { 347 GNUNET_CURL_gnunet_scheduler_reschedule (&rc); 348 } 349 350 351 /** 352 * Callback invoked on every listen socket to start the 353 * respective MHD HTTP daemon. 354 * 355 * @param cls unused 356 * @param lsock the listen socket 357 */ 358 static void 359 start_daemon (void *cls, 360 int lsock) 361 { 362 struct MHD_Daemon *mhd; 363 364 (void) cls; 365 GNUNET_assert (-1 != lsock); 366 mhd = MHD_daemon_create (&url_handler, 367 NULL); 368 if (NULL == mhd) 369 { 370 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 371 "Failed to launch HTTP service, exiting.\n"); 372 global_ret = EXIT_NO_RESTART; 373 GNUNET_SCHEDULER_shutdown (); 374 return; 375 } 376 GNUNET_assert (MHD_SC_OK == 377 MHD_DAEMON_SET_OPTIONS ( 378 mhd, 379 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (10000), 380 MHD_D_OPTION_LISTEN_SOCKET (lsock))); 381 have_daemons = true; 382 TALER_MHD2_daemon_start (mhd); 383 } 384 385 386 /** 387 * Main function that will be run by the scheduler. 388 * 389 * @param cls closure 390 * @param args remaining command-line arguments 391 * @param cfgfile name of the configuration file used (for saving, can be 392 * NULL!) 393 * @param config configuration 394 */ 395 static void 396 run (void *cls, 397 char *const *args, 398 const char *cfgfile, 399 const struct GNUNET_CONFIGURATION_Handle *config) 400 { 401 enum TALER_MHD2_GlobalOptions go; 402 403 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 404 "Starting sync-httpd\n"); 405 go = TALER_MHD2_GO_NONE; 406 if (SH_sync_connection_close) 407 go |= TALER_MHD2_GO_FORCE_CONNECTION_CLOSE; 408 TALER_MHD2_setup (go); 409 global_ret = EXIT_NOTCONFIGURED; 410 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 411 NULL); 412 if (GNUNET_OK != 413 GNUNET_CONFIGURATION_get_value_number (config, 414 "sync", 415 "UPLOAD_LIMIT_MB", 416 &SH_upload_limit_mb)) 417 { 418 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 419 "sync", 420 "UPLOAD_LIMIT_MB"); 421 GNUNET_SCHEDULER_shutdown (); 422 return; 423 } 424 if (GNUNET_OK != 425 TALER_config_get_amount (config, 426 "sync", 427 "INSURANCE", 428 &SH_insurance)) 429 { 430 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 431 "sync", 432 "INSURANCE"); 433 GNUNET_SCHEDULER_shutdown (); 434 return; 435 } 436 if (GNUNET_OK != 437 TALER_config_get_amount (config, 438 "sync", 439 "ANNUAL_FEE", 440 &SH_annual_fee)) 441 { 442 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 443 "sync", 444 "ANNUAL_FEE"); 445 GNUNET_SCHEDULER_shutdown (); 446 return; 447 } 448 if (GNUNET_OK != 449 GNUNET_CONFIGURATION_get_value_string (config, 450 "sync", 451 "PAYMENT_BACKEND_URL", 452 &SH_backend_url)) 453 { 454 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 455 "sync", 456 "PAYMENT_BACKEND_URL"); 457 GNUNET_SCHEDULER_shutdown (); 458 return; 459 } 460 if (GNUNET_OK != 461 GNUNET_CONFIGURATION_get_value_string (config, 462 "sync", 463 "FULFILLMENT_URL", 464 &SH_fulfillment_url)) 465 { 466 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 467 "sync", 468 "BASE_URL"); 469 GNUNET_SCHEDULER_shutdown (); 470 return; 471 } 472 473 /* setup HTTP client event loop */ 474 SH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 475 &rc); 476 rc = GNUNET_CURL_gnunet_rc_create (SH_ctx); 477 if (NULL != userpass) 478 GNUNET_CURL_set_userpass (SH_ctx, 479 userpass); 480 if (NULL != keyfile) 481 GNUNET_CURL_set_tlscert (SH_ctx, 482 certtype, 483 certfile, 484 keyfile, 485 keypass); 486 if (GNUNET_OK == 487 GNUNET_CONFIGURATION_get_value_string (config, 488 "sync", 489 "API_KEY", 490 &apikey)) 491 { 492 char *auth_header; 493 494 GNUNET_asprintf (&auth_header, 495 "%s: %s", 496 MHD_HTTP_HEADER_AUTHORIZATION, 497 apikey); 498 if (GNUNET_OK != 499 GNUNET_CURL_append_header (SH_ctx, 500 auth_header)) 501 { 502 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 503 "Failed to set %s header, trying without\n", 504 MHD_HTTP_HEADER_AUTHORIZATION); 505 } 506 GNUNET_free (auth_header); 507 } 508 509 if (GNUNET_OK != 510 SYNCDB_init (config)) 511 { 512 global_ret = EXIT_NOTCONFIGURED; 513 GNUNET_SCHEDULER_shutdown (); 514 return; 515 } 516 if (GNUNET_OK != 517 SYNCDB_preflight ()) 518 { 519 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 520 "Database not setup. Did you run sync-dbinit?\n"); 521 GNUNET_SCHEDULER_shutdown (); 522 return; 523 } 524 { 525 enum GNUNET_GenericReturnValue ret; 526 527 ret = TALER_MHD_listen_bind (config, 528 "sync", 529 &start_daemon, 530 NULL); 531 switch (ret) 532 { 533 case GNUNET_SYSERR: 534 global_ret = EXIT_NOTCONFIGURED; 535 GNUNET_SCHEDULER_shutdown (); 536 return; 537 case GNUNET_NO: 538 if (! have_daemons) 539 { 540 global_ret = EXIT_NOTCONFIGURED; 541 GNUNET_SCHEDULER_shutdown (); 542 return; 543 } 544 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 545 "Could not open all configured listen sockets\n"); 546 break; 547 case GNUNET_OK: 548 break; 549 } 550 } 551 global_ret = EXIT_SUCCESS; 552 } 553 554 555 /** 556 * The main function of the serve tool 557 * 558 * @param argc number of arguments from the command line 559 * @param argv command line arguments 560 * @return 0 ok, 1 on error 561 */ 562 int 563 main (int argc, 564 char *const *argv) 565 { 566 struct GNUNET_GETOPT_CommandLineOption options[] = { 567 GNUNET_GETOPT_option_string ('A', 568 "auth", 569 "USERNAME:PASSWORD", 570 "use the given USERNAME and PASSWORD for client authentication", 571 &userpass), 572 GNUNET_GETOPT_option_flag ('C', 573 "connection-close", 574 "force HTTP connections to be closed after each request", 575 &SH_sync_connection_close), 576 GNUNET_GETOPT_option_string ('k', 577 "key", 578 "KEYFILE", 579 "file with the private TLS key for TLS client authentication", 580 &keyfile), 581 GNUNET_GETOPT_option_string ('p', 582 "pass", 583 "KEYFILEPASSPHRASE", 584 "passphrase needed to decrypt the TLS client private key file", 585 &keypass), 586 GNUNET_GETOPT_option_string ('t', 587 "type", 588 "CERTTYPE", 589 "type of the TLS client certificate, defaults to PEM if not specified", 590 &certtype), 591 GNUNET_GETOPT_OPTION_END 592 }; 593 enum GNUNET_GenericReturnValue ret; 594 595 ret = GNUNET_PROGRAM_run (SYNC_project_data (), 596 argc, argv, 597 "sync-httpd", 598 "sync HTTP interface", 599 options, 600 &run, NULL); 601 if (GNUNET_NO == ret) 602 return EXIT_SUCCESS; 603 if (GNUNET_SYSERR == ret) 604 return EXIT_INVALIDARGUMENT; 605 return global_ret; 606 }