/*
  This file is part of libmicrohttpd
  Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff

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

  This library 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
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

/**
 * @file lib/daemon_options.c
 * @brief boring functions to manipulate daemon options
 * @author Christian Grothoff
 */
#include "internal.h"
#ifdef HAVE_DLFCN_H
#include <dlfcn.h>
#endif

/**
 * Set logging method.  Specify NULL to disable logging entirely.  By
 * default (if this option is not given), we log error messages to
 * stderr.
 *
 * @param daemon which instance to setup logging for
 * @param logger function to invoke
 * @param logger_cls closure for @a logger
 */
void
MHD_daemon_set_logger (struct MHD_Daemon *daemon,
                       MHD_LoggingCallback logger,
                       void *logger_cls)
{
  daemon->logger = logger;
  daemon->logger_cls = logger_cls;
}


/**
 * Suppress use of "Date" header as this system has no RTC.
 *
 * @param daemon which instance to disable clock for.
 */
void
MHD_daemon_suppress_date_no_clock (struct MHD_Daemon *daemon)
{
  daemon->suppress_date = true;
}


/**
 * Disable use of inter-thread communication channel.
 * #MHD_daemon_disable_itc() can be used with
 * #MHD_daemon_thread_internal() to perform some additional
 * optimizations (in particular, not creating a pipe for IPC
 * signalling).  If it is used, certain functions like
 * #MHD_daemon_quiesce() or #MHD_connection_add() or
 * #MHD_action_suspend() cannot be used anymore.
 * #MHD_daemon_disable_itc() is not beneficial on platforms where
 * select()/poll()/other signal shutdown() of a listen socket.
 *
 * You should only use this function if you are sure you do
 * satisfy all of its requirements and need a generally minor
 * boost in performance.
 *
 * @param daemon which instance to disable itc for
 */
void
MHD_daemon_disable_itc (struct MHD_Daemon *daemon)
{
  daemon->disable_itc = true;
}


/**
 * Enable `turbo`.  Disables certain calls to `shutdown()`,
 * enables aggressive non-blocking optimistic reads and
 * other potentially unsafe optimizations.
 * Most effects only happen with #MHD_ELS_EPOLL.
 *
 * @param daemon which instance to enable turbo for
 */
void
MHD_daemon_enable_turbo (struct MHD_Daemon *daemon)
{
  daemon->enable_turbo = true;
}


/**
 * Disable #MHD_action_suspend() functionality.
 *
 * You should only use this function if you are sure you do
 * satisfy all of its requirements and need a generally minor
 * boost in performance.
 *
 * @param daemon which instance to disable suspend for
 */
void
MHD_daemon_disallow_suspend_resume (struct MHD_Daemon *daemon)
{
  daemon->disallow_suspend_resume = true;
}


/**
 * You need to set this option if you want to disable use of HTTP "Upgrade".
 * "Upgrade" may require usage of additional internal resources,
 * which we can avoid providing if they will not be used.
 *
 * You should only use this function if you are sure you do
 * satisfy all of its requirements and need a generally minor
 * boost in performance.
 *
 * @param daemon which instance to enable suspend/resume for
 */
void
MHD_daemon_disallow_upgrade (struct MHD_Daemon *daemon)
{
  daemon->disallow_upgrade = true;
}


/**
 * Configure TCP_FASTOPEN option, including setting a
 * custom @a queue_length.
 *
 * Note that having a larger queue size can cause resource exhaustion
 * attack as the TCP stack has to now allocate resources for the SYN
 * packet along with its DATA.
 *
 * @param daemon which instance to configure TCP_FASTOPEN for
 * @param fom under which conditions should we use TCP_FASTOPEN?
 * @param queue_length queue length to use, default is 50 if this
 *        option is never given.
 * @return #MHD_YES upon success, #MHD_NO if #MHD_FOM_REQUIRE was
 *         given, but TCP_FASTOPEN is not available on the platform
 */
