/*
  This file is part of TALER
  Copyright (C) 2014--2019 Taler Systems SA
  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 
*/
/**
 * @file mhd_config.c
 * @brief functions to configure and setup MHD
 * @author Florian Dold
 * @author Benedikt Mueller
 * @author Christian Grothoff
 */
#include "platform.h"
#include 
#include "taler_mhd_lib.h"
/**
 * Backlog for listen operation on UNIX domain sockets.
 */
#define UNIX_BACKLOG 500
/**
 * Parse the configuration to determine on which port
 * or UNIX domain path we should run an HTTP service.
 *
 * @param cfg configuration to parse
 * @param section section of the configuration to parse (usually "exchange")
 * @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
 */
int
TALER_MHD_parse_config (const struct GNUNET_CONFIGURATION_Handle *cfg,
                        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 == strcasecmp (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) )
    {
      GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
                                 section,
                                 "port",
                                 "value not in [1,65535]");
      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))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                  "unixpath `%s' is too long\n",
                  *unix_path);
      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;
}
/**
 * Function called for logging by MHD.
 *
 * @param cls closure, NULL
 * @param fm format string (`printf()`-style)
 * @param ap arguments to @a fm
 */
void
TALER_MHD_handle_logs (void *cls,
                       const char *fm,
                       va_list ap)
{
  static int cache;
  char buf[2048];
  (void) cls;
  if (-1 == cache)
    return;
  if (0 == cache)
  {
    if (0 ==
        GNUNET_get_log_call_status (GNUNET_ERROR_TYPE_INFO,
                                    "libmicrohttpd",
                                    __FILE__,
                                    __FUNCTION__,
                                    __LINE__))
    {
      cache = -1;
      return;
    }
  }
  cache = 1;
  vsnprintf (buf,
             sizeof (buf),
             fm,
             ap);
  GNUNET_log_from_nocheck (GNUNET_ERROR_TYPE_INFO,
                           "libmicrohttpd",
                           "%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
 */
int
TALER_MHD_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))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "unixpath `%s' is too long\n",
                unix_path);
    return -1;
  }
  GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
              "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)))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "socket");
    GNUNET_free (un);
    return -1;
  }
  if (GNUNET_OK !=
      GNUNET_NETWORK_socket_bind (nh,
                                  (void *) un,
                                  sizeof (struct sockaddr_un)))
  {
    GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR,
                              "bind",
                              unix_path);
    GNUNET_free (un);
    GNUNET_NETWORK_socket_close (nh);
    return -1;
  }
  GNUNET_free (un);
  if (GNUNET_OK !=
      GNUNET_NETWORK_socket_listen (nh,
                                    UNIX_BACKLOG))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "listen");
    GNUNET_NETWORK_socket_close (nh);
    return -1;
  }
  if (0 != chmod (unix_path,
                  unix_mode))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "chmod");
    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;
}
/**
 * Bind a listen socket to the UNIX domain path
 * or the TCP port and IP address as specified
 * in @a cfg in section @a section.  IF only a
 * port was specified, set @a port and return -1.
 * Otherwise, return the bound file descriptor.
 *
 * @param cfg configuration to parse
 * @param section configuration section to use
 * @param[out] port port to set, if TCP without BINDTO
 * @return -1 and a port of zero on error, otherwise
 *    either -1 and a port, or a bound stream socket
 */
int
TALER_MHD_bind (const struct GNUNET_CONFIGURATION_Handle *cfg,
                const char *section,
                uint16_t *port)
{
  char *bind_to;
  char *serve_unixpath;
  mode_t unixpath_mode;
  int fh;
  char port_str[6];
  struct addrinfo hints;
  struct addrinfo *res;
  int ec;
  struct GNUNET_NETWORK_Handle *nh;
  *port = 0;
  if (GNUNET_OK !=
      TALER_MHD_parse_config (cfg,
                              section,
                              port,
                              &serve_unixpath,
                              &unixpath_mode))
    return -1;
  if (NULL != serve_unixpath)
    return TALER_MHD_open_unix_path (serve_unixpath,
                                     unixpath_mode);
  if (GNUNET_OK !=
      GNUNET_CONFIGURATION_get_value_string (cfg,
                                             section,
                                             "BIND_TO",
                                             &bind_to))
    return -1; /* only set port */
  /* let's have fun binding... */
  GNUNET_snprintf (port_str,
                   sizeof (port_str),
                   "%u",
                   (unsigned int) *port);
  *port = 0; /* do NOT return port in case of errors */
  memset (&hints, 0, sizeof (hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = IPPROTO_TCP;
  hints.ai_flags = AI_PASSIVE
#ifdef AI_IDN
                   | AI_IDN
#endif
  ;
  if (0 !=
      (ec = getaddrinfo (bind_to,
                         port_str,
                         &hints,
                         &res)))
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Failed to resolve BIND_TO address `%s': %s\n",
                bind_to,                  gai_strerror (ec));
    GNUNET_free (bind_to);
    return -1;
  }
  GNUNET_free (bind_to);
  if (NULL == (nh = GNUNET_NETWORK_socket_create (res->ai_family,
                                                  res->ai_socktype,
                                                  res->ai_protocol)))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "socket");
    freeaddrinfo (res);
    return -1;
  }
  if (GNUNET_OK !=
      GNUNET_NETWORK_socket_bind (nh,
                                  res->ai_addr,
                                  res->ai_addrlen))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "bind");
    freeaddrinfo (res);
    return -1;
  }
  freeaddrinfo (res);
  if (GNUNET_OK !=
      GNUNET_NETWORK_socket_listen (nh,
                                    UNIX_BACKLOG))
  {
    GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR,
                         "listen");
    GNUNET_SCHEDULER_shutdown ();
    return -1;
  }
  fh = GNUNET_NETWORK_get_fd (nh);
  GNUNET_NETWORK_socket_free_memory_only_ (nh);
  return fh;
}