sync-httpd2.c (16930B)
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 // FIXME: return bad method! 273 } 274 for (unsigned int i = 0; NULL != handlers[i].url; i++) 275 { 276 struct SH_RequestHandler *rh = &handlers[i]; 277 278 if (0 == strcmp (path->cstr, 279 rh->url)) 280 { 281 if (MHD_HTTP_METHOD_OPTIONS == method) 282 { 283 return TALER_MHD2_reply_cors_preflight (request); 284 } 285 if (rh->method == method) 286 { 287 return rh->handler (request, 288 upload_size); 289 } 290 } 291 } 292 return respond_404 (request, 293 upload_size); 294 } 295 296 297 /** 298 * Shutdown task. Invoked when the application is being terminated. 299 * 300 * @param cls NULL 301 */ 302 static void 303 do_shutdown (void *cls) 304 { 305 (void) cls; 306 TALER_MHD2_daemons_halt (); 307 SH_resume_all_bc (); 308 if (NULL != SH_ctx) 309 { 310 GNUNET_CURL_fini (SH_ctx); 311 SH_ctx = NULL; 312 } 313 if (NULL != rc) 314 { 315 GNUNET_CURL_gnunet_rc_destroy (rc); 316 rc = NULL; 317 } 318 TALER_MHD2_daemons_destroy (); 319 SYNCDB_fini (); 320 } 321 322 323 /** 324 * Kick MHD to run now, to be called after MHD_request_resume(). 325 * Basically, we need to explicitly resume MHD's event loop whenever 326 * we made progress serving a request. This function re-schedules 327 * the task processing MHD's activities to run immediately. 328 */ 329 // FIXME: replace with direct call... 330 void 331 SH_trigger_daemon () 332 { 333 TALER_MHD2_daemons_trigger (); 334 } 335 336 337 /** 338 * Kick GNUnet Curl scheduler to begin curl interactions. 339 */ 340 void 341 SH_trigger_curl () 342 { 343 GNUNET_CURL_gnunet_scheduler_reschedule (&rc); 344 } 345 346 347 /** 348 * Callback invoked on every listen socket to start the 349 * respective MHD HTTP daemon. 350 * 351 * @param cls unused 352 * @param lsock the listen socket 353 */ 354 static void 355 start_daemon (void *cls, 356 int lsock) 357 { 358 struct MHD_Daemon *mhd; 359 360 (void) cls; 361 GNUNET_assert (-1 != lsock); 362 mhd = MHD_daemon_create (&url_handler, 363 NULL); 364 if (NULL == mhd) 365 { 366 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 367 "Failed to launch HTTP service, exiting.\n"); 368 global_ret = EXIT_NO_RESTART; 369 GNUNET_SCHEDULER_shutdown (); 370 return; 371 } 372 GNUNET_assert (MHD_SC_OK == 373 MHD_DAEMON_SET_OPTIONS ( 374 mhd, 375 MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (10000), 376 MHD_D_OPTION_LISTEN_SOCKET (lsock))); 377 have_daemons = true; 378 TALER_MHD2_daemon_start (mhd); 379 } 380 381 382 /** 383 * Main function that will be run by the scheduler. 384 * 385 * @param cls closure 386 * @param args remaining command-line arguments 387 * @param cfgfile name of the configuration file used (for saving, can be 388 * NULL!) 389 * @param config configuration 390 */ 391 static void 392 run (void *cls, 393 char *const *args, 394 const char *cfgfile, 395 const struct GNUNET_CONFIGURATION_Handle *config) 396 { 397 enum TALER_MHD2_GlobalOptions go; 398 399 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 400 "Starting sync-httpd\n"); 401 go = TALER_MHD2_GO_NONE; 402 if (SH_sync_connection_close) 403 go |= TALER_MHD2_GO_FORCE_CONNECTION_CLOSE; 404 TALER_MHD2_setup (go); 405 global_ret = EXIT_NOTCONFIGURED; 406 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 407 NULL); 408 if (GNUNET_OK != 409 GNUNET_CONFIGURATION_get_value_number (config, 410 "sync", 411 "UPLOAD_LIMIT_MB", 412 &SH_upload_limit_mb)) 413 { 414 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 415 "sync", 416 "UPLOAD_LIMIT_MB"); 417 GNUNET_SCHEDULER_shutdown (); 418 return; 419 } 420 if (GNUNET_OK != 421 TALER_config_get_amount (config, 422 "sync", 423 "INSURANCE", 424 &SH_insurance)) 425 { 426 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 427 "sync", 428 "INSURANCE"); 429 GNUNET_SCHEDULER_shutdown (); 430 return; 431 } 432 if (GNUNET_OK != 433 TALER_config_get_amount (config, 434 "sync", 435 "ANNUAL_FEE", 436 &SH_annual_fee)) 437 { 438 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 439 "sync", 440 "ANNUAL_FEE"); 441 GNUNET_SCHEDULER_shutdown (); 442 return; 443 } 444 if (GNUNET_OK != 445 GNUNET_CONFIGURATION_get_value_string (config, 446 "sync", 447 "PAYMENT_BACKEND_URL", 448 &SH_backend_url)) 449 { 450 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 451 "sync", 452 "PAYMENT_BACKEND_URL"); 453 GNUNET_SCHEDULER_shutdown (); 454 return; 455 } 456 if (GNUNET_OK != 457 GNUNET_CONFIGURATION_get_value_string (config, 458 "sync", 459 "FULFILLMENT_URL", 460 &SH_fulfillment_url)) 461 { 462 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 463 "sync", 464 "BASE_URL"); 465 GNUNET_SCHEDULER_shutdown (); 466 return; 467 } 468 469 /* setup HTTP client event loop */ 470 SH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 471 &rc); 472 rc = GNUNET_CURL_gnunet_rc_create (SH_ctx); 473 if (NULL != userpass) 474 GNUNET_CURL_set_userpass (SH_ctx, 475 userpass); 476 if (NULL != keyfile) 477 GNUNET_CURL_set_tlscert (SH_ctx, 478 certtype, 479 certfile, 480 keyfile, 481 keypass); 482 if (GNUNET_OK == 483 GNUNET_CONFIGURATION_get_value_string (config, 484 "sync", 485 "API_KEY", 486 &apikey)) 487 { 488 char *auth_header; 489 490 GNUNET_asprintf (&auth_header, 491 "%s: %s", 492 MHD_HTTP_HEADER_AUTHORIZATION, 493 apikey); 494 if (GNUNET_OK != 495 GNUNET_CURL_append_header (SH_ctx, 496 auth_header)) 497 { 498 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 499 "Failed to set %s header, trying without\n", 500 MHD_HTTP_HEADER_AUTHORIZATION); 501 } 502 GNUNET_free (auth_header); 503 } 504 505 if (GNUNET_OK != 506 SYNCDB_init (config, 507 false)) 508 { 509 global_ret = EXIT_NOTCONFIGURED; 510 GNUNET_SCHEDULER_shutdown (); 511 return; 512 } 513 if (GNUNET_OK != 514 SYNCDB_preflight ()) 515 { 516 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 517 "Database not setup. Did you run sync-dbinit?\n"); 518 GNUNET_SCHEDULER_shutdown (); 519 return; 520 } 521 { 522 enum GNUNET_GenericReturnValue ret; 523 524 ret = TALER_MHD_listen_bind (config, 525 "sync", 526 &start_daemon, 527 NULL); 528 switch (ret) 529 { 530 case GNUNET_SYSERR: 531 global_ret = EXIT_NOTCONFIGURED; 532 GNUNET_SCHEDULER_shutdown (); 533 return; 534 case GNUNET_NO: 535 if (! have_daemons) 536 { 537 global_ret = EXIT_NOTCONFIGURED; 538 GNUNET_SCHEDULER_shutdown (); 539 return; 540 } 541 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 542 "Could not open all configured listen sockets\n"); 543 break; 544 case GNUNET_OK: 545 break; 546 } 547 } 548 global_ret = EXIT_SUCCESS; 549 } 550 551 552 /** 553 * The main function of the serve tool 554 * 555 * @param argc number of arguments from the command line 556 * @param argv command line arguments 557 * @return 0 ok, 1 on error 558 */ 559 int 560 main (int argc, 561 char *const *argv) 562 { 563 struct GNUNET_GETOPT_CommandLineOption options[] = { 564 GNUNET_GETOPT_option_string ('A', 565 "auth", 566 "USERNAME:PASSWORD", 567 "use the given USERNAME and PASSWORD for client authentication", 568 &userpass), 569 GNUNET_GETOPT_option_flag ('C', 570 "connection-close", 571 "force HTTP connections to be closed after each request", 572 &SH_sync_connection_close), 573 GNUNET_GETOPT_option_string ('k', 574 "key", 575 "KEYFILE", 576 "file with the private TLS key for TLS client authentication", 577 &keyfile), 578 GNUNET_GETOPT_option_string ('p', 579 "pass", 580 "KEYFILEPASSPHRASE", 581 "passphrase needed to decrypt the TLS client private key file", 582 &keypass), 583 GNUNET_GETOPT_option_string ('t', 584 "type", 585 "CERTTYPE", 586 "type of the TLS client certificate, defaults to PEM if not specified", 587 &certtype), 588 GNUNET_GETOPT_OPTION_END 589 }; 590 enum GNUNET_GenericReturnValue ret; 591 592 ret = GNUNET_PROGRAM_run (SYNC_project_data (), 593 argc, argv, 594 "sync-httpd", 595 "sync HTTP interface", 596 options, 597 &run, NULL); 598 if (GNUNET_NO == ret) 599 return EXIT_SUCCESS; 600 if (GNUNET_SYSERR == ret) 601 return EXIT_INVALIDARGUMENT; 602 return global_ret; 603 }