enum MHD_Bool
MHD_daemon_tcp_fastopen (struct MHD_Daemon *daemon,
                         enum MHD_FastOpenMethod fom,
                         unsigned int queue_length)
{
  daemon->fast_open_method = fom;
  daemon->fo_queue_length = queue_length;
  switch (fom)
  {
  case MHD_FOM_DISABLE:
    return MHD_YES;
  case MHD_FOM_AUTO:
    return MHD_YES;
  case MHD_FOM_REQUIRE:
#ifdef TCP_FASTOPEN
    return MHD_YES;
#else
    return MHD_NO;
#endif
  }
  return MHD_NO;
}


/**
 * Bind to the given TCP port and address family.
 *
 * Ineffective in conjunction with #MHD_daemon_listen_socket().
 * Ineffective in conjunction with #MHD_daemon_bind_sa().
 *
 * If neither this option nor the other two mentioned above
 * is specified, MHD will simply not listen on any socket!
 *
 * @param daemon which instance to configure the TCP port for
 * @param af address family to use
 * @param port port to use, 0 to bind to a random (free) port
 */
void
MHD_daemon_bind_port (struct MHD_Daemon *daemon,
                      enum MHD_AddressFamily af,
                      uint16_t port)
{
  daemon->listen_af = af;
  daemon->listen_port = port;
}


/**
 * Bind to the given socket address.
 * Ineffective in conjunction with #MHD_daemon_listen_socket().
 *
 * @param daemon which instance to configure the binding address for
 * @param sa address to bind to; can be IPv4 (AF_INET), IPv6 (AF_INET6)
 *        or even a UNIX domain socket (AF_UNIX)
 * @param sa_len number of bytes in @a sa
 */
void
MHD_daemon_bind_socket_address (struct MHD_Daemon *daemon,
                                const struct sockaddr *sa,
                                size_t sa_len)
{
  memcpy (&daemon->listen_sa,
          sa,
          sa_len);
  daemon->listen_sa_len = sa_len;
}


/**
 * Use the given backlog for the listen() call.
 * Ineffective in conjunction with #MHD_daemon_listen_socket().
 *
 * @param daemon which instance to configure the backlog for
 * @param listen_backlog backlog to use
 */
void
MHD_daemon_listen_backlog (struct MHD_Daemon *daemon,
                           int listen_backlog)
{
  daemon->listen_backlog = listen_backlog;
}


/**
 * If present true, allow reusing address:port socket (by using
 * SO_REUSEPORT on most platform, or platform-specific ways).  If
 * present and set to false, disallow reusing address:port socket
 * (does nothing on most platform, but uses SO_EXCLUSIVEADDRUSE on
 * Windows).
 * Ineffective in conjunction with #MHD_daemon_listen_socket().
 *
 * @param daemon daemon to configure address reuse for
 */
void
MHD_daemon_listen_allow_address_reuse (struct MHD_Daemon *daemon)
{
  daemon->allow_address_reuse = true;
}


/**
 * Use SHOUTcast.  This will cause the response to begin
 * with the SHOUTcast "ICY" line instead of "HTTP".
 *
 * @param daemon daemon to set SHOUTcast option for
 */
_MHD_EXTERN void
MHD_daemon_enable_shoutcast (struct MHD_Daemon *daemon)
{
  daemon->enable_shoutcast = true;
}


/**
 * Accept connections from the given socket.  Socket
 * must be a TCP or UNIX domain (stream) socket.
 *
 * Unless -1 is given, this disables other listen options, including
 * #MHD_daemon_bind_sa(), #MHD_daemon_bind_port(),
 * #MHD_daemon_listen_queue() and
 * #MHD_daemon_listen_allow_address_reuse().
 *
 * @param daemon daemon to set listen socket for
 * @param listen_socket listen socket to use,
 *        MHD_INVALID_SOCKET value will cause this call to be
 *        ignored (other binding options may still be effective)
 */
void
MHD_daemon_listen_socket (struct MHD_Daemon *daemon,
                          MHD_socket listen_socket)
{
  daemon->listen_socket = listen_socket;
}


/**
 * Force use of a particular event loop system call.
 *
 * @param daemon daemon to set event loop style for
 * @param els event loop syscall to use
 * @return #MHD_NO on failure, #MHD_YES on success
 */
