LCOV - code coverage report
Current view: top level - exchange - taler-exchange-httpd.c (source / functions) Hit Total Coverage
Test: GNU Taler exchange coverage report Lines: 57 642 8.9 %
Date: 2022-08-25 06:15:09 Functions: 3 22 13.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*
       2             :    This file is part of TALER
       3             :    Copyright (C) 2014-2022 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 Affero General Public License for more details.
      12             : 
      13             :    You should have received a copy of the GNU Affero General Public License along with
      14             :    TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
      15             :  */
      16             : /**
      17             :  * @file taler-exchange-httpd.c
      18             :  * @brief Serve the HTTP interface of the exchange
      19             :  * @author Florian Dold
      20             :  * @author Benedikt Mueller
      21             :  * @author Christian Grothoff
      22             :  */
      23             : #include "platform.h"
      24             : #include <gnunet/gnunet_util_lib.h>
      25             : #include <jansson.h>
      26             : #include <microhttpd.h>
      27             : #include <sched.h>
      28             : #include <sys/resource.h>
      29             : #include <limits.h>
      30             : #include "taler_kyclogic_lib.h"
      31             : #include "taler_templating_lib.h"
      32             : #include "taler_mhd_lib.h"
      33             : #include "taler-exchange-httpd_auditors.h"
      34             : #include "taler-exchange-httpd_batch-deposit.h"
      35             : #include "taler-exchange-httpd_batch-withdraw.h"
      36             : #include "taler-exchange-httpd_contract.h"
      37             : #include "taler-exchange-httpd_csr.h"
      38             : #include "taler-exchange-httpd_deposit.h"
      39             : #include "taler-exchange-httpd_deposits_get.h"
      40             : #include "taler-exchange-httpd_extensions.h"
      41             : #include "taler-exchange-httpd_keys.h"
      42             : #include "taler-exchange-httpd_kyc-check.h"
      43             : #include "taler-exchange-httpd_kyc-proof.h"
      44             : #include "taler-exchange-httpd_kyc-wallet.h"
      45             : #include "taler-exchange-httpd_link.h"
      46             : #include "taler-exchange-httpd_management.h"
      47             : #include "taler-exchange-httpd_melt.h"
      48             : #include "taler-exchange-httpd_metrics.h"
      49             : #include "taler-exchange-httpd_mhd.h"
      50             : #include "taler-exchange-httpd_purses_create.h"
      51             : #include "taler-exchange-httpd_purses_deposit.h"
      52             : #include "taler-exchange-httpd_purses_get.h"
      53             : #include "taler-exchange-httpd_purses_merge.h"
      54             : #include "taler-exchange-httpd_recoup.h"
      55             : #include "taler-exchange-httpd_recoup-refresh.h"
      56             : #include "taler-exchange-httpd_refreshes_reveal.h"
      57             : #include "taler-exchange-httpd_refund.h"
      58             : #include "taler-exchange-httpd_reserves_get.h"
      59             : #include "taler-exchange-httpd_reserves_history.h"
      60             : #include "taler-exchange-httpd_reserves_purse.h"
      61             : #include "taler-exchange-httpd_reserves_status.h"
      62             : #include "taler-exchange-httpd_terms.h"
      63             : #include "taler-exchange-httpd_transfers_get.h"
      64             : #include "taler-exchange-httpd_wire.h"
      65             : #include "taler-exchange-httpd_withdraw.h"
      66             : #include "taler_exchangedb_lib.h"
      67             : #include "taler_exchangedb_plugin.h"
      68             : #include "taler_extensions.h"
      69             : #include <gnunet/gnunet_mhd_compat.h>
      70             : 
      71             : /**
      72             :  * Backlog for listen operation on unix domain sockets.
      73             :  */
      74             : #define UNIX_BACKLOG 50
      75             : 
      76             : /**
      77             :  * Above what request latency do we start to log?
      78             :  */
      79             : #define WARN_LATENCY GNUNET_TIME_relative_multiply ( \
      80             :     GNUNET_TIME_UNIT_MILLISECONDS, 500)
      81             : 
      82             : /**
      83             :  * Are clients allowed to request /keys for times other than the
      84             :  * current time? Allowing this could be abused in a DoS-attack
      85             :  * as building new /keys responses is expensive. Should only be
      86             :  * enabled for testcases, development and test systems.
      87             :  */
      88             : int TEH_allow_keys_timetravel;
      89             : 
      90             : /**
      91             :  * Should we allow two HTTPDs to bind to the same port?
      92             :  */
      93             : static int allow_address_reuse;
      94             : 
      95             : /**
      96             :  * The exchange's configuration (global)
      97             :  */
      98             : const struct GNUNET_CONFIGURATION_Handle *TEH_cfg;
      99             : 
     100             : /**
     101             :  * Handle to the HTTP server.
     102             :  */
     103             : static struct MHD_Daemon *mhd;
     104             : 
     105             : /**
     106             :  * Our KYC configuration.
     107             :  */
     108             : struct TEH_KycOptions TEH_kyc_config;
     109             : 
     110             : /**
     111             :  * How long is caching /keys allowed at most? (global)
     112             :  */
     113             : struct GNUNET_TIME_Relative TEH_max_keys_caching;
     114             : 
     115             : /**
     116             :  * How long is the delay before we close reserves?
     117             :  */
     118             : struct GNUNET_TIME_Relative TEH_reserve_closing_delay;
     119             : 
     120             : /**
     121             :  * Master public key (according to the
     122             :  * configuration in the exchange directory).  (global)
     123             :  */
     124             : struct TALER_MasterPublicKeyP TEH_master_public_key;
     125             : 
     126             : /**
     127             :  * Our DB plugin.  (global)
     128             :  */
     129             : struct TALER_EXCHANGEDB_Plugin *TEH_plugin;
     130             : 
     131             : /**
     132             :  * Our currency.
     133             :  */
     134             : char *TEH_currency;
     135             : 
     136             : /**
     137             :  * Our base URL.
     138             :  */
     139             : char *TEH_base_url;
     140             : 
     141             : /**
     142             :  * Age restriction flags and mask
     143             :  */
     144             : bool TEH_age_restriction_enabled = true;
     145             : 
     146             : /**
     147             :  * Default timeout in seconds for HTTP requests.
     148             :  */
     149             : static unsigned int connection_timeout = 30;
     150             : 
     151             : /**
     152             :  * -C command-line flag given?
     153             :  */
     154             : static int connection_close;
     155             : 
     156             : /**
     157             :  * -I command-line flag given?
     158             :  */
     159             : int TEH_check_invariants_flag;
     160             : 
     161             : /**
     162             :  * True if we should commit suicide once all active
     163             :  * connections are finished.
     164             :  */
     165             : bool TEH_suicide;
     166             : 
     167             : /**
     168             :  * Signature of the configuration of all enabled extensions,
     169             :  * signed by the exchange's offline master key with purpose
     170             :  * TALER_SIGNATURE_MASTER_EXTENSION.
     171             :  */
     172             : struct TALER_MasterSignatureP TEH_extensions_sig;
     173             : 
     174             : /**
     175             :  * Value to return from main()
     176             :  */
     177             : static int global_ret;
     178             : 
     179             : /**
     180             :  * Port to run the daemon on.
     181             :  */
     182             : static uint16_t serve_port;
     183             : 
     184             : /**
     185             :  * Counter for the number of requests this HTTP has processed so far.
     186             :  */
     187             : static unsigned long long req_count;
     188             : 
     189             : /**
     190             :  * Counter for the number of open connections.
     191             :  */
     192             : static unsigned long long active_connections;
     193             : 
     194             : /**
     195             :  * Limit for the number of requests this HTTP may process before restarting.
     196             :  * (This was added as one way of dealing with unavoidable memory fragmentation
     197             :  * happening slowly over time.)
     198             :  */
     199             : static unsigned long long req_max;
     200             : 
     201             : /**
     202             :  * Context for all CURL operations (useful to the event loop)
     203             :  */
     204             : struct GNUNET_CURL_Context *TEH_curl_ctx;
     205             : 
     206             : /**
     207             :  * Context for integrating #TEH_curl_ctx with the
     208             :  * GNUnet event loop.
     209             :  */
     210             : static struct GNUNET_CURL_RescheduleContext *exchange_curl_rc;
     211             : 
     212             : /**
     213             :  * Signature of functions that handle operations on coins.
     214             :  *
     215             :  * @param connection the MHD connection to handle
     216             :  * @param coin_pub the public key of the coin
     217             :  * @param root uploaded JSON data
     218             :  * @return MHD result code
     219             :  */
     220             : typedef MHD_RESULT
     221             : (*CoinOpHandler)(struct MHD_Connection *connection,
     222             :                  const struct TALER_CoinSpendPublicKeyP *coin_pub,
     223             :                  const json_t *root);
     224             : 
     225             : 
     226             : /**
     227             :  * Generate a 404 "not found" reply on @a connection with
     228             :  * the hint @a details.
     229             :  *
     230             :  * @param connection where to send the reply on
     231             :  * @param details details for the error message, can be NULL
     232             :  */
     233             : static MHD_RESULT
     234           0 : r404 (struct MHD_Connection *connection,
     235             :       const char *details)
     236             : {
     237           0 :   return TALER_MHD_reply_with_error (connection,
     238             :                                      MHD_HTTP_NOT_FOUND,
     239             :                                      TALER_EC_EXCHANGE_GENERIC_OPERATION_UNKNOWN,
     240             :                                      details);
     241             : }
     242             : 
     243             : 
     244             : /**
     245             :  * Handle a "/coins/$COIN_PUB/$OP" POST request.  Parses the "coin_pub"
     246             :  * EdDSA key of the coin and demultiplexes based on $OP.
     247             :  *
     248             :  * @param rc request context
     249             :  * @param root uploaded JSON data
     250             :  * @param args array of additional options
     251             :  * @return MHD result code
     252             :  */
     253             : static MHD_RESULT
     254           0 : handle_post_coins (struct TEH_RequestContext *rc,
     255             :                    const json_t *root,
     256             :                    const char *const args[2])
     257             : {
     258             :   struct TALER_CoinSpendPublicKeyP coin_pub;
     259             :   static const struct
     260             :   {
     261             :     /**
     262             :      * Name of the operation (args[1])
     263             :      */
     264             :     const char *op;
     265             : 
     266             :     /**
     267             :      * Function to call to perform the operation.
     268             :      */
     269             :     CoinOpHandler handler;
     270             : 
     271             :   } h[] = {
     272             :     {
     273             :       .op = "deposit",
     274             :       .handler = &TEH_handler_deposit
     275             :     },
     276             :     {
     277             :       .op = "melt",
     278             :       .handler = &TEH_handler_melt
     279             :     },
     280             :     {
     281             :       .op = "recoup",
     282             :       .handler = &TEH_handler_recoup
     283             :     },
     284             :     {
     285             :       .op = "recoup-refresh",
     286             :       .handler = &TEH_handler_recoup_refresh
     287             :     },
     288             :     {
     289             :       .op = "refund",
     290             :       .handler = &TEH_handler_refund
     291             :     },
     292             :     {
     293             :       .op = NULL,
     294             :       .handler = NULL
     295             :     },
     296             :   };
     297             : 
     298           0 :   if (GNUNET_OK !=
     299           0 :       GNUNET_STRINGS_string_to_data (args[0],
     300             :                                      strlen (args[0]),
     301             :                                      &coin_pub,
     302             :                                      sizeof (coin_pub)))
     303             :   {
     304           0 :     GNUNET_break_op (0);
     305           0 :     return TALER_MHD_reply_with_error (rc->connection,
     306             :                                        MHD_HTTP_BAD_REQUEST,
     307             :                                        TALER_EC_EXCHANGE_GENERIC_COINS_INVALID_COIN_PUB,
     308             :                                        args[0]);
     309             :   }
     310           0 :   for (unsigned int i = 0; NULL != h[i].op; i++)
     311           0 :     if (0 == strcmp (h[i].op,
     312           0 :                      args[1]))
     313           0 :       return h[i].handler (rc->connection,
     314             :                            &coin_pub,
     315             :                            root);
     316           0 :   return r404 (rc->connection,
     317           0 :                args[1]);
     318             : }
     319             : 
     320             : 
     321             : /**
     322             :  * Signature of functions that handle operations on reserves.
     323             :  *
     324             :  * @param rc request context
     325             :  * @param reserve_pub the public key of the reserve
     326             :  * @param root uploaded JSON data
     327             :  * @return MHD result code
     328             :  */
     329             : typedef MHD_RESULT
     330             : (*ReserveOpHandler)(struct TEH_RequestContext *rc,
     331             :                     const struct TALER_ReservePublicKeyP *reserve_pub,
     332             :                     const json_t *root);
     333             : 
     334             : 
     335             : /**
     336             :  * Handle a "/reserves/$RESERVE_PUB/$OP" POST request.  Parses the "reserve_pub"
     337             :  * EdDSA key of the reserve and demultiplexes based on $OP.
     338             :  *
     339             :  * @param rc request context
     340             :  * @param root uploaded JSON data
     341             :  * @param args array of additional options
     342             :  * @return MHD result code
     343             :  */
     344             : static MHD_RESULT
     345           0 : handle_post_reserves (struct TEH_RequestContext *rc,
     346             :                       const json_t *root,
     347             :                       const char *const args[2])
     348             : {
     349             :   struct TALER_ReservePublicKeyP reserve_pub;
     350             :   static const struct
     351             :   {
     352             :     /**
     353             :      * Name of the operation (args[1])
     354             :      */
     355             :     const char *op;
     356             : 
     357             :     /**
     358             :      * Function to call to perform the operation.
     359             :      */
     360             :     ReserveOpHandler handler;
     361             : 
     362             :   } h[] = {
     363             :     {
     364             :       .op = "withdraw",
     365             :       .handler = &TEH_handler_withdraw
     366             :     },
     367             :     {
     368             :       .op = "batch-withdraw",
     369             :       .handler = &TEH_handler_batch_withdraw
     370             :     },
     371             :     {
     372             :       .op = "status",
     373             :       .handler = &TEH_handler_reserves_status
     374             :     },
     375             :     {
     376             :       .op = "history",
     377             :       .handler = &TEH_handler_reserves_history
     378             :     },
     379             :     {
     380             :       .op = "purse",
     381             :       .handler = &TEH_handler_reserves_purse
     382             :     },
     383             :     {
     384             :       .op = NULL,
     385             :       .handler = NULL
     386             :     },
     387             :   };
     388             : 
     389           0 :   if (GNUNET_OK !=
     390           0 :       GNUNET_STRINGS_string_to_data (args[0],
     391             :                                      strlen (args[0]),
     392             :                                      &reserve_pub,
     393             :                                      sizeof (reserve_pub)))
     394             :   {
     395           0 :     GNUNET_break_op (0);
     396           0 :     return TALER_MHD_reply_with_error (rc->connection,
     397             :                                        MHD_HTTP_BAD_REQUEST,
     398             :                                        TALER_EC_EXCHANGE_GENERIC_RESERVE_PUB_MALFORMED,
     399             :                                        args[0]);
     400             :   }
     401           0 :   for (unsigned int i = 0; NULL != h[i].op; i++)
     402           0 :     if (0 == strcmp (h[i].op,
     403           0 :                      args[1]))
     404           0 :       return h[i].handler (rc,
     405             :                            &reserve_pub,
     406             :                            root);
     407           0 :   return r404 (rc->connection,
     408           0 :                args[1]);
     409             : }
     410             : 
     411             : 
     412             : /**
     413             :  * Signature of functions that handle operations on purses.
     414             :  *
     415             :  * @param connection HTTP request handle
     416             :  * @param purse_pub the public key of the purse
     417             :  * @param root uploaded JSON data
     418             :  * @return MHD result code
     419             :  */
     420             : typedef MHD_RESULT
     421             : (*PurseOpHandler)(struct MHD_Connection *connection,
     422             :                   const struct TALER_PurseContractPublicKeyP *purse_pub,
     423             :                   const json_t *root);
     424             : 
     425             : 
     426             : /**
     427             :  * Handle a "/purses/$RESERVE_PUB/$OP" POST request.  Parses the "purse_pub"
     428             :  * EdDSA key of the purse and demultiplexes based on $OP.
     429             :  *
     430             :  * @param rc request context
     431             :  * @param root uploaded JSON data
     432             :  * @param args array of additional options
     433             :  * @return MHD result code
     434             :  */
     435             : static MHD_RESULT
     436           0 : handle_post_purses (struct TEH_RequestContext *rc,
     437             :                     const json_t *root,
     438             :                     const char *const args[2])
     439             : {
     440             :   struct TALER_PurseContractPublicKeyP purse_pub;
     441             :   static const struct
     442             :   {
     443             :     /**
     444             :      * Name of the operation (args[1])
     445             :      */
     446             :     const char *op;
     447             : 
     448             :     /**
     449             :      * Function to call to perform the operation.
     450             :      */
     451             :     PurseOpHandler handler;
     452             : 
     453             :   } h[] = {
     454             :     {
     455             :       .op = "create",
     456             :       .handler = &TEH_handler_purses_create
     457             :     },
     458             :     {
     459             :       .op = "deposit",
     460             :       .handler = &TEH_handler_purses_deposit
     461             :     },
     462             :     {
     463             :       .op = "merge",
     464             :       .handler = &TEH_handler_purses_merge
     465             :     },
     466             :     {
     467             :       .op = NULL,
     468             :       .handler = NULL
     469             :     },
     470             :   };
     471             : 
     472           0 :   if (GNUNET_OK !=
     473           0 :       GNUNET_STRINGS_string_to_data (args[0],
     474             :                                      strlen (args[0]),
     475             :                                      &purse_pub,
     476             :                                      sizeof (purse_pub)))
     477             :   {
     478           0 :     GNUNET_break_op (0);
     479           0 :     return TALER_MHD_reply_with_error (rc->connection,
     480             :                                        MHD_HTTP_BAD_REQUEST,
     481             :                                        TALER_EC_EXCHANGE_GENERIC_PURSE_PUB_MALFORMED,
     482             :                                        args[0]);
     483             :   }
     484           0 :   for (unsigned int i = 0; NULL != h[i].op; i++)
     485           0 :     if (0 == strcmp (h[i].op,
     486           0 :                      args[1]))
     487           0 :       return h[i].handler (rc->connection,
     488             :                            &purse_pub,
     489             :                            root);
     490           0 :   return r404 (rc->connection,
     491           0 :                args[1]);
     492             : }
     493             : 
     494             : 
     495             : /**
     496             :  * Increments our request counter and checks if this
     497             :  * process should commit suicide.
     498             :  */
     499             : static void
     500           0 : check_suicide (void)
     501             : {
     502             :   int fd;
     503             :   pid_t chld;
     504             :   unsigned long long cnt;
     505             : 
     506           0 :   cnt = req_count++;
     507           0 :   if (req_max != cnt)
     508           0 :     return;
     509           0 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     510             :               "Restarting exchange service after %llu requests\n",
     511             :               cnt);
     512             :   /* Stop accepting new connections */
     513           0 :   fd = MHD_quiesce_daemon (mhd);
     514           0 :   GNUNET_break (0 == close (fd));
     515             :   /* Continue handling existing connections in child,
     516             :      so that this process can die and be replaced by
     517             :      systemd with a fresh one */
     518           0 :   chld = fork ();
     519           0 :   if (-1 == chld)
     520             :   {
     521           0 :     GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
     522             :                          "fork");
     523           0 :     _exit (1);
     524             :   }
     525           0 :   if (0 != chld)
     526             :   {
     527             :     /* We are the parent, instant-suicide! */
     528           0 :     _exit (0);
     529             :   }
     530           0 :   TEH_suicide = true;
     531             : }
     532             : 
     533             : 
     534             : /**
     535             :  * Function called whenever MHD is done with a request.  If the
     536             :  * request was a POST, we may have stored a `struct Buffer *` in the
     537             :  * @a con_cls that might still need to be cleaned up.  Call the
     538             :  * respective function to free the memory.
     539             :  *
     540             :  * @param cls client-defined closure
     541             :  * @param connection connection handle
     542             :  * @param con_cls value as set by the last call to
     543             :  *        the #MHD_AccessHandlerCallback
     544             :  * @param toe reason for request termination
     545             :  * @see #MHD_OPTION_NOTIFY_COMPLETED
     546             :  * @ingroup request
     547             :  */
     548             : static void
     549           0 : handle_mhd_completion_callback (void *cls,
     550             :                                 struct MHD_Connection *connection,
     551             :                                 void **con_cls,
     552             :                                 enum MHD_RequestTerminationCode toe)
     553             : {
     554           0 :   struct TEH_RequestContext *rc = *con_cls;
     555             :   struct GNUNET_AsyncScopeSave old_scope;
     556             : 
     557             :   (void) cls;
     558           0 :   if (NULL == rc)
     559           0 :     return;
     560           0 :   GNUNET_async_scope_enter (&rc->async_scope_id,
     561             :                             &old_scope);
     562           0 :   check_suicide ();
     563           0 :   TEH_check_invariants ();
     564           0 :   if (NULL != rc->rh_cleaner)
     565           0 :     rc->rh_cleaner (rc);
     566           0 :   TEH_check_invariants ();
     567             :   {
     568             : #if MHD_VERSION >= 0x00097304
     569             :     const union MHD_ConnectionInfo *ci;
     570           0 :     unsigned int http_status = 0;
     571             : 
     572           0 :     ci = MHD_get_connection_info (connection,
     573             :                                   MHD_CONNECTION_INFO_HTTP_STATUS);
     574           0 :     if (NULL != ci)
     575           0 :       http_status = ci->http_status;
     576           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     577             :                 "Request for `%s' completed with HTTP status %u (%d)\n",
     578             :                 rc->url,
     579             :                 http_status,
     580             :                 toe);
     581             : #else
     582             :     (void) connection;
     583             :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     584             :                 "Request for `%s' completed (%d)\n",
     585             :                 rc->url,
     586             :                 toe);
     587             : #endif
     588             :   }
     589             : 
     590           0 :   TALER_MHD_parse_post_cleanup_callback (rc->opaque_post_parsing_context);
     591             :   /* Sanity-check that we didn't leave any transactions hanging */
     592           0 :   GNUNET_break (GNUNET_OK ==
     593             :                 TEH_plugin->preflight (TEH_plugin->cls));
     594             :   {
     595             :     struct GNUNET_TIME_Relative latency;
     596             : 
     597           0 :     latency = GNUNET_TIME_absolute_get_duration (rc->start_time);
     598           0 :     if (latency.rel_value_us >
     599           0 :         WARN_LATENCY.rel_value_us)
     600           0 :       GNUNET_log (GNUNET_ERROR_TYPE_INFO,
     601             :                   "Request for `%s' took %s\n",
     602             :                   rc->url,
     603             :                   GNUNET_STRINGS_relative_time_to_string (latency,
     604             :                                                           GNUNET_YES));
     605             :   }
     606           0 :   GNUNET_free (rc);
     607           0 :   *con_cls = NULL;
     608           0 :   GNUNET_async_scope_restore (&old_scope);
     609             : }
     610             : 
     611             : 
     612             : /**
     613             :  * We found a request handler responsible for handling a request. Parse the
     614             :  * @a upload_data (if applicable) and the @a url and call the
     615             :  * handler.
     616             :  *
     617             :  * @param rc request context
     618             :  * @param url rest of the URL to parse
     619             :  * @param upload_data upload data to parse (if available)
     620             :  * @param[in,out] upload_data_size number of bytes in @a upload_data
     621             :  * @return MHD result code
     622             :  */
     623             : static MHD_RESULT
     624           0 : proceed_with_handler (struct TEH_RequestContext *rc,
     625             :                       const char *url,
     626             :                       const char *upload_data,
     627             :                       size_t *upload_data_size)
     628           0 : {
     629           0 :   const struct TEH_RequestHandler *rh = rc->rh;
     630           0 :   const char *args[rh->nargs + 2];
     631           0 :   size_t ulen = strlen (url) + 1;
     632           0 :   json_t *root = NULL;
     633             :   MHD_RESULT ret;
     634             : 
     635             :   /* We do check for "ulen" here, because we'll later stack-allocate a buffer
     636             :      of that size and don't want to enable malicious clients to cause us
     637             :      huge stack allocations. */
     638           0 :   if (ulen > 512)
     639             :   {
     640             :     /* 512 is simply "big enough", as it is bigger than "6 * 54",
     641             :        which is the longest URL format we ever get (for
     642             :        /deposits/).  The value should be adjusted if we ever define protocol
     643             :        endpoints with plausibly longer inputs.  */
     644           0 :     GNUNET_break_op (0);
     645           0 :     return TALER_MHD_reply_with_error (rc->connection,
     646             :                                        MHD_HTTP_URI_TOO_LONG,
     647             :                                        TALER_EC_GENERIC_URI_TOO_LONG,
     648             :                                        url);
     649             :   }
     650             : 
     651             :   /* All POST endpoints come with a body in JSON format. So we parse
     652             :      the JSON here. */
     653           0 :   if (0 == strcasecmp (rh->method,
     654             :                        MHD_HTTP_METHOD_POST))
     655             :   {
     656             :     enum GNUNET_GenericReturnValue res;
     657             : 
     658           0 :     res = TALER_MHD_parse_post_json (rc->connection,
     659             :                                      &rc->opaque_post_parsing_context,
     660             :                                      upload_data,
     661             :                                      upload_data_size,
     662             :                                      &root);
     663           0 :     if (GNUNET_SYSERR == res)
     664             :     {
     665           0 :       GNUNET_assert (NULL == root);
     666           0 :       return MHD_NO; /* bad upload, could not even generate error */
     667             :     }
     668           0 :     if ( (GNUNET_NO == res) ||
     669           0 :          (NULL == root) )
     670             :     {
     671           0 :       GNUNET_assert (NULL == root);
     672           0 :       return MHD_YES; /* so far incomplete upload or parser error */
     673             :     }
     674             :   }
     675             : 
     676           0 :   {
     677           0 :     char d[ulen];
     678             :     unsigned int i;
     679             :     char *sp;
     680             : 
     681             :     /* Parse command-line arguments */
     682             :     /* make a copy of 'url' because 'strtok_r()' will modify */
     683           0 :     memcpy (d,
     684             :             url,
     685             :             ulen);
     686           0 :     i = 0;
     687           0 :     args[i++] = strtok_r (d, "/", &sp);
     688           0 :     while ( (NULL != args[i - 1]) &&
     689           0 :             (i <= rh->nargs + 1) )
     690           0 :       args[i++] = strtok_r (NULL, "/", &sp);
     691             :     /* make sure above loop ran nicely until completion, and also
     692             :        that there is no excess data in 'd' afterwards */
     693           0 :     if ( ( (rh->nargs_is_upper_bound) &&
     694           0 :            (i - 1 > rh->nargs) ) ||
     695           0 :          ( (! rh->nargs_is_upper_bound) &&
     696           0 :            (i - 1 != rh->nargs) ) )
     697             :     {
     698             :       char emsg[128 + 512];
     699             : 
     700           0 :       GNUNET_snprintf (emsg,
     701             :                        sizeof (emsg),
     702             :                        "Got %u+/%u segments for `%s' request (`%s')",
     703             :                        i - 1,
     704             :                        rh->nargs,
     705             :                        rh->url,
     706             :                        url);
     707           0 :       GNUNET_break_op (0);
     708           0 :       json_decref (root);
     709           0 :       return TALER_MHD_reply_with_error (rc->connection,
     710             :                                          MHD_HTTP_NOT_FOUND,
     711             :                                          TALER_EC_EXCHANGE_GENERIC_WRONG_NUMBER_OF_SEGMENTS,
     712             :                                          emsg);
     713             :     }
     714           0 :     GNUNET_assert (NULL == args[i - 1]);
     715             : 
     716             :     /* Above logic ensures that 'root' is exactly non-NULL for POST operations,
     717             :        so we test for 'root' to decide which handler to invoke. */
     718           0 :     if (NULL != root)
     719           0 :       ret = rh->handler.post (rc,
     720             :                               root,
     721             :                               args);
     722             :     else /* We also only have "POST" or "GET" in the API for at this point
     723             :       (OPTIONS/HEAD are taken care of earlier) */
     724           0 :       ret = rh->handler.get (rc,
     725             :                              args);
     726             :   }
     727           0 :   json_decref (root);
     728           0 :   return ret;
     729             : }
     730             : 
     731             : 
     732             : /**
     733             :  * Handle a "/seed" request.
     734             :  *
     735             :  * @param rc request context
     736             :  * @param args array of additional options (must be empty for this function)
     737             :  * @return MHD result code
     738             :  */
     739             : static MHD_RESULT
     740           0 : handler_seed (struct TEH_RequestContext *rc,
     741             :               const char *const args[])
     742             : {
     743             : #define SEED_SIZE 32
     744             :   char *body;
     745             :   MHD_RESULT ret;
     746             :   struct MHD_Response *resp;
     747             : 
     748             :   (void) args;
     749           0 :   body = malloc (SEED_SIZE); /* must use malloc(), because MHD will use free() */
     750           0 :   if (NULL == body)
     751           0 :     return MHD_NO;
     752           0 :   GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
     753             :                               body,
     754             :                               SEED_SIZE);
     755           0 :   resp = MHD_create_response_from_buffer (SEED_SIZE,
     756             :                                           body,
     757             :                                           MHD_RESPMEM_MUST_FREE);
     758           0 :   TALER_MHD_add_global_headers (resp);
     759           0 :   ret = MHD_queue_response (rc->connection,
     760             :                             MHD_HTTP_OK,
     761             :                             resp);
     762           0 :   GNUNET_break (MHD_YES == ret);
     763           0 :   MHD_destroy_response (resp);
     764           0 :   return ret;
     765             : #undef SEED_SIZE
     766             : }
     767             : 
     768             : 
     769             : /**
     770             :  * Handle POST "/management/..." requests.
     771             :  *
     772             :  * @param rc request context
     773             :  * @param root uploaded JSON data
     774             :  * @param args array of additional options
     775             :  * @return MHD result code
     776             :  */
     777             : static MHD_RESULT
     778           0 : handle_post_management (struct TEH_RequestContext *rc,
     779             :                         const json_t *root,
     780             :                         const char *const args[])
     781             : {
     782           0 :   if (NULL == args[0])
     783             :   {
     784           0 :     GNUNET_break_op (0);
     785           0 :     return r404 (rc->connection,
     786             :                  "/management");
     787             :   }
     788           0 :   if (0 == strcmp (args[0],
     789             :                    "auditors"))
     790             :   {
     791             :     struct TALER_AuditorPublicKeyP auditor_pub;
     792             : 
     793           0 :     if (NULL == args[1])
     794           0 :       return TEH_handler_management_auditors (rc->connection,
     795             :                                               root);
     796           0 :     if ( (NULL == args[1]) ||
     797           0 :          (NULL == args[2]) ||
     798           0 :          (0 != strcmp (args[2],
     799           0 :                        "disable")) ||
     800           0 :          (NULL != args[3]) )
     801           0 :       return r404 (rc->connection,
     802             :                    "/management/auditors/$AUDITOR_PUB/disable");
     803           0 :     if (GNUNET_OK !=
     804           0 :         GNUNET_STRINGS_string_to_data (args[1],
     805           0 :                                        strlen (args[1]),
     806             :                                        &auditor_pub,
     807             :                                        sizeof (auditor_pub)))
     808             :     {
     809           0 :       GNUNET_break_op (0);
     810           0 :       return TALER_MHD_reply_with_error (rc->connection,
     811             :                                          MHD_HTTP_BAD_REQUEST,
     812             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     813           0 :                                          args[1]);
     814             :     }
     815           0 :     return TEH_handler_management_auditors_AP_disable (rc->connection,
     816             :                                                        &auditor_pub,
     817             :                                                        root);
     818             :   }
     819           0 :   if (0 == strcmp (args[0],
     820             :                    "denominations"))
     821             :   {
     822             :     struct TALER_DenominationHashP h_denom_pub;
     823             : 
     824           0 :     if ( (NULL == args[0]) ||
     825           0 :          (NULL == args[1]) ||
     826           0 :          (NULL == args[2]) ||
     827           0 :          (0 != strcmp (args[2],
     828           0 :                        "revoke")) ||
     829           0 :          (NULL != args[3]) )
     830           0 :       return r404 (rc->connection,
     831             :                    "/management/denominations/$HDP/revoke");
     832           0 :     if (GNUNET_OK !=
     833           0 :         GNUNET_STRINGS_string_to_data (args[1],
     834           0 :                                        strlen (args[1]),
     835             :                                        &h_denom_pub,
     836             :                                        sizeof (h_denom_pub)))
     837             :     {
     838           0 :       GNUNET_break_op (0);
     839           0 :       return TALER_MHD_reply_with_error (rc->connection,
     840             :                                          MHD_HTTP_BAD_REQUEST,
     841             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     842           0 :                                          args[1]);
     843             :     }
     844           0 :     return TEH_handler_management_denominations_HDP_revoke (rc->connection,
     845             :                                                             &h_denom_pub,
     846             :                                                             root);
     847             :   }
     848           0 :   if (0 == strcmp (args[0],
     849             :                    "signkeys"))
     850             :   {
     851             :     struct TALER_ExchangePublicKeyP exchange_pub;
     852             : 
     853           0 :     if ( (NULL == args[0]) ||
     854           0 :          (NULL == args[1]) ||
     855           0 :          (NULL == args[2]) ||
     856           0 :          (0 != strcmp (args[2],
     857           0 :                        "revoke")) ||
     858           0 :          (NULL != args[3]) )
     859           0 :       return r404 (rc->connection,
     860             :                    "/management/signkeys/$HDP/revoke");
     861           0 :     if (GNUNET_OK !=
     862           0 :         GNUNET_STRINGS_string_to_data (args[1],
     863           0 :                                        strlen (args[1]),
     864             :                                        &exchange_pub,
     865             :                                        sizeof (exchange_pub)))
     866             :     {
     867           0 :       GNUNET_break_op (0);
     868           0 :       return TALER_MHD_reply_with_error (rc->connection,
     869             :                                          MHD_HTTP_BAD_REQUEST,
     870             :                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
     871           0 :                                          args[1]);
     872             :     }
     873           0 :     return TEH_handler_management_signkeys_EP_revoke (rc->connection,
     874             :                                                       &exchange_pub,
     875             :                                                       root);
     876             :   }
     877           0 :   if (0 == strcmp (args[0],
     878             :                    "keys"))
     879             :   {
     880           0 :     if (NULL != args[1])
     881             :     {
     882           0 :       GNUNET_break_op (0);
     883           0 :       return r404 (rc->connection,
     884             :                    "/management/keys/*");
     885             :     }
     886           0 :     return TEH_handler_management_post_keys (rc->connection,
     887             :                                              root);
     888             :   }
     889           0 :   if (0 == strcmp (args[0],
     890             :                    "wire"))
     891             :   {
     892           0 :     if (NULL == args[1])
     893           0 :       return TEH_handler_management_post_wire (rc->connection,
     894             :                                                root);
     895           0 :     if ( (0 != strcmp (args[1],
     896           0 :                        "disable")) ||
     897           0 :          (NULL != args[2]) )
     898             :     {
     899           0 :       GNUNET_break_op (0);
     900           0 :       return r404 (rc->connection,
     901             :                    "/management/wire/disable");
     902             :     }
     903           0 :     return TEH_handler_management_post_wire_disable (rc->connection,
     904             :                                                      root);
     905             :   }
     906           0 :   if (0 == strcmp (args[0],
     907             :                    "wire-fee"))
     908             :   {
     909           0 :     if (NULL != args[1])
     910             :     {
     911           0 :       GNUNET_break_op (0);
     912           0 :       return r404 (rc->connection,
     913             :                    "/management/wire-fee/*");
     914             :     }
     915           0 :     return TEH_handler_management_post_wire_fees (rc->connection,
     916             :                                                   root);
     917             :   }
     918           0 :   if (0 == strcmp (args[0],
     919             :                    "global-fee"))
     920             :   {
     921           0 :     if (NULL != args[1])
     922             :     {
     923           0 :       GNUNET_break_op (0);
     924           0 :       return r404 (rc->connection,
     925             :                    "/management/global-fee/*");
     926             :     }
     927           0 :     return TEH_handler_management_post_global_fees (rc->connection,
     928             :                                                     root);
     929             :   }
     930           0 :   if (0 == strcmp (args[0],
     931             :                    "extensions"))
     932             :   {
     933           0 :     if (NULL != args[1])
     934             :     {
     935           0 :       GNUNET_break_op (0);
     936           0 :       return r404 (rc->connection,
     937             :                    "/management/extensions/*");
     938             :     }
     939           0 :     return TEH_handler_management_post_extensions (rc->connection,
     940             :                                                    root);
     941             :   }
     942           0 :   if (0 == strcmp (args[0],
     943             :                    "drain"))
     944             :   {
     945           0 :     if (NULL != args[1])
     946             :     {
     947           0 :       GNUNET_break_op (0);
     948           0 :       return r404 (rc->connection,
     949             :                    "/management/drain/*");
     950             :     }
     951           0 :     return TEH_handler_management_post_drain (rc->connection,
     952             :                                               root);
     953             :   }
     954           0 :   GNUNET_break_op (0);
     955           0 :   return r404 (rc->connection,
     956             :                "/management/*");
     957             : }
     958             : 
     959             : 
     960             : /**
     961             :  * Handle a get "/management" request.
     962             :  *
     963             :  * @param rc request context
     964             :  * @param args array of additional options (must be [0] == "keys")
     965             :  * @return MHD result code
     966             :  */
     967             : static MHD_RESULT
     968           0 : handle_get_management (struct TEH_RequestContext *rc,
     969             :                        const char *const args[2])
     970             : {
     971           0 :   if ( (NULL != args[0]) &&
     972           0 :        (0 == strcmp (args[0],
     973           0 :                      "keys")) &&
     974           0 :        (NULL == args[1]) )
     975             :   {
     976           0 :     return TEH_keys_management_get_keys_handler (rc->rh,
     977             :                                                  rc->connection);
     978             :   }
     979           0 :   GNUNET_break_op (0);
     980           0 :   return r404 (rc->connection,
     981             :                "/management/*");
     982             : }
     983             : 
     984             : 
     985             : /**
     986             :  * Handle POST "/auditors/..." requests.
     987             :  *
     988             :  * @param rc request context
     989             :  * @param root uploaded JSON data
     990             :  * @param args array of additional options
     991             :  * @return MHD result code
     992             :  */
     993             : static MHD_RESULT
     994           0 : handle_post_auditors (struct TEH_RequestContext *rc,
     995             :                       const json_t *root,
     996             :                       const char *const args[])
     997             : {
     998             :   struct TALER_AuditorPublicKeyP auditor_pub;
     999             :   struct TALER_DenominationHashP h_denom_pub;
    1000             : 
    1001           0 :   if ( (NULL == args[0]) ||
    1002           0 :        (NULL == args[1]) ||
    1003           0 :        (NULL != args[2]) )
    1004             :   {
    1005           0 :     GNUNET_break_op (0);
    1006           0 :     return r404 (rc->connection,
    1007             :                  "/auditors/$AUDITOR_PUB/$H_DENOM_PUB");
    1008             :   }
    1009             : 
    1010           0 :   if (GNUNET_OK !=
    1011           0 :       GNUNET_STRINGS_string_to_data (args[0],
    1012             :                                      strlen (args[0]),
    1013             :                                      &auditor_pub,
    1014             :                                      sizeof (auditor_pub)))
    1015             :   {
    1016           0 :     GNUNET_break_op (0);
    1017           0 :     return TALER_MHD_reply_with_error (rc->connection,
    1018             :                                        MHD_HTTP_BAD_REQUEST,
    1019             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1020             :                                        args[0]);
    1021             :   }
    1022           0 :   if (GNUNET_OK !=
    1023           0 :       GNUNET_STRINGS_string_to_data (args[1],
    1024           0 :                                      strlen (args[1]),
    1025             :                                      &h_denom_pub,
    1026             :                                      sizeof (h_denom_pub)))
    1027             :   {
    1028           0 :     GNUNET_break_op (0);
    1029           0 :     return TALER_MHD_reply_with_error (rc->connection,
    1030             :                                        MHD_HTTP_BAD_REQUEST,
    1031             :                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    1032           0 :                                        args[1]);
    1033             :   }
    1034           0 :   return TEH_handler_auditors (rc->connection,
    1035             :                                &auditor_pub,
    1036             :                                &h_denom_pub,
    1037             :                                root);
    1038             : }
    1039             : 
    1040             : 
    1041             : /**
    1042             :  * Handle incoming HTTP request.
    1043             :  *
    1044             :  * @param cls closure for MHD daemon (unused)
    1045             :  * @param connection the connection
    1046             :  * @param url the requested url
    1047             :  * @param method the method (POST, GET, ...)
    1048             :  * @param version HTTP version (ignored)
    1049             :  * @param upload_data request data
    1050             :  * @param upload_data_size size of @a upload_data in bytes
    1051             :  * @param con_cls closure for request (a `struct TEH_RequestContext *`)
    1052             :  * @return MHD result code
    1053             :  */
    1054             : static MHD_RESULT
    1055           0 : handle_mhd_request (void *cls,
    1056             :                     struct MHD_Connection *connection,
    1057             :                     const char *url,
    1058             :                     const char *method,
    1059             :                     const char *version,
    1060             :                     const char *upload_data,
    1061             :                     size_t *upload_data_size,
    1062             :                     void **con_cls)
    1063             : {
    1064             :   static struct TEH_RequestHandler handlers[] = {
    1065             :     /* /robots.txt: disallow everything */
    1066             :     {
    1067             :       .url = "robots.txt",
    1068             :       .method = MHD_HTTP_METHOD_GET,
    1069             :       .handler.get = &TEH_handler_static_response,
    1070             :       .mime_type = "text/plain",
    1071             :       .data = "User-agent: *\nDisallow: /\n",
    1072             :       .response_code = MHD_HTTP_OK
    1073             :     },
    1074             :     /* Landing page, tell humans to go away. */
    1075             :     {
    1076             :       .url = "",
    1077             :       .method = MHD_HTTP_METHOD_GET,
    1078             :       .handler.get = TEH_handler_static_response,
    1079             :       .mime_type = "text/plain",
    1080             :       .data =
    1081             :         "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n",
    1082             :       .response_code = MHD_HTTP_OK
    1083             :     },
    1084             :     /* AGPL licensing page, redirect to source. As per the AGPL-license, every
    1085             :        deployment is required to offer the user a download of the source of
    1086             :        the actual deployment. We make this easy by including a redirect to the
    1087             :        source here. */
    1088             :     {
    1089             :       .url = "agpl",
    1090             :       .method = MHD_HTTP_METHOD_GET,
    1091             :       .handler.get = &TEH_handler_agpl_redirect
    1092             :     },
    1093             :     {
    1094             :       .url = "seed",
    1095             :       .method = MHD_HTTP_METHOD_GET,
    1096             :       .handler.get = &handler_seed
    1097             :     },
    1098             :     /* Performance metrics */
    1099             :     {
    1100             :       .url = "metrics",
    1101             :       .method = MHD_HTTP_METHOD_GET,
    1102             :       .handler.get = &TEH_handler_metrics
    1103             :     },
    1104             :     /* Terms of service */
    1105             :     {
    1106             :       .url = "terms",
    1107             :       .method = MHD_HTTP_METHOD_GET,
    1108             :       .handler.get = &TEH_handler_terms
    1109             :     },
    1110             :     /* Privacy policy */
    1111             :     {
    1112             :       .url = "privacy",
    1113             :       .method = MHD_HTTP_METHOD_GET,
    1114             :       .handler.get = &TEH_handler_privacy
    1115             :     },
    1116             :     /* Return key material and fundamental properties for this exchange */
    1117             :     {
    1118             :       .url = "keys",
    1119             :       .method = MHD_HTTP_METHOD_GET,
    1120             :       .handler.get = &TEH_keys_get_handler,
    1121             :     },
    1122             :     /* Requests for wiring information */
    1123             :     {
    1124             :       .url = "wire",
    1125             :       .method = MHD_HTTP_METHOD_GET,
    1126             :       .handler.get = &TEH_handler_wire
    1127             :     },
    1128             :     {
    1129             :       .url = "batch-deposit",
    1130             :       .method = MHD_HTTP_METHOD_POST,
    1131             :       .handler.post = &TEH_handler_batch_deposit,
    1132             :       .nargs = 0
    1133             :     },
    1134             :     /* request R, used in clause schnorr withdraw and refresh */
    1135             :     {
    1136             :       .url = "csr-melt",
    1137             :       .method = MHD_HTTP_METHOD_POST,
    1138             :       .handler.post = &TEH_handler_csr_melt,
    1139             :       .nargs = 0
    1140             :     },
    1141             :     {
    1142             :       .url = "csr-withdraw",
    1143             :       .method = MHD_HTTP_METHOD_POST,
    1144             :       .handler.post = &TEH_handler_csr_withdraw,
    1145             :       .nargs = 0
    1146             :     },
    1147             :     /* Withdrawing coins / interaction with reserves */
    1148             :     {
    1149             :       .url = "reserves",
    1150             :       .method = MHD_HTTP_METHOD_GET,
    1151             :       .handler.get = &TEH_handler_reserves_get,
    1152             :       .nargs = 1
    1153             :     },
    1154             :     {
    1155             :       .url = "reserves",
    1156             :       .method = MHD_HTTP_METHOD_POST,
    1157             :       .handler.post = &handle_post_reserves,
    1158             :       .nargs = 2
    1159             :     },
    1160             :     /* coins */
    1161             :     {
    1162             :       .url = "coins",
    1163             :       .method = MHD_HTTP_METHOD_POST,
    1164             :       .handler.post = &handle_post_coins,
    1165             :       .nargs = 2
    1166             :     },
    1167             :     {
    1168             :       .url = "coins",
    1169             :       .method = MHD_HTTP_METHOD_GET,
    1170             :       .handler.get = TEH_handler_link,
    1171             :       .nargs = 2,
    1172             :     },
    1173             :     /* refreshes/$RCH/reveal */
    1174             :     {
    1175             :       .url = "refreshes",
    1176             :       .method = MHD_HTTP_METHOD_POST,
    1177             :       .handler.post = &TEH_handler_reveal,
    1178             :       .nargs = 2
    1179             :     },
    1180             :     /* tracking transfers */
    1181             :     {
    1182             :       .url = "transfers",
    1183             :       .method = MHD_HTTP_METHOD_GET,
    1184             :       .handler.get = &TEH_handler_transfers_get,
    1185             :       .nargs = 1
    1186             :     },
    1187             :     /* tracking deposits */
    1188             :     {
    1189             :       .url = "deposits",
    1190             :       .method = MHD_HTTP_METHOD_GET,
    1191             :       .handler.get = &TEH_handler_deposits_get,
    1192             :       .nargs = 4
    1193             :     },
    1194             :     /* Operating on purses */
    1195             :     {
    1196             :       .url = "purses",
    1197             :       .method = MHD_HTTP_METHOD_POST,
    1198             :       .handler.post = &handle_post_purses,
    1199             :       .nargs = 2 // ??
    1200             :     },
    1201             :     /* Getting purse status */
    1202             :     {
    1203             :       .url = "purses",
    1204             :       .method = MHD_HTTP_METHOD_GET,
    1205             :       .handler.get = &TEH_handler_purses_get,
    1206             :       .nargs = 2
    1207             :     },
    1208             :     /* Getting contracts */
    1209             :     {
    1210             :       .url = "contracts",
    1211             :       .method = MHD_HTTP_METHOD_GET,
    1212             :       .handler.get = &TEH_handler_contracts_get,
    1213             :       .nargs = 1
    1214             :     },
    1215             :     /* KYC endpoints */
    1216             :     {
    1217             :       .url = "kyc-check",
    1218             :       .method = MHD_HTTP_METHOD_GET,
    1219             :       .handler.get = &TEH_handler_kyc_check,
    1220             :       .nargs = 3
    1221             :     },
    1222             :     {
    1223             :       .url = "kyc-proof",
    1224             :       .method = MHD_HTTP_METHOD_GET,
    1225             :       .handler.get = &TEH_handler_kyc_proof,
    1226             :       .nargs = 128,
    1227             :       .nargs_is_upper_bound = true
    1228             :     },
    1229             :     {
    1230             :       .url = "kyc-wallet",
    1231             :       .method = MHD_HTTP_METHOD_POST,
    1232             :       .handler.post = &TEH_handler_kyc_wallet,
    1233             :       .nargs = 0
    1234             :     },
    1235             :     /* POST management endpoints */
    1236             :     {
    1237             :       .url = "management",
    1238             :       .method = MHD_HTTP_METHOD_POST,
    1239             :       .handler.post = &handle_post_management,
    1240             :       .nargs = 4,
    1241             :       .nargs_is_upper_bound = true
    1242             :     },
    1243             :     /* GET management endpoints (we only really have "/management/keys") */
    1244             :     {
    1245             :       .url = "management",
    1246             :       .method = MHD_HTTP_METHOD_GET,
    1247             :       .handler.get = &handle_get_management,
    1248             :       .nargs = 1
    1249             :     },
    1250             :     /* auditor endpoints */
    1251             :     {
    1252             :       .url = "auditors",
    1253             :       .method = MHD_HTTP_METHOD_POST,
    1254             :       .handler.post = &handle_post_auditors,
    1255             :       .nargs = 4,
    1256             :       .nargs_is_upper_bound = true
    1257             :     },
    1258             :     /* mark end of list */
    1259             :     {
    1260             :       .url = NULL
    1261             :     }
    1262             :   };
    1263           0 :   struct TEH_RequestContext *rc = *con_cls;
    1264             :   struct GNUNET_AsyncScopeSave old_scope;
    1265           0 :   const char *correlation_id = NULL;
    1266             : 
    1267             :   (void) cls;
    1268             :   (void) version;
    1269           0 :   if (NULL == rc)
    1270             :   {
    1271           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1272             :                 "Handling new request\n");
    1273             : 
    1274             :     /* We're in a new async scope! */
    1275           0 :     rc = *con_cls = GNUNET_new (struct TEH_RequestContext);
    1276           0 :     rc->start_time = GNUNET_TIME_absolute_get ();
    1277           0 :     GNUNET_async_scope_fresh (&rc->async_scope_id);
    1278           0 :     TEH_check_invariants ();
    1279           0 :     rc->url = url;
    1280           0 :     rc->connection = connection;
    1281             :     /* We only read the correlation ID on the first callback for every client */
    1282           0 :     correlation_id = MHD_lookup_connection_value (connection,
    1283             :                                                   MHD_HEADER_KIND,
    1284             :                                                   "Taler-Correlation-Id");
    1285           0 :     if ( (NULL != correlation_id) &&
    1286             :          (GNUNET_YES !=
    1287           0 :           GNUNET_CURL_is_valid_scope_id (correlation_id)) )
    1288             :     {
    1289           0 :       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    1290             :                   "illegal incoming correlation ID\n");
    1291           0 :       correlation_id = NULL;
    1292             :     }
    1293             : 
    1294             :     /* Check if upload is in bounds */
    1295           0 :     if (0 == strcasecmp (method,
    1296             :                          MHD_HTTP_METHOD_POST))
    1297             :     {
    1298             :       const char *cl;
    1299             : 
    1300             :       /* Maybe check for maximum upload size
    1301             :          and refuse requests if they are just too big. */
    1302           0 :       cl = MHD_lookup_connection_value (connection,
    1303             :                                         MHD_HEADER_KIND,
    1304             :                                         MHD_HTTP_HEADER_CONTENT_LENGTH);
    1305           0 :       if (NULL != cl)
    1306             :       {
    1307             :         unsigned long long cv;
    1308             :         char dummy;
    1309             : 
    1310           0 :         if (1 != sscanf (cl,
    1311             :                          "%llu%c",
    1312             :                          &cv,
    1313             :                          &dummy))
    1314             :         {
    1315             :           /* Not valid HTTP request, just close connection. */
    1316           0 :           GNUNET_break_op (0);
    1317           0 :           return MHD_NO;
    1318             :         }
    1319           0 :         if (cv > TALER_MHD_REQUEST_BUFFER_MAX)
    1320           0 :           return TALER_MHD_reply_request_too_large (connection);
    1321             :       }
    1322             :     }
    1323             :   }
    1324             : 
    1325           0 :   GNUNET_async_scope_enter (&rc->async_scope_id,
    1326             :                             &old_scope);
    1327           0 :   TEH_check_invariants ();
    1328           0 :   if (NULL != correlation_id)
    1329           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1330             :                 "Handling request (%s) for URL '%s', correlation_id=%s\n",
    1331             :                 method,
    1332             :                 url,
    1333             :                 correlation_id);
    1334             :   else
    1335           0 :     GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1336             :                 "Handling request (%s) for URL '%s'\n",
    1337             :                 method,
    1338             :                 url);
    1339             :   /* on repeated requests, check our cache first */
    1340           0 :   if (NULL != rc->rh)
    1341             :   {
    1342             :     MHD_RESULT ret;
    1343             :     const char *start;
    1344             : 
    1345           0 :     if ('\0' == url[0])
    1346             :       /* strange, should start with '/', treat as just "/" */
    1347           0 :       url = "/";
    1348           0 :     start = strchr (url + 1, '/');
    1349           0 :     if (NULL == start)
    1350           0 :       start = "";
    1351           0 :     ret = proceed_with_handler (rc,
    1352             :                                 start,
    1353             :                                 upload_data,
    1354             :                                 upload_data_size);
    1355           0 :     GNUNET_async_scope_restore (&old_scope);
    1356           0 :     return ret;
    1357             :   }
    1358             : 
    1359           0 :   if ( (0 == strcasecmp (method,
    1360           0 :                          MHD_HTTP_METHOD_OPTIONS)) &&
    1361           0 :        (0 == strcmp ("*",
    1362             :                      url)) )
    1363           0 :     return TALER_MHD_reply_cors_preflight (connection);
    1364             : 
    1365           0 :   if (0 == strcasecmp (method,
    1366             :                        MHD_HTTP_METHOD_HEAD))
    1367           0 :     method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */
    1368             : 
    1369             :   /* parse first part of URL */
    1370             :   {
    1371           0 :     bool found = false;
    1372             :     size_t tok_size;
    1373             :     const char *tok;
    1374             :     const char *rest;
    1375             : 
    1376           0 :     if ('\0' == url[0])
    1377             :       /* strange, should start with '/', treat as just "/" */
    1378           0 :       url = "/";
    1379           0 :     tok = url + 1;
    1380           0 :     rest = strchr (tok, '/');
    1381           0 :     if (NULL == rest)
    1382             :     {
    1383           0 :       tok_size = strlen (tok);
    1384             :     }
    1385             :     else
    1386             :     {
    1387           0 :       tok_size = rest - tok;
    1388           0 :       rest++; /* skip over '/' */
    1389             :     }
    1390           0 :     for (unsigned int i = 0; NULL != handlers[i].url; i++)
    1391             :     {
    1392           0 :       struct TEH_RequestHandler *rh = &handlers[i];
    1393             : 
    1394           0 :       if ( (0 != strncmp (tok,
    1395             :                           rh->url,
    1396           0 :                           tok_size)) ||
    1397           0 :            (tok_size != strlen (rh->url) ) )
    1398           0 :         continue;
    1399           0 :       found = true;
    1400             :       /* The URL is a match!  What we now do depends on the method. */
    1401           0 :       if (0 == strcasecmp (method, MHD_HTTP_METHOD_OPTIONS))
    1402             :       {
    1403           0 :         GNUNET_async_scope_restore (&old_scope);
    1404           0 :         return TALER_MHD_reply_cors_preflight (connection);
    1405             :       }
    1406           0 :       GNUNET_assert (NULL != rh->method);
    1407           0 :       if (0 == strcasecmp (method,
    1408             :                            rh->method))
    1409             :       {
    1410             :         MHD_RESULT ret;
    1411             : 
    1412             :         /* cache to avoid the loop next time */
    1413           0 :         rc->rh = rh;
    1414             :         /* run handler */
    1415           0 :         ret = proceed_with_handler (rc,
    1416           0 :                                     url + tok_size + 1,
    1417             :                                     upload_data,
    1418             :                                     upload_data_size);
    1419           0 :         GNUNET_async_scope_restore (&old_scope);
    1420           0 :         return ret;
    1421             :       }
    1422             :     }
    1423             : 
    1424           0 :     if (found)
    1425             :     {
    1426             :       /* we found a matching address, but the method is wrong */
    1427             :       struct MHD_Response *reply;
    1428             :       MHD_RESULT ret;
    1429           0 :       char *allowed = NULL;
    1430             : 
    1431           0 :       GNUNET_break_op (0);
    1432           0 :       for (unsigned int i = 0; NULL != handlers[i].url; i++)
    1433             :       {
    1434           0 :         struct TEH_RequestHandler *rh = &handlers[i];
    1435             : 
    1436           0 :         if ( (0 != strncmp (tok,
    1437             :                             rh->url,
    1438           0 :                             tok_size)) ||
    1439           0 :              (tok_size != strlen (rh->url) ) )
    1440           0 :           continue;
    1441           0 :         if (NULL == allowed)
    1442             :         {
    1443           0 :           allowed = GNUNET_strdup (rh->method);
    1444             :         }
    1445             :         else
    1446             :         {
    1447             :           char *tmp;
    1448             : 
    1449           0 :           GNUNET_asprintf (&tmp,
    1450             :                            "%s, %s",
    1451             :                            allowed,
    1452             :                            rh->method);
    1453           0 :           GNUNET_free (allowed);
    1454           0 :           allowed = tmp;
    1455             :         }
    1456           0 :         if (0 == strcasecmp (rh->method,
    1457             :                              MHD_HTTP_METHOD_GET))
    1458             :         {
    1459             :           char *tmp;
    1460             : 
    1461           0 :           GNUNET_asprintf (&tmp,
    1462             :                            "%s, %s",
    1463             :                            allowed,
    1464             :                            MHD_HTTP_METHOD_HEAD);
    1465           0 :           GNUNET_free (allowed);
    1466           0 :           allowed = tmp;
    1467             :         }
    1468             :       }
    1469           0 :       reply = TALER_MHD_make_error (TALER_EC_GENERIC_METHOD_INVALID,
    1470             :                                     method);
    1471           0 :       GNUNET_break (MHD_YES ==
    1472             :                     MHD_add_response_header (reply,
    1473             :                                              MHD_HTTP_HEADER_ALLOW,
    1474             :                                              allowed));
    1475           0 :       GNUNET_free (allowed);
    1476           0 :       ret = MHD_queue_response (connection,
    1477             :                                 MHD_HTTP_METHOD_NOT_ALLOWED,
    1478             :                                 reply);
    1479           0 :       MHD_destroy_response (reply);
    1480           0 :       return ret;
    1481             :     }
    1482             :   }
    1483             : 
    1484             :   /* No handler matches, generate not found */
    1485             :   {
    1486             :     MHD_RESULT ret;
    1487             : 
    1488           0 :     ret = TALER_MHD_reply_with_error (connection,
    1489             :                                       MHD_HTTP_NOT_FOUND,
    1490             :                                       TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
    1491             :                                       url);
    1492           0 :     GNUNET_async_scope_restore (&old_scope);
    1493           0 :     return ret;
    1494             :   }
    1495             : }
    1496             : 
    1497             : 
    1498             : /**
    1499             :  * Load general KYC configuration parameters for the exchange server into the
    1500             :  * #TEH_kyc_config variable.
    1501             :  *
    1502             :  * @return #GNUNET_OK on success
    1503             :  */
    1504             : static enum GNUNET_GenericReturnValue
    1505           0 : parse_kyc_settings (void)
    1506             : {
    1507           0 :   if (GNUNET_OK !=
    1508           0 :       GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
    1509             :                                            "exchange",
    1510             :                                            "KYC_WITHDRAW_PERIOD",
    1511             :                                            &TEH_kyc_config.withdraw_period))
    1512             :   {
    1513           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1514             :                                "exchange",
    1515             :                                "KYC_WITHDRAW_PERIOD",
    1516             :                                "valid relative time expected");
    1517           0 :     return GNUNET_SYSERR;
    1518             :   }
    1519           0 :   if (GNUNET_TIME_relative_is_zero (TEH_kyc_config.withdraw_period))
    1520           0 :     return GNUNET_OK;
    1521           0 :   if (GNUNET_OK !=
    1522           0 :       TALER_config_get_amount (TEH_cfg,
    1523             :                                "exchange",
    1524             :                                "KYC_WITHDRAW_LIMIT",
    1525             :                                &TEH_kyc_config.withdraw_limit))
    1526           0 :     return GNUNET_SYSERR;
    1527           0 :   if (0 != strcasecmp (TEH_kyc_config.withdraw_limit.currency,
    1528             :                        TEH_currency))
    1529             :   {
    1530           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1531             :                                "exchange",
    1532             :                                "KYC_WITHDRAW_LIMIT",
    1533             :                                "currency mismatch");
    1534           0 :     return GNUNET_SYSERR;
    1535             :   }
    1536           0 :   return GNUNET_OK;
    1537             : }
    1538             : 
    1539             : 
    1540             : /**
    1541             :  * Load OAuth2.0 configuration parameters for the exchange server into the
    1542             :  * #TEH_kyc_config variable.
    1543             :  *
    1544             :  * @return #GNUNET_OK on success
    1545             :  */
    1546             : static enum GNUNET_GenericReturnValue
    1547           0 : parse_kyc_oauth_cfg (void)
    1548             : {
    1549             :   char *s;
    1550             : 
    1551           0 :   if (GNUNET_OK !=
    1552           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1553             :                                              "exchange-kyc-oauth2",
    1554             :                                              "KYC_OAUTH2_AUTH_URL",
    1555             :                                              &s))
    1556             :   {
    1557           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1558             :                                "exchange-kyc-oauth2",
    1559             :                                "KYC_OAUTH2_AUTH_URL");
    1560           0 :     return GNUNET_SYSERR;
    1561             :   }
    1562           0 :   if ( (! TALER_url_valid_charset (s)) ||
    1563           0 :        ( (0 != strncasecmp (s,
    1564             :                             "http://",
    1565           0 :                             strlen ("http://"))) &&
    1566           0 :          (0 != strncasecmp (s,
    1567             :                             "https://",
    1568             :                             strlen ("https://"))) ) )
    1569             :   {
    1570           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1571             :                                "exchange-kyc-oauth2",
    1572             :                                "KYC_OAUTH2_AUTH_URL",
    1573             :                                "not a valid URL");
    1574           0 :     GNUNET_free (s);
    1575           0 :     return GNUNET_SYSERR;
    1576             :   }
    1577           0 :   TEH_kyc_config.details.oauth2.auth_url = s;
    1578             : 
    1579           0 :   if (GNUNET_OK !=
    1580           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1581             :                                              "exchange-kyc-oauth2",
    1582             :                                              "KYC_OAUTH2_LOGIN_URL",
    1583             :                                              &s))
    1584             :   {
    1585           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1586             :                                "exchange-kyc-oauth2",
    1587             :                                "KYC_OAUTH2_LOGIN_URL");
    1588           0 :     return GNUNET_SYSERR;
    1589             :   }
    1590           0 :   if ( (! TALER_url_valid_charset (s)) ||
    1591           0 :        ( (0 != strncasecmp (s,
    1592             :                             "http://",
    1593           0 :                             strlen ("http://"))) &&
    1594           0 :          (0 != strncasecmp (s,
    1595             :                             "https://",
    1596             :                             strlen ("https://"))) ) )
    1597             :   {
    1598           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1599             :                                "exchange-kyc-oauth2",
    1600             :                                "KYC_OAUTH2_LOGIN_URL",
    1601             :                                "not a valid URL");
    1602           0 :     GNUNET_free (s);
    1603           0 :     return GNUNET_SYSERR;
    1604             :   }
    1605           0 :   TEH_kyc_config.details.oauth2.login_url = s;
    1606             : 
    1607           0 :   if (GNUNET_OK !=
    1608           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1609             :                                              "exchange-kyc-oauth2",
    1610             :                                              "KYC_INFO_URL",
    1611             :                                              &s))
    1612             :   {
    1613           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1614             :                                "exchange-kyc-oauth2",
    1615             :                                "KYC_INFO_URL");
    1616           0 :     return GNUNET_SYSERR;
    1617             :   }
    1618           0 :   if ( (! TALER_url_valid_charset (s)) ||
    1619           0 :        ( (0 != strncasecmp (s,
    1620             :                             "http://",
    1621           0 :                             strlen ("http://"))) &&
    1622           0 :          (0 != strncasecmp (s,
    1623             :                             "https://",
    1624             :                             strlen ("https://"))) ) )
    1625             :   {
    1626           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1627             :                                "exchange-kyc-oauth2",
    1628             :                                "KYC_INFO_URL",
    1629             :                                "not a valid URL");
    1630           0 :     GNUNET_free (s);
    1631           0 :     return GNUNET_SYSERR;
    1632             :   }
    1633           0 :   TEH_kyc_config.details.oauth2.info_url = s;
    1634             : 
    1635           0 :   if (GNUNET_OK !=
    1636           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1637             :                                              "exchange-kyc-oauth2",
    1638             :                                              "KYC_OAUTH2_CLIENT_ID",
    1639             :                                              &s))
    1640             :   {
    1641           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1642             :                                "exchange-kyc-oauth2",
    1643             :                                "KYC_OAUTH2_CLIENT_ID");
    1644           0 :     return GNUNET_SYSERR;
    1645             :   }
    1646           0 :   TEH_kyc_config.details.oauth2.client_id = s;
    1647             : 
    1648           0 :   if (GNUNET_OK !=
    1649           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1650             :                                              "exchange-kyc-oauth2",
    1651             :                                              "KYC_OAUTH2_CLIENT_SECRET",
    1652             :                                              &s))
    1653             :   {
    1654           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1655             :                                "exchange-kyc-oauth2",
    1656             :                                "KYC_OAUTH2_CLIENT_SECRET");
    1657           0 :     return GNUNET_SYSERR;
    1658             :   }
    1659           0 :   TEH_kyc_config.details.oauth2.client_secret = s;
    1660             : 
    1661           0 :   if (GNUNET_OK !=
    1662           0 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1663             :                                              "exchange-kyc-oauth2",
    1664             :                                              "KYC_OAUTH2_POST_URL",
    1665             :                                              &s))
    1666             :   {
    1667           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1668             :                                "exchange-kyc-oauth2",
    1669             :                                "KYC_OAUTH2_POST_URL");
    1670           0 :     return GNUNET_SYSERR;
    1671             :   }
    1672           0 :   TEH_kyc_config.details.oauth2.post_kyc_redirect_url = s;
    1673           0 :   return GNUNET_OK;
    1674             : }
    1675             : 
    1676             : 
    1677             : /**
    1678             :  * Load configuration parameters for the exchange
    1679             :  * server into the corresponding global variables.
    1680             :  *
    1681             :  * @return #GNUNET_OK on success
    1682             :  */
    1683             : static enum GNUNET_GenericReturnValue
    1684           1 : exchange_serve_process_config (void)
    1685             : {
    1686           1 :   if (GNUNET_OK !=
    1687           1 :       TALER_KYCLOGIC_kyc_init (TEH_cfg))
    1688             :   {
    1689           0 :     return GNUNET_SYSERR;
    1690             :   }
    1691             :   {
    1692             :     char *kyc_mode;
    1693             : 
    1694           1 :     if (GNUNET_OK !=
    1695           1 :         GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1696             :                                                "exchange",
    1697             :                                                "KYC_MODE",
    1698             :                                                &kyc_mode))
    1699             :     {
    1700           0 :       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1701             :                                  "exchange",
    1702             :                                  "KYC_MODE");
    1703           0 :       return GNUNET_SYSERR;
    1704             :     }
    1705           1 :     if (0 == strcasecmp (kyc_mode,
    1706             :                          "NONE"))
    1707             :     {
    1708           1 :       TEH_kyc_config.mode = TEH_KYC_NONE;
    1709             :     }
    1710           0 :     else if (0 == strcasecmp (kyc_mode,
    1711             :                               "OAUTH2"))
    1712             :     {
    1713           0 :       TEH_kyc_config.mode = TEH_KYC_OAUTH2;
    1714           0 :       if (GNUNET_OK !=
    1715           0 :           parse_kyc_oauth_cfg ())
    1716             :       {
    1717           0 :         GNUNET_free (kyc_mode);
    1718           0 :         return GNUNET_SYSERR;
    1719             :       }
    1720             :     }
    1721             :     else
    1722             :     {
    1723           0 :       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1724             :                                  "exchange",
    1725             :                                  "KYC_MODE",
    1726             :                                  "Must be 'NONE' or 'OAUTH2'");
    1727           0 :       GNUNET_free (kyc_mode);
    1728           0 :       return GNUNET_SYSERR;
    1729             :     }
    1730           1 :     GNUNET_free (kyc_mode);
    1731             :   }
    1732           1 :   if (GNUNET_OK !=
    1733           1 :       GNUNET_CONFIGURATION_get_value_number (TEH_cfg,
    1734             :                                              "exchange",
    1735             :                                              "MAX_REQUESTS",
    1736             :                                              &req_max))
    1737             :   {
    1738           1 :     req_max = ULLONG_MAX;
    1739             :   }
    1740           1 :   if (GNUNET_OK !=
    1741           1 :       GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
    1742             :                                            "exchangedb",
    1743             :                                            "IDLE_RESERVE_EXPIRATION_TIME",
    1744             :                                            &TEH_reserve_closing_delay))
    1745             :   {
    1746           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1747             :                                "exchangedb",
    1748             :                                "IDLE_RESERVE_EXPIRATION_TIME");
    1749             :     /* use default */
    1750             :     TEH_reserve_closing_delay
    1751           0 :       = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_WEEKS,
    1752             :                                        4);
    1753             :   }
    1754             : 
    1755           1 :   if (GNUNET_OK !=
    1756           1 :       GNUNET_CONFIGURATION_get_value_time (TEH_cfg,
    1757             :                                            "exchange",
    1758             :                                            "MAX_KEYS_CACHING",
    1759             :                                            &TEH_max_keys_caching))
    1760             :   {
    1761           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1762             :                                "exchange",
    1763             :                                "MAX_KEYS_CACHING",
    1764             :                                "valid relative time expected");
    1765           0 :     return GNUNET_SYSERR;
    1766             :   }
    1767           1 :   if (GNUNET_OK !=
    1768           1 :       TALER_config_get_currency (TEH_cfg,
    1769             :                                  &TEH_currency))
    1770             :   {
    1771           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1772             :                                "taler",
    1773             :                                "CURRENCY");
    1774           0 :     return GNUNET_SYSERR;
    1775             :   }
    1776           1 :   if (GNUNET_OK !=
    1777           1 :       GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1778             :                                              "exchange",
    1779             :                                              "BASE_URL",
    1780             :                                              &TEH_base_url))
    1781             :   {
    1782           0 :     GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1783             :                                "exchange",
    1784             :                                "BASE_URL");
    1785           0 :     return GNUNET_SYSERR;
    1786             :   }
    1787           1 :   if (! TALER_url_valid_charset (TEH_base_url))
    1788             :   {
    1789           0 :     GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1790             :                                "exchange",
    1791             :                                "BASE_URL",
    1792             :                                "invalid URL");
    1793           0 :     return GNUNET_SYSERR;
    1794             :   }
    1795             : 
    1796           1 :   if (TEH_KYC_NONE != TEH_kyc_config.mode)
    1797             :   {
    1798           0 :     if (GNUNET_YES ==
    1799           0 :         GNUNET_CONFIGURATION_have_value (TEH_cfg,
    1800             :                                          "exchange",
    1801             :                                          "KYC_WALLET_BALANCE_LIMIT"))
    1802             :     {
    1803           0 :       if ( (GNUNET_OK !=
    1804           0 :             TALER_config_get_amount (TEH_cfg,
    1805             :                                      "exchange",
    1806             :                                      "KYC_WALLET_BALANCE_LIMIT",
    1807           0 :                                      &TEH_kyc_config.wallet_balance_limit)) ||
    1808           0 :            (0 != strcasecmp (TEH_currency,
    1809             :                              TEH_kyc_config.wallet_balance_limit.currency)) )
    1810             :       {
    1811           0 :         GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1812             :                                    "exchange",
    1813             :                                    "KYC_WALLET_BALANCE_LIMIT",
    1814             :                                    "valid amount expected");
    1815           0 :         return GNUNET_SYSERR;
    1816             :       }
    1817             :     }
    1818             :     else
    1819             :     {
    1820           0 :       memset (&TEH_kyc_config.wallet_balance_limit,
    1821             :               0,
    1822             :               sizeof (TEH_kyc_config.wallet_balance_limit));
    1823             :     }
    1824             :   }
    1825             :   {
    1826             :     char *master_public_key_str;
    1827             : 
    1828           1 :     if (GNUNET_OK !=
    1829           1 :         GNUNET_CONFIGURATION_get_value_string (TEH_cfg,
    1830             :                                                "exchange",
    1831             :                                                "MASTER_PUBLIC_KEY",
    1832             :                                                &master_public_key_str))
    1833             :     {
    1834           0 :       GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
    1835             :                                  "exchange",
    1836             :                                  "MASTER_PUBLIC_KEY");
    1837           0 :       return GNUNET_SYSERR;
    1838             :     }
    1839           1 :     if (GNUNET_OK !=
    1840           1 :         GNUNET_CRYPTO_eddsa_public_key_from_string (master_public_key_str,
    1841             :                                                     strlen (
    1842             :                                                       master_public_key_str),
    1843             :                                                     &TEH_master_public_key.
    1844             :                                                     eddsa_pub))
    1845             :     {
    1846           0 :       GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
    1847             :                                  "exchange",
    1848             :                                  "MASTER_PUBLIC_KEY",
    1849             :                                  "invalid base32 encoding for a master public key");
    1850           0 :       GNUNET_free (master_public_key_str);
    1851           0 :       return GNUNET_SYSERR;
    1852             :     }
    1853           1 :     GNUNET_free (master_public_key_str);
    1854             :   }
    1855           1 :   if (TEH_KYC_NONE != TEH_kyc_config.mode)
    1856             :   {
    1857           0 :     if (GNUNET_OK !=
    1858           0 :         parse_kyc_settings ())
    1859           0 :       return GNUNET_SYSERR;
    1860             :   }
    1861           1 :   GNUNET_log (GNUNET_ERROR_TYPE_INFO,
    1862             :               "Launching exchange with public key `%s'...\n",
    1863             :               GNUNET_p2s (&TEH_master_public_key.eddsa_pub));
    1864             : 
    1865           1 :   if (NULL ==
    1866           1 :       (TEH_plugin = TALER_EXCHANGEDB_plugin_load (TEH_cfg)))
    1867             :   {
    1868           1 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    1869             :                 "Failed to initialize DB subsystem\n");
    1870           1 :     return GNUNET_SYSERR;
    1871             :   }
    1872           0 :   return GNUNET_OK;
    1873             : }
    1874             : 
    1875             : 
    1876             : /**
    1877             :  * Called when the main thread exits, writes out performance
    1878             :  * stats if requested.
    1879             :  */
    1880             : static void
    1881           0 : write_stats (void)
    1882             : {
    1883             :   struct GNUNET_DISK_FileHandle *fh;
    1884           0 :   pid_t pid = getpid ();
    1885             :   char *benchmark_dir;
    1886             :   char *s;
    1887             :   struct rusage usage;
    1888             : 
    1889           0 :   benchmark_dir = getenv ("GNUNET_BENCHMARK_DIR");
    1890           0 :   if (NULL == benchmark_dir)
    1891           0 :     return;
    1892           0 :   GNUNET_asprintf (&s,
    1893             :                    "%s/taler-exchange-%llu.txt",
    1894             :                    benchmark_dir,
    1895             :                    (unsigned long long) pid);
    1896           0 :   fh = GNUNET_DISK_file_open (s,
    1897             :                               (GNUNET_DISK_OPEN_WRITE
    1898             :                                | GNUNET_DISK_OPEN_TRUNCATE
    1899             :                                | GNUNET_DISK_OPEN_CREATE),
    1900             :                               (GNUNET_DISK_PERM_USER_READ
    1901             :                                | GNUNET_DISK_PERM_USER_WRITE));
    1902           0 :   GNUNET_free (s);
    1903           0 :   if (NULL == fh)
    1904           0 :     return; /* permission denied? */
    1905             : 
    1906             :   /* Collect stats, summed up for all threads */
    1907           0 :   GNUNET_assert (0 ==
    1908             :                  getrusage (RUSAGE_SELF,
    1909             :                             &usage));
    1910           0 :   GNUNET_asprintf (&s,
    1911             :                    "time_exchange sys %llu user %llu\n",
    1912           0 :                    (unsigned long long) (usage.ru_stime.tv_sec * 1000 * 1000
    1913           0 :                                          + usage.ru_stime.tv_usec),
    1914           0 :                    (unsigned long long) (usage.ru_utime.tv_sec * 1000 * 1000
    1915           0 :                                          + usage.ru_utime.tv_usec));
    1916           0 :   GNUNET_assert (GNUNET_SYSERR !=
    1917             :                  GNUNET_DISK_file_write_blocking (fh,
    1918             :                                                   s,
    1919             :                                                   strlen (s)));
    1920           0 :   GNUNET_free (s);
    1921           0 :   GNUNET_assert (GNUNET_OK ==
    1922             :                  GNUNET_DISK_file_close (fh));
    1923             : }
    1924             : 
    1925             : 
    1926             : /* Developer logic for supporting the `-f' option. */
    1927             : #if HAVE_DEVELOPER
    1928             : 
    1929             : /**
    1930             :  * Option `-f' (specifies an input file to give to the HTTP server).
    1931             :  */
    1932             : static char *input_filename;
    1933             : 
    1934             : 
    1935             : /**
    1936             :  * Run 'nc' or 'ncat' as a fake HTTP client using #input_filename
    1937             :  * as the input for the request.  If launching the client worked,
    1938             :  * run the #TEH_KS_loop() event loop as usual.
    1939             :  *
    1940             :  * @return child pid
    1941             :  */
    1942             : static pid_t
    1943           0 : run_fake_client (void)
    1944             : {
    1945             :   pid_t cld;
    1946             :   char ports[6];
    1947             :   int fd;
    1948             : 
    1949           0 :   if (0 == strcmp (input_filename,
    1950             :                    "-"))
    1951           0 :     fd = STDIN_FILENO;
    1952             :   else
    1953           0 :     fd = open (input_filename,
    1954             :                O_RDONLY);
    1955           0 :   if (-1 == fd)
    1956             :   {
    1957           0 :     fprintf (stderr,
    1958             :              "Failed to open `%s': %s\n",
    1959             :              input_filename,
    1960           0 :              strerror (errno));
    1961           0 :     return -1;
    1962             :   }
    1963             :   /* Fake HTTP client request with #input_filename as input.
    1964             :      We do this using the nc tool. */
    1965           0 :   GNUNET_snprintf (ports,
    1966             :                    sizeof (ports),
    1967             :                    "%u",
    1968             :                    serve_port);
    1969           0 :   if (0 == (cld = fork ()))
    1970             :   {
    1971           0 :     GNUNET_break (0 == close (0));
    1972           0 :     GNUNET_break (0 == dup2 (fd, 0));
    1973           0 :     GNUNET_break (0 == close (fd));
    1974           0 :     if ( (0 != execlp ("nc",
    1975             :                        "nc",
    1976             :                        "localhost",
    1977             :                        ports,
    1978             :                        "-w", "30",
    1979           0 :                        NULL)) &&
    1980           0 :          (0 != execlp ("ncat",
    1981             :                        "ncat",
    1982             :                        "localhost",
    1983             :                        ports,
    1984             :                        "-i", "30",
    1985             :                        NULL)) )
    1986             :     {
    1987           0 :       fprintf (stderr,
    1988             :                "Failed to run both `nc' and `ncat': %s\n",
    1989           0 :                strerror (errno));
    1990             :     }
    1991           0 :     _exit (1);
    1992             :   }
    1993             :   /* parent process */
    1994           0 :   if (0 != strcmp (input_filename,
    1995             :                    "-"))
    1996           0 :     GNUNET_break (0 == close (fd));
    1997           0 :   return cld;
    1998             : }
    1999             : 
    2000             : 
    2001             : /**
    2002             :  * Run the exchange to serve a single request only, without threads.
    2003             :  *
    2004             :  * @return #GNUNET_OK on success
    2005             :  */
    2006             : static void
    2007           0 : run_single_request (void)
    2008             : {
    2009             :   pid_t xfork;
    2010             : 
    2011           0 :   xfork = fork ();
    2012           0 :   if (-1 == xfork)
    2013             :   {
    2014           0 :     global_ret = EXIT_FAILURE;
    2015           0 :     GNUNET_SCHEDULER_shutdown ();
    2016           0 :     return;
    2017             :   }
    2018           0 :   if (0 == xfork)
    2019             :   {
    2020             :     pid_t cld;
    2021             : 
    2022           0 :     cld = run_fake_client ();
    2023           0 :     if (-1 == cld)
    2024           0 :       _exit (EXIT_FAILURE);
    2025           0 :     _exit (EXIT_SUCCESS);
    2026             :   }
    2027             : 
    2028             :   {
    2029             :     int status;
    2030             : 
    2031           0 :     if (xfork != waitpid (xfork,
    2032             :                           &status,
    2033             :                           0))
    2034           0 :       GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    2035             :                   "Waiting for `nc' child failed: %s\n",
    2036             :                   strerror (errno));
    2037             :   }
    2038             : }
    2039             : 
    2040             : 
    2041             : /* end of HAVE_DEVELOPER */
    2042             : #endif
    2043             : 
    2044             : 
    2045             : /**
    2046             :  * Signature of the callback used by MHD to notify the application
    2047             :  * about completed connections.  If we are running in test-mode with
    2048             :  * an input_filename, this function is used to terminate the HTTPD
    2049             :  * after the first request has been processed.
    2050             :  *
    2051             :  * @param cls client-defined closure, NULL
    2052             :  * @param connection connection handle (ignored)
    2053             :  * @param socket_context socket-specific pointer (ignored)
    2054             :  * @param toe reason for connection notification
    2055             :  */
    2056             : static void
    2057           0 : connection_done (void *cls,
    2058             :                  struct MHD_Connection *connection,
    2059             :                  void **socket_context,
    2060             :                  enum MHD_ConnectionNotificationCode toe)
    2061             : {
    2062             :   (void) cls;
    2063             :   (void) connection;
    2064             :   (void) socket_context;
    2065             : 
    2066           0 :   switch (toe)
    2067             :   {
    2068           0 :   case MHD_CONNECTION_NOTIFY_STARTED:
    2069           0 :     active_connections++;
    2070           0 :     break;
    2071           0 :   case MHD_CONNECTION_NOTIFY_CLOSED:
    2072           0 :     active_connections--;
    2073           0 :     if (TEH_suicide &&
    2074           0 :         (0 == active_connections) )
    2075           0 :       GNUNET_SCHEDULER_shutdown ();
    2076           0 :     break;
    2077             :   }
    2078             : #if HAVE_DEVELOPER
    2079             :   /* We only act if the connection is closed. */
    2080           0 :   if (MHD_CONNECTION_NOTIFY_CLOSED != toe)
    2081           0 :     return;
    2082           0 :   if (NULL != input_filename)
    2083           0 :     GNUNET_SCHEDULER_shutdown ();
    2084             : #endif
    2085             : }
    2086             : 
    2087             : 
    2088             : /**
    2089             :  * Function run on shutdown.
    2090             :  *
    2091             :  * @param cls NULL
    2092             :  */
    2093             : static void
    2094           0 : do_shutdown (void *cls)
    2095             : {
    2096             :   struct MHD_Daemon *mhd;
    2097             :   (void) cls;
    2098             : 
    2099           0 :   mhd = TALER_MHD_daemon_stop ();
    2100           0 :   TEH_resume_keys_requests (true);
    2101           0 :   TEH_reserves_get_cleanup ();
    2102           0 :   TEH_purses_get_cleanup ();
    2103           0 :   TEH_kyc_check_cleanup ();
    2104           0 :   TEH_kyc_proof_cleanup ();
    2105           0 :   TALER_KYCLOGIC_kyc_done ();
    2106           0 :   if (NULL != mhd)
    2107             :   {
    2108           0 :     MHD_stop_daemon (mhd);
    2109           0 :     mhd = NULL;
    2110             :   }
    2111           0 :   TEH_wire_done ();
    2112           0 :   TEH_extensions_done ();
    2113           0 :   TEH_keys_finished ();
    2114           0 :   if (NULL != TEH_plugin)
    2115             :   {
    2116           0 :     TALER_EXCHANGEDB_plugin_unload (TEH_plugin);
    2117           0 :     TEH_plugin = NULL;
    2118             :   }
    2119           0 :   if (NULL != TEH_curl_ctx)
    2120             :   {
    2121           0 :     GNUNET_CURL_fini (TEH_curl_ctx);
    2122           0 :     TEH_curl_ctx = NULL;
    2123             :   }
    2124           0 :   if (NULL != exchange_curl_rc)
    2125             :   {
    2126           0 :     GNUNET_CURL_gnunet_rc_destroy (exchange_curl_rc);
    2127           0 :     exchange_curl_rc = NULL;
    2128             :   }
    2129           0 :   TALER_TEMPLATING_done ();
    2130           0 : }
    2131             : 
    2132             : 
    2133             : /**
    2134             :  * Main function that will be run by the scheduler.
    2135             :  *
    2136             :  * @param cls closure
    2137             :  * @param args remaining command-line arguments
    2138             :  * @param cfgfile name of the configuration file used (for saving, can be
    2139             :  *        NULL!)
    2140             :  * @param config configuration
    2141             :  */
    2142             : static void
    2143           1 : run (void *cls,
    2144             :      char *const *args,
    2145             :      const char *cfgfile,
    2146             :      const struct GNUNET_CONFIGURATION_Handle *config)
    2147             : {
    2148             :   enum TALER_MHD_GlobalOptions go;
    2149             :   int fh;
    2150             : 
    2151             :   (void) cls;
    2152             :   (void) args;
    2153             :   (void ) cfgfile;
    2154           1 :   go = TALER_MHD_GO_NONE;
    2155           1 :   if (connection_close)
    2156           0 :     go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
    2157           1 :   TALER_MHD_setup (go);
    2158           1 :   TEH_cfg = config;
    2159             : 
    2160           1 :   if (GNUNET_OK !=
    2161           1 :       exchange_serve_process_config ())
    2162             :   {
    2163           1 :     global_ret = EXIT_NOTCONFIGURED;
    2164           1 :     GNUNET_SCHEDULER_shutdown ();
    2165           1 :     return;
    2166             :   }
    2167           0 :   if (GNUNET_OK !=
    2168           0 :       TALER_TEMPLATING_init ("exchange"))
    2169             :   {
    2170           0 :     global_ret = EXIT_FAILURE;
    2171           0 :     GNUNET_SCHEDULER_shutdown ();
    2172           0 :     return;
    2173             :   }
    2174           0 :   if (GNUNET_SYSERR ==
    2175           0 :       TEH_plugin->preflight (TEH_plugin->cls))
    2176             :   {
    2177           0 :     global_ret = EXIT_FAILURE;
    2178           0 :     GNUNET_SCHEDULER_shutdown ();
    2179           0 :     return;
    2180             :   }
    2181           0 :   if (GNUNET_OK !=
    2182           0 :       TEH_extensions_init ())
    2183             :   {
    2184           0 :     global_ret = EXIT_FAILURE;
    2185           0 :     GNUNET_SCHEDULER_shutdown ();
    2186           0 :     return;
    2187             :   }
    2188           0 :   if (GNUNET_OK !=
    2189           0 :       TEH_keys_init ())
    2190             :   {
    2191           0 :     global_ret = EXIT_FAILURE;
    2192           0 :     GNUNET_SCHEDULER_shutdown ();
    2193           0 :     return;
    2194             :   }
    2195           0 :   if (GNUNET_OK !=
    2196           0 :       TEH_wire_init ())
    2197             :   {
    2198           0 :     global_ret = EXIT_FAILURE;
    2199           0 :     GNUNET_SCHEDULER_shutdown ();
    2200           0 :     return;
    2201             :   }
    2202             : 
    2203           0 :   TEH_load_terms (TEH_cfg);
    2204             :   TEH_curl_ctx
    2205           0 :     = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
    2206             :                         &exchange_curl_rc);
    2207           0 :   if (NULL == TEH_curl_ctx)
    2208             :   {
    2209           0 :     GNUNET_break (0);
    2210           0 :     global_ret = EXIT_FAILURE;
    2211           0 :     GNUNET_SCHEDULER_shutdown ();
    2212           0 :     return;
    2213             :   }
    2214           0 :   exchange_curl_rc = GNUNET_CURL_gnunet_rc_create (TEH_curl_ctx);
    2215           0 :   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    2216             :                                  NULL);
    2217           0 :   fh = TALER_MHD_bind (TEH_cfg,
    2218             :                        "exchange",
    2219             :                        &serve_port);
    2220           0 :   if ( (0 == serve_port) &&
    2221             :        (-1 == fh) )
    2222             :   {
    2223           0 :     GNUNET_SCHEDULER_shutdown ();
    2224           0 :     return;
    2225             :   }
    2226           0 :   mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME
    2227             :                           | MHD_USE_PIPE_FOR_SHUTDOWN
    2228             :                           | MHD_USE_DEBUG | MHD_USE_DUAL_STACK
    2229             :                           | MHD_USE_TCP_FASTOPEN,
    2230             :                           (-1 == fh) ? serve_port : 0,
    2231             :                           NULL, NULL,
    2232             :                           &handle_mhd_request, NULL,
    2233             :                           MHD_OPTION_LISTEN_BACKLOG_SIZE,
    2234             :                           (unsigned int) 1024,
    2235             :                           MHD_OPTION_LISTEN_SOCKET,
    2236             :                           fh,
    2237             :                           MHD_OPTION_EXTERNAL_LOGGER,
    2238             :                           &TALER_MHD_handle_logs,
    2239             :                           NULL,
    2240             :                           MHD_OPTION_NOTIFY_COMPLETED,
    2241             :                           &handle_mhd_completion_callback,
    2242             :                           NULL,
    2243             :                           MHD_OPTION_NOTIFY_CONNECTION,
    2244             :                           &connection_done,
    2245             :                           NULL,
    2246             :                           MHD_OPTION_CONNECTION_TIMEOUT,
    2247             :                           connection_timeout,
    2248           0 :                           (0 == allow_address_reuse)
    2249             :                           ? MHD_OPTION_END
    2250             :                           : MHD_OPTION_LISTENING_ADDRESS_REUSE,
    2251             :                           (unsigned int) allow_address_reuse,
    2252             :                           MHD_OPTION_END);
    2253           0 :   if (NULL == mhd)
    2254             :   {
    2255           0 :     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
    2256             :                 "Failed to launch HTTP service. Is the port in use?\n");
    2257           0 :     GNUNET_SCHEDULER_shutdown ();
    2258           0 :     return;
    2259             :   }
    2260           0 :   global_ret = EXIT_SUCCESS;
    2261           0 :   TALER_MHD_daemon_start (mhd);
    2262           0 :   atexit (&write_stats);
    2263             : 
    2264             : #if HAVE_DEVELOPER
    2265           0 :   if (NULL != input_filename)
    2266           0 :     run_single_request ();
    2267             : #endif
    2268             : }
    2269             : 
    2270             : 
    2271             : /**
    2272             :  * The main function of the taler-exchange-httpd server ("the exchange").
    2273             :  *
    2274             :  * @param argc number of arguments from the command line
    2275             :  * @param argv command line arguments
    2276             :  * @return 0 ok, 1 on error
    2277             :  */
    2278             : int
    2279           1 : main (int argc,
    2280             :       char *const *argv)
    2281             : {
    2282           1 :   const struct GNUNET_GETOPT_CommandLineOption options[] = {
    2283           1 :     GNUNET_GETOPT_option_flag ('a',
    2284             :                                "allow-timetravel",
    2285             :                                "allow clients to request /keys for arbitrary timestamps (for testing and development only)",
    2286             :                                &TEH_allow_keys_timetravel),
    2287           1 :     GNUNET_GETOPT_option_flag ('C',
    2288             :                                "connection-close",
    2289             :                                "force HTTP connections to be closed after each request",
    2290             :                                &connection_close),
    2291           1 :     GNUNET_GETOPT_option_flag ('I',
    2292             :                                "check-invariants",
    2293             :                                "enable expensive invariant checks",
    2294             :                                &TEH_check_invariants_flag),
    2295           1 :     GNUNET_GETOPT_option_flag ('r',
    2296             :                                "allow-reuse-address",
    2297             :                                "allow multiple HTTPDs to listen to the same port",
    2298             :                                &allow_address_reuse),
    2299           1 :     GNUNET_GETOPT_option_uint ('t',
    2300             :                                "timeout",
    2301             :                                "SECONDS",
    2302             :                                "after how long do connections timeout by default (in seconds)",
    2303             :                                &connection_timeout),
    2304           1 :     GNUNET_GETOPT_option_timetravel ('T',
    2305             :                                      "timetravel"),
    2306             : #if HAVE_DEVELOPER
    2307           1 :     GNUNET_GETOPT_option_filename ('f',
    2308             :                                    "file-input",
    2309             :                                    "FILENAME",
    2310             :                                    "run in test-mode using FILENAME as the HTTP request to process, use '-' to read from stdin",
    2311             :                                    &input_filename),
    2312             : #endif
    2313           1 :     GNUNET_GETOPT_option_help (
    2314             :       "HTTP server providing a RESTful API to access a Taler exchange"),
    2315             :     GNUNET_GETOPT_OPTION_END
    2316             :   };
    2317             :   enum GNUNET_GenericReturnValue ret;
    2318             : 
    2319           1 :   TALER_OS_init ();
    2320           1 :   ret = GNUNET_PROGRAM_run (argc, argv,
    2321             :                             "taler-exchange-httpd",
    2322             :                             "Taler exchange HTTP service",
    2323             :                             options,
    2324             :                             &run, NULL);
    2325           1 :   if (GNUNET_SYSERR == ret)
    2326           0 :     return EXIT_INVALIDARGUMENT;
    2327           1 :   if (GNUNET_NO == ret)
    2328           0 :     return EXIT_SUCCESS;
    2329           1 :   return global_ret;
    2330             : }
    2331             : 
    2332             : 
    2333             : /* end of taler-exchange-httpd.c */

Generated by: LCOV version 1.14