sync

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

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 }