enum MHD_Bool
MHD_daemon_event_loop (struct MHD_Daemon *daemon,
                       enum MHD_EventLoopSyscall els)
{
  switch (els)
  {
  case MHD_ELS_AUTO:
    break; /* should always be OK */
  case MHD_ELS_SELECT:
    break; /* should always be OK */
  case MHD_ELS_POLL:
#ifdef HAVE_POLL
    break;
#else
    return MHD_NO; /* not supported */
#endif
  case MHD_ELS_EPOLL:
#ifdef EPOLL_SUPPORT
    break;
#else
    return MHD_NO; /* not supported */
#endif
  default:
    return MHD_NO; /* not supported (presumably future ABI extension) */
  }
  daemon->event_loop_syscall = els;
  return MHD_YES;
}


/**
 * Set how strictly MHD will enforce the HTTP protocol.
 *
 * @param daemon daemon to configure strictness for
 * @param sl how strict should we be
 */
void
MHD_daemon_protocol_strict_level (struct MHD_Daemon *daemon,
                                  enum MHD_ProtocolStrictLevel sl)
{
  daemon->protocol_strict_level = sl;
}


/**
 * Enable and configure TLS.
 *
 * @param daemon which instance should be configured
 * @param tls_backend which TLS backend should be used,
 *    currently only "gnutls" is supported.  You can
 *    also specify NULL for best-available (which is the default).
 * @param ciphers which ciphers should be used by TLS, default is
 *     "NORMAL"
 * @return status code, #MHD_SC_OK upon success
 *     #MHD_TLS_BACKEND_UNSUPPORTED if the @a backend is unknown
 *     #MHD_TLS_DISABLED if this build of MHD does not support TLS
 *     #MHD_TLS_CIPHERS_INVALID if the given @a ciphers are not supported
 *     by this backend
 */
enum MHD_StatusCode
MHD_daemon_set_tls_backend (struct MHD_Daemon *daemon,
                            const char *tls_backend,
                            const char *ciphers)
{
#if ! (defined(HTTPS_SUPPORT) && defined (HAVE_DLFCN_H))
  return MHD_SC_TLS_DISABLED;
#else
  char filename[1024];
  int res;
  MHD_TLS_PluginInit init;

  /* todo: .dll on W32? */
  res = MHD_snprintf_ (filename,
                       sizeof (filename),
                       "%s/libmicrohttpd_tls_%s.so",
                       MHD_PLUGIN_INSTALL_PREFIX,
                       tls_backend);
  if (0 >= res)
    return MHD_SC_TLS_BACKEND_UNSUPPORTED; /* string too long? */
  if (NULL ==
      (daemon->tls_backend_lib = dlopen (filename,
                                         RTLD_NOW | RTLD_LOCAL)))
    return MHD_SC_TLS_BACKEND_UNSUPPORTED; /* plugin not found */
  if (NULL == (init = dlsym (daemon->tls_backend_lib,
                             "MHD_TLS_init_" MHD_TLS_ABI_VERSION_STR)))

  {
    dlclose (daemon->tls_backend_lib);
    daemon->tls_backend_lib = NULL;
    return MHD_SC_TLS_BACKEND_UNSUPPORTED; /* possibly wrong version installed */
  }
  if (NULL == (daemon->tls_api = init (ciphers)))
  {
    dlclose (daemon->tls_backend_lib);
    daemon->tls_backend_lib = NULL;
    return MHD_SC_TLS_CIPHERS_INVALID; /* possibly wrong version installed */
  }
  return MHD_SC_OK;
#endif
}


/**
 * Provide TLS key and certificate data in-memory.
 *
 * @param daemon which instance should be configured
 * @param mem_key private key (key.pem) to be used by the
 *     HTTPS daemon.  Must be the actual data in-memory, not a filename.
 * @param mem_cert certificate (cert.pem) to be used by the
 *     HTTPS daemon.  Must be the actual data in-memory, not a filename.
 * @param pass passphrase phrase to decrypt 'key.pem', NULL
 *     if @param mem_key is in cleartext already
 * @return #MHD_SC_OK upon success; MHD_BACKEND_UNINITIALIZED
 *     if the TLS backend is not yet setup.
 */
