/*
  This file is part of Challenger
  (C) 2023, 2024, 2025 Taler Systems SA

  Challenger is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero General Public License as published by the Free Software
  Foundation; either version 3, or (at your option) any later version.

  Challenger is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

  You should have received a copy of the GNU General Public License along with
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file challenger/challenger-httpd.c
 * @brief OAuth 2.0 server challenging users to demonstrate ability to receive messages
 * @author Christian Grothoff
 */
#include "platform.h"
#include <microhttpd.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "challenger_util.h"
#include "challenger-httpd.h"
#include "challenger-httpd_agpl.h"
#include "challenger-httpd_authorize.h"
#include "challenger-httpd_challenge.h"
#include "challenger-httpd_config.h"
#include "challenger-httpd_info.h"
#include "challenger-httpd_mhd.h"
#include "challenger-httpd_setup.h"
#include "challenger-httpd_solve.h"
#include "challenger-httpd_token.h"
#include "challenger-httpd_spa.h"
#include "challenger_database_lib.h"


/**
 * Should a "Connection: close" header be added to each HTTP response?
 */
static int CH_challenger_connection_close;

/**
 * Our context for making HTTP requests.
 */
struct GNUNET_CURL_Context *CH_ctx;

/**
 * Reschedule context for #CH_ctx.
 */
static struct GNUNET_CURL_RescheduleContext *rc;

/**
 * Global return code
 */
static int global_ret;

/**
 * True if we started any HTTP daemon.
 */
static bool have_daemons;

/**
 * Connection handle to the our database
 */
struct CHALLENGER_DatabasePlugin *CH_db;

/**
 * (external) base URL of this service.
 */
char *CH_base_url;

/**
 * Mustach template for the letter to send.
 * WARNING: not 0-terminated! Allocated via mmap(), free with munmap()!
 */
void *CH_message_template;

/**
 * Number of bytes in #CH_message_template
 */
size_t CH_message_template_len;

/**
 * How long is an individual validation request valid?
 */
struct GNUNET_TIME_Relative CH_validation_duration;

/**
 * How long validated data considered to be valid?
 */
struct GNUNET_TIME_Relative CH_validation_expiration;

/**
 * How often do we retransmit the challenge.
 */
struct GNUNET_TIME_Relative CH_pin_retransmission_frequency;

/**
 * JSON object with key-object pairs mapping address keys (from the
 * form) to an object with a field "regex" containing a regular
 * expressions expressing restrictions on values for the address and a
 * field "hint" (and possibly "hint_i18n") containing a human-readable
 * message explaining the restriction. Missing map entries indicate
 * that the input is unrestricted.
 */
json_t *CH_restrictions;

/**
 * Type of addresses this challenger validates.
 */
char *CH_address_type;

/**
 * Hint to show to users about the address to enter.
 */
char *CH_address_hint;

/**
 * Helper command to run for transmission of
 * challenge values.
 */
char *CH_auth_command;


/**
 * Function called first by MHD with the full URL.
 *
 * @param cls NULL
 * @param full_url the full URL
 * @param con MHD connection object
 * @return our handler context
 */
static void *
full_url_track_callback (void *cls,
                         const char *full_url,
                         struct MHD_Connection *con)
{
  struct CH_HandlerContext *hc;

  hc = GNUNET_new (struct CH_HandlerContext);
  GNUNET_async_scope_fresh (&hc->async_scope_id);
  GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  hc->full_url = GNUNET_strdup (full_url);
  return hc;
}


