sync

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

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 }