enum MHD_StatusCode
MHD_daemon_tls_key_and_cert_from_memory (struct MHD_Daemon *daemon,
                                         const char *mem_key,
                                         const char *mem_cert,
                                         const char *pass)
{
#ifndef HTTPS_SUPPORT
  return MHD_SC_TLS_DISABLED;
#else
  struct MHD_TLS_Plugin *plugin;

  if (NULL == (plugin = daemon->tls_api))
    return MHD_SC_TLS_BACKEND_UNINITIALIZED;
  return plugin->init_kcp (plugin->cls,
                           mem_key,
                           mem_cert,
                           pass);
#endif
}


/**
 * Configure DH parameters (dh.pem) to use for the TLS key
 * exchange.
 *
 * @param daemon daemon to configure tls for
 * @param dh parameters to use
 * @return #MHD_SC_OK upon success; MHD_BACKEND_UNINITIALIZED
 *     if the TLS backend is not yet setup.
 */
enum MHD_StatusCode
MHD_daemon_tls_mem_dhparams (struct MHD_Daemon *daemon,
                             const char *dh)
{
#ifndef HTTPS_SUPPORT
  return MHD_SC_TLS_DISABLED;
#else
  struct MHD_TLS_Plugin *plugin;

  if (NULL == (plugin = daemon->tls_api))
    return MHD_SC_TLS_BACKEND_UNINITIALIZED;
  return plugin->init_dhparams (plugin->cls,
                                dh);
#endif
}


/**
 * Memory pointer for the certificate (ca.pem) to be used by the
 * HTTPS daemon for client authentication.
 *
 * @param daemon daemon to configure tls for
 * @param mem_trust memory pointer to the certificate
 * @return #MHD_SC_OK upon success; MHD_BACKEND_UNINITIALIZED
 *     if the TLS backend is not yet setup.
 */
enum MHD_StatusCode
MHD_daemon_tls_mem_trust (struct MHD_Daemon *daemon,
                          const char *mem_trust)
{
#ifndef HTTPS_SUPPORT
  return MHD_SC_TLS_DISABLED;
#else
  struct MHD_TLS_Plugin *plugin;

  if (NULL == (plugin = daemon->tls_api))
    return MHD_SC_TLS_BACKEND_UNINITIALIZED;
  return plugin->init_mem_trust (plugin->cls,
                                 mem_trust);
#endif
}


/**
 * Configure daemon credentials type for GnuTLS.
 *
 * @param gnutls_credentials must be a value of
 *   type `gnutls_credentials_type_t`
 * @return #MHD_SC_OK upon success; TODO: define failure modes
 */
enum MHD_StatusCode
MHD_daemon_gnutls_credentials (struct MHD_Daemon *daemon,
                               int gnutls_credentials)
{
#ifndef HTTPS_SUPPORT
  return MHD_SC_TLS_DISABLED;
#else
  struct MHD_TLS_Plugin *plugin;

  if (NULL == (plugin = daemon->tls_api))
    return MHD_SC_TLS_BACKEND_UNINITIALIZED;
  return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED;
#endif
}


/**
 * Provide TLS key and certificate data via callback.
 *
 * Use a callback to determine which X.509 certificate should be used
 * for a given HTTPS connection.  This option provides an alternative
 * to #MHD_daemon_tls_key_and_cert_from_memory().  You must use this
 * version if multiple domains are to be hosted at the same IP address
 * using TLS's Server Name Indication (SNI) extension.  In this case,
 * the callback is expected to select the correct certificate based on
 * the SNI information provided.  The callback is expected to access
 * the SNI data using `gnutls_server_name_get()`.  Using this option
 * requires GnuTLS 3.0 or higher.
 *
 * @param daemon daemon to configure callback for
 * @param cb must be of type `gnutls_certificate_retrieve_function2 *`.
 * @return #MHD_SC_OK on success
 */