/**
 * A client has requested the given url using the given method
 * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT,
 * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc).  The callback
 * must call MHD callbacks to provide content to give back to the
 * client and return an HTTP status code (i.e. #MHD_HTTP_OK,
 * #MHD_HTTP_NOT_FOUND, etc.).
 *
 * @param cls argument given together with the function
 *        pointer when the handler was registered with MHD
 * @param connection connection handle
 * @param url the requested url
 * @param method the HTTP method used (#MHD_HTTP_METHOD_GET,
 *        #MHD_HTTP_METHOD_PUT, etc.)
 * @param version the HTTP version string (i.e.
 *        #MHD_HTTP_VERSION_1_1)
 * @param upload_data the data being uploaded (excluding HEADERS,
 *        for a POST that fits into memory and that is encoded
 *        with a supported encoding, the POST data will NOT be
 *        given in upload_data and is instead available as
 *        part of #MHD_get_connection_values; very large POST
 *        data *will* be made available incrementally in
 *        @a upload_data)
 * @param upload_data_size set initially to the size of the
 *        @a upload_data provided; the method must update this
 *        value to the number of bytes NOT processed;
 * @param con_cls pointer that the callback can set to some
 *        address and that will be preserved by MHD for future
 *        calls for this request; since the access handler may
 *        be called many times (i.e., for a PUT/POST operation
 *        with plenty of upload data) this allows the application
 *        to easily associate some request-specific state.
 *        If necessary, this state can be cleaned up in the
 *        global #MHD_RequestCompletedCallback (which
 *        can be set with the #MHD_OPTION_NOTIFY_COMPLETED).
 *        Initially, `*con_cls` will be NULL.
 * @return #MHD_YES if the connection was handled successfully,
 *         #MHD_NO if the socket must be closed due to a serious
 *         error while handling the request
 */
