sync

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

sync-httpd2.c (27422B)


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