diff options
| author | Christian Grothoff <christian@grothoff.org> | 2018-10-06 17:29:03 +0200 | 
|---|---|---|
| committer | Christian Grothoff <christian@grothoff.org> | 2018-10-06 17:29:03 +0200 | 
| commit | eb1b6fbc97895356f2794927f81463d43c23c76a (patch) | |
| tree | d28933c6706a66ba76895334fe65cdc67fe6cfe6 /src/auditor | |
| parent | a56e2e34bce77d41a6d3a2cbbcb89119b98ef750 (diff) | |
add skeleton for auditor httpd
Diffstat (limited to 'src/auditor')
| -rw-r--r-- | src/auditor/.gitignore | 1 | ||||
| -rw-r--r-- | src/auditor/Makefile.am | 18 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd.c | 828 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd.h | 99 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_mhd.c | 158 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_mhd.h | 111 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_parsing.c | 282 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_parsing.h | 139 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_responses.c | 479 | ||||
| -rw-r--r-- | src/auditor/taler-auditor-httpd_responses.h | 245 | 
10 files changed, 2360 insertions, 0 deletions
| diff --git a/src/auditor/.gitignore b/src/auditor/.gitignore new file mode 100644 index 00000000..d6cf77f8 --- /dev/null +++ b/src/auditor/.gitignore @@ -0,0 +1 @@ +taler-auditor-httpd diff --git a/src/auditor/Makefile.am b/src/auditor/Makefile.am index 105f91c2..23776f43 100644 --- a/src/auditor/Makefile.am +++ b/src/auditor/Makefile.am @@ -13,6 +13,7 @@ pkgcfg_DATA = \  bin_PROGRAMS = \    taler-auditor \ +  taler-auditor-httpd \    taler-wire-auditor \    taler-auditor-sign \    taler-auditor-dbinit @@ -45,6 +46,23 @@ taler_auditor_LDADD = \    -lgnunetjson \    -lgnunetutil +taler_auditor_httpd_SOURCES = \ +  taler-auditor-httpd.c taler-auditor-httpd.h \ +  taler-auditor-httpd_mhd.c taler-auditor-httpd_mhd.h \ +  taler-auditor-httpd_parsing.c taler-auditor-httpd_parsing.h \ +  taler-auditor-httpd_responses.c taler-auditor-httpd_responses.h +taler_auditor_httpd_LDADD = \ +  $(LIBGCRYPT_LIBS) \ +  $(top_builddir)/src/util/libtalerutil.la \ +  $(top_builddir)/src/json/libtalerjson.la \ +  $(top_builddir)/src/wire/libtalerwire.la \ +  $(top_builddir)/src/auditordb/libtalerauditordb.la \ +  -lmicrohttpd \ +  -ljansson \ +  -lgnunetjson \ +  -lgnunetutil \ +  -lz +  taler_wire_auditor_SOURCES = \    taler-wire-auditor.c  taler_wire_auditor_LDADD = \ diff --git a/src/auditor/taler-auditor-httpd.c b/src/auditor/taler-auditor-httpd.c new file mode 100644 index 00000000..a023fd6c --- /dev/null +++ b/src/auditor/taler-auditor-httpd.c @@ -0,0 +1,828 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016, 2018 Inria and GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-auditor-httpd.c + * @brief Serve the HTTP interface of the auditor + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include <sys/resource.h> +#include "taler_auditordb_lib.h" +#include "taler-auditor-httpd_parsing.h" +#include "taler-auditor-httpd_mhd.h" +#include "taler-auditor-httpd.h" + + +/** + * Backlog for listen operation on unix domain sockets. + */ +#define UNIX_BACKLOG 500 + +/** + * Should we return "Connection: close" in each response? + */ +int TAH_auditor_connection_close; + +/** + * The auditor's configuration (global) + */ +struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +struct TALER_AUDITORDB_Plugin *TAH_plugin; + +/** + * Default timeout in seconds for HTTP requests. + */ +static unsigned int connection_timeout = 30; + +/** + * The HTTP Daemon. + */ +static struct MHD_Daemon *mhd; + +/** + * Port to run the daemon on. + */ +static uint16_t serve_port; + +/** + * Path for the unix domain-socket to run the daemon on. + */ +static char *serve_unixpath; + +/** + * File mode for unix-domain socket. + */ +static mode_t unixpath_mode; + + +/** + * Pipe used for signaling reloading of our key state. + */ +static int reload_pipe[2]; + + +/** + * Handle a signal, writing relevant signal numbers to the pipe. + * + * @param signal_number the signal number + */ +static void +handle_signal (int signal_number) +{ +  ssize_t res; +  char c = signal_number; + +  res = write (reload_pipe[1], +               &c, +               1); +  if ( (res < 0) && +       (EINTR != errno) ) +  { +    GNUNET_break (0); +    return; +  } +  if (0 == res) +  { +    GNUNET_break (0); +    return; +  } +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigint () +{ +  handle_signal (SIGINT); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigterm () +{ +  handle_signal (SIGTERM); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sighup () +{ +  handle_signal (SIGHUP); +} + + +/** + * Call #handle_signal() to pass the received signal via + * the control pipe. + */ +static void +handle_sigchld () +{ +  handle_signal (SIGCHLD); +} + + +/** + * Read signals from a pipe in a loop, and reload keys from disk if + * SIGUSR1 is received, terminate if SIGTERM/SIGINT is received, and + * restart if SIGHUP is received. + * + * @return #GNUNET_SYSERR on errors, + *         #GNUNET_OK to terminate normally + *         #GNUNET_NO to restart an update version of the binary + */ +static int +signal_loop (void) +{ +  struct GNUNET_SIGNAL_Context *sigterm; +  struct GNUNET_SIGNAL_Context *sigint; +  struct GNUNET_SIGNAL_Context *sighup; +  struct GNUNET_SIGNAL_Context *sigchld; +  int ret; +  char c; +  ssize_t res; + +  if (0 != pipe (reload_pipe)) +  { +    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                         "pipe"); +    return GNUNET_SYSERR; +  } +  sigterm = GNUNET_SIGNAL_handler_install (SIGTERM, +                                           &handle_sigterm); +  sigint = GNUNET_SIGNAL_handler_install (SIGINT, +                                          &handle_sigint); +  sighup = GNUNET_SIGNAL_handler_install (SIGHUP, +                                          &handle_sighup); +  sigchld = GNUNET_SIGNAL_handler_install (SIGCHLD, +                                           &handle_sigchld); + +  ret = 2; +  while (2 == ret) +  { +    errno = 0; +    res = read (reload_pipe[0], +                &c, +                1); +    if ((res < 0) && (EINTR != errno)) +    { +      GNUNET_break (0); +      ret = GNUNET_SYSERR; +      break; +    } +    if (EINTR == errno) +      { +        ret = 2; +        continue; +      } +    switch (c) +    { +    case SIGTERM: +    case SIGINT: +      /* terminate */ +      ret = GNUNET_OK; +      break; +    case SIGHUP: +      /* restart updated binary */ +      ret = GNUNET_NO; +      break; +#if HAVE_DEVELOPER +    case SIGCHLD: +      /* running in test-mode, test finished, terminate */ +      ret = GNUNET_OK; +      break; +#endif +    default: +      /* unexpected character */ +      GNUNET_break (0); +      break; +    } +  } +  GNUNET_SIGNAL_handler_uninstall (sigterm); +  GNUNET_SIGNAL_handler_uninstall (sigint); +  GNUNET_SIGNAL_handler_uninstall (sighup); +  GNUNET_SIGNAL_handler_uninstall (sigchld); +  GNUNET_break (0 == close (reload_pipe[0])); +  GNUNET_break (0 == close (reload_pipe[1])); +  return ret; +} + + + +/** + * 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) +{ +  if (NULL == *con_cls) +    return; +  TAH_PARSE_post_cleanup_callback (*con_cls); +  *con_cls = NULL; +} + + +/** + * Handle incoming HTTP request. + * + * @param cls closure for MHD daemon (unused) + * @param connection the connection + * @param url the requested url + * @param method the method (POST, GET, ...) + * @param version HTTP version (ignored) + * @param upload_data request data + * @param upload_data_size size of @a upload_data in bytes + * @param con_cls closure for request (a `struct Buffer *`) + * @return MHD result code + */ +static int +handle_mhd_request (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 TAH_RequestHandler handlers[] = +    { +      /* Landing page, tell humans to go away. */ +      { "/", MHD_HTTP_METHOD_GET, "text/plain", +        "Hello, I'm the Taler auditor. This HTTP server is not for humans.\n", 0, +        &TAH_MHD_handler_static_response, MHD_HTTP_OK }, +      /* /robots.txt: disallow everything */ +      { "/robots.txt", MHD_HTTP_METHOD_GET, "text/plain", +        "User-agent: *\nDisallow: /\n", 0, +        &TAH_MHD_handler_static_response, MHD_HTTP_OK }, +      /* AGPL licensing page, redirect to source. As per the AGPL-license, +         every deployment is required to offer the user a download of the +         source. We make this easy by including a redirect to the source +         here. */ +      { "/agpl", MHD_HTTP_METHOD_GET, "text/plain", +        NULL, 0, +        &TAH_MHD_handler_agpl_redirect, MHD_HTTP_FOUND }, + +      { NULL, NULL, NULL, NULL, 0, 0 } +    }; +  static struct TAH_RequestHandler h404 = +    { +      "", NULL, "text/html", +      "<html><title>404: not found</title></html>", 0, +      &TAH_MHD_handler_static_response, MHD_HTTP_NOT_FOUND +    }; +  struct TAH_RequestHandler *rh; + +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Handling request for URL '%s'\n", +              url); +  if (0 == strcasecmp (method, +                       MHD_HTTP_METHOD_HEAD)) +    method = MHD_HTTP_METHOD_GET; /* treat HEAD as GET here, MHD will do the rest */ +  for (unsigned int i=0;NULL != handlers[i].url;i++) +  { +    rh = &handlers[i]; +    if ( (0 == strcasecmp (url, +                           rh->url)) && +         ( (NULL == rh->method) || +           (0 == strcasecmp (method, +                             rh->method)) ) ) +      return rh->handler (rh, +                          connection, +                          con_cls, +                          upload_data, +                          upload_data_size); +  } +  return TAH_MHD_handler_static_response (&h404, +                                          connection, +                                          con_cls, +                                          upload_data, +                                          upload_data_size); +} + + +/** + * Parse the configuration to determine on which port + * or UNIX domain path we should run an HTTP service. + * + * @param section section of the configuration to parse ("auditor" or "auditor-admin") + * @param[out] rport set to the port number, or 0 for none + * @param[out] unix_path set to the UNIX path, or NULL for none + * @param[out] unix_mode set to the mode to be used for @a unix_path + * @return #GNUNET_OK on success + */ +static int +parse_port_config (const char *section, +                   uint16_t *rport, +                   char **unix_path, +                   mode_t *unix_mode) +{ +  const char *choices[] = {"tcp", "unix"}; +  const char *serve_type; +  unsigned long long port; + +  if (GNUNET_OK != +      GNUNET_CONFIGURATION_get_value_choice (cfg, +                                             section, +                                             "serve", +                                             choices, +                                             &serve_type)) +  { +    GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, +                               section, +                               "serve", +                               "serve type required"); +    return GNUNET_SYSERR; +  } + +  if (0 == strcmp (serve_type, "tcp")) +  { +    if (GNUNET_OK != +        GNUNET_CONFIGURATION_get_value_number (cfg, +                                               section, +                                               "port", +                                               &port)) +    { +      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, +                                 section, +                                 "port", +                                 "port number required"); +      return GNUNET_SYSERR; +    } + +    if ( (0 == port) || +         (port > UINT16_MAX) ) +    { +      fprintf (stderr, +               "Invalid configuration (value out of range): %llu is not a valid port\n", +               port); +      return GNUNET_SYSERR; +    } +    *rport = (uint16_t) port; +    *unix_path = NULL; +    return GNUNET_OK; +  } +  if (0 == strcmp (serve_type, "unix")) +  { +    struct sockaddr_un s_un; +    char *modestring; + +    if (GNUNET_OK != +        GNUNET_CONFIGURATION_get_value_filename (cfg, +                                                 section, +                                                 "unixpath", +                                                 unix_path)) +    { +      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, +                                 section, +                                 "unixpath", +                                 "unixpath required"); +      return GNUNET_SYSERR; +    } +    if (strlen (*unix_path) >= sizeof (s_un.sun_path)) +    { +      fprintf (stderr, +               "Invalid configuration: unix path too long\n"); +      return GNUNET_SYSERR; +    } + +    if (GNUNET_OK != +        GNUNET_CONFIGURATION_get_value_string (cfg, +                                               section, +                                               "UNIXPATH_MODE", +                                               &modestring)) +    { +      GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, +                                 section, +                                 "UNIXPATH_MODE"); +      return GNUNET_SYSERR; +    } +    errno = 0; +    *unix_mode = (mode_t) strtoul (modestring, NULL, 8); +    if (0 != errno) +    { +      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR, +                                 section, +                                 "UNIXPATH_MODE", +                                 "must be octal number"); +      GNUNET_free (modestring); +      return GNUNET_SYSERR; +    } +    GNUNET_free (modestring); +    return GNUNET_OK; +  } +  /* not reached */ +  GNUNET_assert (0); +  return GNUNET_SYSERR; +} + + +/** + * Load configuration parameters for the auditor + * server into the corresponding global variables. + * + * @return #GNUNET_OK on success + */ +static int +auditor_serve_process_config () +{ +  if (NULL == +      (TAH_plugin = TALER_AUDITORDB_plugin_load (cfg))) +  { +    fprintf (stderr, +             "Failed to initialize DB subsystem\n"); +    return GNUNET_SYSERR; +  } +  if (GNUNET_OK != +      parse_port_config ("auditor", +                         &serve_port, +                         &serve_unixpath, +                         &unixpath_mode)) +  { +    return GNUNET_SYSERR; +  } +  return GNUNET_OK; +} + + +/** + * Function called for logging by MHD. + * + * @param cls closure, NULL + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + */ +static void +handle_mhd_logs (void *cls, +                 const char *fm, +                 va_list ap) +{ +  static int cache; +  char buf[2048]; + +  if (-1 == cache) +    return; +  if (0 == cache) +  { +    if (0 == +        GNUNET_get_log_call_status (GNUNET_ERROR_TYPE_INFO, +                                    "auditor-httpd", +                                    __FILE__, +                                    __FUNCTION__, +                                    __LINE__)) +    { +      cache = -1; +      return; +    } +  } +  cache = 1; +  vsnprintf (buf, +             sizeof (buf), +             fm, +             ap); +  GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_INFO, +                           "auditor-httpd", +                           "%s", +                           buf); +} + + +/** + * Open UNIX domain socket for listining at @a unix_path with + * permissions @a unix_mode. + * + * @param unix_path where to listen + * @param unix_mode access permissions to set + * @return -1 on error, otherwise the listen socket + */ +static int +open_unix_path (const char *unix_path, +                mode_t unix_mode) +{ +  struct GNUNET_NETWORK_Handle *nh; +  struct sockaddr_un *un; +  int fd; + +  if (sizeof (un->sun_path) <= strlen (unix_path)) +  { +    fprintf (stderr, +             "unixpath `%s' too long\n", +             unix_path); +    return -1; +  } +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "Creating listen socket '%s' with mode %o\n", +              unix_path, +              unix_mode); + +  if (GNUNET_OK != +      GNUNET_DISK_directory_create_for_file (unix_path)) +  { +    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, +                              "mkdir", +                              unix_path); +  } + +  un = GNUNET_new (struct sockaddr_un); +  un->sun_family = AF_UNIX; +  strncpy (un->sun_path, +           unix_path, +           sizeof (un->sun_path) - 1); +  GNUNET_NETWORK_unix_precheck (un); + +  if (NULL == (nh = GNUNET_NETWORK_socket_create (AF_UNIX, +                                                  SOCK_STREAM, +                                                  0))) +  { +    fprintf (stderr, +             "create failed for AF_UNIX\n"); +    GNUNET_free (un); +    return -1; +  } +  if (GNUNET_OK != +      GNUNET_NETWORK_socket_bind (nh, +                                  (void *) un, +                                  sizeof (struct sockaddr_un))) +  { +    fprintf (stderr, +             "bind failed for AF_UNIX\n"); +    GNUNET_free (un); +    GNUNET_NETWORK_socket_close (nh); +    return -1; +  } +  GNUNET_free (un); +  if (GNUNET_OK != +      GNUNET_NETWORK_socket_listen (nh, +                                    UNIX_BACKLOG)) +  { +    fprintf (stderr, +             "listen failed for AF_UNIX\n"); +    GNUNET_NETWORK_socket_close (nh); +    return -1; +  } + +  if (0 != chmod (unix_path, +                  unix_mode)) +  { +    fprintf (stderr, +             "chmod failed: %s\n", +             strerror (errno)); +    GNUNET_NETWORK_socket_close (nh); +    return -1; +  } +  GNUNET_log (GNUNET_ERROR_TYPE_INFO, +              "set socket '%s' to mode %o\n", +              unix_path, +              unix_mode); +  fd = GNUNET_NETWORK_get_fd (nh); +  GNUNET_NETWORK_socket_free_memory_only_ (nh); +  return fd; +} + + +/** + * The main function of the taler-auditor-httpd server ("the auditor"). + * + * @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) +{ +  char *cfgfile = NULL; +  char *loglev = NULL; +  char *logfile = NULL; +  const struct GNUNET_GETOPT_CommandLineOption options[] = { +    GNUNET_GETOPT_option_flag ('C', +                               "connection-close", +                               "force HTTP connections to be closed after each request", +                               &TAH_auditor_connection_close), +    GNUNET_GETOPT_option_cfgfile (&cfgfile), +    GNUNET_GETOPT_option_uint ('t', +                               "timeout", +                               "SECONDS", +                               "after how long do connections timeout by default (in seconds)", +                               &connection_timeout), +    GNUNET_GETOPT_option_help ("HTTP server providing a RESTful API to access a Taler auditor"), +    GNUNET_GETOPT_option_loglevel (&loglev), +    GNUNET_GETOPT_option_logfile (&logfile), +    GNUNET_GETOPT_option_version (VERSION "-" VCS_VERSION), +    GNUNET_GETOPT_OPTION_END +  }; +  int ret; +  const char *listen_pid; +  const char *listen_fds; +  int fh = -1; + +  if (0 >= +      GNUNET_GETOPT_run ("taler-auditor-httpd", +                         options, +                         argc, argv)) +    return 1; +  GNUNET_assert (GNUNET_OK == +                 GNUNET_log_setup ("taler-auditor-httpd", +                                   (NULL == loglev) ? "INFO" : loglev, +                                   logfile)); +  if (NULL == cfgfile) +    cfgfile = GNUNET_strdup (GNUNET_OS_project_data_get ()->user_config_file); +  cfg = GNUNET_CONFIGURATION_create (); +  if (GNUNET_SYSERR == +      GNUNET_CONFIGURATION_load (cfg, cfgfile)) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                _("Malformed configuration file `%s', exit ...\n"), +                cfgfile); +    GNUNET_free_non_null (cfgfile); +    return 1; +  } +  GNUNET_free_non_null (cfgfile); +  if (GNUNET_OK != +      auditor_serve_process_config ()) +    return 1; + +  /* check for systemd-style FD passing */ +  listen_pid = getenv ("LISTEN_PID"); +  listen_fds = getenv ("LISTEN_FDS"); +  if ( (NULL != listen_pid) && +       (NULL != listen_fds) && +       (getpid() == strtol (listen_pid, +                            NULL, +                            10)) && +       (1 == strtoul (listen_fds, +                      NULL, +                      10)) ) +  { +    int flags; + +    fh = 3; +    flags = fcntl (fh, +                   F_GETFD); +    if ( (-1 == flags) && +         (EBADF == errno) ) +    { +      fprintf (stderr, +               "Bad listen socket passed, ignored\n"); +      fh = -1; +    } +    flags |= FD_CLOEXEC; +    if ( (-1 != fh) && +         (0 != fcntl (fh, +                      F_SETFD, +                      flags)) ) +      GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                           "fcntl"); +  } + +  /* consider unix path */ +  if ( (-1 == fh) && +       (NULL != serve_unixpath) ) +  { +    fh = open_unix_path (serve_unixpath, +                         unixpath_mode); +    if (-1 == fh) +      return 1; +  } + +  mhd +    = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_PIPE_FOR_SHUTDOWN | MHD_USE_DEBUG | MHD_USE_DUAL_STACK | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_TCP_FASTOPEN, +                        (-1 == fh) ? serve_port : 0, +                        NULL, NULL, +                        &handle_mhd_request, NULL, +                        MHD_OPTION_THREAD_POOL_SIZE, (unsigned int) 32, +                        MHD_OPTION_LISTEN_BACKLOG_SIZE, (unsigned int) 1024, +                        MHD_OPTION_LISTEN_SOCKET, fh, +                        MHD_OPTION_EXTERNAL_LOGGER, &handle_mhd_logs, NULL, +                        MHD_OPTION_NOTIFY_COMPLETED, &handle_mhd_completion_callback, NULL, +                        MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout, +                        MHD_OPTION_END); +  if (NULL == mhd) +  { +    fprintf (stderr, +             "Failed to start HTTP server.\n"); +    return 1; +  } + +  /* normal behavior */ +  ret = signal_loop (); +  switch (ret) +  { +  case GNUNET_OK: +  case GNUNET_SYSERR: +    MHD_stop_daemon (mhd); +    break; +  case GNUNET_NO: +    { +      MHD_socket sock = MHD_quiesce_daemon (mhd); +      pid_t chld; +      int flags; + +      /* Set flags to make 'sock' inherited by child */ +      flags = fcntl (sock, F_GETFD); +      GNUNET_assert (-1 != flags); +      flags &= ~FD_CLOEXEC; +      GNUNET_assert (-1 != fcntl (sock, F_SETFD, flags)); +      chld = fork (); +      if (-1 == chld) +      { +        /* fork() failed, continue clean up, unhappily */ +        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                             "fork"); +      } +      if (0 == chld) +      { +        char pids[12]; + +        /* exec another taler-auditor-httpd, passing on the listen socket; +           as in systemd it is expected to be on FD #3 */ +        if (3 != dup2 (sock, 3)) +        { +          GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                               "dup2"); +          _exit (1); +        } +        /* Tell the child that it is the desired recipient for FD #3 */ +        GNUNET_snprintf (pids, +                         sizeof (pids), +                         "%u", +                         getpid ()); +        setenv ("LISTEN_PID", pids, 1); +        setenv ("LISTEN_FDS", "1", 1); +        /* Finally, exec the (presumably) more recent auditor binary */ +        execvp ("taler-auditor-httpd", +                argv); +        GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, +                             "execvp"); +        _exit (1); +      } +      /* we're the original process, handle remaining contextions +         before exiting; as the listen socket is no longer used, +         close it here */ +      GNUNET_break (0 == close (sock)); +      while (0 != MHD_get_daemon_info (mhd, +                                       MHD_DAEMON_INFO_CURRENT_CONNECTIONS)->num_connections) +        sleep (1); +      /* Now we're really done, practice clean shutdown */ +      MHD_stop_daemon (mhd); +    } +    break; +  default: +    GNUNET_break (0); +    MHD_stop_daemon (mhd); +    break; +  } +  TALER_AUDITORDB_plugin_unload (TAH_plugin); +  return (GNUNET_SYSERR == ret) ? 1 : 0; +} + +/* end of taler-auditor-httpd.c */ diff --git a/src/auditor/taler-auditor-httpd.h b/src/auditor/taler-auditor-httpd.h new file mode 100644 index 00000000..c03cc490 --- /dev/null +++ b/src/auditor/taler-auditor-httpd.h @@ -0,0 +1,99 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2018 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-auditor-httpd.h + * @brief Global declarations for the auditor + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_AUDITOR_HTTPD_H +#define TALER_AUDITOR_HTTPD_H + +#include <microhttpd.h> + +/** + * Should we return "Connection: close" in each response? + */ +extern int TAH_auditor_connection_close; + +/** + * The exchange's configuration. + */ +extern struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Our DB plugin. + */ +extern struct TALER_AUDITORDB_Plugin *TAH_plugin; + + +/** + * @brief Struct describing an URL and the handler for it. + */ +struct TAH_RequestHandler +{ + +  /** +   * URL the handler is for. +   */ +  const char *url; + +  /** +   * Method the handler is for, NULL for "all". +   */ +  const char *method; + +  /** +   * Mime type to use in reply (hint, can be NULL). +   */ +  const char *mime_type; + +  /** +   * Raw data for the @e handler +   */ +  const void *data; + +  /** +   * Number of bytes in @e data, 0 for 0-terminated. +   */ +  size_t data_size; + +  /** +   * Function to call to handle the request. +   * +   * @param rh this struct +   * @param mime_type the @e mime_type for the reply (hint, can be NULL) +   * @param connection the MHD connection to handle +   * @param[in,out] connection_cls the connection's closure (can be updated) +   * @param upload_data upload data +   * @param[in,out] upload_data_size number of bytes (left) in @a upload_data +   * @return MHD result code +   */ +  int (*handler)(struct TAH_RequestHandler *rh, +                 struct MHD_Connection *connection, +                 void **connection_cls, +                 const char *upload_data, +                 size_t *upload_data_size); + +  /** +   * Default response code. +   */ +  int response_code; +}; + + +#endif diff --git a/src/auditor/taler-auditor-httpd_mhd.c b/src/auditor/taler-auditor-httpd_mhd.c new file mode 100644 index 00000000..b8fc65af --- /dev/null +++ b/src/auditor/taler-auditor-httpd_mhd.c @@ -0,0 +1,158 @@ +/* +  This file is part of TALER +  Copyright (C) 2014 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-auditor-httpd_mhd.c + * @brief helpers for MHD interaction; these are TALER_AUDITOR_handler_ functions + *        that generate simple MHD replies that do not require any real operations + *        to be performed (error handling, static pages, etc.) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler-auditor-httpd_responses.h" +#include "taler-auditor-httpd.h" +#include "taler-auditor-httpd_mhd.h" + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh, +                                    struct MHD_Connection *connection, +                                    void **connection_cls, +                                    const char *upload_data, +                                    size_t *upload_data_size) +{ +  struct MHD_Response *response; +  int ret; + +  if (0 == rh->data_size) +    rh->data_size = strlen ((const char *) rh->data); +  response = MHD_create_response_from_buffer (rh->data_size, +                                              (void *) rh->data, +                                              MHD_RESPMEM_PERSISTENT); +  if (NULL == response) +  { +    GNUNET_break (0); +    return MHD_NO; +  } +  TAH_RESPONSE_add_global_headers (response); +  if (NULL != rh->mime_type) +    (void) MHD_add_response_header (response, +                                    MHD_HTTP_HEADER_CONTENT_TYPE, +                                    rh->mime_type); +  ret = MHD_queue_response (connection, +                            rh->response_code, +                            response); +  MHD_destroy_response (response); +  return ret; +} + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_agpl_redirect (struct TAH_RequestHandler *rh, +                               struct MHD_Connection *connection, +                               void **connection_cls, +                               const char *upload_data, +                               size_t *upload_data_size) +{ +  const char *agpl = +    "This server is licensed under the Affero GPL. You will now be redirected to the source code."; +  struct MHD_Response *response; +  int ret; + +  response = MHD_create_response_from_buffer (strlen (agpl), +                                              (void *) agpl, +                                              MHD_RESPMEM_PERSISTENT); +  if (NULL == response) +  { +    GNUNET_break (0); +    return MHD_NO; +  } +  TAH_RESPONSE_add_global_headers (response); +  if (NULL != rh->mime_type) +    (void) MHD_add_response_header (response, +                                    MHD_HTTP_HEADER_CONTENT_TYPE, +                                    rh->mime_type); +  if (MHD_NO == +      MHD_add_response_header (response, +                               MHD_HTTP_HEADER_LOCATION, +                               "http://www.git.taler.net/?p=auditor.git")) +  { +    GNUNET_break (0); +    MHD_destroy_response (response); +    return MHD_NO; +  } +  ret = MHD_queue_response (connection, +                            rh->response_code, +                            response); +  MHD_destroy_response (response); +  return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_send_json_pack_error (struct TAH_RequestHandler *rh, +                                         struct MHD_Connection *connection, +                                         void **connection_cls, +                                         const char *upload_data, +                                         size_t *upload_data_size) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                     rh->response_code, +                                     "{s:s}", +                                     "error", +                                     rh->data); +} + + +/* end of taler-auditor-httpd_mhd.c */ diff --git a/src/auditor/taler-auditor-httpd_mhd.h b/src/auditor/taler-auditor-httpd_mhd.h new file mode 100644 index 00000000..8fd2140e --- /dev/null +++ b/src/auditor/taler-auditor-httpd_mhd.h @@ -0,0 +1,111 @@ +/* +  This file is part of TALER +  Copyright (C) 2014 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-auditor-httpd_mhd.h + * @brief helpers for MHD interaction, used to generate simple responses + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_AUDITOR_HTTPD_MHD_H +#define TALER_AUDITOR_HTTPD_MHD_H +#include <gnunet/gnunet_util_lib.h> +#include <microhttpd.h> +#include "taler-auditor-httpd.h" + + +/** + * Function to call to handle the request by sending + * back static data from the @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_static_response (struct TAH_RequestHandler *rh, +                                 struct MHD_Connection *connection, +                                 void **connection_cls, +                                 const char *upload_data, +                                 size_t *upload_data_size); + + +/** + * Function to call to handle the request by sending + * back a redirect to the AGPL source code. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_agpl_redirect (struct TAH_RequestHandler *rh, +                               struct MHD_Connection *connection, +                               void **connection_cls, +                               const char *upload_data, +                               size_t *upload_data_size); + + +/** + * Function to call to handle the request by building a JSON + * reply from varargs. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param response_code HTTP response code to use + * @param do_cache can the response be cached? (0: no, 1: yes) + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TAH_MHD_helper_send_json_pack (struct TAH_RequestHandler *rh, +                               struct MHD_Connection *connection, +                               void *connection_cls, +                               int response_code, +                               int do_cache, +                               const char *fmt, +                               ...); + + +/** + * Function to call to handle the request by building a JSON + * reply with an error message from @a rh. + * + * @param rh context of the handler + * @param connection the MHD connection to handle + * @param[in,out] connection_cls the connection's closure (can be updated) + * @param upload_data upload data + * @param[in,out] upload_data_size number of bytes (left) in @a upload_data + * @return MHD result code + */ +int +TAH_MHD_handler_send_json_pack_error (struct TAH_RequestHandler *rh, +                                      struct MHD_Connection *connection, +                                      void **connection_cls, +                                      const char *upload_data, +                                      size_t *upload_data_size); + + +#endif diff --git a/src/auditor/taler-auditor-httpd_parsing.c b/src/auditor/taler-auditor-httpd_parsing.c new file mode 100644 index 00000000..ebbe50ee --- /dev/null +++ b/src/auditor/taler-auditor-httpd_parsing.c @@ -0,0 +1,282 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-auditor-httpd_parsing.c + * @brief functions to parse incoming requests (MHD arguments and JSON snippets) + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ + +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#include "taler_json_lib.h" +#include "taler-auditor-httpd_parsing.h" +#include "taler-auditor-httpd_responses.h" + + +/** + * Maximum POST request size. + */ +#define REQUEST_BUFFER_MAX (1024*1024) + + + +/** + * Process a POST request containing a JSON object.  This function + * realizes an MHD POST processor that will (incrementally) process + * JSON data uploaded to the HTTP server.  It will store the required + * state in the @a con_cls, which must be cleaned up using + * #TAH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + *    #GNUNET_YES if json object was parsed or at least + *               may be parsed in the future (call again); + *               `*json` will be NULL if we need to be called again, + *                and non-NULL if we are done. + *    #GNUNET_NO is request incomplete or invalid + *               (error message was generated) + *    #GNUNET_SYSERR on internal error + *               (we could not even queue an error message, + *                close HTTP session with MHD_NO) + */ +int +TAH_PARSE_post_json (struct MHD_Connection *connection, +                     void **con_cls, +                     const char *upload_data, +                     size_t *upload_data_size, +                     json_t **json) +{ +  enum GNUNET_JSON_PostResult pr; + +  pr = GNUNET_JSON_post_parser (REQUEST_BUFFER_MAX, +                                con_cls, +                                upload_data, +                                upload_data_size, +                                json); +  switch (pr) +  { +  case GNUNET_JSON_PR_OUT_OF_MEMORY: +    return (MHD_NO == +            TAH_RESPONSE_reply_internal_error (connection, +					       TALER_EC_PARSER_OUT_OF_MEMORY, +                                               "out of memory")) +      ? GNUNET_SYSERR : GNUNET_NO; +  case GNUNET_JSON_PR_CONTINUE: +    return GNUNET_YES; +  case GNUNET_JSON_PR_REQUEST_TOO_LARGE: +    return (MHD_NO == +            TAH_RESPONSE_reply_request_too_large (connection)) +      ? GNUNET_SYSERR : GNUNET_NO; +  case GNUNET_JSON_PR_JSON_INVALID: +    return (MHD_YES == +            TAH_RESPONSE_reply_invalid_json (connection)) +      ? GNUNET_NO : GNUNET_SYSERR; +  case GNUNET_JSON_PR_SUCCESS: +    GNUNET_break (NULL != *json); +    return GNUNET_YES; +  } +  /* this should never happen */ +  GNUNET_break (0); +  return GNUNET_SYSERR; +} + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + *        #TAH_PARSE_post_json(), to be cleaned up + */ +void +TAH_PARSE_post_cleanup_callback (void *con_cls) +{ +  GNUNET_JSON_post_parser_cleanup (con_cls); +} + + +/** + * Extract base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is + * missing or invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of data + * @return + *   #GNUNET_YES if the the argument is present + *   #GNUNET_NO if the argument is absent or malformed + *   #GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TAH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, +                                const char *param_name, +                                void *out_data, +                                size_t out_size) +{ +  const char *str; + +  str = MHD_lookup_connection_value (connection, +                                     MHD_GET_ARGUMENT_KIND, +                                     param_name); +  if (NULL == str) +  { +    return (MHD_NO == +            TAH_RESPONSE_reply_arg_missing (connection, +					    TALER_EC_PARAMETER_MISSING, +					    param_name)) +      ? GNUNET_SYSERR : GNUNET_NO; +  } +  if (GNUNET_OK != +      GNUNET_STRINGS_string_to_data (str, +                                     strlen (str), +                                     out_data, +                                     out_size)) +    return (MHD_NO == +            TAH_RESPONSE_reply_arg_invalid (connection, +					    TALER_EC_PARAMETER_MALFORMED, +					    param_name)) +      ? GNUNET_SYSERR : GNUNET_NO; +  return GNUNET_OK; +} + + +/** + * Parse JSON object into components based on the given field + * specification.  Generates error response on parse errors. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @return + *    #GNUNET_YES if navigation was successful (caller is responsible + *                for freeing allocated variable-size data using + *                GNUNET_JSON_parse_free() when done) + *    #GNUNET_NO if json is malformed, error response was generated + *    #GNUNET_SYSERR on internal error + */ +int +TAH_PARSE_json_data (struct MHD_Connection *connection, +                     const json_t *root, +                     struct GNUNET_JSON_Specification *spec) +{ +  int ret; +  const char *error_json_name; +  unsigned int error_line; + +  ret = GNUNET_JSON_parse (root, +                           spec, +                           &error_json_name, +                           &error_line); +  if (GNUNET_SYSERR == ret) +  { +    if (NULL == error_json_name) +      error_json_name = "<no field>"; +    ret = (MHD_YES == +           TAH_RESPONSE_reply_json_pack (connection, +                                         MHD_HTTP_BAD_REQUEST, +                                         "{s:s, s:I, s:s, s:I}", +                                         "error", "parse error", +					 "code", (json_int_t) TALER_EC_JSON_INVALID_WITH_DETAILS, +                                         "field", error_json_name, +                                         "line", (json_int_t) error_line)) +      ? GNUNET_NO : GNUNET_SYSERR; +    return ret; +  } +  return GNUNET_YES; +} + + +/** + * Parse JSON array into components based on the given field + * specification.  Generates error response on parse errors. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @param ... -1-terminated list of array offsets of type 'int' + * @return + *    #GNUNET_YES if navigation was successful (caller is responsible + *                for freeing allocated variable-size data using + *                GNUNET_JSON_parse_free() when done) + *    #GNUNET_NO if json is malformed, error response was generated + *    #GNUNET_SYSERR on internal error + */ +int +TAH_PARSE_json_array (struct MHD_Connection *connection, +                      const json_t *root, +                      struct GNUNET_JSON_Specification *spec, +                      ...) +{ +  int ret; +  const char *error_json_name; +  unsigned int error_line; +  va_list ap; +  json_int_t dim; + +  va_start (ap, spec); +  dim = 0; +  while ( (-1 != (ret = va_arg (ap, int))) && +          (NULL != root) ) +  { +    dim++; +    root = json_array_get (root, ret); +  } +  va_end (ap); +  if (NULL == root) +  { +    ret = (MHD_YES == +           TAH_RESPONSE_reply_json_pack (connection, +                                         MHD_HTTP_BAD_REQUEST, +                                         "{s:s, s:I}", +                                         "error", "parse error", +                                         "dimension", dim)) +      ? GNUNET_NO : GNUNET_SYSERR; +    return ret; +  } +  ret = GNUNET_JSON_parse (root, +                           spec, +                           &error_json_name, +                           &error_line); +  if (GNUNET_SYSERR == ret) +  { +    if (NULL == error_json_name) +      error_json_name = "<no field>"; +    ret = (MHD_YES == +           TAH_RESPONSE_reply_json_pack (connection, +                                         MHD_HTTP_BAD_REQUEST, +                                         "{s:s, s:s, s:I}", +                                         "error", "parse error", +                                         "field", error_json_name, +                                         "line", (json_int_t) error_line)) +      ? GNUNET_NO : GNUNET_SYSERR; +    return ret; +  } +  return GNUNET_YES; +} + + +/* end of taler-auditor-httpd_parsing.c */ diff --git a/src/auditor/taler-auditor-httpd_parsing.h b/src/auditor/taler-auditor-httpd_parsing.h new file mode 100644 index 00000000..7df76ef5 --- /dev/null +++ b/src/auditor/taler-auditor-httpd_parsing.h @@ -0,0 +1,139 @@ +/* +  This file is part of TALER +  Copyright (C) 2014, 2015, 2016 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-auditor-httpd_parsing.h + * @brief functions to parse incoming requests + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_AUDITOR_HTTPD_PARSING_H +#define TALER_AUDITOR_HTTPD_PARSING_H + +#include <microhttpd.h> +#include <jansson.h> +#include "taler_util.h" +#include "taler_json_lib.h" + + +/** + * Process a POST request containing a JSON object.  This + * function realizes an MHD POST processor that will + * (incrementally) process JSON data uploaded to the HTTP + * server.  It will store the required state in the + * "connection_cls", which must be cleaned up using + * #TAH_PARSE_post_cleanup_callback(). + * + * @param connection the MHD connection + * @param con_cls the closure (points to a `struct Buffer *`) + * @param upload_data the POST data + * @param upload_data_size number of bytes in @a upload_data + * @param json the JSON object for a completed request + * @return + *    #GNUNET_YES if json object was parsed or at least + *               may be parsed in the future (call again); + *               `*json` will be NULL if we need to be called again, + *                and non-NULL if we are done. + *    #GNUNET_NO is request incomplete or invalid + *               (error message was generated) + *    #GNUNET_SYSERR on internal error + *               (we could not even queue an error message, + *                close HTTP session with MHD_NO) + */ +int +TAH_PARSE_post_json (struct MHD_Connection *connection, +                     void **con_cls, +                     const char *upload_data, +                     size_t *upload_data_size, +                     json_t **json); + + +/** + * Function called whenever we are done with a request + * to clean up our state. + * + * @param con_cls value as it was left by + *        #TAH_PARSE_post_json(), to be cleaned up + */ +void +TAH_PARSE_post_cleanup_callback (void *con_cls); + + +/** + * Parse JSON object into components based on the given field + * specification. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param spec field specification for the parser + * @return + *    #GNUNET_YES if navigation was successful (caller is responsible + *                for freeing allocated variable-size data using + *                GNUNET_JSON_parse_free() when done) + *    #GNUNET_NO if json is malformed, error response was generated + *    #GNUNET_SYSERR on internal error + */ +int +TAH_PARSE_json_data (struct MHD_Connection *connection, +                     const json_t *root, +                     struct GNUNET_JSON_Specification *spec); + + +/** + * Parse JSON array into components based on the given field + * specification.  Generates error response on parse errors. + * + * @param connection the connection to send an error response to + * @param root the JSON node to start the navigation at. + * @param[in,out] spec field specification for the parser + * @param ... -1-terminated list of array offsets of type 'int' + * @return + *    #GNUNET_YES if navigation was successful (caller is responsible + *                for freeing allocated variable-size data using + *                GNUNET_JSON_parse_free() when done) + *    #GNUNET_NO if json is malformed, error response was generated + *    #GNUNET_SYSERR on internal error + */ +int +TAH_PARSE_json_array (struct MHD_Connection *connection, +                      const json_t *root, +                      struct GNUNET_JSON_Specification *spec, +                      ...); + + +/** + * Extraxt fixed-size base32crockford encoded data from request. + * + * Queues an error response to the connection if the parameter is missing or + * invalid. + * + * @param connection the MHD connection + * @param param_name the name of the parameter with the key + * @param[out] out_data pointer to store the result + * @param out_size expected size of @a out_data + * @return + *   #GNUNET_YES if the the argument is present + *   #GNUNET_NO if the argument is absent or malformed + *   #GNUNET_SYSERR on internal error (error response could not be sent) + */ +int +TAH_PARSE_mhd_request_arg_data (struct MHD_Connection *connection, +                                const char *param_name, +                                void *out_data, +                                size_t out_size); + + +#endif /* TALER_AUDITOR_HTTPD_PARSING_H */ diff --git a/src/auditor/taler-auditor-httpd_responses.c b/src/auditor/taler-auditor-httpd_responses.c new file mode 100644 index 00000000..045a7777 --- /dev/null +++ b/src/auditor/taler-auditor-httpd_responses.c @@ -0,0 +1,479 @@ +/* +  This file is part of TALER +  Copyright (C) 2014-2017 Inria & GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_responses.c + * @brief API for generating genric replies of the exchange; these + *        functions are called TAH_RESPONSE_reply_ and they generate + *        and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#include "platform.h" +#include <zlib.h> +#include "taler-auditor-httpd_responses.h" +#include "taler_util.h" +#include "taler_json_lib.h" + + +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TAH_RESPONSE_add_global_headers (struct MHD_Response *response) +{ +  if (TAH_auditor_connection_close) +    GNUNET_break (MHD_YES == +                  MHD_add_response_header (response, +                                           MHD_HTTP_HEADER_CONNECTION, +                                           "close")); +} + + +/** + * Is HTTP body deflate compression supported by the client? + * + * @param connection connection to check + * @return #MHD_YES if 'deflate' compression is allowed + * + * Note that right now we're ignoring q-values, which is technically + * not correct, and also do not support "*" anywhere but in a line by + * itself.  This should eventually be fixed, see also + * https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + */ +int +TAH_RESPONSE_can_compress (struct MHD_Connection *connection) +{ +  const char *ae; +  const char *de; + +  ae = MHD_lookup_connection_value (connection, +				    MHD_HEADER_KIND, +				    MHD_HTTP_HEADER_ACCEPT_ENCODING); +  if (NULL == ae) +    return MHD_NO; +  if (0 == strcmp (ae, +                   "*")) +    return MHD_YES; +  de = strstr (ae, +	       "deflate"); +  if (NULL == de) +    return MHD_NO; +  if ( ( (de == ae) || +	 (de[-1] == ',') || +	 (de[-1] == ' ') ) && +       ( (de[strlen ("deflate")] == '\0') || +	 (de[strlen ("deflate")] == ',') || +         (de[strlen ("deflate")] == ';') ) ) +    return MHD_YES; +  return MHD_NO; +} + + +/** + * Try to compress a response body.  Updates @a buf and @a buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return #MHD_YES if @a buf was compressed + */ +int +TAH_RESPONSE_body_compress (void **buf, +			    size_t *buf_size) +{ +  Bytef *cbuf; +  uLongf cbuf_size; +  int ret; + +  cbuf_size = compressBound (*buf_size); +  cbuf = malloc (cbuf_size); +  if (NULL == cbuf) +    return MHD_NO; +  ret = compress (cbuf, +		  &cbuf_size, +		  (const Bytef *) *buf, +		  *buf_size); +  if ( (Z_OK != ret) || +       (cbuf_size >= *buf_size) ) +  { +    /* compression failed */ +    free (cbuf); +    return MHD_NO; +  } +  free (*buf); +  *buf = (void *) cbuf; +  *buf_size = (size_t) cbuf_size; +  return MHD_YES; +} + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TAH_RESPONSE_reply_json (struct MHD_Connection *connection, +                         const json_t *json, +                         unsigned int response_code) +{ +  struct MHD_Response *resp; +  void *json_str; +  size_t json_len; +  int ret; +  int comp; + +  json_str = json_dumps (json, +			 JSON_INDENT(2)); +  if (NULL == json_str) +  { +    /** +     * This log helps to figure out which +     * function called this one and assert-failed. +     */ +    TALER_LOG_ERROR ("Aborting json-packing for HTTP code: %u\n", +                     response_code); + +    GNUNET_assert (0); +    return MHD_NO; +  } +  json_len = strlen (json_str); +  /* try to compress the body */ +  comp = MHD_NO; +  if (MHD_YES == +      TAH_RESPONSE_can_compress (connection)) +    comp = TAH_RESPONSE_body_compress (&json_str, +				       &json_len); +  resp = MHD_create_response_from_buffer (json_len, +                                          json_str, +                                          MHD_RESPMEM_MUST_FREE); +  if (NULL == resp) +  { +    free (json_str); +    GNUNET_break (0); +    return MHD_NO; +  } +  TAH_RESPONSE_add_global_headers (resp); +  (void) MHD_add_response_header (resp, +                                  MHD_HTTP_HEADER_CONTENT_TYPE, +                                  "application/json"); +  if (MHD_YES == comp) +  { +    /* Need to indicate to client that body is compressed */ +    if (MHD_NO == +	MHD_add_response_header (resp, +				 MHD_HTTP_HEADER_CONTENT_ENCODING, +				 "deflate")) +    { +      GNUNET_break (0); +      MHD_destroy_response (resp); +      return MHD_NO; +    } +  } +  ret = MHD_queue_response (connection, +                            response_code, +                            resp); +  MHD_destroy_response (resp); +  return ret; +} + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TAH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, +                              unsigned int response_code, +                              const char *fmt, +                              ...) +{ +  json_t *json; +  va_list argp; +  int ret; +  json_error_t jerror; + +  va_start (argp, fmt); +  json = json_vpack_ex (&jerror, 0, fmt, argp); +  va_end (argp); +  if (NULL == json) +  { +    GNUNET_log (GNUNET_ERROR_TYPE_ERROR, +                "Failed to pack JSON with format `%s': %s\n", +                fmt, +                jerror.text); +    GNUNET_break (0); +    return MHD_NO; +  } +  ret = TAH_RESPONSE_reply_json (connection, +                                 json, +                                 response_code); +  json_decref (json); +  return ret; +} + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       "{s:s, s:I, s:s}", +                                       "error", "invalid parameter", +				       "code", (json_int_t) ec, +                                       "parameter", param_name); +} + + +/** + * Send a response indicating an argument refering to a + * resource unknown to the auditor (i.e. unknown reserve or + * denomination key). + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_arg_unknown (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_NOT_FOUND, +                                       "{s:s, s:I, s:s}", +                                       "error", "unknown entity referenced", +				       "code", (json_int_t) ec, +                                       "parameter", param_name); +} + + +/** + * Send a response indicating an invalid signature. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_signature_invalid (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec, +                                      const char *param_name) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_UNAUTHORIZED, +                                       "{s:s, s:I, s:s}", +                                       "error", "invalid signature", +				       "code", (json_int_t) ec, +                                       "parameter", param_name); +} + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       "{s:s, s:I, s:s}", +                                       "error", "missing parameter", +				       "code", (json_int_t) ec, +                                       "parameter", param_name); +} + + +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec, +                                      const char *hint) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_FORBIDDEN, +                                       "{s:s, s:I, s:s}", +                                       "error", "permission denied", +				       "code", (json_int_t) ec, +                                       "hint", hint); +} + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, +				   enum TALER_ErrorCode ec, +                                   const char *hint) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_INTERNAL_SERVER_ERROR, +                                       "{s:s, s:I, s:s}", +                                       "error", "internal error", +				       "code", (json_int_t) ec, +                                       "hint", hint); +} + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_external_error (struct MHD_Connection *connection, +				   enum TALER_ErrorCode ec, +                                   const char *hint) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       "{s:s, s:I, s:s}", +                                       "error", "client error", +				       "code", (json_int_t) ec, +                                       "hint", hint); +} + + +/** + * Send a response indicating an error committing a + * transaction (concurrent interference). + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_commit_error (struct MHD_Connection *connection, +				 enum TALER_ErrorCode ec) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_INTERNAL_SERVER_ERROR, +                                       "{s:s, s:I}", +                                       "error", "commit failure", +				       "code", (json_int_t) ec); +} + + +/** + * Send a response indicating a failure to talk to the Auditor's + * database. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec) +{ +  return TAH_RESPONSE_reply_internal_error (connection, +					    ec, +                                            "Failure in database interaction"); +} + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection) +{ +  struct MHD_Response *resp; +  int ret; + +  resp = MHD_create_response_from_buffer (0, +                                          NULL, +                                          MHD_RESPMEM_PERSISTENT); +  if (NULL == resp) +    return MHD_NO; +  TAH_RESPONSE_add_global_headers (resp); +  ret = MHD_queue_response (connection, +                            MHD_HTTP_REQUEST_ENTITY_TOO_LARGE, +                            resp); +  MHD_destroy_response (resp); +  return ret; +} + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_invalid_json (struct MHD_Connection *connection) +{ +  return TAH_RESPONSE_reply_json_pack (connection, +                                       MHD_HTTP_BAD_REQUEST, +                                       "{s:s, s:I}", +                                       "error", "invalid json", +				       "code", (json_int_t) TALER_EC_JSON_INVALID); +} + + +/* end of taler-auditor-httpd_responses.c */ diff --git a/src/auditor/taler-auditor-httpd_responses.h b/src/auditor/taler-auditor-httpd_responses.h new file mode 100644 index 00000000..95e6183e --- /dev/null +++ b/src/auditor/taler-auditor-httpd_responses.h @@ -0,0 +1,245 @@ +/* +  This file is part of TALER +  Copyright (C) 2014 GNUnet e.V. + +  TALER 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. + +  TALER 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 Affero General Public License for more details. + +  You should have received a copy of the GNU Affero General Public License along with +  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/> +*/ + +/** + * @file taler-auditor-httpd_responses.h + * @brief API for generating generic replies of the auditor; these + *        functions are called TAH_RESPONSE_reply_ and they generate + *        and queue MHD response objects for a given connection. + * @author Florian Dold + * @author Benedikt Mueller + * @author Christian Grothoff + */ +#ifndef TALER_AUDITOR_HTTPD_RESPONSES_H +#define TALER_AUDITOR_HTTPD_RESPONSES_H +#include <gnunet/gnunet_util_lib.h> +#include <jansson.h> +#include <microhttpd.h> +#include <pthread.h> +#include "taler_error_codes.h" +#include "taler-auditor-httpd.h" + + +/** + * Add headers we want to return in every response. + * Useful for testing, like if we want to always close + * connections. + * + * @param response response to modify + */ +void +TAH_RESPONSE_add_global_headers (struct MHD_Response *response); + + +/** + * Try to compress a response body.  Updates @a buf and @a buf_size. + * + * @param[in,out] buf pointer to body to compress + * @param[in,out] buf_size pointer to initial size of @a buf + * @return #MHD_YES if @a buf was compressed + */ +int +TAH_RESPONSE_body_compress (void **buf, +			    size_t *buf_size); + + +/** + * Is HTTP body deflate compression supported by the client? + * + * @param connection connection to check + * @return #MHD_YES if 'deflate' compression is allowed + */ +int +TAH_RESPONSE_can_compress (struct MHD_Connection *connection); + + +/** + * Send JSON object as response. + * + * @param connection the MHD connection + * @param json the json object + * @param response_code the http response code + * @return MHD result code + */ +int +TAH_RESPONSE_reply_json (struct MHD_Connection *connection, +                         const json_t *json, +                         unsigned int response_code); + + +/** + * Function to call to handle the request by building a JSON + * reply from a format string and varargs. + * + * @param connection the MHD connection to handle + * @param response_code HTTP response code to use + * @param fmt format string for pack + * @param ... varargs + * @return MHD result code + */ +int +TAH_RESPONSE_reply_json_pack (struct MHD_Connection *connection, +                              unsigned int response_code, +                              const char *fmt, +                              ...); + + +/** + * Send a response indicating an invalid signature. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_signature_invalid (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec, +                                      const char *param_name); + + +/** + * Send a response indicating an invalid argument. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return MHD result code + */ +int +TAH_RESPONSE_reply_arg_invalid (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name); + + +/** + * Send a response indicating an argument refering to a + * resource unknown to the auditor (i.e. unknown reserve or + * denomination key). + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is invalid + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_arg_unknown (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name); + + +/** + * Send a response indicating a missing argument. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param param_name the parameter that is missing + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_arg_missing (struct MHD_Connection *connection, +				enum TALER_ErrorCode ec, +                                const char *param_name); + + +/** + * Send a response indicating permission denied. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about why access was denied + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_permission_denied (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec, +                                      const char *hint); + + +/** + * Send a response indicating an internal error. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about the internal error's nature + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_internal_error (struct MHD_Connection *connection, +				   enum TALER_ErrorCode ec, +                                   const char *hint); + + +/** + * Send a response indicating an external error. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @param hint hint about the error's nature + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_external_error (struct MHD_Connection *connection, +				   enum TALER_ErrorCode ec, +                                   const char *hint); + + +/** + * Send a response indicating an error committing a + * transaction (concurrent interference). + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_commit_error (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec); + +/** + * Send a response indicating a failure to talk to the Auditor's + * database. + * + * @param connection the MHD connection to use + * @param ec error code uniquely identifying the error + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_internal_db_error (struct MHD_Connection *connection, +				      enum TALER_ErrorCode ec); + + +/** + * Send a response indicating that the request was too big. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_request_too_large (struct MHD_Connection *connection); + + +/** + * Send a response indicating that the JSON was malformed. + * + * @param connection the MHD connection to use + * @return a MHD result code + */ +int +TAH_RESPONSE_reply_invalid_json (struct MHD_Connection *connectionx); + + +#endif | 
