sync-httpd.c (19790B)
1 /* 2 This file is part of TALER 3 (C) 2019 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 <microhttpd.h> 23 #include <gnunet/gnunet_util_lib.h> 24 #include "sync/sync_util.h" 25 #include "sync-httpd.h" 26 #include "sync-httpd_mhd.h" 27 #include "sync/sync_database_lib.h" 28 #include "sync-httpd_backup.h" 29 #include "sync-httpd_config.h" 30 31 32 /** 33 * Should a "Connection: close" header be added to each HTTP response? 34 */ 35 static int SH_sync_connection_close; 36 37 /** 38 * Upload limit to the service, in megabytes. 39 */ 40 unsigned long long int SH_upload_limit_mb; 41 42 /** 43 * Annual fee for the backup account. 44 */ 45 struct TALER_Amount SH_annual_fee; 46 47 /** 48 * Our Taler backend to process payments. 49 */ 50 char *SH_backend_url; 51 52 /** 53 * Our fulfillment URL. 54 */ 55 char *SH_fulfillment_url; 56 57 /** 58 * Our context for making HTTP requests. 59 */ 60 struct GNUNET_CURL_Context *SH_ctx; 61 62 /** 63 * Reschedule context for #SH_ctx. 64 */ 65 static struct GNUNET_CURL_RescheduleContext *rc; 66 67 /** 68 * Global return code 69 */ 70 static int global_ret; 71 72 /** 73 * Set to true if we have started an MHD daemons. 74 */ 75 static bool have_daemons; 76 77 /** 78 * Username and password to use for client authentication 79 * (optional). 80 */ 81 static char *userpass; 82 83 /** 84 * Type of the client's TLS certificate (optional). 85 */ 86 static char *certtype; 87 88 /** 89 * File with the client's TLS certificate (optional). 90 */ 91 static char *certfile; 92 93 /** 94 * File with the client's TLS private key (optional). 95 */ 96 static char *keyfile; 97 98 /** 99 * This value goes in the Authorization:-header. 100 */ 101 static char *apikey; 102 103 /** 104 * Passphrase to decrypt client's TLS private key file (optional). 105 */ 106 static char *keypass; 107 108 /** 109 * Amount of insurance. 110 */ 111 struct TALER_Amount SH_insurance; 112 113 114 /** 115 * A client has requested the given url using the given method 116 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, 117 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback 118 * must call MHD callbacks to provide content to give back to the 119 * client and return an HTTP status code (i.e. #MHD_HTTP_OK, 120 * #MHD_HTTP_NOT_FOUND, etc.). 121 * 122 * @param cls argument given together with the function 123 * pointer when the handler was registered with MHD 124 * @param connection connection handle 125 * @param url the requested url 126 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, 127 * #MHD_HTTP_METHOD_PUT, etc.) 128 * @param version the HTTP version string (i.e. 129 * #MHD_HTTP_VERSION_1_1) 130 * @param upload_data the data being uploaded (excluding HEADERS, 131 * for a POST that fits into memory and that is encoded 132 * with a supported encoding, the POST data will NOT be 133 * given in upload_data and is instead available as 134 * part of #MHD_get_connection_values; very large POST 135 * data *will* be made available incrementally in 136 * @a upload_data) 137 * @param upload_data_size set initially to the size of the 138 * @a upload_data provided; the method must update this 139 * value to the number of bytes NOT processed; 140 * @param con_cls pointer that the callback can set to some 141 * address and that will be preserved by MHD for future 142 * calls for this request; since the access handler may 143 * be called many times (i.e., for a PUT/POST operation 144 * with plenty of upload data) this allows the application 145 * to easily associate some request-specific state. 146 * If necessary, this state can be cleaned up in the 147 * global #MHD_RequestCompletedCallback (which 148 * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). 149 * Initially, `*con_cls` will be NULL. 150 * @return #MHD_YES if the connection was handled successfully, 151 * #MHD_NO if the socket must be closed due to a serious 152 * error while handling the request 153 */ 154 static enum MHD_Result 155 url_handler (void *cls, 156 struct MHD_Connection *connection, 157 const char *url, 158 const char *method, 159 const char *version, 160 const char *upload_data, 161 size_t *upload_data_size, 162 void **con_cls) 163 { 164 static struct SH_RequestHandler handlers[] = { 165 /* Landing page, tell humans to go away. */ 166 { "/", MHD_HTTP_METHOD_GET, "text/plain", 167 "Hello, I'm sync. This HTTP server is not for humans.\n", 0, 168 &SH_MHD_handler_static_response, MHD_HTTP_OK }, 169 { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", 170 NULL, 0, 171 &SH_handler_config, MHD_HTTP_FOUND }, 172 { "/config", MHD_HTTP_METHOD_GET, "text/json", 173 NULL, 0, 174 &SH_handler_config, MHD_HTTP_OK }, 175 {NULL, NULL, NULL, NULL, 0, 0 } 176 }; 177 static struct SH_RequestHandler h404 = { 178 "", NULL, "text/html", 179 "<html><title>404: not found</title></html>", 0, 180 &SH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND 181 }; 182 183 struct TM_HandlerContext *hc = *con_cls; 184 struct GNUNET_AsyncScopeId aid; 185 const char *correlation_id = NULL; 186 struct SYNC_AccountPublicKeyP account_pub; 187 188 (void) cls; 189 (void) version; 190 if (NULL == hc) 191 { 192 GNUNET_async_scope_fresh (&aid); 193 /* We only read the correlation ID on the first callback for every client */ 194 correlation_id = MHD_lookup_connection_value (connection, 195 MHD_HEADER_KIND, 196 "Sync-Correlation-Id"); 197 if ((NULL != correlation_id) && 198 (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id))) 199 { 200 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 201 "illegal incoming correlation ID\n"); 202 correlation_id = NULL; 203 } 204 } 205 else 206 { 207 aid = hc->async_scope_id; 208 } 209 GNUNET_SCHEDULER_begin_async_scope (&aid); 210 211 if (NULL != correlation_id) 212 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 213 "Handling request for (%s) URL '%s', correlation_id=%s\n", 214 method, 215 url, 216 correlation_id); 217 else 218 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 219 "Handling request (%s) for URL '%s'\n", 220 method, 221 url); 222 223 if (0 == strncmp (url, 224 "/backups/", 225 strlen ("/backups/"))) 226 { 227 const char *ac = &url[strlen ("/backups/")]; 228 229 if (GNUNET_OK != 230 GNUNET_CRYPTO_eddsa_public_key_from_string (ac, 231 strlen (ac), 232 &account_pub.eddsa_pub)) 233 { 234 GNUNET_break_op (0); 235 return TALER_MHD_reply_with_error (connection, 236 MHD_HTTP_BAD_REQUEST, 237 TALER_EC_GENERIC_PARAMETER_MALFORMED, 238 ac); 239 } 240 if (0 == strcasecmp (method, 241 MHD_HTTP_METHOD_OPTIONS)) 242 { 243 return TALER_MHD_reply_cors_preflight (connection); 244 } 245 if (0 == strcasecmp (method, 246 MHD_HTTP_METHOD_GET)) 247 { 248 return SH_backup_get (connection, 249 &account_pub); 250 } 251 if (0 == strcasecmp (method, 252 MHD_HTTP_METHOD_POST)) 253 { 254 int ret; 255 256 ret = SH_backup_post (connection, 257 con_cls, 258 &account_pub, 259 upload_data, 260 upload_data_size); 261 hc = *con_cls; 262 if (NULL != hc) 263 { 264 /* Store the async context ID, so we can restore it if 265 * we get another callback for this request. */ 266 hc->async_scope_id = aid; 267 } 268 return ret; 269 } 270 } 271 for (unsigned int i = 0; NULL != handlers[i].url; i++) 272 { 273 struct SH_RequestHandler *rh = &handlers[i]; 274 275 if (0 == strcmp (url, 276 rh->url)) 277 { 278 if (0 == strcasecmp (method, 279 MHD_HTTP_METHOD_OPTIONS)) 280 { 281 return TALER_MHD_reply_cors_preflight (connection); 282 } 283 if ( (NULL == rh->method) || 284 (0 == strcasecmp (method, 285 rh->method)) ) 286 { 287 enum MHD_Result ret; 288 289 ret = rh->handler (rh, 290 connection, 291 con_cls, 292 upload_data, 293 upload_data_size); 294 hc = *con_cls; 295 if (NULL != hc) 296 { 297 /* Store the async context ID, so we can restore it if 298 * we get another callback for this request. */ 299 hc->async_scope_id = aid; 300 } 301 return ret; 302 } 303 } 304 } 305 return SH_MHD_handler_static_response (&h404, 306 connection, 307 con_cls, 308 upload_data, 309 upload_data_size); 310 } 311 312 313 /** 314 * Shutdown task. Invoked when the application is being terminated. 315 * 316 * @param cls NULL 317 */ 318 static void 319 do_shutdown (void *cls) 320 { 321 (void) cls; 322 SH_resume_all_bc (); 323 TALER_MHD_daemons_halt (); 324 if (NULL != SH_ctx) 325 { 326 GNUNET_CURL_fini (SH_ctx); 327 SH_ctx = NULL; 328 } 329 if (NULL != rc) 330 { 331 GNUNET_CURL_gnunet_rc_destroy (rc); 332 rc = NULL; 333 } 334 TALER_MHD_daemons_destroy (); 335 SYNCDB_fini (); 336 } 337 338 339 /** 340 * Function called whenever MHD is done with a request. If the 341 * request was a POST, we may have stored a `struct Buffer *` in the 342 * @a con_cls that might still need to be cleaned up. Call the 343 * respective function to free the memory. 344 * 345 * @param cls client-defined closure 346 * @param connection connection handle 347 * @param con_cls value as set by the last call to 348 * the #MHD_AccessHandlerCallback 349 * @param toe reason for request termination 350 * @see #MHD_OPTION_NOTIFY_COMPLETED 351 * @ingroup request 352 */ 353 static void 354 handle_mhd_completion_callback (void *cls, 355 struct MHD_Connection *connection, 356 void **con_cls, 357 enum MHD_RequestTerminationCode toe) 358 { 359 struct TM_HandlerContext *hc = *con_cls; 360 361 (void) cls; 362 (void) connection; 363 if (NULL == hc) 364 return; 365 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 366 "Finished handling request with status %d\n", 367 (int) toe); 368 hc->cc (hc); 369 *con_cls = NULL; 370 } 371 372 373 /** 374 * Kick MHD to run now, to be called after MHD_resume_connection(). 375 * Basically, we need to explicitly resume MHD's event loop whenever 376 * we made progress serving a request. This function re-schedules 377 * the task processing MHD's activities to run immediately. 378 */ 379 void 380 SH_trigger_daemon () 381 { 382 TALER_MHD_daemon_trigger (); 383 } 384 385 386 /** 387 * Kick GNUnet Curl scheduler to begin curl interactions. 388 */ 389 void 390 SH_trigger_curl () 391 { 392 GNUNET_CURL_gnunet_scheduler_reschedule (&rc); 393 } 394 395 396 /** 397 * Callback invoked on every listen socket to start the 398 * respective MHD HTTP daemon. 399 * 400 * @param cls unused 401 * @param lsock the listen socket 402 */ 403 static void 404 start_daemon (void *cls, 405 int lsock) 406 { 407 struct MHD_Daemon *mhd; 408 409 (void) cls; 410 GNUNET_assert (-1 != lsock); 411 mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK, 412 0 /* port */, 413 NULL, NULL, 414 &url_handler, NULL, 415 MHD_OPTION_LISTEN_SOCKET, lsock, 416 MHD_OPTION_NOTIFY_COMPLETED, 417 &handle_mhd_completion_callback, NULL, 418 MHD_OPTION_CONNECTION_TIMEOUT, 419 (unsigned int) 10 /* 10s */, 420 MHD_OPTION_END); 421 if (NULL == mhd) 422 { 423 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 424 "Failed to launch HTTP service, exiting.\n"); 425 global_ret = EXIT_NO_RESTART; 426 GNUNET_SCHEDULER_shutdown (); 427 return; 428 } 429 have_daemons = true; 430 TALER_MHD_daemon_start (mhd); 431 } 432 433 434 /** 435 * Main function that will be run by the scheduler. 436 * 437 * @param cls closure 438 * @param args remaining command-line arguments 439 * @param cfgfile name of the configuration file used (for saving, can be 440 * NULL!) 441 * @param config configuration 442 */ 443 static void 444 run (void *cls, 445 char *const *args, 446 const char *cfgfile, 447 const struct GNUNET_CONFIGURATION_Handle *config) 448 { 449 enum TALER_MHD_GlobalOptions go; 450 451 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 452 "Starting sync-httpd\n"); 453 go = TALER_MHD_GO_NONE; 454 if (SH_sync_connection_close) 455 go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE; 456 TALER_MHD_setup (go); 457 global_ret = EXIT_NOTCONFIGURED; 458 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 459 NULL); 460 if (GNUNET_OK != 461 GNUNET_CONFIGURATION_get_value_number (config, 462 "sync", 463 "UPLOAD_LIMIT_MB", 464 &SH_upload_limit_mb)) 465 { 466 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 467 "sync", 468 "UPLOAD_LIMIT_MB"); 469 GNUNET_SCHEDULER_shutdown (); 470 return; 471 } 472 if (GNUNET_OK != 473 TALER_config_get_amount (config, 474 "sync", 475 "INSURANCE", 476 &SH_insurance)) 477 { 478 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 479 "sync", 480 "INSURANCE"); 481 GNUNET_SCHEDULER_shutdown (); 482 return; 483 } 484 if (GNUNET_OK != 485 TALER_config_get_amount (config, 486 "sync", 487 "ANNUAL_FEE", 488 &SH_annual_fee)) 489 { 490 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 491 "sync", 492 "ANNUAL_FEE"); 493 GNUNET_SCHEDULER_shutdown (); 494 return; 495 } 496 if (GNUNET_OK != 497 GNUNET_CONFIGURATION_get_value_string (config, 498 "sync", 499 "PAYMENT_BACKEND_URL", 500 &SH_backend_url)) 501 { 502 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 503 "sync", 504 "PAYMENT_BACKEND_URL"); 505 GNUNET_SCHEDULER_shutdown (); 506 return; 507 } 508 if (GNUNET_OK != 509 GNUNET_CONFIGURATION_get_value_string (config, 510 "sync", 511 "FULFILLMENT_URL", 512 &SH_fulfillment_url)) 513 { 514 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, 515 "sync", 516 "BASE_URL"); 517 GNUNET_SCHEDULER_shutdown (); 518 return; 519 } 520 521 /* setup HTTP client event loop */ 522 SH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule, 523 &rc); 524 rc = GNUNET_CURL_gnunet_rc_create (SH_ctx); 525 if (NULL != userpass) 526 GNUNET_CURL_set_userpass (SH_ctx, 527 userpass); 528 if (NULL != keyfile) 529 GNUNET_CURL_set_tlscert (SH_ctx, 530 certtype, 531 certfile, 532 keyfile, 533 keypass); 534 if (GNUNET_OK == 535 GNUNET_CONFIGURATION_get_value_string (config, 536 "sync", 537 "API_KEY", 538 &apikey)) 539 { 540 char *auth_header; 541 542 GNUNET_asprintf (&auth_header, 543 "%s: %s", 544 MHD_HTTP_HEADER_AUTHORIZATION, 545 apikey); 546 if (GNUNET_OK != 547 GNUNET_CURL_append_header (SH_ctx, 548 auth_header)) 549 { 550 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 551 "Failed to set %s header, trying without\n", 552 MHD_HTTP_HEADER_AUTHORIZATION); 553 } 554 GNUNET_free (auth_header); 555 } 556 557 if (GNUNET_OK != 558 SYNCDB_init (config, 559 false)) 560 { 561 global_ret = EXIT_NOTCONFIGURED; 562 GNUNET_SCHEDULER_shutdown (); 563 return; 564 } 565 if (GNUNET_OK != 566 SYNCDB_preflight ()) 567 { 568 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, 569 "Database not setup. Did you run sync-dbinit?\n"); 570 GNUNET_SCHEDULER_shutdown (); 571 return; 572 } 573 { 574 enum GNUNET_GenericReturnValue ret; 575 576 ret = TALER_MHD_listen_bind (config, 577 "sync", 578 &start_daemon, 579 NULL); 580 switch (ret) 581 { 582 case GNUNET_SYSERR: 583 global_ret = EXIT_NOTCONFIGURED; 584 GNUNET_SCHEDULER_shutdown (); 585 return; 586 case GNUNET_NO: 587 if (! have_daemons) 588 { 589 global_ret = EXIT_NOTCONFIGURED; 590 GNUNET_SCHEDULER_shutdown (); 591 return; 592 } 593 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 594 "Could not open all configured listen sockets\n"); 595 break; 596 case GNUNET_OK: 597 break; 598 } 599 } 600 global_ret = EXIT_SUCCESS; 601 } 602 603 604 /** 605 * The main function of the serve tool 606 * 607 * @param argc number of arguments from the command line 608 * @param argv command line arguments 609 * @return 0 ok, 1 on error 610 */ 611 int 612 main (int argc, 613 char *const *argv) 614 { 615 struct GNUNET_GETOPT_CommandLineOption options[] = { 616 GNUNET_GETOPT_option_string ('A', 617 "auth", 618 "USERNAME:PASSWORD", 619 "use the given USERNAME and PASSWORD for client authentication", 620 &userpass), 621 GNUNET_GETOPT_option_flag ('C', 622 "connection-close", 623 "force HTTP connections to be closed after each request", 624 &SH_sync_connection_close), 625 GNUNET_GETOPT_option_string ('k', 626 "key", 627 "KEYFILE", 628 "file with the private TLS key for TLS client authentication", 629 &keyfile), 630 GNUNET_GETOPT_option_string ('p', 631 "pass", 632 "KEYFILEPASSPHRASE", 633 "passphrase needed to decrypt the TLS client private key file", 634 &keypass), 635 GNUNET_GETOPT_option_string ('t', 636 "type", 637 "CERTTYPE", 638 "type of the TLS client certificate, defaults to PEM if not specified", 639 &certtype), 640 GNUNET_GETOPT_OPTION_END 641 }; 642 enum GNUNET_GenericReturnValue ret; 643 644 ret = GNUNET_PROGRAM_run (SYNC_project_data (), 645 argc, argv, 646 "sync-httpd", 647 "sync HTTP interface", 648 options, 649 &run, NULL); 650 if (GNUNET_NO == ret) 651 return EXIT_SUCCESS; 652 if (GNUNET_SYSERR == ret) 653 return EXIT_INVALIDARGUMENT; 654 return global_ret; 655 }