enum MHD_StatusCode
MHD_daemon_gnutls_key_and_cert_from_callback (struct MHD_Daemon *daemon,
                                              void *cb)
{
#ifndef HTTPS_SUPPORT
  return MHD_SC_TLS_DISABLED;
#else
  struct MHD_TLS_Plugin *plugin;

  if (NULL == (plugin = daemon->tls_api))
    return MHD_SC_TLS_BACKEND_UNINITIALIZED;
  return MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED;
#endif
}


/**
 * Specify threading mode to use.
 *
 * @param daemon daemon to configure
 * @param tm mode to use (positive values indicate the
 *        number of worker threads to be used)
 */
void
MHD_daemon_threading_mode (struct MHD_Daemon *daemon,
                           enum MHD_ThreadingMode tm)
{
  daemon->threading_mode = tm;
}


/**
 * Set  a policy callback that accepts/rejects connections
 * based on the client's IP address.  This function will be called
 * before a connection object is created.
 *
 * @param daemon daemon to set policy for
 * @param apc function to call to check the policy
 * @param apc_cls closure for @a apc
 */
void
MHD_daemon_accept_policy (struct MHD_Daemon *daemon,
                          MHD_AcceptPolicyCallback apc,
                          void *apc_cls)
{
  daemon->accept_policy_cb = apc;
  daemon->accept_policy_cb_cls = apc_cls;
}


/**
 * Register a callback to be called first for every request
 * (before any parsing of the header).  Makes it easy to
 * log the full URL.
 *
 * @param daemon daemon for which to set the logger
 * @param cb function to call
 * @param cb_cls closure for @a cb
 */
void
MHD_daemon_set_early_uri_logger (struct MHD_Daemon *daemon,
                                 MHD_EarlyUriLogCallback cb,
                                 void *cb_cls)
{
  daemon->early_uri_logger_cb = cb;
  daemon->early_uri_logger_cb_cls = cb_cls;
}


/**
 * Register a function that should be called whenever a connection is
 * started or closed.
 *
 * @param daemon daemon to set callback for
 * @param ncc function to call to check the policy
 * @param ncc_cls closure for @a apc
 */
void
MHD_daemon_set_notify_connection (struct MHD_Daemon *daemon,
                                  MHD_NotifyConnectionCallback ncc,
                                  void *ncc_cls)
{
  daemon->notify_connection_cb = ncc;
  daemon->notify_connection_cb_cls = ncc_cls;
}


/**
 * Maximum memory size per connection.
 * Default is 32 kb (#MHD_POOL_SIZE_DEFAULT).
 * Values above 128k are unlikely to result in much benefit, as half
 * of the memory will be typically used for IO, and TCP buffers are
 * unlikely to support window sizes above 64k on most systems.
 *
 * @param daemon daemon to configure
 * @param memory_limit_b connection memory limit to use in bytes
 * @param memory_increment_b increment to use when growing the read buffer, must be smaller than @a memory_limit_b
 */
void
MHD_daemon_connection_memory_limit (struct MHD_Daemon *daemon,
                                    size_t memory_limit_b,
                                    size_t memory_increment_b)
{
  if (memory_increment_b >= memory_limit_b)
    MHD_PANIC ("Sane memory increment must be below memory limit.\n");
  daemon->connection_memory_limit_b = memory_limit_b;
  daemon->connection_memory_increment_b = memory_increment_b;
}


/**
 * Desired size of the stack for threads created by MHD.  Use 0 for
 * system default.  Only useful if the selected threading mode
 * is not #MHD_TM_EXTERNAL_EVENT_LOOP.
 *
 * @param daemon daemon to configure
 * @param stack_limit_b stack size to use in bytes
 */
void
MHD_daemon_thread_stack_size (struct MHD_Daemon *daemon,
                              size_t stack_limit_b)
{
  daemon->thread_stack_limit_b = stack_limit_b;
}


/**
 * Set maximum number of concurrent connections to accept.  If not
 * given, MHD will not enforce any limits (modulo running into
 * OS limits).  Values of 0 mean no limit.
 *
 * @param daemon daemon to configure
 * @param global_connection_limit maximum number of (concurrent)
          connections
 * @param ip_connection_limit limit on the number of (concurrent)
 *        connections made to the server from the same IP address.
 *        Can be used to prevent one IP from taking over all of
 *        the allowed connections.  If the same IP tries to
 *        establish more than the specified number of
 *        connections, they will be immediately rejected.
 */
