libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit f6e1ee426f9caebe49476ec88720d87032a433a6
parent b90f356c47e467407ae407723ddb02161b849f6a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed,  7 Feb 2018 23:28:38 +0100

more work on mhd2 API implementation

Diffstat:
Msrc/include/microhttpd2.h | 173++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/lib/daemon.c | 954++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Asrc/lib/daemon_create.c | 134+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/daemon_destroy.c | 178+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/daemon_info.c | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/daemon_options.c | 30++++++++++++++++++++++++++++--
Asrc/lib/daemon_quiesce.c | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/init.c | 156+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/init.h | 29+++++++++++++++++++++++++++++
Msrc/lib/internal.h | 604++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Asrc/lib/panic.c | 36++++++++++++++++++++++++++++++++++++
Msrc/lib/request.c | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Asrc/lib/version.c | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/microhttpd/mhd_sockets.c | 2+-
Msrc/microhttpd/mhd_sockets.h | 2+-
15 files changed, 2653 insertions(+), 122 deletions(-)

diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h @@ -129,34 +129,181 @@ enum MHD_StatusCode */ MHD_SC_DAEMON_STARTED = 10000, + + /** + * MHD does not support the requested combination of + * EPOLL with thread-per-connection mode. + */ + MHD_SC_SYSCALL_THREAD_COMBINATION_INVALID = 40000, + + /** + * MHD does not support quiescing if ITC was disabled + * and threads are used. + */ + MHD_SC_SYSCALL_QUIESCE_REQUIRES_ITC = 40001, + /** * This build of MHD does not support TLS, but the application * requested TLS. */ - MHD_TLS_DISABLED = 50000, + MHD_SC_TLS_DISABLED = 50000, /** * The application requested an unsupported TLS backend to be used. */ - MHD_TLS_BACKEND_UNSUPPORTED = 50001, + MHD_SC_TLS_BACKEND_UNSUPPORTED = 50001, /** * The application requested a TLS cipher suite which is not * supported by the selected backend. */ - MHD_TLS_CIPHERS_INVALID = 50002 + MHD_SC_TLS_CIPHERS_INVALID = 50002 /** * The application attempted to setup TLS paramters before * enabling TLS. */ - MHD_TLS_BACKEND_UNINITIALIZED = 50003, + MHD_SC_TLS_BACKEND_UNINITIALIZED = 50003, /** * The selected TLS backend does not yet support this operation. */ - MHD_TLS_BACKEND_OPERATION_UNSUPPORTED = 50004, + MHD_SC_TLS_BACKEND_OPERATION_UNSUPPORTED = 50004, + + /** + * Failed to setup ITC channel. + */ + MHD_SC_ITC_INITIALIZATION_FAILED = 50005, + + /** + * File descriptor for ITC channel too large. + */ + MHD_SC_ITC_DESCRIPTOR_TOO_LARGE = 50006, + + /** + * The specified value for the NC length is way too large + * for this platform (integer overflow on `size_t`). + */ + MHD_SC_DIGEST_AUTH_NC_LENGTH_TOO_BIG = 50007, + + /** + * We failed to allocate memory for the specified nonce + * counter array. The option was not set. + */ + MHD_SC_DIGEST_AUTH_NC_ALLOCATION_FAILURE = 50008, + + /** + * This build of the library does not support + * digest authentication. + */ + MHD_SC_DIGEST_AUTH_NOT_SUPPORTED_BY_BUILD = 50009, + + /** + * IPv6 requested but not supported by this build. + */ + MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD = 50010, + + /** + * We failed to open the listen socket. Maybe the build + * supports IPv6, but your kernel does not? + */ + MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET = 50011, + + /** + * Specified address family is not supported by this build. + */ + MHD_SC_AF_NOT_SUPPORTED_BY_BUILD = 50012, + + /** + * Failed to enable listen address reuse. + */ + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED = 50013, + + /** + * Enabling listen address reuse is not supported by this platform. + */ + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED = 50014, + + /** + * Failed to disable listen address reuse. + */ + MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_FAILED = 50015, + + /** + * Disabling listen address reuse is not supported by this platform. + */ + MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_NOT_SUPPORTED = 50016, + + /** + * We failed to explicitly enable or disable dual stack for + * the IPv6 listen socket. The socket will be used in whatever + * the default is the OS gives us. + */ + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_FAILED = 50017, + + /** + * On this platform, MHD does not support explicitly configuring + * dual stack behavior. + */ + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_NOT_SUPPORTED = 50018, + + /** + * We failed to bind the listen socket. + */ + MHD_SC_LISTEN_SOCKET_BIND_FAILED = 50019, + + /** + * Failed to enable TCP FAST OPEN option. + */ + MHD_SC_FAST_OPEN_FAILURE = 50020, + /** + * Failed to start listening on listen socket. + */ + MHD_SC_LISTEN_FAILURE = 50021, + + /** + * Failed to obtain our listen port via introspection. + */ + MHD_SC_LISTEN_PORT_INTROSPECTION_FAILURE = 50022, + + /** + * Failed to obtain our listen port via introspection + * due to unsupported address family being used. + */ + MHD_SC_LISTEN_PORT_INTROSPECTION_UNKNOWN_AF = 50023, + + /** + * We failed to set the listen socket to non-blocking. + */ + MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE = 50024, + + /** + * Listen socket value is too large (for use with select()). + */ + MHD_SC_LISTEN_SOCKET_TOO_LARGE = 50025, + + /** + * We failed to allocate memory for the thread pool. + */ + MHD_SC_THREAD_POOL_MALLOC_FAILURE = 50026, + + /** + * We failed to allocate mutex for thread pool worker. + */ + MHD_SC_THREAD_POOL_CREATE_MUTEX_FAILURE = 50027, + + /** + * We failed to initialize the main thread for listening. + */ + MHD_SC_THREAD_MAIN_LAUNCH_FAILURE = 50030, + + /** + * We failed to initialize the threads for the worker pool. + */ + MHD_SC_THREAD_POOL_LAUNCH_FAILURE = 50031, + + }; @@ -631,10 +778,17 @@ MHD_daemon_tcp_fastopen (struct MHD_Daemon *daemon, enum MHD_AddressFamily { /** + * Option not given, do not listen at all + * (unless listen socket or address specified by + * other means). + */ + MHD_AF_NONE = 0, + + /** * Pick "best" available method automatically. */ - MHD_AF_AUTO = 0, - + MHD_AF_AUTO, + /** * Use IPv4. */ @@ -662,8 +816,7 @@ enum MHD_AddressFamily * 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, i.e. #AF_INET or #AF_INET6, - * or #AF_UNSPEC for dual stack + * @param af address family to use * @param port port to use, 0 to bind to a random (free) port */ _MHD_EXTERN void @@ -1194,7 +1347,7 @@ MHD_daemon_digest_auth_random (struct MHD_Daemon *daemon, * @param daemon daemon to configure * @param nc_length desired array length */ -_MHD_EXTERN void +_MHD_EXTERN enum MHD_StatusCode MHD_daemon_digest_auth_nc_length (struct MHD_Daemon *daemon, size_t nc_length); diff --git a/src/lib/daemon.c b/src/lib/daemon.c @@ -19,154 +19,910 @@ /** * @file lib/daemon.c - * @brief main functions to create, start, quiesce and destroy a daemon + * @brief main functions to start a daemon * @author Christian Grothoff */ #include "internal.h" +/* ************************* event loops ********************** */ + + + +/* TODO: migrate! */ + + +/* ************* Functions for MHD_daemon_start() ************ */ + /** - * Logging implementation that logs to a file given - * as the @a cls. + * Set listen socket options to allow port rebinding (or not) + * depending on how MHD was configured. * - * @param cls a `FILE *` to log to - * @param sc status code of the event (ignored) - * @param fm format string (`printf()`-style) - * @param ap arguments to @a fm - * @ingroup logging + * @param daemon[in,out] the daemon with the listen socket to configure + * @return #MHD_SC_OK on success (or non-fatal errors) */ -static void -file_logger (void *cls, - enum MHD_StatusCode sc, - const char *fm, - va_list ap) +static enum MHD_StatusCode +configure_listen_reuse (struct MHD_Daemon *daemon) { - FILE *f = cls; + const MHD_SCKT_OPT_BOOL_ on = 1; - (void) sc; - (void) vfprintf (f, - fm, - ap); + /* Apply the socket options according to + listening_address_reuse. */ + /* FIXME: used to be -1/0/1, now defined as a bool! + MISMATCH! */ + if (0 == daemon->listening_address_reuse) + { +#ifndef MHD_WINSOCK_SOCKETS + /* No user requirement, use "traditional" default SO_REUSEADDR + * on non-W32 platforms, and do not fail if it doesn't work. + * Don't use it on W32, because on W32 it will allow multiple + * bind to the same address:port, like SO_REUSEPORT on others. */ + if (0 > setsockopt (listen_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*) &on, sizeof (on))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + } +#endif /* ! MHD_WINSOCK_SOCKETS */ + return MHD_SC_OK; + } + if (daemon->listening_address_reuse > 0) + { + /* User requested to allow reusing listening address:port. */ +#ifndef MHD_WINSOCK_SOCKETS + /* Use SO_REUSEADDR on non-W32 platforms, and do not fail if + * it doesn't work. */ + if (0 > setsockopt (listen_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*)&on, sizeof (on))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED; + } +#endif /* ! MHD_WINSOCK_SOCKETS */ + /* Use SO_REUSEADDR on Windows and SO_REUSEPORT on most platforms. + * Fail if SO_REUSEPORT is not defined or setsockopt fails. + */ + /* SO_REUSEADDR on W32 has the same semantics + as SO_REUSEPORT on BSD/Linux */ +#if defined(MHD_WINSOCK_SOCKETS) || defined(SO_REUSEPORT) + if (0 > setsockopt (listen_fd, + SOL_SOCKET, +#ifndef MHD_WINSOCK_SOCKETS + SO_REUSEPORT, +#else /* MHD_WINSOCK_SOCKETS */ + SO_REUSEADDR, +#endif /* MHD_WINSOCK_SOCKETS */ + (void *) &on, + sizeof (on))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_FAILED; + } +#else /* !MHD_WINSOCK_SOCKETS && !SO_REUSEPORT */ + /* we're supposed to allow address:port re-use, but + on this platform we cannot; fail hard */ +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED, + _("Cannot allow listening address reuse: SO_REUSEPORT not defined\n")); +#endif + return MHD_SC_LISTEN_ADDRESS_REUSE_ENABLE_NOT_SUPPORTED; +#endif /* !MHD_WINSOCK_SOCKETS && !SO_REUSEPORT */ + } + + /* if (daemon->listening_address_reuse < 0) */ + /* User requested to disallow reusing listening address:port. + * Do nothing except for Windows where SO_EXCLUSIVEADDRUSE + * is used and Solaris with SO_EXCLBIND. + * Fail if MHD was compiled for W32 without SO_EXCLUSIVEADDRUSE + * or setsockopt fails. + */ +#if (defined(MHD_WINSOCK_SOCKETS) && defined(SO_EXCLUSIVEADDRUSE)) || \ + (defined(__sun) && defined(SO_EXCLBIND)) + if (0 > setsockopt (listen_fd, + SOL_SOCKET, +#ifdef SO_EXCLUSIVEADDRUSE + SO_EXCLUSIVEADDRUSE, +#else /* SO_EXCLBIND */ + SO_EXCLBIND, +#endif /* SO_EXCLBIND */ + (void *) &on, + sizeof (on))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_FAILED, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_FAILED; + } +#elif defined(MHD_WINSOCK_SOCKETS) /* SO_EXCLUSIVEADDRUSE not defined on W32? */ +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_NOT_SUPPORTED, + _("Cannot disallow listening address reuse: SO_EXCLUSIVEADDRUSE not defined\n")); +#endif + return MHD_SC_LISTEN_ADDRESS_REUSE_DISABLE_NOT_SUPPORTED; +#endif /* MHD_WINSOCK_SOCKETS */ + return MHD_SC_OK; } /** - * Process escape sequences ('%HH') Updates val in place; the - * result should be UTF-8 encoded and cannot be larger than the input. - * The result must also still be 0-terminated. + * Open, configure and bind the listen socket (if required). * - * @param cls closure (use NULL) - * @param req handle to request, not used - * @param val value to unescape (modified in the process) - * @return length of the resulting val (strlen(val) maybe - * shorter afterwards due to elimination of escape sequences) + * @param daemon[in,out] daemon to open the socket for + * @return #MHD_SC_OK on success */ -static size_t -unescape_wrapper (void *cls, - struct MHD_Request *req, - char *val) +static enum MHD_StatusCode +open_listen_socket (struct MHD_Daemon *daemon) { - (void) cls; /* Mute compiler warning. */ - (void) req; /* Mute compiler warning. */ - return MHD_http_unescape (val); + enum MHD_StatusCode sc; + bool usev6; + socklen_t addrlen; + struct sockaddr_storage ss; + const struct sockaddr *sa; + + if (MHD_INVALID_SOCKET != daemon->listen_fd) + return MHD_SC_OK; /* application opened it for us! */ + + /* Determine address family */ + if (MHD_AF_NONE != daemon->address_family) + { + switch (daemon->address_family) + { + case MHD_AF_NONE: + abort (); + case MHD_AF_AUTO: +#if HAVE_INET6 + use_v6 = true; +#else + use_v6 = false; +#endif + break; + case MHD_AF_INET: + use_v6 = false; + break; + case MHD_AF_INET6: + case MHD_AF_DUAL: +#if HAVE_INET6 + use_v6 = true; + break; +#else +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD, + _("IPv6 not supported by this build\n")); +#endif + return MHD_SC_IPV6_NOT_SUPPORTED_BY_BUILD; +#endif + } + } + else if (0 != daemon->listen_sa_len) + { + /* we have a listen address, get AF from there! */ + switch (daemon->listen_sa.ss_family) + { + case AF_INET: + use_v6 = false; + break; +#ifdef AF_INET6 + case AF_INET6: + use_v6 = true; + break; +#endif +#ifdef AF_UNIX + case AF_UNIX: + // FIXME: not implemented + // (need to change MHD_socket_create_listen_() API!) +#endif + default: + return MHD_SC_AF_NOT_SUPPORTED_BY_BUILD; + } + } + else + { + /* no listening desired, that's OK */ + return MHD_SC_OK; + } + + /* try to open listen socket */ + try_open_listen_socket: + daemon->listen_socket = MHD_socket_create_listen_(use_v6); + if ( (MHD_INVALID_SOCKET == daemon->listen_socket) && + (MHD_AF_AUTO == daemon->address_family) && + (use_v6) ) + { + use_v6 = false; + goto try_open_listen_socket; + } + if (MHD_INVALID_SOCKET == daemon->listen_socket) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET, + _("Failed to create socket for listening: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_FAILED_TO_OPEN_LISTEN_SOCKET; + } + + if (MHD_SC_OK != + (sc = configure_listen_reuse (daemon))) + return sc; + + /* configure for dual stack (or not) */ + if (use_v6) + { +#if defined IPPROTO_IPV6 && defined IPV6_V6ONLY + /* Note: "IPV6_V6ONLY" is declared by Windows Vista ff., see "IPPROTO_IPV6 Socket Options" + (http://msdn.microsoft.com/en-us/library/ms738574%28v=VS.85%29.aspx); + and may also be missing on older POSIX systems; good luck if you have any of those, + your IPv6 socket may then also bind against IPv4 anyway... */ + const MHD_SCKT_OPT_BOOL_ v6_only = + (MHD_AF_INET6 == daemon->address_family); + if (0 > setsockopt (listen_fd, + IPPROTO_IPV6, + IPV6_V6ONLY, + (const void *) &v6_only, + sizeof (v6_only))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_FAILED, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + } +#else +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_DUAL_STACK_CONFIGURATION_NOT_SUPPORTED, + _("Cannot explicitly setup dual stack behavior on this platform\n")); +#endif +#endif + } + + /* Determine address to bind to */ + if (0 != daemon->listen_sa_len) + { + /* Bind address explicitly given */ + sa = daemon->listen_sa; + addrlen = daemon->listen_sa_len; + } + else + { + /* Compute bind address based on port and AF */ +#if HAVE_INET6 + if (use_v6) + { +#ifdef IN6ADDR_ANY_INIT + static const struct in6_addr static_in6any = IN6ADDR_ANY_INIT; +#endif + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *) &ss; + + addrlen = sizeof (struct sockaddr_in6); + memset (sin6, + 0, + sizeof (struct sockaddr_in6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons (daemon->listen_port); +#ifdef IN6ADDR_ANY_INIT + sin6->sin6_addr = static_in6any; +#endif +#if HAVE_SOCKADDR_IN_SIN_LEN + sin6->sin6_len = sizeof (struct sockaddr_in6); +#endif + } + else +#endif + { + struct sockaddr_in *sin4 = (struct sockaddr_in *) &ss; + + addrlen = sizeof (struct sockaddr_in); + memset (sin4, + 0, + sizeof (struct sockaddr_in)); + sin4->sin_family = AF_INET; + sin4->sin_port = htons (daemon->listen_port); + if (0 != INADDR_ANY) + sin4->sin_addr.s_addr = htonl (INADDR_ANY); +#if HAVE_SOCKADDR_IN_SIN_LEN + sin4->sin_len = sizeof (struct sockaddr_in); +#endif + } + sa = (const struct sockaddr *) ss; + } + + /* actually do the bind() */ + if (-1 == bind (daemon->listen_socket, + sa, + addrlen)) + { +#ifdef HAVE_MESSAGES + unsigned int port = 0; + + switch (sa->sa_family) + { + case AF_INET: + if (addrlen == sizeof (struct sockaddr_in)) + port = ntohs (((const struct sockaddr_in*)sa)->sin_port); + else + port = UINT16_MAX + 1; /* indicate size error */ + break; + case AF_INET6: + if (addrlen == sizeof (struct sockaddr_in6)) + port = ntohs (((const struct sockaddr_in6*)sa)->sin_port); + else + port = UINT16_MAX + 1; /* indicate size error */ + break; + default: + port = UINT_MAX; /* AF_UNIX? */ + break; + } + MHD_DLOG (daemon, + MHD_SC_LISTEN_SOCKET_BIND_FAILED, + _("Failed to bind to port %u: %s\n"), + port, + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_LISTEN_SOCKET_BIND_FAILED; + } + + /* setup TCP_FASTOPEN */ +#ifdef TCP_FASTOPEN + if (MHD_FOM_DISABLE != daemon->fast_open_method) + { + if (0 != setsockopt (daemon->listen_socket, + IPPROTO_TCP, + TCP_FASTOPEN, + &daemon->fastopen_queue_size, + sizeof (daemon->fastopen_queue_size))) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_FAST_OPEN_FAILURE, + _("setsockopt failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + if (MHD_FOM_REQUIRE == daemon->fast_open_method) + return MHD_SC_FAST_OPEN_FAILURE; + } + } +#endif + + /* setup listening */ + if (0 > listen (daemon->listen_socket, + daemon->listen_backlog_size)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_FAILURE, + _("Failed to listen for connections: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_SC_LISTEN_FAILURE; + } + return MHD_SC_OK; } -/** - * Create (but do not yet start) an MHD daemon. - * Usually, you will want to set various options before - * starting the daemon with #MHD_daemon_start(). +/** + * Obtain the listen port number from the socket (if it + * was not explicitly set by us, i.e. if we were given + * a listen socket or if the port was 0 and the OS picked + * a free one). * - * @param cb function to be called for incoming requests - * @param cb_cls closure for @a cb - * @return NULL on error + * @param daemon[in,out] daemon to obtain the port number for */ -struct MHD_Daemon * -MHD_daemon_create (MHD_RequestCallback cb, - void *cb_cls) +static void +get_listen_port_number (struct MHD_Daemon *daemon) { - struct MHD_Daemon *daemon; - - MHD_check_global_init_(); - if (NULL == cb) - return NULL; - if (NULL == (daemon = malloc (sizeof (struct MHD_Daemon)))) - return NULL; - memset (daemon, + struct sockaddr_storage servaddr; + socklen_t addrlen; + + if ( (0 != daemon->port) || + (MHD_INVALID_SOCKET == daemon->listen_socket) ) + return; /* nothing to be done */ + + memset (&servaddr, 0, - sizeof (struct MHD_Daemon)); - daemon->rc = cb; - daemon->rc_cls = cb_cls; - daemon->logger = &file_logger; - daemon->logger_cls = stderr; - daemon->unescape_cb = &unescape_wrapper; - daemon->tls_ciphers = TLS_CIPHERS_DEFAULT; - daemon->connection_memory_limit_b = MHD_POOL_SIZE_DEFAULT; - daemon->connection_memory_increment_b = BUF_INC_SIZE_DEFAULT; -#if ENABLE_DAUTH - daemon->digest_nc_length = DIGEST_NC_LENGTH_DEFAULT; -#endif - daemon->listen_backlog = LISTEN_BACKLOG_DEFAULT; - daemon->fo_queue_length = FO_QUEUE_LENGTH_DEFAULT; - daemon->listen_socket = MHD_INVALID_SOCKET; - return daemon; + sizeof (struct sockaddr_storage)); + addrlen = sizeof (servaddr); + if (0 != getsockname (daemon->listen_socket, + (struct sockaddr *) &servaddr, + &addrlen)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_PORT_INTROSPECTION_FAILURE, + _("Failed to get listen port number: %s\n"), + MHD_socket_last_strerr_ ()); +#endif /* HAVE_MESSAGES */ + return; + } +#ifdef MHD_POSIX_SOCKETS + if (sizeof (servaddr) < addrlen) + { + /* should be impossible with `struct sockaddr_storage` */ +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_PORT_INTROSPECTION_FAILURE, + _("Failed to get listen port number (`struct sockaddr_storage` too small!?)\n")); +#endif /* HAVE_MESSAGES */ + return; + } +#endif /* MHD_POSIX_SOCKETS */ + switch (servaddr.ss_family) + { + case AF_INET: + { + struct sockaddr_in *s4 = (struct sockaddr_in *) &servaddr; + + daemon->port = ntohs (s4->sin_port); + break; + } +#ifdef HAVE_INET6 + case AF_INET6: + { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &servaddr; + + daemon->port = ntohs(s6->sin6_port); + break; + } +#endif /* HAVE_INET6 */ +#ifdef AF_UNIX + case AF_UNIX: + daemon->port = 0; /* special value for UNIX domain sockets */ + break; +#endif + default: +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_PORT_INTROSPECTION_UNKNOWN_AF, + _("Unknown address family!\n")); +#endif + daemon->port = 0; /* ugh */ + break; + } } /** - * Start a webserver. + * Setup epoll() FD for the daemon and initialize it to listen + * on the listen FD. + * @remark To be called only from thread that process + * daemon's select()/poll()/etc. * - * @param daemon daemon to start; you can no longer set - * options on this daemon after this call! + * @param daemon daemon to initialize for epoll() * @return #MHD_SC_OK on success - * @ingroup event */ -enum MHD_StatusCode -MHD_daemon_start (struct MHD_Daemon *daemon) +static enum MHD_StatusCode +setup_epoll_to_listen (struct MHD_Daemon *daemon) { - - return -1; + struct epoll_event event; + MHD_socket ls; + + /* FIXME: update function! */ + daemon->epoll_fd = setup_epoll_fd (daemon); + if (-1 == daemon->epoll_fd) + return MHD_NO; +#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT) + if (0 != (MHD_ALLOW_UPGRADE & daemon->options)) + { + daemon->epoll_upgrade_fd = setup_epoll_fd (daemon); + if (MHD_INVALID_SOCKET == daemon->epoll_upgrade_fd) + return MHD_NO; + } +#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */ + if ( (MHD_INVALID_SOCKET == (ls = daemon->listen_fd)) || + (daemon->was_quiesced) ) + return MHD_YES; /* non-listening daemon */ + event.events = EPOLLIN; + event.data.ptr = daemon; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + ls, + &event)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _("Call to epoll_ctl failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + daemon->listen_socket_in_epoll = true; + if (MHD_ITC_IS_VALID_(daemon->itc)) + { + event.events = EPOLLIN; + event.data.ptr = (void *) epoll_itc_marker; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + MHD_itc_r_fd_ (daemon->itc), + &event)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + _("Call to epoll_ctl failed: %s\n"), + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + } + return MHD_SC_OK; } +#endif /** - * Stop accepting connections from the listening socket. Allows - * clients to continue processing, but stops accepting new - * connections. Note that the caller is responsible for closing the - * returned socket; however, if MHD is run using threads (anything but - * external select mode), it must not be closed until AFTER - * #MHD_stop_daemon has been called (as it is theoretically possible - * that an existing thread is still using it). + * Thread that runs the polling loop until the daemon + * is explicitly shut down. * - * Note that some thread modes require the caller to have passed - * #MHD_USE_ITC when using this API. If this daemon is - * in one of those modes and this option was not given to - * #MHD_start_daemon, this function will return #MHD_INVALID_SOCKET. + * @param cls `struct MHD_Deamon` to run select loop in a thread for + * @return always 0 (on shutdown) + */ +static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_ +MHD_polling_thread (void *cls) +{ + struct MHD_Daemon *daemon = cls; + + MHD_thread_init_ (&daemon->pid); + while (! daemon->shutdown) + { + switch (daemon->event_loop_syscall) + { + case MHD_ELS_AUTO: + MHD_PANIC ("MHD_ELS_AUTO should have been mapped to preferred style"); + break; + case MHD_ELS_SELECT: + MHD_select (daemon, + MHD_YES); + break; + case MHD_ELS_POLL: + MHD_poll (daemon, + MHD_YES); + break; + case MHD_ELS_EPOLL: +#ifdef EPOLL_SUPPORT + MHD_epoll (daemon, + MHD_YES); +#else + MHD_PANIC ("MHD_ELS_EPOLL not supported, should have failed earlier"); +#endif + break; + } + MHD_cleanup_connections (daemon); + } + /* Resume any pending for resume connections, join + * all connection's threads (if any) and finally cleanup + * everything. */ + close_all_connections (daemon); + + return (MHD_THRD_RTRN_TYPE_)0; +} + + +/** + * Setup the thread pool (if needed). * - * @param daemon daemon to stop accepting new connections for - * @return old listen socket on success, #MHD_INVALID_SOCKET if - * the daemon was already not listening anymore, or - * was never started - * @ingroup specialized + * @param daemon[in,out] daemon to setup thread pool for + * @return #MHD_SC_OK on success */ -MHD_socket -MHD_daemon_quiesce (struct MHD_Daemon *daemon) +static enum MHD_StatusCode +setup_thread_pool (struct MHD_Daemon *daemon) { - return -1; + /* Coarse-grained count of connections per thread (note error + * due to integer division). Also keep track of how many + * connections are leftover after an equal split. */ + unsigned int conns_per_thread = daemon->connection_limit + / daemon->threading_model; + unsigned int leftover_conns = daemon->connection_limit + % daemon->threading_model; + unsigned int i; + enum MHD_StatusCode sc; + + /* Allocate memory for pooled objects */ + daemon->worker_pool = calloc (daemon->threading_model, + sizeof (struct MHD_Daemon)); + if (NULL == daemon->worker_pool) + return MHD_SC_THREAD_POOL_MALLOC_FAILURE; + + /* Start the workers in the pool */ + for (i = 0; i < daemon->threading_model; i++) + { + /* Create copy of the Daemon object for each worker */ + struct MHD_Daemon *d = &daemon->worker_pool[i]; + + memcpy (d, + daemon, + sizeof (struct MHD_Daemon)); + /* Adjust pooling params for worker daemons; note that memcpy() + has already copied MHD_USE_INTERNAL_POLLING_THREAD thread model into + the worker threads. */ + d->master = daemon; + d->worker_pool_size = 0; + d->worker_pool = NULL; + /* Divide available connections evenly amongst the threads. + * Thread indexes in [0, leftover_conns) each get one of the + * leftover connections. */ + d->connection_limit = conns_per_thread; + if (i < leftover_conns) + ++d->connection_limit; + + if (! daemon->disable_itc) + { + if (! MHD_itc_init_ (d->itc)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_ITC_INITIALIZATION_FAILED, + _("Failed to create worker inter-thread communication channel: %s\n"), + MHD_itc_last_strerror_() ); +#endif + sc = MHD_SC_ITC_INITIALIZATION_FAILED; + goto thread_failed; + } + if ( (MHD_ELS_SELECT == daemon->event_loop_syscall) && + (! MHD_SCKT_FD_FITS_FDSET_(MHD_itc_r_fd_ (d->itc), + NULL)) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_ITC_DESCRIPTOR_TOO_LARGE, + _("File descriptor for inter-thread communication channel exceeds maximum value\n")); +#endif + MHD_itc_destroy_chk_ (d->itc); + sc = MHD_SC_ITC_DESCRIPTOR_TOO_LARGE; + goto thread_failed; + } + } + else + { + MHD_itc_set_invalid_ (d->itc); + } + +#ifdef EPOLL_SUPPORT + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && + (MHD_SC_OK != (sc = setup_epoll_to_listen (d))) ) + goto thread_failed; +#endif + + /* Must init cleanup connection mutex for each worker */ + if (! MHD_mutex_init_ (&d->cleanup_connection_mutex)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_THREAD_POOL_CREATE_MUTEX_FAILURE, + _("MHD failed to initialize cleanup connection mutex\n")); +#endif + if (! daemon->disable_itc) + MHD_itc_destroy_chk_ (d->itc); + sc = MHD_SC_THREAD_POOL_CREATE_MUTEX_FAILURE; + goto thread_failed; + } + + /* Spawn the worker thread */ + if (! MHD_create_named_thread_ (&d->pid, + "MHD-worker", + daemon->thread_stack_size, + &MHD_polling_thread, + d)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_THREAD_POOL_LAUNCH_FAILURE, + _("Failed to create pool thread: %s\n"), + MHD_strerror_ (errno)); +#endif + sc = MHD_SC_THREAD_POOL_LAUNCH_FAILURE; + /* Free memory for this worker; cleanup below handles + * all previously-created workers. */ + if (! daemon->disable_itc) + MHD_itc_destroy_chk_ (d->itc); + MHD_mutex_destroy_chk_ (&d->cleanup_connection_mutex); + goto thread_failed; + } + } /* end for() */ + return MHD_SC_OK; + +thread_failed: + /* If no worker threads created, then shut down normally. Calling + MHD_stop_daemon (as we do below) doesn't work here since it + assumes a 0-sized thread pool means we had been in the default + MHD_USE_INTERNAL_POLLING_THREAD mode. */ + if (0 == i) + { + if (NULL != daemon->worker_pool) + { + free (daemon->worker_pool); + daemon->worker_pool = NULL; + } + return MHD_SC_THREAD_LAUNCH_FAILURE; + } + /* Shutdown worker threads we've already created. Pretend + as though we had fully initialized our daemon, but + with a smaller number of threads than had been + requested. */ + daemon->worker_pool_size = i; + daemon->listen_socket = MHD_daemon_quiesce (daemon); + return MHD_SC_THREAD_LAUNCH_FAILURE; } /** - * Shutdown and destroy an HTTP daemon. + * Start a webserver. * - * @param daemon daemon to stop + * @param daemon daemon to start; you can no longer set + * options on this daemon after this call! + * @return #MHD_SC_OK on success * @ingroup event */ -void -MHD_daemon_destroy (struct MHD_Daemon *daemon) +enum MHD_StatusCode +MHD_daemon_start (struct MHD_Daemon *daemon) { - free (daemon); + enum MHD_StatusCode sc; + + if (MHD_ELS_AUTO == daemon->event_loop_syscall) + { +#if EPOLL_SUPPORT + /* We do not support thread-per-connection in combination + with epoll, so use poll in this case, otherwise prefer + epoll. */ + if (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_model) + daemon->event_loop_syscall = MHD_ELS_POLL; + else + daemon->event_loop_syscall = MHD_ELS_EPOLL; +#elif HAVE_POLL + daemon->event_loop_syscall = MHD_ELS_POLL; +#else + daemon->event_loop_syscall = MHD_ELS_SELECT; +#endif + } + +#ifdef EPOLL_SUPPORT + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && + (0 == daemon->worker_pool_size) && + (MHD_INVALID_SOCKET != daemon->listen_socket) && + (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_model) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_SYSCALL_THREAD_COMBINATION_INVALID, + _("Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_EPOLL is not supported.\n")); +#endif + return MHD_SC_SYSCALL_THREAD_COMBINATION_INVALID; + } +#endif + + /* Setup ITC */ + if ( (! daemon->disable_itc) && + (0 == daemon->worker_pool_size) ) + { + if (! MHD_itc_init_ (daemon->itc)) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_ITC_INITIALIZATION_FAILED, + _("Failed to create inter-thread communication channel: %s\n"), + MHD_itc_last_strerror_ ()); +#endif + return MHD_SC_ITC_INITIALIZATION_FAILED; + } + if ( (MHD_ELS_SELECT == daemon->event_loop_syscall) && + (! MHD_SCKT_FD_FITS_FDSET_(MHD_itc_r_fd_ (daemon->itc), + NULL)) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_ITC_DESCRIPTOR_TOO_LARGE, + _("File descriptor for inter-thread communication channel exceeds maximum value\n")); +#endif + return MHD_SC_ITC_DESCRIPTOR_TOO_LARGE; + } + } + + if (MHD_SC_OK != (sc = open_listen_socket (daemon))) + return sc; + + /* Check listen socket is in range (if we are limited) */ + if ( (MHD_INVALID_SOCKET != daemon->listen_socket) && + (MHD_ELS_SELECT == daemon->event_loop_syscall) && + (! MHD_SCKT_FD_FITS_FDSET_(daemon->listen_socket, + NULL)) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_SOCKET_TOO_LARGE, + _("Socket descriptor larger than FD_SETSIZE: %d > %d\n"), + daemon->listen_socket, + FD_SETSIZE); +#endif + return MHD_SC_LISTEN_SOCKET_TOO_LARGE; + } + + /* set listen socket to non-blocking */ + if ( (MHD_INVALID_SOCKET != daemon->listen_socket) && + (! MHD_socket_nonblocking_ (daemon->listen_socket)) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE, + _("Failed to set nonblocking mode on listening socket: %s\n"), + MHD_socket_last_strerr_()); +#endif + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) || + (daemon->worker_pool_size > 0) ) + { + /* Accept must be non-blocking. Multiple children may wake + * up to handle a new connection, but only one will win the + * race. The others must immediately return. As this is + * not possible, we must fail hard here. */ + return MHD_SC_LISTEN_SOCKET_NONBLOCKING_FAILURE; + } + } + +#ifdef EPOLL_SUPPORT + /* Setup epoll */ + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && + (0 == daemon->worker_pool_size) && + (MHD_INVALID_SOCKET != daemon->listen_socket) && + (MHD_SC_OK != (sc = setup_epoll_to_listen (daemon))) ) + return sc; +#endif + + /* Setup main listen thread (only if we have no thread pool or + external event loop and do have a listen socket) */ + /* FIXME: why no worker thread if we have no listen socket? */ + if ( ( (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_model) || + (1 == daemon->threading_model) ) && + (MHD_INVALID_SOCKET != daemon->listen_socket) && + (! MHD_create_named_thread_ (&daemon->pid, + (MHD_TM_THREAD_PER_CONNECTION == daemon->threading_model) + ? "MHD-listen" + : "MHD-single", + daemon->thread_stack_size, + &MHD_polling_thread, + daemon) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_THREAD_MAIN_LAUNCH_FAILURE, + _("Failed to create listen thread: %s\n"), + MHD_strerror_ (errno)); +#endif + return MHD_SC_THREAD_MAIN_LAUNCH_FAILURE; + } + + /* Setup worker threads */ + /* FIXME: why no thread pool if we have no listen socket? */ + if ( (1 < daemon->threading_model) && + (MHD_INVALID_SOCKET != daemon->listen_socket) && + (MHD_SC_OK != (sc = setup_thread_pool (daemon))) ) + return sc; + + return MHD_SC_OK; } + /* end of daemon.c */ diff --git a/src/lib/daemon_create.c b/src/lib/daemon_create.c @@ -0,0 +1,134 @@ +/* + 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_create.c + * @brief main functions to create a daemon + * @author Christian Grothoff + */ +#include "internal.h" + + +/** + * Logging implementation that logs to a file given + * as the @a cls. + * + * @param cls a `FILE *` to log to + * @param sc status code of the event (ignored) + * @param fm format string (`printf()`-style) + * @param ap arguments to @a fm + * @ingroup logging + */ +static void +file_logger (void *cls, + enum MHD_StatusCode sc, + const char *fm, + va_list ap) +{ + FILE *f = cls; + + (void) sc; + (void) vfprintf (f, + fm, + ap); +} + + +/** + * Process escape sequences ('%HH') Updates val in place; the + * result should be UTF-8 encoded and cannot be larger than the input. + * The result must also still be 0-terminated. + * + * @param cls closure (use NULL) + * @param req handle to request, not used + * @param val value to unescape (modified in the process) + * @return length of the resulting val (strlen(val) maybe + * shorter afterwards due to elimination of escape sequences) + */ +static size_t +unescape_wrapper (void *cls, + struct MHD_Request *req, + char *val) +{ + (void) cls; /* Mute compiler warning. */ + (void) req; /* Mute compiler warning. */ + return MHD_http_unescape (val); +} + + +/** + * Create (but do not yet start) an MHD daemon. + * Usually, you will want to set various options before + * starting the daemon with #MHD_daemon_start(). + * + * @param cb function to be called for incoming requests + * @param cb_cls closure for @a cb + * @return NULL on error + */ +struct MHD_Daemon * +MHD_daemon_create (MHD_RequestCallback cb, + void *cb_cls) +{ + struct MHD_Daemon *daemon; + + MHD_check_global_init_(); + if (NULL == cb) + return NULL; + if (NULL == (daemon = malloc (sizeof (struct MHD_Daemon)))) + return NULL; + memset (daemon, + 0, + sizeof (struct MHD_Daemon)); + daemon->rc = cb; + daemon->rc_cls = cb_cls; + daemon->logger = &file_logger; + daemon->logger_cls = stderr; + daemon->unescape_cb = &unescape_wrapper; + daemon->tls_ciphers = TLS_CIPHERS_DEFAULT; + daemon->connection_memory_limit_b = MHD_POOL_SIZE_DEFAULT; + daemon->connection_memory_increment_b = BUF_INC_SIZE_DEFAULT; +#if ENABLE_DAUTH + daemon->digest_nc_length = DIGEST_NC_LENGTH_DEFAULT; +#endif + daemon->listen_backlog = LISTEN_BACKLOG_DEFAULT; + daemon->fo_queue_length = FO_QUEUE_LENGTH_DEFAULT; + daemon->listen_socket = MHD_INVALID_SOCKET; + + if (! MHD_mutex_init_ (&daemon->cleanup_connection_mutex)) + { + free (daemon); + return NULL; + } + if (! MHD_mutex_init_ (&daemon->per_ip_connection_mutex)) + { + MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + free (daemon); + return NULL; + } +#ifdef DAUTH_SUPPORT + if (! MHD_mutex_init_ (&daemon->nnc_lock)) + { + MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex); + free (daemon); + return NULL; + } +#endif + return daemon; +} diff --git a/src/lib/daemon_destroy.c b/src/lib/daemon_destroy.c @@ -0,0 +1,178 @@ +/* + 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_destroy.c + * @brief main functions to destroy a daemon + * @author Christian Grothoff + */ +#include "internal.h" + +/* TODO: migrate logic below! */ + +/** + * Shutdown and destroy an HTTP daemon. + * + * @param daemon daemon to stop + * @ingroup event + */ +void +MHD_daemon_destroy (struct MHD_Daemon *daemon) +{ + MHD_socket fd; + unsigned int i; + + if (NULL == daemon) + return; + daemon->shutdown = true; + if (daemon->was_quiesced) + fd = MHD_INVALID_SOCKET; /* Do not use FD if daemon was quiesced */ + else + fd = daemon->listen_socket; + + /* FIXME: convert from here to microhttpd2-style API! */ + + if (NULL != daemon->worker_pool) + { /* Master daemon with worker pool. */ + mhd_assert (1 < daemon->worker_pool_size); + mhd_assert (0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)); + + /* Let workers shutdown in parallel. */ + for (i = 0; i < daemon->worker_pool_size; ++i) + { + daemon->worker_pool[i].shutdown = true; + if (MHD_ITC_IS_VALID_(daemon->worker_pool[i].itc)) + { + if (! MHD_itc_activate_ (daemon->worker_pool[i].itc, "e")) + MHD_PANIC (_("Failed to signal shutdown via inter-thread communication channel.")); + } + else + mhd_assert (MHD_INVALID_SOCKET != fd); + } +#ifdef HAVE_LISTEN_SHUTDOWN + if (MHD_INVALID_SOCKET != fd) + { + (void) shutdown (fd, + SHUT_RDWR); + } +#endif /* HAVE_LISTEN_SHUTDOWN */ + for (i = 0; i < daemon->worker_pool_size; ++i) + { + MHD_stop_daemon (&daemon->worker_pool[i]); + } + free (daemon->worker_pool); + mhd_assert (MHD_ITC_IS_INVALID_(daemon->itc)); +#ifdef EPOLL_SUPPORT + mhd_assert (-1 == daemon->epoll_fd); +#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT) + mhd_assert (-1 == daemon->epoll_upgrade_fd); +#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */ +#endif /* EPOLL_SUPPORT */ + } + else + { /* Worker daemon or single daemon. */ + if (0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) + { /* Worker daemon or single daemon with internal thread(s). */ + mhd_assert (0 == daemon->worker_pool_size); + if (0 != (MHD_TEST_ALLOW_SUSPEND_RESUME & daemon->options)) + resume_suspended_connections (daemon); + + if (0 != (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) + { + /* Separate thread(s) is used for polling sockets. */ + if (MHD_ITC_IS_VALID_(daemon->itc)) + { + if (! MHD_itc_activate_ (daemon->itc, "e")) + MHD_PANIC (_("Failed to signal shutdown via inter-thread communication channel")); + } + else + { +#ifdef HAVE_LISTEN_SHUTDOWN + if (MHD_INVALID_SOCKET != fd) + { + if (NULL == daemon->master) + (void) shutdown (fd, + SHUT_RDWR); + } + else +#endif /* HAVE_LISTEN_SHUTDOWN */ + mhd_assert (false); /* Should never happen */ + } + + if (! MHD_join_thread_ (daemon->pid.handle)) + { + MHD_PANIC (_("Failed to join a thread\n")); + } + /* close_all_connections() was called in daemon thread. */ + } + } + else + { + /* No internal threads are used for polling sockets. */ + close_all_connections (daemon); + } + if (MHD_ITC_IS_VALID_ (daemon->itc)) + MHD_itc_destroy_chk_ (daemon->itc); + +#ifdef EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL)) && + (-1 != daemon->epoll_fd) ) + MHD_socket_close_chk_ (daemon->epoll_fd); +#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT) + if ( (0 != (daemon->options & MHD_USE_EPOLL)) && + (-1 != daemon->epoll_upgrade_fd) ) + MHD_socket_close_chk_ (daemon->epoll_upgrade_fd); +#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */ +#endif /* EPOLL_SUPPORT */ + + MHD_mutex_destroy_chk_ (&daemon->cleanup_connection_mutex); + } + + if (NULL != daemon->master) + return; + /* Cleanup that should be done only one time in master/single daemon. + * Do not perform this cleanup in worker daemons. */ + + if (MHD_INVALID_SOCKET != fd) + MHD_socket_close_chk_ (fd); + + /* TLS clean up */ +#ifdef HTTPS_SUPPORT + if (daemon->have_dhparams) + { + gnutls_dh_params_deinit (daemon->https_mem_dhparams); + daemon->have_dhparams = false; + } + if (0 != (daemon->options & MHD_USE_TLS)) + { + gnutls_priority_deinit (daemon->priority_cache); + if (daemon->x509_cred) + gnutls_certificate_free_credentials (daemon->x509_cred); + } +#endif /* HTTPS_SUPPORT */ + +#ifdef DAUTH_SUPPORT + free (daemon->nnc); + MHD_mutex_destroy_chk_ (&daemon->nnc_lock); +#endif + MHD_mutex_destroy_chk_ (&daemon->per_ip_connection_mutex); + free (daemon); +} + +/* end of daemon_destroy.c */ diff --git a/src/lib/daemon_info.c b/src/lib/daemon_info.c @@ -0,0 +1,60 @@ + +/** + * Obtain information about the given daemon + * (not fully implemented!). + * + * @param daemon what daemon to get information about + * @param info_type what information is desired? + * @param ... depends on @a info_type + * @return NULL if this information is not available + * (or if the @a info_type is unknown) + * @ingroup specialized + */ +const union MHD_DaemonInfo * +MHD_get_daemon_info (struct MHD_Daemon *daemon, + enum MHD_DaemonInfoType info_type, + ...) +{ + if (NULL == daemon) + return NULL; + switch (info_type) + { + case MHD_DAEMON_INFO_KEY_SIZE: + return NULL; /* no longer supported */ + case MHD_DAEMON_INFO_MAC_KEY_SIZE: + return NULL; /* no longer supported */ + case MHD_DAEMON_INFO_LISTEN_FD: + return (const union MHD_DaemonInfo *) &daemon->listen_fd; +#ifdef EPOLL_SUPPORT + case MHD_DAEMON_INFO_EPOLL_FD: + return (const union MHD_DaemonInfo *) &daemon->epoll_fd; +#endif + case MHD_DAEMON_INFO_CURRENT_CONNECTIONS: + if (0 == (daemon->options & MHD_USE_INTERNAL_POLLING_THREAD)) + { + /* Assume that MHD_run() in not called in other thread + * at the same time. */ + MHD_cleanup_connections (daemon); + } + else if (daemon->worker_pool) + { + unsigned int i; + /* Collect the connection information stored in the workers. */ + daemon->connections = 0; + for (i = 0; i < daemon->worker_pool_size; i++) + { + /* FIXME: next line is thread-safe only if read is atomic. */ + daemon->connections += daemon->worker_pool[i].connections; + } + } + return (const union MHD_DaemonInfo *) &daemon->connections; + case MHD_DAEMON_INFO_FLAGS: + return (const union MHD_DaemonInfo *) &daemon->options; + case MHD_DAEMON_INFO_BIND_PORT: + return (const union MHD_DaemonInfo *) &daemon->port; + default: + return NULL; + } +} + + diff --git a/src/lib/daemon_options.c b/src/lib/daemon_options.c @@ -698,14 +698,40 @@ MHD_daemon_digest_auth_random (struct MHD_Daemon *daemon, * @param daemon daemon to configure * @param nc_length desired array length */ -void +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_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_DIGEST_AUTH_NC_ALLOCATION_FAILURE; + } + } daemon->digest_nc_length = nc_length; + return MHD_SC_OK; #else - MHD_PANIC ("digest authentication not supported by this build"); + return MHD_DIGEST_AUTH_NOT_SUPPORTED_BY_BUILD; #endif } diff --git a/src/lib/daemon_quiesce.c b/src/lib/daemon_quiesce.c @@ -0,0 +1,127 @@ +/* + 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.c + * @brief main functions to quiesce a daemon + * @author Christian Grothoff + */ +#include "internal.h" + + +/** + * Stop accepting connections from the listening socket. Allows + * clients to continue processing, but stops accepting new + * connections. Note that the caller is responsible for closing the + * returned socket; however, if MHD is run using threads (anything but + * external select mode), it must not be closed until AFTER + * #MHD_stop_daemon has been called (as it is theoretically possible + * that an existing thread is still using it). + * + * Note that some thread modes require the caller to have passed + * #MHD_USE_ITC when using this API. If this daemon is + * in one of those modes and this option was not given to + * #MHD_start_daemon, this function will return #MHD_INVALID_SOCKET. + * + * @param daemon daemon to stop accepting new connections for + * @return old listen socket on success, #MHD_INVALID_SOCKET if + * the daemon was already not listening anymore, or + * was never started + * @ingroup specialized + */ +MHD_socket +MHD_daemon_quiesce (struct MHD_Daemon *daemon) +{ + MHD_socket listen_socket; + + if (MHD_INVALID_SOCKET == (listen_socket = daemon->listen_socket)) + return MHD_INVALID_SOCKET; + if ( (daemon->disable_itc) && + (MHD_TM_EXTERNAL_EVENT_LOOP != daemon->threading_model) ) + { +#ifdef HAVE_MESSAGES + MHD_DLOG (daemon, + MHD_SC_SYSCALL_QUIESCE_REQUIRES_ITC, + "Using MHD_quiesce_daemon in this mode requires ITC\n"); +#endif + return MHD_INVALID_SOCKET; + } + + if (NULL != daemon->worker_pool) + { + unsigned int i; + + for (i = 0; i < daemon->threading_model; i++) + { + struct MHD_Daemon *worker = &daemon->worker_pool[i]; + + worker->was_quiesced = true; +#ifdef EPOLL_SUPPORT + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && + (-1 != worker->epoll_fd) && + (worker->listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (worker->epoll_fd, + EPOLL_CTL_DEL, + listen_socket, + NULL)) + MHD_PANIC (_("Failed to remove listen FD from epoll set\n")); + worker->listen_socket_in_epoll = false; + } + else +#endif + if (MHD_ITC_IS_VALID_(worker->itc)) + { + if (! MHD_itc_activate_ (worker->itc, + "q")) + MHD_PANIC (_("Failed to signal quiesce via inter-thread communication channel")); + } + } + daemon->was_quiesced = true; +#ifdef EPOLL_SUPPORT + if ( (MHD_ELS_EPOLL == daemon->event_loop_syscall) && + (-1 != daemon->epoll_fd) && + (daemon->listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + listen_socket, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->listen_socket_in_epoll = false; + } +#endif + } + + if ( (MHD_ITC_IS_VALID_(daemon->itc)) && + (! MHD_itc_activate_ (daemon->itc, + "q")) ) + MHD_PANIC (_("Failed to signal quiesce via inter-thread communication channel")); + + /* FIXME: we might want some bi-directional communication here + (in both the thread-pool and single-thread case!) + to be sure that the threads have stopped using the listen + socket, otherwise there is still the possibility of a race + between a thread accept()ing and the caller closing and + re-binding the socket. */ + + return listen_socket; +} + + diff --git a/src/lib/init.c b/src/lib/init.c @@ -0,0 +1,156 @@ +#include "init.h" + + +#ifdef MHD_HTTPS_REQUIRE_GRYPT +#if defined(HTTPS_SUPPORT) && GCRYPT_VERSION_NUMBER < 0x010600 +#if defined(MHD_USE_POSIX_THREADS) +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#elif defined(MHD_W32_MUTEX_) + +static int +gcry_w32_mutex_init (void **ppmtx) +{ + *ppmtx = malloc (sizeof (MHD_mutex_)); + + if (NULL == *ppmtx) + return ENOMEM; + if (!MHD_mutex_init_ ((MHD_mutex_*)*ppmtx)) + { + free (*ppmtx); + *ppmtx = NULL; + return EPERM; + } + + return 0; +} + + +static int +gcry_w32_mutex_destroy (void **ppmtx) +{ + int res = (MHD_mutex_destroy_ ((MHD_mutex_*)*ppmtx)) ? 0 : EINVAL; + free (*ppmtx); + return res; +} + + +static int +gcry_w32_mutex_lock (void **ppmtx) +{ + return MHD_mutex_lock_ ((MHD_mutex_*)*ppmtx) ? 0 : EINVAL; +} + + +static int +gcry_w32_mutex_unlock (void **ppmtx) +{ + return MHD_mutex_unlock_ ((MHD_mutex_*)*ppmtx) ? 0 : EINVAL; +} + + +static struct gcry_thread_cbs gcry_threads_w32 = { + (GCRY_THREAD_OPTION_USER | (GCRY_THREAD_OPTION_VERSION << 8)), + NULL, gcry_w32_mutex_init, gcry_w32_mutex_destroy, + gcry_w32_mutex_lock, gcry_w32_mutex_unlock, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; + +#endif /* defined(MHD_W32_MUTEX_) */ +#endif /* HTTPS_SUPPORT && GCRYPT_VERSION_NUMBER < 0x010600 */ +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ + + +#ifndef _AUTOINIT_FUNCS_ARE_SUPPORTED + +/** + * Track global initialisation + */ +volatile int global_init_count = 0; +#ifdef MHD_MUTEX_STATIC_DEFN_INIT_ +/** + * Global initialisation mutex + */ +MHD_MUTEX_STATIC_DEFN_INIT_(global_init_mutex_); +#endif /* MHD_MUTEX_STATIC_DEFN_INIT_ */ + + +/** + * Check whether global initialisation was performed + * and call initialiser if necessary. + */ +void +MHD_check_global_init_ (void) +{ +#ifdef MHD_MUTEX_STATIC_DEFN_INIT_ + MHD_mutex_lock_chk_(&global_init_mutex_); +#endif /* MHD_MUTEX_STATIC_DEFN_INIT_ */ + if (0 == global_init_count++) + MHD_init (); +#ifdef MHD_MUTEX_STATIC_DEFN_INIT_ + MHD_mutex_unlock_chk_(&global_init_mutex_); +#endif /* MHD_MUTEX_STATIC_DEFN_INIT_ */ +} + + +/** + * Initialize do setup work. + */ +void +MHD_init(void) +{ +#if defined(_WIN32) && ! defined(__CYGWIN__) + WSADATA wsd; +#endif /* _WIN32 && ! __CYGWIN__ */ + + if (NULL == mhd_panic) + mhd_panic = &mhd_panic_std; + +#if defined(_WIN32) && ! defined(__CYGWIN__) + if (0 != WSAStartup(MAKEWORD(2, 2), &wsd)) + MHD_PANIC (_("Failed to initialize winsock\n")); + mhd_winsock_inited_ = 1; + if (2 != LOBYTE(wsd.wVersion) && 2 != HIBYTE(wsd.wVersion)) + MHD_PANIC (_("Winsock version 2.2 is not available\n")); +#endif +#ifdef HTTPS_SUPPORT +#ifdef MHD_HTTPS_REQUIRE_GRYPT +#if GCRYPT_VERSION_NUMBER < 0x010600 +#if defined(MHD_USE_POSIX_THREADS) + if (0 != gcry_control (GCRYCTL_SET_THREAD_CBS, + &gcry_threads_pthread)) + MHD_PANIC (_("Failed to initialise multithreading in libgcrypt\n")); +#elif defined(MHD_W32_MUTEX_) + if (0 != gcry_control (GCRYCTL_SET_THREAD_CBS, + &gcry_threads_w32)) + MHD_PANIC (_("Failed to initialise multithreading in libgcrypt\n")); +#endif /* defined(MHD_W32_MUTEX_) */ + gcry_check_version (NULL); +#else + if (NULL == gcry_check_version ("1.6.0")) + MHD_PANIC (_("libgcrypt is too old. MHD was compiled for libgcrypt 1.6.0 or newer\n")); +#endif +#endif /* MHD_HTTPS_REQUIRE_GRYPT */ + gnutls_global_init (); +#endif /* HTTPS_SUPPORT */ + MHD_monotonic_sec_counter_init(); +#ifdef HAVE_FREEBSD_SENDFILE + MHD_conn_init_static_ (); +#endif /* HAVE_FREEBSD_SENDFILE */ +} + + +void +MHD_fini(void) +{ +#ifdef HTTPS_SUPPORT + gnutls_global_deinit (); +#endif /* HTTPS_SUPPORT */ +#if defined(_WIN32) && ! defined(__CYGWIN__) + if (mhd_winsock_inited_) + WSACleanup(); +#endif + MHD_monotonic_sec_counter_finish(); +} + +#ifdef _AUTOINIT_FUNCS_ARE_SUPPORTED +_SET_INIT_AND_DEINIT_FUNCS(MHD_init, MHD_fini); +#endif /* _AUTOINIT_FUNCS_ARE_SUPPORTED */ diff --git a/src/lib/init.h b/src/lib/init.h @@ -0,0 +1,29 @@ + + +#ifndef INIT_H +#define INIT_H + +/** + * Globally initialise library. + */ +void +MHD_init(void); + + +#ifdef _AUTOINIT_FUNCS_ARE_SUPPORTED +/** + * Do nothing - global initialisation is + * performed by library constructor. + */ +#define MHD_check_global_init_() (void)0 +#else /* ! _AUTOINIT_FUNCS_ARE_SUPPORTED */ +/** + * Check whether global initialisation was performed + * and call initialiser if necessary. + */ +void +MHD_check_global_init_ (void); +#endif /* ! _AUTOINIT_FUNCS_ARE_SUPPORTED */ + + +#endif /* INIT_H */ diff --git a/src/lib/internal.h b/src/lib/internal.h @@ -126,6 +126,599 @@ extern void *mhd_panic_cls; #endif /* ! MHD_STATICSTR_LEN_ */ + +/** + * Ability to use same connection for next request + */ +enum MHD_ConnKeepAlive +{ + /** + * Connection must be closed after sending response. + */ + MHD_CONN_MUST_CLOSE = -1, + + /** + * KeelAlive state is not yet determined + */ + MHD_CONN_KEEPALIVE_UNKOWN = 0, + + /** + * Connection can be used for serving next request + */ + MHD_CONN_USE_KEEPALIVE = 1 +}; + + +/** + * Function to receive plaintext data. + * + * @param conn the connection struct + * @param write_to where to write received data + * @param max_bytes maximum number of bytes to receive + * @return number of bytes written to @a write_to + */ +typedef ssize_t +(*ReceiveCallback) (struct MHD_Connection *conn, + void *write_to, + size_t max_bytes); + + +/** + * Function to transmit plaintext data. + * + * @param conn the connection struct + * @param read_from where to read data to transmit + * @param max_bytes maximum number of bytes to transmit + * @return number of bytes transmitted + */ +typedef ssize_t +(*TransmitCallback) (struct MHD_Connection *conn, + const void *read_from, + size_t max_bytes); + + +/** + * States in a state machine for a request. + * + * The main transitions are any-state to #MHD_REQUEST_CLOSED, any + * state to state+1, #MHD_REQUEST_FOOTERS_SENT to + * #MHD_REQUEST_INIT. #MHD_REQUEST_CLOSED is the terminal state + * and #MHD_REQUEST_INIT the initial state. + * + * Note that transitions for *reading* happen only after the input has + * been processed; transitions for *writing* happen after the + * respective data has been put into the write buffer (the write does + * not have to be completed yet). A transition to + * #MHD_REQUEST_CLOSED or #MHD_REQUEST_INIT requires the write + * to be complete. + */ +enum MHD_REQUEST_STATE +{ + /** + * Request just started (no headers received). + * Waiting for the line with the request type, URL and version. + */ + MHD_REQUEST_INIT = 0, + + /** + * 1: We got the URL (and request type and version). Wait for a header line. + */ + MHD_REQUEST_URL_RECEIVED = MHD_REQUEST_INIT + 1, + + /** + * 2: We got part of a multi-line request header. Wait for the rest. + */ + MHD_REQUEST_HEADER_PART_RECEIVED = MHD_REQUEST_URL_RECEIVED + 1, + + /** + * 3: We got the request headers. Process them. + */ + MHD_REQUEST_HEADERS_RECEIVED = MHD_REQUEST_HEADER_PART_RECEIVED + 1, + + /** + * 4: We have processed the request headers. Send 100 continue. + */ + MHD_REQUEST_HEADERS_PROCESSED = MHD_REQUEST_HEADERS_RECEIVED + 1, + + /** + * 5: We have processed the headers and need to send 100 CONTINUE. + */ + MHD_REQUEST_CONTINUE_SENDING = MHD_REQUEST_HEADERS_PROCESSED + 1, + + /** + * 6: We have sent 100 CONTINUE (or do not need to). Read the message body. + */ + MHD_REQUEST_CONTINUE_SENT = MHD_REQUEST_CONTINUE_SENDING + 1, + + /** + * 7: We got the request body. Wait for a line of the footer. + */ + MHD_REQUEST_BODY_RECEIVED = MHD_REQUEST_CONTINUE_SENT + 1, + + /** + * 8: We got part of a line of the footer. Wait for the + * rest. + */ + MHD_REQUEST_FOOTER_PART_RECEIVED = MHD_REQUEST_BODY_RECEIVED + 1, + + /** + * 9: We received the entire footer. Wait for a response to be queued + * and prepare the response headers. + */ + MHD_REQUEST_FOOTERS_RECEIVED = MHD_REQUEST_FOOTER_PART_RECEIVED + 1, + + /** + * 10: We have prepared the response headers in the writ buffer. + * Send the response headers. + */ + MHD_REQUEST_HEADERS_SENDING = MHD_REQUEST_FOOTERS_RECEIVED + 1, + + /** + * 11: We have sent the response headers. Get ready to send the body. + */ + MHD_REQUEST_HEADERS_SENT = MHD_REQUEST_HEADERS_SENDING + 1, + + /** + * 12: We are ready to send a part of a non-chunked body. Send it. + */ + MHD_REQUEST_NORMAL_BODY_READY = MHD_REQUEST_HEADERS_SENT + 1, + + /** + * 13: We are waiting for the client to provide more + * data of a non-chunked body. + */ + MHD_REQUEST_NORMAL_BODY_UNREADY = MHD_REQUEST_NORMAL_BODY_READY + 1, + + /** + * 14: We are ready to send a chunk. + */ + MHD_REQUEST_CHUNKED_BODY_READY = MHD_REQUEST_NORMAL_BODY_UNREADY + 1, + + /** + * 15: We are waiting for the client to provide a chunk of the body. + */ + MHD_REQUEST_CHUNKED_BODY_UNREADY = MHD_REQUEST_CHUNKED_BODY_READY + 1, + + /** + * 16: We have sent the response body. Prepare the footers. + */ + MHD_REQUEST_BODY_SENT = MHD_REQUEST_CHUNKED_BODY_UNREADY + 1, + + /** + * 17: We have prepared the response footer. Send it. + */ + MHD_REQUEST_FOOTERS_SENDING = MHD_REQUEST_BODY_SENT + 1, + + /** + * 18: We have sent the response footer. Shutdown or restart. + */ + MHD_REQUEST_FOOTERS_SENT = MHD_REQUEST_FOOTERS_SENDING + 1, + + /** + * 19: This request is to be closed. + */ + MHD_REQUEST_CLOSED = MHD_REQUEST_FOOTERS_SENT + 1, + + /** + * 20: This request is finished (only to be freed) + */ + MHD_REQUEST_IN_CLEANUP = MHD_REQUEST_CLOSED + 1, + +#ifdef UPGRADE_SUPPORT + /** + * Request was "upgraded" and socket is now under the + * control of the application. + */ + MHD_REQUEST_UPGRADE +#endif /* UPGRADE_SUPPORT */ + +}; + + +/** + * Header or cookie in HTTP request or response. + */ +struct MHD_HTTP_Header +{ + /** + * Headers are kept in a linked list. + */ + struct MHD_HTTP_Header *next; + + /** + * The name of the header (key), without the colon. + */ + char *header; + + /** + * The value of the header. + */ + char *value; + + /** + * Type of the header (where in the HTTP protocol is this header + * from). + */ + enum MHD_ValueKind kind; + +}; + + +/** + * State kept for each HTTP request. + */ +struct MHD_Request +{ + + /** + * Reference to the MHD_Daemon struct. + */ + struct MHD_Daemon *daemon; + + /** + * Connection this request is associated with. + */ + struct MHD_Connection *connection; + + /** + * Linked list of parsed headers. + */ + struct MHD_HTTP_Header *headers_received; + + /** + * Tail of linked list of parsed headers. + */ + struct MHD_HTTP_Header *headers_received_tail; + + /** + * The memory pool is created whenever we first read from the TCP + * stream and destroyed at the end of each request (and re-created + * for the next request). In the meantime, this pointer is NULL. + * The pool is used for all request-related data except for the + * response (which maybe shared between requests) and the IP + * address (which persists across individual requests). + */ + struct MemoryPool *pool; + + /** + * We allow the main application to associate some pointer with the + * HTTP request, which is passed to each #MHD_AccessHandlerCallback + * and some other API calls. Here is where we store it. (MHD does + * not know or care what it is). + */ + void *client_context; + + /** + * Request method. Should be GET/POST/etc. Allocated in pool. + */ + char *method; + + /** + * Requested URL (everything after "GET" only). Allocated + * in pool. + */ + const char *url; + + /** + * HTTP version string (i.e. http/1.1). Allocated + * in pool. + */ + char *version; + + /** + * Close connection after sending response? + * Functions may change value from "Unknown" or "KeepAlive" to "Must close", + * but no functions reset value "Must Close" to any other value. + */ + enum MHD_ConnKeepAlive keepalive; + + /** + * Buffer for reading requests. Allocated in pool. Actually one + * byte larger than @e read_buffer_size (if non-NULL) to allow for + * 0-termination. + */ + char *read_buffer; + + /** + * Buffer for writing response (headers only). Allocated + * in pool. + */ + char *write_buffer; + + /** + * Last incomplete header line during parsing of headers. + * Allocated in pool. Only valid if state is + * either #MHD_REQUEST_HEADER_PART_RECEIVED or + * #MHD_REQUEST_FOOTER_PART_RECEIVED. + */ + char *last; + + /** + * Position after the colon on the last incomplete header + * line during parsing of headers. + * Allocated in pool. Only valid if state is + * either #MHD_REQUEST_HEADER_PART_RECEIVED or + * #MHD_REQUEST_FOOTER_PART_RECEIVED. + */ + char *colon; + + + /** + * Function used for reading HTTP request stream. + */ + ReceiveCallback recv_cls; + + /** + * Function used for writing HTTP response stream. + */ + TransmitCallback send_cls; + +#ifdef UPGRADE_SUPPORT + /** + * If this connection was upgraded, this points to + * the upgrade response details such that the + * #thread_main_connection_upgrade()-logic can perform the + * bi-directional forwarding. + */ + struct MHD_UpgradeResponseHandle *urh; +#endif /* UPGRADE_SUPPORT */ + + /** + * Foreign address (of length @e addr_len). + */ + struct sockaddr_storage addr; + + /** + * Thread handle for this connection (if we are using + * one thread per connection). + */ + MHD_thread_handle_ID_ pid; + + /** + * Size of @e read_buffer (in bytes). This value indicates + * how many bytes we're willing to read into the buffer; + * the real buffer is one byte longer to allow for + * adding zero-termination (when needed). + */ + size_t read_buffer_size; + + /** + * Position where we currently append data in + * @e read_buffer (last valid position). + */ + size_t read_buffer_offset; + + /** + * Size of @e write_buffer (in bytes). + */ + size_t write_buffer_size; + + /** + * Offset where we are with sending from @e write_buffer. + */ + size_t write_buffer_send_offset; + + /** + * Last valid location in write_buffer (where do we + * append and up to where is it safe to send?) + */ + size_t write_buffer_append_offset; + + /** + * Number of bytes we had in the HTTP header, set once we + * pass #MHD_REQUEST_HEADERS_RECEIVED. + */ + size_t header_size; + + /** + * How many more bytes of the body do we expect + * to read? #MHD_SIZE_UNKNOWN for unknown. + */ + uint64_t remaining_upload_size; + + /** + * If we are receiving with chunked encoding, where are we right + * now? Set to 0 if we are waiting to receive the chunk size; + * otherwise, this is the size of the current chunk. A value of + * zero is also used when we're at the end of the chunks. + */ + uint64_t current_chunk_size; + + /** + * If we are receiving with chunked encoding, where are we currently + * with respect to the current chunk (at what offset / position)? + */ + uint64_t current_chunk_offset; + + /** + * Current write position in the actual response + * (excluding headers, content only; should be 0 + * while sending headers). + */ + uint64_t response_write_position; + +#if defined(_MHD_HAVE_SENDFILE) + enum MHD_resp_sender_ + { + MHD_resp_sender_std = 0, + MHD_resp_sender_sendfile + } resp_sender; +#endif /* _MHD_HAVE_SENDFILE */ + + /** + * Position in the 100 CONTINUE message that + * we need to send when receiving http 1.1 requests. + */ + size_t continue_message_write_offset; + + /** + * State in the FSM for this request. + */ + enum MHD_REQUEST_STATE state; + + /** + * What is this request waiting for? + */ + enum MHD_RequestEventLoopInfo event_loop_info; + + /** + * HTTP response code. Only valid if response object + * is already set. + */ + unsigned int responseCode; + + /** + * Did we ever call the "default_handler" on this request? (this + * flag will determine if we call the #MHD_OPTION_NOTIFY_COMPLETED + * handler when the request closes down). + */ + bool client_aware; + + /** + * Are we currently inside the "idle" handler (to avoid recursively + * invoking it). + */ + bool in_idle; + + /** + * Are we currently inside the "idle" handler (to avoid recursively + * invoking it). + */ + bool in_cleanup; + + /** + * Are we receiving with chunked encoding? This will be set to + * #MHD_YES after we parse the headers and are processing the body + * with chunks. After we are done with the body and we are + * processing the footers; once the footers are also done, this will + * be set to #MHD_NO again (before the final call to the handler). + */ + bool have_chunked_upload; + + /** + * Is the request suspended? + */ + bool suspended; + + /** + * Is the request wanting to resume? + */ + bool resuming; +}; + + +/** + * State kept per HTTP connection. + */ +struct MHD_Connection +{ + +#ifdef EPOLL_SUPPORT + /** + * Next pointer for the EDLL listing connections that are epoll-ready. + */ + struct MHD_Connection *nextE; + + /** + * Previous pointer for the EDLL listing connections that are epoll-ready. + */ + struct MHD_Connection *prevE; +#endif + + /** + * Next pointer for the DLL describing our IO state. + */ + struct MHD_Connection *next; + + /** + * Previous pointer for the DLL describing our IO state. + */ + struct MHD_Connection *prev; + + /** + * Next pointer for the XDLL organizing connections by timeout. + * This DLL can be either the + * 'manual_timeout_head/manual_timeout_tail' or the + * 'normal_timeout_head/normal_timeout_tail', depending on whether a + * custom timeout is set for the connection. + */ + struct MHD_Connection *nextX; + + /** + * Previous pointer for the XDLL organizing connections by timeout. + */ + struct MHD_Connection *prevX; + + /** + * Reference to the MHD_Daemon struct. + */ + struct MHD_Daemon *daemon; + + /** + * Information about the current request we are processing + * on this connection. + */ + struct MHD_Request request; + + + /** + * Set to `true` if the thread has been joined. + */ + bool thread_joined; + + /** + * true if #socket_fd is non-blocking, false otherwise. + */ + bool sk_nonblck; + + /** + * Has this socket been closed for reading (i.e. other side closed + * the connection)? If so, we must completely close the connection + * once we are done sending our response (and stop trying to read + * from this socket). + */ + bool read_closed; + + + /** + * Length of the foreign address. + */ + socklen_t addr_len; + + /** + * Last time this connection had any activity + * (reading or writing). + */ + time_t last_activity; + + /** + * After how many seconds of inactivity should + * this connection time out? Zero for no timeout. + */ + time_t connection_timeout; + + /** + * Socket for this connection. Set to #MHD_INVALID_SOCKET if + * this connection has died (daemon should clean + * up in that case). + */ + MHD_socket socket_fd; + + +#ifdef EPOLL_SUPPORT + /** + * What is the state of this socket in relation to epoll? + */ + enum MHD_EpollState epoll_state; +#endif + + +}; + + + + + /** * State kept for each MHD daemon. All connections are kept in two * doubly-linked lists. The first one reflects the state of the @@ -324,8 +917,14 @@ struct MHD_Daemon MHD_socket listen_socket; /** + * Inter-thread communication channel. + */ + struct MHD_itc_ itc; + + /** * Which threading model do we use? Postive * numbers indicate the number of worker threads to be used. + * Values larger than 1 imply a thread pool. */ enum MHD_ThreadingModel threading_model; @@ -337,7 +936,7 @@ struct MHD_Daemon /** * Address family to use when listening. - * Default is #MHD_AF_AUTO. + * Default is #MHD_AF_NONE (do not listen). */ enum MHD_AddressFamily listen_af; @@ -357,8 +956,7 @@ struct MHD_Daemon /** * On which port should we listen on? Only effective if we were not * given a listen socket or a full address via - * #MHD_daemon_bind_sa(). 0 means not set, which means to default - * to 80 (http) or 443 (https) respectively. + * #MHD_daemon_bind_sa(). 0 means to bind to random free port. */ uint16_t listen_port; diff --git a/src/lib/panic.c b/src/lib/panic.c @@ -0,0 +1,36 @@ + + +/** + * Handler for fatal errors. + */ +MHD_PanicCallback mhd_panic = NULL; + +/** + * Closure argument for #mhd_panic. + */ +void *mhd_panic_cls = NULL; + + +/** + * Sets the global error handler to a different implementation. @a cb + * will only be called in the case of typically fatal, serious + * internal consistency issues. These issues should only arise in the + * case of serious memory corruption or similar problems with the + * architecture. While @a cb is allowed to return and MHD will then + * try to continue, this is never safe. + * + * The default implementation that is used if no panic function is set + * simply prints an error message and calls `abort()`. Alternative + * implementations might call `exit()` or other similar functions. + * + * @param cb new error handler + * @param cls passed to @a cb + * @ingroup logging + */ +void +MHD_set_panic_func (MHD_PanicCallback cb, + void *cls) +{ + mhd_panic = cb; + mhd_panic_cls = cls; +} diff --git a/src/lib/request.c b/src/lib/request.c @@ -1,3 +1,31 @@ +/* + 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 requests.c + * @brief Methods for managing HTTP requests + * @author Daniel Pittman + * @author Christian Grothoff + * @author Karlson2k (Evgeny Grin) + */ + + /** * Get all of the headers from the request. * @@ -9,11 +37,35 @@ * @return number of entries iterated over * @ingroup request */ -_MHD_EXTERN unsigned int +unsigned int MHD_request_get_values (struct MHD_Request *request, enum MHD_ValueKind kind, MHD_KeyValueIterator iterator, - void *iterator_cls); + void *iterator_cls) +{ + int ret; + struct MHD_HTTP_Header *pos; + + if (NULL == request) + return -1; + ret = 0; + for (pos = request->headers_received; + NULL != pos; + pos = pos->next) + { + if (0 != (pos->kind & kind)) + { + ret++; + if ( (NULL != iterator) && + (MHD_YES != iterator (iterator_cls, + pos->kind, + pos->header, + pos->value)) ) + return ret; + } + } + return ret; +} /** @@ -41,11 +93,36 @@ MHD_request_get_values (struct MHD_Request *request, * #MHD_YES on success * @ingroup request */ -_MHD_EXTERN enum MHD_Bool +enum MHD_Bool MHD_request_set_value (struct MHD_Request *request, enum MHD_ValueKind kind, const char *key, - const char *value); + const char *value) +{ + struct MHD_HTTP_Header *pos; + + pos = MHD_pool_allocate (request->pool, + sizeof (struct MHD_HTTP_Header), + MHD_YES); + if (NULL == pos) + return MHD_NO; + pos->header = (char *) key; + pos->value = (char *) value; + pos->kind = kind; + pos->next = NULL; + /* append 'pos' to the linked list of headers */ + if (NULL == request->headers_received_tail) + { + request->headers_received = pos; + request->headers_received_tail = pos; + } + else + { + request->headers_received_tail->next = pos; + request->headers_received_tail = pos; + } + return MHD_YES; +} /** @@ -58,11 +135,31 @@ MHD_request_set_value (struct MHD_Request *request, * @return NULL if no such item was found * @ingroup request */ -_MHD_EXTERN const char * +const char * MHD_request_lookup_value (struct MHD_Request *request, enum MHD_ValueKind kind, - const char *key); + const char *key) +{ + struct MHD_HTTP_Header *pos; + + if (NULL == request) + return NULL; + for (pos = request->headers_received; + NULL != pos; + pos = pos->next) + { + if ((0 != (pos->kind & kind)) && + ( (key == pos->header) || + ( (NULL != pos->header) && + (NULL != key) && + (MHD_str_equal_caseless_(key, + pos->header))))) + return pos->value; + } + return NULL; +} +/* end of request.c */ diff --git a/src/lib/version.c b/src/lib/version.c @@ -0,0 +1,181 @@ + + +/** + * Obtain the version of this library + * + * @return static version string, e.g. "0.9.9" + * @ingroup specialized + */ +const char * +MHD_get_version (void) +{ +#ifdef PACKAGE_VERSION + return PACKAGE_VERSION; +#else /* !PACKAGE_VERSION */ + static char ver[12] = "\0\0\0\0\0\0\0\0\0\0\0"; + if (0 == ver[0]) + { + int res = MHD_snprintf_(ver, + sizeof(ver), + "%x.%x.%x", + (((int)MHD_VERSION >> 24) & 0xFF), + (((int)MHD_VERSION >> 16) & 0xFF), + (((int)MHD_VERSION >> 8) & 0xFF)); + if (0 >= res || sizeof(ver) <= res) + return "0.0.0"; /* Can't return real version*/ + } + return ver; +#endif /* !PACKAGE_VERSION */ +} + + +/** + * Get information about supported MHD features. + * Indicate that MHD was compiled with or without support for + * particular feature. Some features require additional support + * by kernel. Kernel support is not checked by this function. + * + * @param feature type of requested information + * @return #MHD_YES if feature is supported by MHD, #MHD_NO if + * feature is not supported or feature is unknown. + * @ingroup specialized + */ +_MHD_EXTERN int +MHD_is_feature_supported(enum MHD_FEATURE feature) +{ + switch(feature) + { + case MHD_FEATURE_MESSAGES: +#ifdef HAVE_MESSAGES + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_TLS: +#ifdef HTTPS_SUPPORT + return MHD_YES; +#else /* ! HTTPS_SUPPORT */ + return MHD_NO; +#endif /* ! HTTPS_SUPPORT */ + case MHD_FEATURE_HTTPS_CERT_CALLBACK: +#if defined(HTTPS_SUPPORT) && GNUTLS_VERSION_MAJOR >= 3 + return MHD_YES; +#else /* !HTTPS_SUPPORT || GNUTLS_VERSION_MAJOR < 3 */ + return MHD_NO; +#endif /* !HTTPS_SUPPORT || GNUTLS_VERSION_MAJOR < 3 */ + case MHD_FEATURE_IPv6: +#ifdef HAVE_INET6 + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_IPv6_ONLY: +#if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_POLL: +#ifdef HAVE_POLL + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_EPOLL: +#ifdef EPOLL_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_SHUTDOWN_LISTEN_SOCKET: +#ifdef HAVE_LISTEN_SHUTDOWN + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_SOCKETPAIR: +#ifdef _MHD_ITC_SOCKETPAIR + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_TCP_FASTOPEN: +#ifdef TCP_FASTOPEN + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_BASIC_AUTH: +#ifdef BAUTH_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_DIGEST_AUTH: +#ifdef DAUTH_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_POSTPROCESSOR: +#ifdef HAVE_POSTPROCESSOR + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_HTTPS_KEY_PASSWORD: +#if defined(HTTPS_SUPPORT) && GNUTLS_VERSION_NUMBER >= 0x030111 + return MHD_YES; +#else /* !HTTPS_SUPPORT || GNUTLS_VERSION_NUMBER < 0x030111 */ + return MHD_NO; +#endif /* !HTTPS_SUPPORT || GNUTLS_VERSION_NUMBER < 0x030111 */ + case MHD_FEATURE_LARGE_FILE: +#if defined(HAVE_PREAD64) || defined(_WIN32) + return MHD_YES; +#elif defined(HAVE_PREAD) + return (sizeof(uint64_t) > sizeof(off_t)) ? MHD_NO : MHD_YES; +#elif defined(HAVE_LSEEK64) + return MHD_YES; +#else + return (sizeof(uint64_t) > sizeof(off_t)) ? MHD_NO : MHD_YES; +#endif + case MHD_FEATURE_THREAD_NAMES: +#if defined(MHD_USE_THREAD_NAME_) + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_UPGRADE: +#if defined(UPGRADE_SUPPORT) + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_RESPONSES_SHARED_FD: +#if defined(HAVE_PREAD64) || defined(HAVE_PREAD) || defined(_WIN32) + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_AUTODETECT_BIND_PORT: +#ifdef MHD_USE_GETSOCKNAME + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_AUTOSUPPRESS_SIGPIPE: +#if defined(MHD_WINSOCK_SOCKETS) || defined(MHD_socket_nosignal_) || defined (MSG_NOSIGNAL) + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_SENDFILE: +#ifdef _MHD_HAVE_SENDFILE + return MHD_YES; +#else + return MHD_NO; +#endif + + } + return MHD_NO; +} diff --git a/src/microhttpd/mhd_sockets.c b/src/microhttpd/mhd_sockets.c @@ -468,7 +468,7 @@ MHD_socket_noninheritable_ (MHD_socket sock) * @return created socket or MHD_INVALID_SOCKET in case of errors */ MHD_socket -MHD_socket_create_listen_ (int use_ipv6) +MHD_socket_create_listen_ (bool use_ipv6) { int domain; MHD_socket fd; diff --git a/src/microhttpd/mhd_sockets.h b/src/microhttpd/mhd_sockets.h @@ -755,6 +755,6 @@ MHD_socket_noninheritable_ (MHD_socket sock); * @return created socket or MHD_INVALID_SOCKET in case of errors */ MHD_socket -MHD_socket_create_listen_ (int use_ipv6); +MHD_socket_create_listen_ (bool use_ipv6); #endif /* ! MHD_SOCKETS_H */