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