sync

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

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 }