sync

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

sync-httpd2.c (27699B)


      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-httpd2.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_config.h"
     27 #include "sync-httpd2_post-backups-KEY-blocks-NONCE.h"
     28 #include "sync-httpd2_delete-backups-KEY-blocks-NONCE.h"
     29 #include "sync-httpd2_get-backups-KEY-objects-UID.h"
     30 #include "sync-httpd2_post-backups-KEY-objects-UID.h"
     31 #include "sync-httpd2_get-backups-KEY.h"
     32 #include "sync-httpd2_get-backups-KEY-blocks.h"
     33 #include "sync-httpd2_get-backups-KEY-blocks-reconcile.h"
     34 
     35 
     36 /**
     37  * @brief A handler for a backup sub-operation under /backups/$KEY/.
     38  *
     39  * Each entry maps an operation name ("block" or "object") and an
     40  * HTTP method to a handler function.
     41  */
     42 struct SH_BackupOperationHandler
     43 {
     44 
     45   /**
     46    * Operation name, e.g. "block" or "object".
     47    */
     48   const char *operation;
     49 
     50   /**
     51    * HTTP method this handler is for.
     52    */
     53   enum MHD_HTTP_Method method;
     54 
     55   /**
     56    * Function to call.
     57    *
     58    * @param request the MHD request to handle
     59    * @param account_pub public key of the account
     60    * @param args path segments after the operation name
     61    * @param upload_size number of bytes being uploaded
     62    * @param is_update whether the operation is an update
     63    * @return MHD action
     64    */
     65   const struct MHD_Action *(*handler)(
     66     struct MHD_Request *request,
     67     const struct SYNC_AccountPublicKeyP *account_pub,
     68     const char *const args[],
     69     uint_fast64_t upload_size,
     70     bool is_update);
     71 
     72   /**
     73    * Number of path segments this handler expects
     74    * after the operation name.
     75    */
     76   unsigned int nargs;
     77 
     78   /**
     79    * Is @e nargs only an upper bound?
     80    */
     81   bool nargs_is_upper_bound;
     82 };
     83 
     84 
     85 /**
     86  * Should a "Connection: close" header be added to each HTTP response?
     87  */
     88 static int SH_sync_connection_close;
     89 
     90 /**
     91  * Upload limit to the service, in megabytes.
     92  */
     93 unsigned long long int SH_upload_limit_mb;
     94 
     95 /**
     96  * How many uploads of the maximum permitted size the shared upload
     97  * buffer pool must have room for.
     98  *
     99  * Block uploads are handed to the handler in one piece
    100  * (#MHD_action_process_upload_full()), so MHD buffers the whole body
    101  * out of a daemon-wide pool.  The pool is a budget, not a
    102  * pre-allocation -- MHD mallocs per request and gives the space back
    103  * when the request ends -- so this bounds the worst-case resident size
    104  * of concurrent uploads rather than costing anything up front.
    105  */
    106 #define UPLOAD_POOL_CONCURRENCY 8
    107 
    108 
    109 bool
    110 SH_upload_too_large (uint_fast64_t upload_size,
    111                      size_t *buff_size)
    112 {
    113   uint_fast64_t limit;
    114 
    115   limit = ((uint_fast64_t) SH_upload_limit_mb) * 1024 * 1024;
    116   if (MHD_SIZE_UNKNOWN == upload_size)
    117   {
    118     /* Chunked upload: the length is not known yet, so bound the buffer
    119        by the limit and let MHD fail the request if it does not fit. */
    120     *buff_size = (size_t) GNUNET_MIN (limit,
    121                                       (uint_fast64_t) SIZE_MAX);
    122     return false;
    123   }
    124   if ( (upload_size >= limit) ||
    125        (upload_size > SIZE_MAX) )
    126     return true;
    127   *buff_size = (size_t) upload_size;
    128   return false;
    129 }
    130 
    131 
    132 /**
    133  * Annual fee for the backup account.
    134  */
    135 struct TALER_Amount SH_annual_fee;
    136 
    137 /**
    138  * Our Taler backend to process payments.
    139  */
    140 char *SH_backend_url;
    141 
    142 /**
    143  * Our fulfillment URL.
    144  */
    145 char *SH_fulfillment_url;
    146 
    147 /**
    148  * Our context for making HTTP requests.
    149  */
    150 struct GNUNET_CURL_Context *SH_ctx;
    151 
    152 /**
    153  * Reschedule context for #SH_ctx.
    154  */
    155 static struct GNUNET_CURL_RescheduleContext *rc;
    156 
    157 /**
    158  * Global return code
    159  */
    160 static int global_ret;
    161 
    162 /**
    163  * Set to true if we have started an MHD daemons.
    164  */
    165 static bool have_daemons;
    166 
    167 /**
    168  * Username and password to use for client authentication
    169  * (optional).
    170  */
    171 static char *userpass;
    172 
    173 /**
    174  * Type of the client's TLS certificate (optional).
    175  */
    176 static char *certtype;
    177 
    178 /**
    179  * File with the client's TLS certificate (optional).
    180  */
    181 static char *certfile;
    182 
    183 /**
    184  * File with the client's TLS private key (optional).
    185  */
    186 static char *keyfile;
    187 
    188 /**
    189  * This value goes in the Authorization:-header.
    190  */
    191 static char *apikey;
    192 
    193 /**
    194  * Passphrase to decrypt client's TLS private key file (optional).
    195  */
    196 static char *keypass;
    197 
    198 /**
    199  * Amount of insurance.
    200  */
    201 struct TALER_Amount SH_insurance;
    202 
    203 
    204 /**
    205  * Function to respond to GET requests on '/'.
    206  *
    207  * @param request the MHD request to handle
    208  * @param upload_size number of bytes uploaded
    209  * @return MHD action
    210  */
    211 static const struct MHD_Action *
    212 respond_root (struct MHD_Request *request,
    213               uint_fast64_t upload_size)
    214 {
    215   const char *msg = "Hello, I'm sync. This HTTP server is not for humans.\n";
    216   struct MHD_Response *resp;
    217 
    218   GNUNET_break_op (0 == upload_size);
    219   resp = MHD_response_from_buffer_static (
    220     MHD_HTTP_STATUS_OK,
    221     strlen (msg),
    222     msg);
    223   GNUNET_break (MHD_SC_OK ==
    224                 MHD_response_add_header (resp,
    225                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    226                                          "text/plain"));
    227   return MHD_action_from_response (request,
    228                                    resp);
    229 }
    230 
    231 
    232 /**
    233  * Function to respond to GET requests on unknown URLs.
    234  *
    235  * @param request the MHD request to handle
    236  * @param upload_size number of bytes uploaded
    237  * @return MHD action
    238  */
    239 static const struct MHD_Action *
    240 respond_404 (struct MHD_Request *request,
    241              uint_fast64_t upload_size)
    242 {
    243   const char *msg = "<html><title>404: not found</title></html>";
    244   struct MHD_Response *resp;
    245 
    246   GNUNET_break_op (0 == upload_size);
    247   resp = MHD_response_from_buffer_static (
    248     MHD_HTTP_STATUS_NOT_FOUND,
    249     strlen (msg),
    250     msg);
    251   GNUNET_break (MHD_SC_OK ==
    252                 MHD_response_add_header (resp,
    253                                          MHD_HTTP_HEADER_CONTENT_TYPE,
    254                                          "text/html"));
    255   return MHD_action_from_response (request,
    256                                    resp);
    257 }
    258 
    259 
    260 /**
    261  * Function to respond to GET requests on '/agpl'.
    262  *
    263  * @param request the MHD request to handle
    264  * @param upload_size number of bytes uploaded
    265  * @return MHD action
    266  */
    267 static const struct MHD_Action *
    268 respond_agpl (struct MHD_Request *request,
    269               uint_fast64_t upload_size)
    270 {
    271   GNUNET_break_op (0 == upload_size);
    272   return TALER_MHD2_reply_agpl (request,
    273                                 "https://git.taler.net/sync.git/");
    274 }
    275 
    276 
    277 /**
    278  * Maximum length of the encoded account public key in the URL.
    279  * An EdDSA public key encodes to 52 characters.
    280  */
    281 #define ACCOUNT_KEY_MAX 64
    282 
    283 /**
    284  * Maximum reasonable subpath length after /backups/$KEY/.
    285  */
    286 #define SUBPATH_MAX 256
    287 
    288 /**
    289  * Maximum number of path segments we split a subpath into,
    290  * including the operation name.
    291  */
    292 #define SUBPATH_MAX_SEGMENTS 3
    293 
    294 
    295 /**
    296  * Handle a request on a subpath under /backups/$KEY/.
    297  *
    298  * Parses the subpath (e.g. "block/NONCE" or "object/UID"),
    299  * splits it into segments, and dispatches to the appropriate
    300  * handler using the data-driven #SH_BackupOperationHandler
    301  * table.
    302  *
    303  * @param request the MHD request to handle
    304  * @param account_pub public key of the account
    305  * @param method HTTP method of the request
    306  * @param subpath remainder of the URL after /backups/$KEY/
    307  * @param upload_size number of bytes being uploaded
    308  * @return MHD action
    309  */
    310 static const struct MHD_Action *
    311 handle_backup_subpath (struct MHD_Request *request,
    312                        const struct SYNC_AccountPublicKeyP *account_pub,
    313                        enum MHD_HTTP_Method method,
    314                        const char *subpath,
    315                        uint_fast64_t upload_size)
    316 {
    317   static struct SH_BackupOperationHandler bhandlers[] = {
    318     {
    319       .operation = "blocks",
    320       .method = MHD_HTTP_METHOD_POST,
    321       .handler = &SH_backup_block_post,
    322       .nargs = 1,
    323     },
    324     {
    325       .operation = "blocks",
    326       /* FIXME: replace with PATCH when mhd2 supports it */
    327       .method = MHD_HTTP_METHOD_PUT,
    328       .handler = &SH_backup_block_post,
    329       .nargs = 1,
    330     },
    331     {
    332       .operation = "blocks",
    333       .method = MHD_HTTP_METHOD_DELETE,
    334       .handler = &SH_backup_block_delete,
    335       .nargs = 1,
    336     },
    337     {
    338       .operation = "blocks",
    339       .method = MHD_HTTP_METHOD_GET,
    340       .handler = &SH_backup_blocks_get,
    341       .nargs = 0,
    342     },
    343     {
    344       .operation = "blocks",
    345       .method = MHD_HTTP_METHOD_GET,
    346       .handler = &SH_backup_blocks_reconcile_get,
    347       .nargs = 1,
    348       .nargs_is_upper_bound = true,
    349     },
    350     {
    351       .operation = "objects",
    352       .method = MHD_HTTP_METHOD_GET,
    353       .handler = &SH_backup_object_get,
    354       .nargs = 1,
    355     },
    356     {
    357       .operation = "objects",
    358       .method = MHD_HTTP_METHOD_POST,
    359       .handler = &SH_backup_object_post,
    360       .nargs = 1,
    361     },
    362     { .operation = NULL }
    363   };
    364   size_t slen;
    365   char d[SUBPATH_MAX];
    366   char *sp;
    367   /* NULL-terminated, hence one more entry than we ever fill */
    368   const char *args[SUBPATH_MAX_SEGMENTS + 1] = { NULL };
    369   unsigned int i;
    370 
    371   GNUNET_assert (NULL != request);
    372   GNUNET_assert (NULL != subpath);
    373   slen = strlen (subpath);
    374   if (slen >= SUBPATH_MAX)
    375   {
    376     GNUNET_break_op (0);
    377     return TALER_MHD2_reply_with_error (
    378       request,
    379       MHD_HTTP_STATUS_URI_TOO_LONG,
    380       TALER_EC_GENERIC_URI_TOO_LONG,
    381       subpath);
    382   }
    383   memcpy (d,
    384           subpath,
    385           slen + 1);
    386 
    387   i = 0;
    388   args[i++] = strtok_r (d,
    389                         "/",
    390                         &sp);
    391   while ( (NULL != args[i - 1]) &&
    392           (i < SUBPATH_MAX_SEGMENTS) )
    393     args[i++] = strtok_r (NULL,
    394                           "/",
    395                           &sp);
    396 
    397   if (NULL == args[0])
    398   {
    399     GNUNET_break_op (0);
    400     return TALER_MHD2_reply_with_error (
    401       request,
    402       MHD_HTTP_STATUS_NOT_FOUND,
    403       TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    404       subpath);
    405   }
    406 
    407   if (MHD_HTTP_METHOD_OPTIONS == method)
    408     return TALER_MHD2_reply_cors_preflight (request);
    409 
    410   for (unsigned int j = 0; NULL != bhandlers[j].operation; j++)
    411   {
    412     const struct SH_BackupOperationHandler *bh = &bhandlers[j];
    413     unsigned int nargs;
    414 
    415     if (0 != strcmp (args[0],
    416                      bh->operation))
    417       continue;
    418     if (bh->method != method)
    419       continue;
    420 
    421     /* Count path segments after the operation name */
    422     nargs = 0;
    423     while (NULL != args[nargs + 1])
    424       nargs++;
    425 
    426     if (bh->nargs_is_upper_bound
    427         ? (nargs > bh->nargs)
    428         : (nargs != bh->nargs))
    429     {
    430       /* Not this handler's shape; there may be another handler for
    431          the same (operation, method) with a different number of
    432          arguments (e.g. GET blocks vs. GET blocks/reconcile). */
    433       continue;
    434     }
    435     return bh->handler (request,
    436                         account_pub,
    437                         &args[1],
    438                         upload_size,
    439                         /* FIXME: replace with PATCH when mhd2 supports it */
    440                         MHD_HTTP_METHOD_PUT == bh->method);
    441   }
    442 
    443   GNUNET_break_op (0);
    444   return TALER_MHD2_reply_with_error (
    445     request,
    446     MHD_HTTP_STATUS_NOT_FOUND,
    447     TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    448     args[0]);
    449 }
    450 
    451 
    452 /**
    453  * A client has requested the given url using the given method
    454  * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
    455  * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).  The callback
    456  * must call MHD callbacks to provide content to give back to the
    457  * client.
    458  *
    459  * @param cls argument given together with the function
    460  *        pointer when the handler was registered with MHD
    461  * @param request the request to handle
    462  * @param path the requested uri (without arguments after "?")
    463  * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
    464  *        #MHD_HTTP_METHOD_PUT, etc.)
    465  * @param upload_size the size of the message upload content payload,
    466  *        #MHD_SIZE_UNKNOWN for chunked uploads (if the final chunk
    467  *        has not been processed yet)
    468  * @return next action
    469  */
    470 static const struct MHD_Action *
    471 url_handler (void *cls,
    472              struct MHD_Request *request,
    473              const struct MHD_String *path,
    474              enum MHD_HTTP_Method method,
    475              uint_fast64_t upload_size)
    476 {
    477   static struct SH_RequestHandler handlers[] = {
    478     /* Landing page, tell humans to go away. */
    479     {
    480       .url = "/",
    481       .method = MHD_HTTP_METHOD_GET,
    482       .handler =  &respond_root
    483     },
    484     {
    485       .url = "/agpl",
    486       .method = MHD_HTTP_METHOD_GET,
    487       .handler = &respond_agpl,
    488     },
    489     {
    490       .url = "/config",
    491       .method = MHD_HTTP_METHOD_GET,
    492       .handler = &SH_handler_config,
    493     },
    494     {
    495       .url = NULL
    496     }
    497   };
    498   struct SYNC_AccountPublicKeyP account_pub;
    499 
    500   (void) cls;
    501   /* Backup API */
    502   if (0 == strncmp (path->cstr,
    503                     "/backups/",
    504                     strlen ("/backups/")))
    505   {
    506     const char *ac = path->cstr + strlen ("/backups/");
    507     const char *next_slash;
    508     size_t ac_len;
    509 
    510     /* Find end of account key (next '/' or end of string) */
    511     next_slash = strchr (ac,
    512                          '/');
    513     if (NULL == next_slash)
    514       ac_len = strlen (ac);
    515     else
    516       ac_len = next_slash - ac;
    517 
    518     {
    519       /* Bound before we put it on the stack; the segment is
    520          client-controlled */
    521       char ac_str[ACCOUNT_KEY_MAX + 1];
    522 
    523       if (ac_len > ACCOUNT_KEY_MAX)
    524       {
    525         GNUNET_break_op (0);
    526         return TALER_MHD2_reply_with_error (
    527           request,
    528           MHD_HTTP_STATUS_BAD_REQUEST,
    529           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    530           "account public key");
    531       }
    532       memcpy (ac_str,
    533               ac,
    534               ac_len);
    535       ac_str[ac_len] = '\0';
    536       if (GNUNET_OK !=
    537           GNUNET_CRYPTO_eddsa_public_key_from_string (
    538             ac_str,
    539             ac_len,
    540             &account_pub.eddsa_pub))
    541       {
    542         GNUNET_break_op (0);
    543         return TALER_MHD2_reply_with_error (
    544           request,
    545           MHD_HTTP_STATUS_BAD_REQUEST,
    546           TALER_EC_GENERIC_PARAMETER_MALFORMED,
    547           ac_str);
    548       }
    549     }
    550 
    551     /* Without a subpath this is the account itself; only GET makes
    552        sense on it, everything else needs an operation */
    553     if ( (NULL == next_slash) ||
    554          ('\0' == next_slash[1]) )
    555     {
    556       if (MHD_HTTP_METHOD_OPTIONS == method)
    557         return TALER_MHD2_reply_cors_preflight (request);
    558       if (MHD_HTTP_METHOD_GET == method)
    559         return SH_backup_account_get (request,
    560                                       &account_pub);
    561       GNUNET_break_op (0);
    562       return TALER_MHD2_reply_with_error (
    563         request,
    564         MHD_HTTP_STATUS_NOT_FOUND,
    565         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    566         path->cstr);
    567     }
    568 
    569     /* Dispatch to sub-handler */
    570     return handle_backup_subpath (request,
    571                                   &account_pub,
    572                                   method,
    573                                   next_slash + 1,
    574                                   upload_size);
    575   }
    576 
    577   for (unsigned int i = 0; NULL != handlers[i].url; i++)
    578   {
    579     struct SH_RequestHandler *rh = &handlers[i];
    580 
    581     if (0 == strcmp (path->cstr,
    582                      rh->url))
    583     {
    584       if (MHD_HTTP_METHOD_OPTIONS == method)
    585       {
    586         return TALER_MHD2_reply_cors_preflight (request);
    587       }
    588       if (rh->method == method)
    589       {
    590         return rh->handler (request,
    591                             upload_size);
    592       }
    593     }
    594   }
    595   return respond_404 (request,
    596                       upload_size);
    597 }
    598 
    599 
    600 /**
    601  * Shutdown task. Invoked when the application is being terminated.
    602  *
    603  * @param cls NULL
    604  */
    605 static void
    606 do_shutdown (void *cls)
    607 {
    608   (void) cls;
    609   TALER_MHD2_daemons_halt ();
    610   if (NULL != SH_ctx)
    611   {
    612     GNUNET_CURL_fini (SH_ctx);
    613     SH_ctx = NULL;
    614   }
    615   if (NULL != rc)
    616   {
    617     GNUNET_CURL_gnunet_rc_destroy (rc);
    618     rc = NULL;
    619   }
    620   TALER_MHD2_daemons_destroy ();
    621   SYNCDB_fini ();
    622 }
    623 
    624 
    625 /**
    626  * Kick MHD to run now, to be called after MHD_request_resume().
    627  * Basically, we need to explicitly resume MHD's event loop whenever
    628  * we made progress serving a request.  This function re-schedules
    629  * the task processing MHD's activities to run immediately.
    630  */
    631 /* FIXME: replace with direct call... */
    632 void
    633 SH_trigger_daemon (void)
    634 {
    635   TALER_MHD2_daemons_trigger ();
    636 }
    637 
    638 
    639 /**
    640  * Kick GNUnet Curl scheduler to begin curl interactions.
    641  */
    642 void
    643 SH_trigger_curl (void)
    644 {
    645   GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
    646 }
    647 
    648 
    649 /**
    650  * Callback invoked on every listen socket to start the
    651  * respective MHD HTTP daemon.
    652  *
    653  * @param cls unused
    654  * @param lsock the listen socket
    655  */
    656 static void
    657 start_daemon (void *cls,
    658               int lsock)
    659 {
    660   struct MHD_Daemon *mhd;
    661 
    662   (void) cls;
    663   GNUNET_assert (-1 != lsock);
    664   mhd = MHD_daemon_create (&url_handler,
    665                            NULL);
    666   if (NULL == mhd)
    667   {
    668     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    669                 "Failed to launch HTTP service, exiting.\n");
    670     global_ret = EXIT_NO_RESTART;
    671     GNUNET_SCHEDULER_shutdown ();
    672     return;
    673   }
    674   /* Without this, the pool defaults to a fraction of the memory the
    675      daemon would use for connections -- about 1 MB, far below
    676      UPLOAD_LIMIT_MB -- and MHD answers anything bigger with its own
    677      413 before the handler ever runs, whatever the configured limit
    678      says.  See the FIXME in sync-httpd2_post-backups-KEY-blocks-NONCE.c
    679      for the incremental-upload alternative that would drop the pool
    680      dependency entirely. */
    681   GNUNET_assert (MHD_SC_OK ==
    682                  MHD_DAEMON_SET_OPTIONS (
    683                    mhd,
    684                    MHD_D_OPTION_DEFAULT_TIMEOUT_MILSEC (10000),
    685                    MHD_D_OPTION_LARGE_POOL_SIZE (
    686                      (size_t) GNUNET_MIN (
    687                        ((uint_fast64_t) SH_upload_limit_mb)
    688                        * 1024 * 1024
    689                        * UPLOAD_POOL_CONCURRENCY,
    690                        (uint_fast64_t) SIZE_MAX)),
    691                    MHD_D_OPTION_LISTEN_SOCKET (lsock)));
    692   have_daemons = true;
    693   TALER_MHD2_daemon_start (mhd);
    694 }
    695 
    696 
    697 /**
    698  * Main function that will be run by the scheduler.
    699  *
    700  * @param cls closure
    701  * @param args remaining command-line arguments
    702  * @param cfgfile name of the configuration file used (for saving, can be
    703  *        NULL!)
    704  * @param config configuration
    705  */
    706 static void
    707 run (void *cls,
    708      char *const *args,
    709      const char *cfgfile,
    710      const struct GNUNET_CONFIGURATION_Handle *config)
    711 {
    712   enum TALER_MHD2_GlobalOptions go;
    713 
    714   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    715               "Starting sync-httpd2\n");
    716   go = TALER_MHD2_GO_NONE;
    717   if (SH_sync_connection_close)
    718     go |= TALER_MHD2_GO_FORCE_CONNECTION_CLOSE;
    719   TALER_MHD2_setup (go);
    720   global_ret = EXIT_NOTCONFIGURED;
    721   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    722                                  NULL);
    723   if (GNUNET_OK !=
    724       GNUNET_CONFIGURATION_get_value_number (config,
    725                                              "sync",
    726                                              "UPLOAD_LIMIT_MB",
    727                                              &SH_upload_limit_mb))
    728   {
    729     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    730                                "sync",
    731                                "UPLOAD_LIMIT_MB");
    732     GNUNET_SCHEDULER_shutdown ();
    733     return;
    734   }
    735   if (GNUNET_OK !=
    736       TALER_config_get_amount (config,
    737                                "sync",
    738                                "INSURANCE",
    739                                &SH_insurance))
    740   {
    741     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    742                                "sync",
    743                                "INSURANCE");
    744     GNUNET_SCHEDULER_shutdown ();
    745     return;
    746   }
    747   if (GNUNET_OK !=
    748       TALER_config_get_amount (config,
    749                                "sync",
    750                                "ANNUAL_FEE",
    751                                &SH_annual_fee))
    752   {
    753     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    754                                "sync",
    755                                "ANNUAL_FEE");
    756     GNUNET_SCHEDULER_shutdown ();
    757     return;
    758   }
    759   if (GNUNET_OK !=
    760       GNUNET_CONFIGURATION_get_value_string (config,
    761                                              "sync",
    762                                              "PAYMENT_BACKEND_URL",
    763                                              &SH_backend_url))
    764   {
    765     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    766                                "sync",
    767                                "PAYMENT_BACKEND_URL");
    768     GNUNET_SCHEDULER_shutdown ();
    769     return;
    770   }
    771   /* The merchant backend API expects the base URL to end with '/'
    772      (see TALER_url_join), so ensure it does. */
    773   if ('/' != SH_backend_url[strlen (SH_backend_url) - 1])
    774   {
    775     char *tmp;
    776 
    777     GNUNET_asprintf (&tmp,
    778                      "%s/",
    779                      SH_backend_url);
    780     GNUNET_free (SH_backend_url);
    781     SH_backend_url = tmp;
    782   }
    783   /* The payment flow turns this into a "taler://" URI, so it needs a
    784      scheme and a host */
    785   {
    786     const char *host = NULL;
    787 
    788     if (0 == strncasecmp ("https://",
    789                           SH_backend_url,
    790                           strlen ("https://")))
    791       host = &SH_backend_url[strlen ("https://")];
    792     else if (0 == strncasecmp ("http://",
    793                                SH_backend_url,
    794                                strlen ("http://")))
    795       host = &SH_backend_url[strlen ("http://")];
    796     if ( (NULL == host) ||
    797          ('\0' == *host) )
    798     {
    799       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    800                                  "sync",
    801                                  "PAYMENT_BACKEND_URL",
    802                                  "must be an http:// or https:// URL"
    803                                  " with a host name");
    804       GNUNET_SCHEDULER_shutdown ();
    805       return;
    806     }
    807   }
    808   if (GNUNET_OK !=
    809       GNUNET_CONFIGURATION_get_value_string (config,
    810                                              "sync",
    811                                              "FULFILLMENT_URL",
    812                                              &SH_fulfillment_url))
    813   {
    814     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    815                                "sync",
    816                                "FULFILLMENT_URL");
    817     GNUNET_SCHEDULER_shutdown ();
    818     return;
    819   }
    820 
    821   /* setup HTTP client event loop */
    822   SH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    823                              &rc);
    824   if (NULL == SH_ctx)
    825   {
    826     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    827                 "Failed to initialize HTTP client, exiting.\n");
    828     global_ret = EXIT_NO_RESTART;
    829     GNUNET_SCHEDULER_shutdown ();
    830     return;
    831   }
    832   rc = GNUNET_CURL_gnunet_rc_create (SH_ctx);
    833   if (NULL != userpass)
    834     GNUNET_CURL_set_userpass (SH_ctx,
    835                               userpass);
    836   if (NULL != keyfile)
    837     GNUNET_CURL_set_tlscert (SH_ctx,
    838                              certtype,
    839                              certfile,
    840                              keyfile,
    841                              keypass);
    842   if (GNUNET_OK ==
    843       GNUNET_CONFIGURATION_get_value_string (config,
    844                                              "sync",
    845                                              "API_KEY",
    846                                              &apikey))
    847   {
    848     char *auth_header;
    849 
    850     GNUNET_asprintf (&auth_header,
    851                      "%s: %s",
    852                      MHD_HTTP_HEADER_AUTHORIZATION,
    853                      apikey);
    854     if (GNUNET_OK !=
    855         GNUNET_CURL_append_header (SH_ctx,
    856                                    auth_header))
    857     {
    858       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    859                   "Failed to set %s header, trying without\n",
    860                   MHD_HTTP_HEADER_AUTHORIZATION);
    861     }
    862     GNUNET_free (auth_header);
    863   }
    864 
    865   if (GNUNET_OK !=
    866       SYNCDB_init (config))
    867   {
    868     global_ret = EXIT_NOTCONFIGURED;
    869     GNUNET_SCHEDULER_shutdown ();
    870     return;
    871   }
    872   if (GNUNET_OK !=
    873       SYNCDB_preflight ())
    874   {
    875     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    876                 "Database not setup. Did you run sync-dbinit?\n");
    877     GNUNET_SCHEDULER_shutdown ();
    878     return;
    879   }
    880   {
    881     enum GNUNET_GenericReturnValue ret;
    882 
    883     ret = TALER_MHD2_listen_bind (config,
    884                                   "sync",
    885                                   &start_daemon,
    886                                   NULL);
    887     switch (ret)
    888     {
    889     case GNUNET_SYSERR:
    890       global_ret = EXIT_NOTCONFIGURED;
    891       GNUNET_SCHEDULER_shutdown ();
    892       return;
    893     case GNUNET_NO:
    894       if (! have_daemons)
    895       {
    896         global_ret = EXIT_NOTCONFIGURED;
    897         GNUNET_SCHEDULER_shutdown ();
    898         return;
    899       }
    900       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    901                   "Could not open all configured listen sockets\n");
    902       break;
    903     case GNUNET_OK:
    904       break;
    905     }
    906   }
    907   global_ret = EXIT_SUCCESS;
    908 }
    909 
    910 
    911 /**
    912  * The main function of the serve tool
    913  *
    914  * @param argc number of arguments from the command line
    915  * @param argv command line arguments
    916  * @return 0 ok, 1 on error
    917  */
    918 int
    919 main (int argc,
    920       char *const *argv)
    921 {
    922   struct GNUNET_GETOPT_CommandLineOption options[] = {
    923     GNUNET_GETOPT_option_string ('A',
    924                                  "auth",
    925                                  "USERNAME:PASSWORD",
    926                                  "use the given USERNAME and PASSWORD for client authentication",
    927                                  &userpass),
    928     /* Note: -c, -h, -L, -l and -v are reserved by GNUNET_PROGRAM_run().
    929        We use -E for the client certificate, as curl does. */
    930     GNUNET_GETOPT_option_string ('E',
    931                                  "cert",
    932                                  "CERTFILE",
    933                                  "file with the TLS client certificate for TLS client authentication",
    934                                  &certfile),
    935     GNUNET_GETOPT_option_flag ('C',
    936                                "connection-close",
    937                                "force HTTP connections to be closed after each request",
    938                                &SH_sync_connection_close),
    939     GNUNET_GETOPT_option_string ('k',
    940                                  "key",
    941                                  "KEYFILE",
    942                                  "file with the private TLS key for TLS client authentication",
    943                                  &keyfile),
    944     GNUNET_GETOPT_option_string ('p',
    945                                  "pass",
    946                                  "KEYFILEPASSPHRASE",
    947                                  "passphrase needed to decrypt the TLS client private key file",
    948                                  &keypass),
    949     GNUNET_GETOPT_option_string ('t',
    950                                  "type",
    951                                  "CERTTYPE",
    952                                  "type of the TLS client certificate, defaults to PEM if not specified",
    953                                  &certtype),
    954     GNUNET_GETOPT_OPTION_END
    955   };
    956   enum GNUNET_GenericReturnValue ret;
    957 
    958   ret = GNUNET_PROGRAM_run (SYNC_project_data (),
    959                             argc, argv,
    960                             "sync-httpd2",
    961                             "sync HTTP interface",
    962                             options,
    963                             &run, NULL);
    964   if (GNUNET_NO == ret)
    965     return EXIT_SUCCESS;
    966   if (GNUNET_SYSERR == ret)
    967     return EXIT_INVALIDARGUMENT;
    968   return global_ret;
    969 }