static MHD_RESULT
url_handler (void *cls,
             struct MHD_Connection *connection,
             const char *url,
             const char *method,
             const char *version,
             const char *upload_data,
             size_t *upload_data_size,
             void **con_cls)
{
  static struct CH_RequestHandler handlers[] = {
    /* Landing page, tell humans to go away. */
    {
      .url = "/",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_spa_redirect
    },
    {
      .url = "/webui/",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_handler_spa,
    },
    {
      .url = "/agpl",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_handler_agpl
    },
    {
      .url = "/config",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_handler_config
    },
    {
      .url = "/setup/",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &CH_handler_setup
    },
    {
      .url = "/authorize/",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_handler_authorize
    },
    {
      .url = "/authorize/",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &CH_handler_authorize
    },
    {
      .url = "/challenge/",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &CH_handler_challenge
    },
    {
      .url = "/solve/",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &CH_handler_solve
    },
    {
      .url = "/token",
      .method = MHD_HTTP_METHOD_POST,
      .handler = &CH_handler_token
    },
    {
      .url = "/info",
      .method = MHD_HTTP_METHOD_GET,
      .handler = &CH_handler_info
    },
    {
      NULL, NULL, NULL
    }
  };
  struct CH_HandlerContext *hc = *con_cls;

  (void) cls;
  (void) version;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Handling %s request for `%s'\n",
              method,
              url);
  if (NULL == hc->connection)
  {
    const char *correlation_id;
    bool found = false;

    hc->connection = connection;
    /* We only read the correlation ID on the first callback for every client */
    correlation_id = MHD_lookup_connection_value (connection,
                                                  MHD_HEADER_KIND,
                                                  "Challenger-Correlation-Id");
    if ( (NULL != correlation_id) &&
         (GNUNET_YES != GNUNET_CURL_is_valid_scope_id (correlation_id)) )
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Illegal incoming correlation ID\n");
      correlation_id = NULL;
    }
    if (NULL != correlation_id)
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Handling request for (%s) URL '%s', correlation_id=%s\n",
                  method,
                  url,
                  correlation_id);
    else
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Handling request (%s) for URL '%s'\n",
                  method,
                  url);

    for (unsigned int i = 0; NULL != handlers[i].url; i++)
    {
      struct CH_RequestHandler *rh = &handlers[i];

      if ( (0 == strcmp (url,
                         rh->url)) ||
           ( (0 == strncmp (url,
                            rh->url,
                            strlen (rh->url))) &&
             (1 < strlen (rh->url)) &&
             ('/' == rh->url[strlen (rh->url) - 1]) ) )
      {
        found = true;
        if (0 == strcasecmp (method,
                             MHD_HTTP_METHOD_OPTIONS))
        {
          return TALER_MHD_reply_cors_preflight (connection);
        }
        if (0 == strcasecmp (method,
                             rh->method))
        {
          hc->rh = rh;
          break;
        }
      }
    }
    if (NULL == hc->rh)
    {
      if (found)
      {
        GNUNET_break_op (0);
        /* FIXME-#9424: return which methods are allowed ... */
        return TALER_MHD_reply_static (hc->connection,
                                       MHD_HTTP_METHOD_NOT_ALLOWED,
                                       "text/plain",
                                       NULL,
                                       0);
      }
      GNUNET_break_op (0);
      return TALER_MHD_reply_static (hc->connection,
                                     MHD_HTTP_NOT_FOUND,
                                     "text/plain",
                                     NULL,
                                     0);
    }
  }
  else
  {
    GNUNET_SCHEDULER_begin_async_scope (&hc->async_scope_id);
  }
  GNUNET_assert (NULL != hc->rh);
  hc->path = &url[strlen (hc->rh->url)];
  return hc->rh->handler (hc,
                          upload_data,
                          upload_data_size);
}


/**
 * Shutdown task. Invoked when the application is being terminated.
 *
 * @param cls NULL
 */
static void
do_shutdown (void *cls)
{
  (void) cls;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Stopping challenger-httpd\n");
  TALER_MHD_daemons_halt ();
  CH_wakeup_challenge_on_shutdown ();
  if ( (NULL != CH_message_template) &&
       (MAP_FAILED != CH_message_template) )
  {
    if (0 !=
        munmap (CH_message_template,
                CH_message_template_len))
      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                           "munmap");
    CH_message_template = NULL;
    CH_message_template_len = 0;
  }
  TALER_MHD_daemons_destroy ();
  if (NULL != CH_ctx)
  {
    GNUNET_CURL_fini (CH_ctx);
    CH_ctx = NULL;
  }
  if (NULL != rc)
  {
    GNUNET_CURL_gnunet_rc_destroy (rc);
    rc = NULL;
  }
  if (NULL != CH_db)
  {
    CHALLENGER_DB_plugin_unload (CH_db);
    CH_db = NULL;
  }
}


/**
 * Function called whenever MHD is done with a request.  If the
 * request was a POST, we may have stored a `struct Buffer *` in the
 * @a con_cls that might still need to be cleaned up.  Call the
 * respective function to free the memory.
 *
 * @param cls client-defined closure
 * @param connection connection handle
 * @param con_cls value as set by the last call to
 *        the #MHD_AccessHandlerCallback
 * @param toe reason for request termination
 * @see #MHD_OPTION_NOTIFY_COMPLETED
 * @ingroup request
 */
static void
handle_mhd_completion_callback (void *cls,
                                struct MHD_Connection *connection,
                                void **con_cls,
                                enum MHD_RequestTerminationCode toe)
{
  struct CH_HandlerContext *hc = *con_cls;
  const union MHD_ConnectionInfo *ci;

  (void) cls;
  if (NULL == hc)
    return;
  GNUNET_assert (hc->connection == connection);
  GNUNET_break (GNUNET_OK ==
                CH_db->preflight (CH_db->cls));
  ci = MHD_get_connection_info (connection,
                                MHD_CONNECTION_INFO_HTTP_STATUS);
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Finished handling request with status %d (HTTP status %u)\n",
              (int) toe,
              (NULL != ci) ? ci->http_status : 0);
  if (NULL != hc->cc)
    hc->cc (hc->ctx);
  GNUNET_free (hc->full_url);
  GNUNET_free (hc);
  *con_cls = NULL;
}


/**
 * Kick MHD to run now, to be called after MHD_resume_connection().
 * Basically, we need to explicitly resume MHD's event loop whenever
 * we made progress serving a request.  This function re-schedules
 * the task processing MHD's activities to run immediately.
 *
 * FIXME: replace by direct call...
 */
void
CH_trigger_daemon ()
{
  TALER_MHD_daemon_trigger ();
}


/**
 * Kick GNUnet Curl scheduler to begin curl interactions.
 */
void
CH_trigger_curl ()
{
  GNUNET_CURL_gnunet_scheduler_reschedule (&rc);
}


/**
 * Callback invoked on every listen socket to start the
 * respective MHD HTTP daemon.
 *
 * @param cls unused
 * @param lsock the listen socket
 */
static void
start_daemon (void *cls,
              int lsock)
{
  struct MHD_Daemon *mhd;

  (void) cls;
  GNUNET_assert (-1 != lsock);
  mhd = MHD_start_daemon (MHD_USE_SUSPEND_RESUME | MHD_USE_DUAL_STACK,
                          0 /* port */,
                          NULL, NULL,
                          &url_handler, NULL,
                          MHD_OPTION_LISTEN_SOCKET, lsock,
                          MHD_OPTION_URI_LOG_CALLBACK,
                          &full_url_track_callback, NULL,
                          MHD_OPTION_NOTIFY_COMPLETED,
                          &handle_mhd_completion_callback, NULL,
                          MHD_OPTION_CONNECTION_TIMEOUT,
                          (unsigned int) 10 /* 10s */,
                          MHD_OPTION_END);
  if (NULL == mhd)
  {
    global_ret = EXIT_NO_RESTART;
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to launch HTTP daemon.\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  have_daemons = true;
  TALER_MHD_daemon_start (mhd);
}


/**
 * Main function that will be run by the scheduler.
 *
 * @param cls closure
 * @param args remaining command-line arguments
 * @param cfgfile name of the configuration file used (for saving, can be
 *        NULL!)
 * @param config configuration
 */
static void
run (void *cls,
     char *const *args,
     const char *cfgfile,
     const struct GNUNET_CONFIGURATION_Handle *config)
{
  enum TALER_MHD_GlobalOptions go;

  (void) cls;
  (void) args;
  (void) cfgfile;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Starting challenger-httpd\n");
  go = TALER_MHD_GO_NONE;
  if (CH_challenger_connection_close)
    go |= TALER_MHD_GO_FORCE_CONNECTION_CLOSE;
  TALER_MHD_setup (go);

  if (GNUNET_OK !=
      CH_spa_init ())
  {
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }

  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (config,
                                           "CHALLENGER",
                                           "VALIDATION_DURATION",
                                           &CH_validation_duration))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "VALIDATION_DURATION");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_time (config,
                                           "CHALLENGER",
                                           "VALIDATION_EXPIRATION",
                                           &CH_validation_expiration))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "VALIDATION_EXPIRATION");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "CHALLENGER",
                                             "AUTH_COMMAND",
                                             &CH_auth_command))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "AUTH_COMMAND");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "CHALLENGER",
                                             "ADDRESS_TYPE",
                                             &CH_address_type))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "ADDRESS_TYPE");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "CHALLENGER",
                                             "ADDRESS_HINT",
                                             &CH_address_hint))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
                               "CHALLENGER",
                               "ADDRESS_HINT");
    CH_address_hint = GNUNET_strdup ("");
  }
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (config,
                                             "CHALLENGER",
                                             "BASE_URL",
                                             &CH_base_url))
  {
    GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "BASE_URL");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if ( (! TALER_url_valid_charset (CH_base_url)) ||
       (! TALER_is_web_url (CH_base_url) ) ||
       (0 == strlen (CH_base_url)) ||
       ('/' != CH_base_url[strlen (CH_base_url) - 1]) )
  {
    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                               "CHALLENGER",
                               "BASE_URL",
                               "invalid URL");
    global_ret = EXIT_NOTCONFIGURED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  {
    char *tmpl_file;
    int fd;
    struct stat s;

    if (GNUNET_OK !=
        GNUNET_CONFIGURATION_get_value_filename (config,
                                                 "CHALLENGER",
                                                 "MESSAGE_TEMPLATE_FILE",
                                                 &tmpl_file))
    {
      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_WARNING,
                                 "CHALLENGER",
                                 "MESSAGE_TEMPLATE_FILE");
    }
    else
    {
      fd = open (tmpl_file,
                 O_RDONLY);
      GNUNET_free (tmpl_file);
      if (-1 == fd)
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "CHALLENGER",
                                   "MESSAGE_TEMPLATE_FILE",
                                   strerror (errno));
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
      if (0 != fstat (fd,
                      &s))
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "CHALLENGER",
                                   "MESSAGE_TEMPLATE_FILE",
                                   strerror (errno));
        GNUNET_free (tmpl_file);
        GNUNET_break (0 == close (fd));
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
      CH_message_template_len = (size_t) s.st_size;
      CH_message_template = mmap (NULL,
                                  CH_message_template_len,
                                  PROT_READ,
                                  MAP_SHARED,
                                  fd,
                                  0);
      GNUNET_break (0 == close (fd));
      if (MAP_FAILED == CH_message_template)
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "CHALLENGER",
                                   "MESSAGE_TEMPLATE_FILE",
                                   strerror (errno));
        GNUNET_free (tmpl_file);
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
    }
  }

  {
    char *restrictions;

    if (GNUNET_OK ==
        GNUNET_CONFIGURATION_get_value_string (config,
                                               "CHALLENGER",
                                               "ADDRESS_RESTRICTIONS",
                                               &restrictions))
    {
      json_error_t err;

      CH_restrictions = json_loads (restrictions,
                                    JSON_REJECT_DUPLICATES,
                                    &err);
      GNUNET_free (restrictions);
      if (NULL == CH_restrictions)
      {
        GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                   "CHALLENGER",
                                   "ADDRESS_RESTRICTIONS",
                                   err.text);
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
    }
    else
    {
      CH_restrictions = json_object ();
      GNUNET_assert (NULL != CH_restrictions);
    }
  }

  global_ret = EXIT_NOTCONFIGURED;
  GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
                                 NULL);
  /* setup HTTP client event loop */
  CH_ctx = GNUNET_CURL_init (&GNUNET_CURL_gnunet_scheduler_reschedule,
                             &rc);
  rc = GNUNET_CURL_gnunet_rc_create (CH_ctx);
  if (NULL ==
      (CH_db = CHALLENGER_DB_plugin_load (config,
                                          false)))
  {
    global_ret = EXIT_NOTINSTALLED;
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  if (GNUNET_OK !=
      CH_db->preflight (CH_db->cls))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Database not setup. Did you run challenger-dbinit?\n");
    GNUNET_SCHEDULER_shutdown ();
    return;
  }
  {
    enum GNUNET_GenericReturnValue ret;

    ret = TALER_MHD_listen_bind (config,
                                 "challenger",
                                 &start_daemon,
                                 NULL);
    switch (ret)
    {
    case GNUNET_SYSERR:
      global_ret = EXIT_NOTCONFIGURED;
      GNUNET_SCHEDULER_shutdown ();
      return;
    case GNUNET_NO:
      if (! have_daemons)
      {
        global_ret = EXIT_NOTCONFIGURED;
        GNUNET_SCHEDULER_shutdown ();
        return;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Could not open all configured listen sockets\n");
      break;
    case GNUNET_OK:
      break;
    }
  }
  global_ret = EXIT_SUCCESS;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Challenger-httpd ready\n");
}


/**
 * The main function of the serve tool
 *
 * @param argc number of arguments from the command line
 * @param argv command line arguments
 * @return 0 ok, 1 on error
 */
int
main (int argc,
      char *const *argv)
{
  struct GNUNET_GETOPT_CommandLineOption options[] = {
    GNUNET_GETOPT_option_flag ('C',
                               "connection-close",
                               "force HTTP connections to be closed after each request",
                               &CH_challenger_connection_close),
    GNUNET_GETOPT_OPTION_END
  };
  enum GNUNET_GenericReturnValue ret;

  ret = GNUNET_PROGRAM_run (CHALLENGER_project_data (),
                            argc, argv,
                            "challenger-httpd",
                            "challenger REST and OAuth 2.0 API",
                            options,
                            &run, NULL);
  if (GNUNET_NO == ret)
    return EXIT_SUCCESS;
  if (GNUNET_SYSERR == ret)
    return EXIT_INVALIDARGUMENT;
  return global_ret;
}
