sync

Backup service to store encrypted wallet databases (experimental)
Log | Files | Refs | Submodules | README | LICENSE

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 }