void
MHD_daemon_connection_limits (struct MHD_Daemon *daemon,
                              unsigned int global_connection_limit,
                              unsigned int ip_connection_limit)
{
  daemon->global_connection_limit = global_connection_limit;
  daemon->ip_connection_limit = ip_connection_limit;
}


/**
 * After how many seconds of inactivity should a
 * connection automatically be timed out?
 * Use zero for no timeout, which is also the (unsafe!) default.
 *
 * @param daemon daemon to configure
 * @param timeout_s number of seconds of timeout to use
 */
void
MHD_daemon_connection_default_timeout (struct MHD_Daemon *daemon,
                                       unsigned int timeout_s)
{
  daemon->connection_default_timeout = (time_t) timeout_s;
}


/**
 * Specify a function that should be called for unescaping escape
 * sequences in URIs and URI arguments.  Note that this function
 * will NOT be used by the `struct MHD_PostProcessor`.  If this
 * option is not specified, the default method will be used which
 * decodes escape sequences of the form "%HH".
 *
 * @param daemon daemon to configure
 * @param unescape_cb function to use, NULL for default
 * @param unescape_cb_cls closure for @a unescape_cb
 */
void
MHD_daemon_unescape_cb (struct MHD_Daemon *daemon,
                        MHD_UnescapeCallback unescape_cb,
                        void *unescape_cb_cls)
{
  daemon->unescape_cb = unescape_cb;
  daemon->unescape_cb_cls = unescape_cb_cls;
}


/**
 * Set random values to be used by the Digest Auth module.  Note that
 * the application must ensure that @a buf remains allocated and
 * unmodified while the daemon is running.
 *
 * @param daemon daemon to configure
 * @param buf_size number of bytes in @a buf
 * @param buf entropy buffer
 */
void
MHD_daemon_digest_auth_random (struct MHD_Daemon *daemon,
                               size_t buf_size,
                               const void *buf)
{
#if ENABLE_DAUTH
  daemon->digest_auth_random_buf = buf;
  daemon->digest_auth_random_buf_size = buf_size;
#else
  (void) daemon;
  (void) buf_size;
  (void) buf;
  MHD_PANIC ("Digest authentication not supported by this build.\n");
#endif
}


/**
 * Length of the internal array holding the map of the nonce and
 * the nonce counter.
 *
 * @param daemon daemon to configure
 * @param nc_length desired array length
 */
enum MHD_StatusCode
MHD_daemon_digest_auth_nc_length (struct MHD_Daemon *daemon,
                                  size_t nc_length)
{
#if ENABLE_DAUTH
  if ( ( (size_t) (nc_length * sizeof (struct MHD_NonceNc)))
       / sizeof (struct MHD_NonceNc) != nc_length)
  {
#ifdef HAVE_MESSAGES
    MHD_DLOG (daemon,
              _ ("Specified value for NC_SIZE too large.\n"));
#endif
    return MHD_SC_DIGEST_AUTH_NC_LENGTH_TOO_BIG;
  }
  if (0 < nc_length)
  {
    if (NULL != daemon->nnc)
      free (daemon->nnc);
    daemon->nnc = malloc (daemon->nonce_nc_size
                          * sizeof (struct MHD_NonceNc));
    if (NULL == daemon->nnc)
    {
#ifdef HAVE_MESSAGES
      MHD_DLOG (daemon,
                _ ("Failed to allocate memory for nonce-nc map: %s\n"),
                MHD_strerror_ (errno));
#endif
      return MHD_SC_DIGEST_AUTH_NC_ALLOCATION_FAILURE;
    }
  }
  daemon->digest_nc_length = nc_length;
  return MHD_SC_OK;
#else
  (void) daemon;
  (void) nc_length;
  return MHD_SC_DIGEST_AUTH_NOT_SUPPORTED_BY_BUILD;
#endif
}


/* end of daemon_options.c */
