diff options
Diffstat (limited to 'src/microhttpd/daemon.c')
-rw-r--r-- | src/microhttpd/daemon.c | 4844 |
1 files changed, 4844 insertions, 0 deletions
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c new file mode 100644 index 00000000..1aa7d8d3 --- /dev/null +++ b/src/microhttpd/daemon.c @@ -0,0 +1,4844 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2007-2014 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 microhttpd/daemon.c + * @brief A minimal-HTTP server library + * @author Daniel Pittman + * @author Christian Grothoff + */ +#if defined(_WIN32) && !defined(__CYGWIN__) +/* override small default value */ +#define FD_SETSIZE 1024 +#define MHD_DEFAULT_FD_SETSIZE 64 +#else +#define MHD_DEFAULT_FD_SETSIZE FD_SETSIZE +#endif +#include "platform.h" +#include "internal.h" +#include "response.h" +#include "connection.h" +#include "memorypool.h" +#include <limits.h> +#include "autoinit_funcs.h" + +#if HAVE_SEARCH_H +#include <search.h> +#else +#include "tsearch.h" +#endif + +#if HTTPS_SUPPORT +#include "connection_https.h" +#include <gcrypt.h> +#endif + +#if defined(HAVE_POLL_H) && defined(HAVE_POLL) +#include <poll.h> +#endif + +#ifdef LINUX +#include <sys/sendfile.h> +#endif + +#ifdef _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif /* !WIN32_LEAN_AND_MEAN */ +#include <windows.h> +#include <process.h> +#endif + +#ifndef HAVE_ACCEPT4 +#define HAVE_ACCEPT4 0 +#endif + +/** + * Default connection limit. + */ +#ifndef WINDOWS +#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE - 4 +#else +#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE +#endif + +/** + * Default memory allowed per connection. + */ +#define MHD_POOL_SIZE_DEFAULT (32 * 1024) + +#ifdef TCP_FASTOPEN +/** + * Default TCP fastopen queue size. + */ +#define MHD_TCP_FASTOPEN_QUEUE_SIZE_DEFAULT 10 +#endif + +/** + * Print extra messages with reasons for closing + * sockets? (only adds non-error messages). + */ +#define DEBUG_CLOSE MHD_NO + +/** + * Print extra messages when establishing + * connections? (only adds non-error messages). + */ +#define DEBUG_CONNECT MHD_NO + +#ifndef LINUX +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif +#endif + +#ifndef SOCK_CLOEXEC +#define SOCK_CLOEXEC 0 +#endif + +#ifndef EPOLL_CLOEXEC +#define EPOLL_CLOEXEC 0 +#endif + + +/** + * Default implementation of the panic function, + * prints an error message and aborts. + * + * @param cls unused + * @param file name of the file with the problem + * @param line line number with the problem + * @param reason error message with details + */ +static void +mhd_panic_std (void *cls, + const char *file, + unsigned int line, + const char *reason) +{ +#if HAVE_MESSAGES + fprintf (stderr, "Fatal error in GNU libmicrohttpd %s:%u: %s\n", + file, line, reason); +#endif + abort (); +} + + +/** + * Handler for fatal errors. + */ +MHD_PanicCallback mhd_panic; + +/** + * Closure argument for "mhd_panic". + */ +void *mhd_panic_cls; + +#ifdef _WIN32 +/** + * Track initialization of winsock + */ +static int mhd_winsock_inited_ = 0; +#endif + +/** + * Trace up to and return master daemon. If the supplied daemon + * is a master, then return the daemon itself. + * + * @param daemon handle to a daemon + * @return master daemon handle + */ +static struct MHD_Daemon* +MHD_get_master (struct MHD_Daemon *daemon) +{ + while (NULL != daemon->master) + daemon = daemon->master; + return daemon; +} + + +/** + * Maintain connection count for single address. + */ +struct MHD_IPCount +{ + /** + * Address family. AF_INET or AF_INET6 for now. + */ + int family; + + /** + * Actual address. + */ + union + { + /** + * IPv4 address. + */ + struct in_addr ipv4; +#if HAVE_INET6 + /** + * IPv6 address. + */ + struct in6_addr ipv6; +#endif + } addr; + + /** + * Counter. + */ + unsigned int count; +}; + + +/** + * Lock shared structure for IP connection counts and connection DLLs. + * + * @param daemon handle to daemon where lock is + */ +static void +MHD_ip_count_lock (struct MHD_Daemon *daemon) +{ + if (MHD_YES != MHD_mutex_lock_(&daemon->per_ip_connection_mutex)) + { + MHD_PANIC ("Failed to acquire IP connection limit mutex\n"); + } +} + + +/** + * Unlock shared structure for IP connection counts and connection DLLs. + * + * @param daemon handle to daemon where lock is + */ +static void +MHD_ip_count_unlock (struct MHD_Daemon *daemon) +{ + if (MHD_YES != MHD_mutex_unlock_(&daemon->per_ip_connection_mutex)) + { + MHD_PANIC ("Failed to release IP connection limit mutex\n"); + } +} + + +/** + * Tree comparison function for IP addresses (supplied to tsearch() family). + * We compare everything in the struct up through the beginning of the + * 'count' field. + * + * @param a1 first address to compare + * @param a2 second address to compare + * @return -1, 0 or 1 depending on result of compare + */ +static int +MHD_ip_addr_compare (const void *a1, const void *a2) +{ + return memcmp (a1, a2, offsetof (struct MHD_IPCount, count)); +} + + +/** + * Parse address and initialize 'key' using the address. + * + * @param addr address to parse + * @param addrlen number of bytes in addr + * @param key where to store the parsed address + * @return #MHD_YES on success and #MHD_NO otherwise (e.g., invalid address type) + */ +static int +MHD_ip_addr_to_key (const struct sockaddr *addr, + socklen_t addrlen, + struct MHD_IPCount *key) +{ + memset(key, 0, sizeof(*key)); + + /* IPv4 addresses */ + if (sizeof (struct sockaddr_in) == addrlen) + { + const struct sockaddr_in *addr4 = (const struct sockaddr_in*) addr; + key->family = AF_INET; + memcpy (&key->addr.ipv4, &addr4->sin_addr, sizeof(addr4->sin_addr)); + return MHD_YES; + } + +#if HAVE_INET6 + /* IPv6 addresses */ + if (sizeof (struct sockaddr_in6) == addrlen) + { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*) addr; + key->family = AF_INET6; + memcpy (&key->addr.ipv6, &addr6->sin6_addr, sizeof(addr6->sin6_addr)); + return MHD_YES; + } +#endif + + /* Some other address */ + return MHD_NO; +} + + +/** + * Check if IP address is over its limit. + * + * @param daemon handle to daemon where connection counts are tracked + * @param addr address to add (or increment counter) + * @param addrlen number of bytes in addr + * @return Return #MHD_YES if IP below limit, #MHD_NO if IP has surpassed limit. + * Also returns #MHD_NO if fails to allocate memory. + */ +static int +MHD_ip_limit_add (struct MHD_Daemon *daemon, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct MHD_IPCount *key; + void **nodep; + void *node; + int result; + + daemon = MHD_get_master (daemon); + /* Ignore if no connection limit assigned */ + if (0 == daemon->per_ip_connection_limit) + return MHD_YES; + + if (NULL == (key = malloc (sizeof(*key)))) + return MHD_NO; + + /* Initialize key */ + if (MHD_NO == MHD_ip_addr_to_key (addr, addrlen, key)) + { + /* Allow unhandled address types through */ + free (key); + return MHD_YES; + } + MHD_ip_count_lock (daemon); + + /* Search for the IP address */ + if (NULL == (nodep = tsearch (key, + &daemon->per_ip_connection_count, + &MHD_ip_addr_compare))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to add IP connection count node\n"); +#endif + MHD_ip_count_unlock (daemon); + free (key); + return MHD_NO; + } + node = *nodep; + /* If we got an existing node back, free the one we created */ + if (node != key) + free(key); + key = (struct MHD_IPCount *) node; + /* Test if there is room for another connection; if so, + * increment count */ + result = (key->count < daemon->per_ip_connection_limit); + if (MHD_YES == result) + ++key->count; + + MHD_ip_count_unlock (daemon); + return result; +} + + +/** + * Decrement connection count for IP address, removing from table + * count reaches 0. + * + * @param daemon handle to daemon where connection counts are tracked + * @param addr address to remove (or decrement counter) + * @param addrlen number of bytes in @a addr + */ +static void +MHD_ip_limit_del (struct MHD_Daemon *daemon, + const struct sockaddr *addr, + socklen_t addrlen) +{ + struct MHD_IPCount search_key; + struct MHD_IPCount *found_key; + void **nodep; + + daemon = MHD_get_master (daemon); + /* Ignore if no connection limit assigned */ + if (0 == daemon->per_ip_connection_limit) + return; + /* Initialize search key */ + if (MHD_NO == MHD_ip_addr_to_key (addr, addrlen, &search_key)) + return; + + MHD_ip_count_lock (daemon); + + /* Search for the IP address */ + if (NULL == (nodep = tfind (&search_key, + &daemon->per_ip_connection_count, + &MHD_ip_addr_compare))) + { + /* Something's wrong if we couldn't find an IP address + * that was previously added */ + MHD_PANIC ("Failed to find previously-added IP address\n"); + } + found_key = (struct MHD_IPCount *) *nodep; + /* Validate existing count for IP address */ + if (0 == found_key->count) + { + MHD_PANIC ("Previously-added IP address had 0 count\n"); + } + /* Remove the node entirely if count reduces to 0 */ + if (0 == --found_key->count) + { + tdelete (found_key, + &daemon->per_ip_connection_count, + &MHD_ip_addr_compare); + free (found_key); + } + + MHD_ip_count_unlock (daemon); +} + + +#if HTTPS_SUPPORT +/** + * Callback for receiving data from the socket. + * + * @param connection the MHD_Connection structure + * @param other where to write received data to + * @param i maximum size of other (in bytes) + * @return number of bytes actually received + */ +static ssize_t +recv_tls_adapter (struct MHD_Connection *connection, void *other, size_t i) +{ + int res; + + if (MHD_YES == connection->tls_read_ready) + { + connection->daemon->num_tls_read_ready--; + connection->tls_read_ready = MHD_NO; + } + res = gnutls_record_recv (connection->tls_session, other, i); + if ( (GNUTLS_E_AGAIN == res) || + (GNUTLS_E_INTERRUPTED == res) ) + { + MHD_set_socket_errno_ (EINTR); +#if EPOLL_SUPPORT + connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY; +#endif + return -1; + } + if (res < 0) + { + /* Likely 'GNUTLS_E_INVALID_SESSION' (client communication + disrupted); set errno to something caller will interpret + correctly as a hard error */ + MHD_set_socket_errno_ (ECONNRESET); + return res; + } + if (res == i) + { + connection->tls_read_ready = MHD_YES; + connection->daemon->num_tls_read_ready++; + } + return res; +} + + +/** + * Callback for writing data to the socket. + * + * @param connection the MHD connection structure + * @param other data to write + * @param i number of bytes to write + * @return actual number of bytes written + */ +static ssize_t +send_tls_adapter (struct MHD_Connection *connection, + const void *other, size_t i) +{ + int res; + + res = gnutls_record_send (connection->tls_session, other, i); + if ( (GNUTLS_E_AGAIN == res) || + (GNUTLS_E_INTERRUPTED == res) ) + { + MHD_set_socket_errno_ (EINTR); +#if EPOLL_SUPPORT + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; +#endif + return -1; + } + if (res < 0) + { + /* some other GNUTLS error, should set 'errno'; as we do not + really understand the error (not listed in GnuTLS + documentation explicitly), we set 'errno' to something that + will cause the connection to fail. */ + MHD_set_socket_errno_ (ECONNRESET); + return -1; + } + return res; +} + + +/** + * Read and setup our certificate and key. + * + * @param daemon handle to daemon to initialize + * @return 0 on success + */ +static int +MHD_init_daemon_certificate (struct MHD_Daemon *daemon) +{ + gnutls_datum_t key; + gnutls_datum_t cert; + int ret; + +#if GNUTLS_VERSION_MAJOR >= 3 + if (NULL != daemon->cert_callback) + { + gnutls_certificate_set_retrieve_function2 (daemon->x509_cred, + daemon->cert_callback); + } +#endif + if (NULL != daemon->https_mem_trust) + { + cert.data = (unsigned char *) daemon->https_mem_trust; + cert.size = strlen (daemon->https_mem_trust); + if (gnutls_certificate_set_x509_trust_mem (daemon->x509_cred, &cert, + GNUTLS_X509_FMT_PEM) < 0) + { +#if HAVE_MESSAGES + MHD_DLOG(daemon, + "Bad trust certificate format\n"); +#endif + return -1; + } + } + + if (MHD_YES == daemon->have_dhparams) + { + gnutls_certificate_set_dh_params (daemon->x509_cred, + daemon->https_mem_dhparams); + } + /* certificate & key loaded from memory */ + if ( (NULL != daemon->https_mem_cert) && + (NULL != daemon->https_mem_key) ) + { + key.data = (unsigned char *) daemon->https_mem_key; + key.size = strlen (daemon->https_mem_key); + cert.data = (unsigned char *) daemon->https_mem_cert; + cert.size = strlen (daemon->https_mem_cert); + + if (NULL != daemon->https_key_password) { +#if GNUTLS_VERSION_NUMBER >= 0x030111 + ret = gnutls_certificate_set_x509_key_mem2 (daemon->x509_cred, + &cert, &key, + GNUTLS_X509_FMT_PEM, + daemon->https_key_password, + 0); +#else +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to setup x509 certificate/key: pre 3.X.X version " \ + "of GnuTLS does not support setting key password"); +#endif + return -1; +#endif + } + else + ret = gnutls_certificate_set_x509_key_mem (daemon->x509_cred, + &cert, &key, + GNUTLS_X509_FMT_PEM); +#if HAVE_MESSAGES + if (0 != ret) + MHD_DLOG (daemon, + "GnuTLS failed to setup x509 certificate/key: %s\n", + gnutls_strerror (ret)); +#endif + return ret; + } +#if GNUTLS_VERSION_MAJOR >= 3 + if (NULL != daemon->cert_callback) + return 0; +#endif +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "You need to specify a certificate and key location\n"); +#endif + return -1; +} + + +/** + * Initialize security aspects of the HTTPS daemon + * + * @param daemon handle to daemon to initialize + * @return 0 on success + */ +static int +MHD_TLS_init (struct MHD_Daemon *daemon) +{ + switch (daemon->cred_type) + { + case GNUTLS_CRD_CERTIFICATE: + if (0 != + gnutls_certificate_allocate_credentials (&daemon->x509_cred)) + return GNUTLS_E_MEMORY_ERROR; + return MHD_init_daemon_certificate (daemon); + default: +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Error: invalid credentials type %d specified.\n", + daemon->cred_type); +#endif + return -1; + } +} +#endif + + +/** + * Add @a fd to the @a set. If @a fd is + * greater than @a max_fd, set @a max_fd to @a fd. + * + * @param fd file descriptor to add to the @a set + * @param set set to modify + * @param max_fd maximum value to potentially update + * @param fd_setsize value of FD_SETSIZE + * @return #MHD_YES on success, #MHD_NO otherwise + */ +static int +add_to_fd_set (MHD_socket fd, + fd_set *set, + MHD_socket *max_fd, + unsigned int fd_setsize) +{ + if (NULL == set) + return MHD_NO; +#ifdef MHD_WINSOCK_SOCKETS + if (set->fd_count >= fd_setsize) + { + if (FD_ISSET(fd, set)) + return MHD_YES; + else + return MHD_NO; + } +#else // ! MHD_WINSOCK_SOCKETS + if (fd >= fd_setsize) + return MHD_NO; +#endif // ! MHD_WINSOCK_SOCKETS + FD_SET (fd, set); + if ( (NULL != max_fd) && (MHD_INVALID_SOCKET != fd) && + ((fd > *max_fd) || (MHD_INVALID_SOCKET == *max_fd)) ) + *max_fd = fd; + + return MHD_YES; +} + +#undef MHD_get_fdset + +/** + * Obtain the `select()` sets for this daemon. + * Daemon's FDs will be added to fd_sets. To get only + * daemon FDs in fd_sets, call FD_ZERO for each fd_set + * before calling this function. FD_SETSIZE is assumed + * to be platform's default. + * + * @param daemon daemon to get sets from + * @param read_fd_set read set + * @param write_fd_set write set + * @param except_fd_set except set + * @param max_fd increased to largest FD added (if larger + * than existing value); can be NULL + * @return #MHD_YES on success, #MHD_NO if this + * daemon was not started with the right + * options for this call or any FD didn't + * fit fd_set. + * @ingroup event + */ +int +MHD_get_fdset (struct MHD_Daemon *daemon, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + MHD_socket *max_fd) +{ + return MHD_get_fdset2(daemon, read_fd_set, + write_fd_set, except_fd_set, + max_fd, MHD_DEFAULT_FD_SETSIZE); +} + +/** + * Obtain the `select()` sets for this daemon. + * Daemon's FDs will be added to fd_sets. To get only + * daemon FDs in fd_sets, call FD_ZERO for each fd_set + * before calling this function. Passing custom FD_SETSIZE + * as @a fd_setsize allow usage of larger/smaller than + * platform's default fd_sets. + * + * @param daemon daemon to get sets from + * @param read_fd_set read set + * @param write_fd_set write set + * @param except_fd_set except set + * @param max_fd increased to largest FD added (if larger + * than existing value); can be NULL + * @param fd_setsize value of FD_SETSIZE + * @return #MHD_YES on success, #MHD_NO if this + * daemon was not started with the right + * options for this call or any FD didn't + * fit fd_set. + * @ingroup event + */ +int +MHD_get_fdset2 (struct MHD_Daemon *daemon, + fd_set *read_fd_set, + fd_set *write_fd_set, + fd_set *except_fd_set, + MHD_socket *max_fd, + unsigned int fd_setsize) +{ + struct MHD_Connection *pos; + + if ( (NULL == daemon) + || (NULL == read_fd_set) + || (NULL == write_fd_set) + || (MHD_YES == daemon->shutdown) + || (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + || (0 != (daemon->options & MHD_USE_POLL))) + return MHD_NO; +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + /* we're in epoll mode, use the epoll FD as a stand-in for + the entire event set */ + + return add_to_fd_set (daemon->epoll_fd, read_fd_set, max_fd, fd_setsize); + } +#endif + if (MHD_INVALID_SOCKET != daemon->socket_fd && + MHD_YES != add_to_fd_set (daemon->socket_fd, read_fd_set, max_fd, fd_setsize)) + return MHD_NO; + + for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + { + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if (MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize)) + return MHD_NO; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if (MHD_YES != add_to_fd_set (pos->socket_fd, write_fd_set, max_fd, fd_setsize)) + return MHD_NO; + if (pos->read_buffer_size > pos->read_buffer_offset && + MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize)) + return MHD_NO; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if (pos->read_buffer_size > pos->read_buffer_offset && + MHD_YES != add_to_fd_set (pos->socket_fd, read_fd_set, max_fd, fd_setsize)) + return MHD_NO; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* this should never happen */ + break; + } + } +#if DEBUG_CONNECT +#if HAVE_MESSAGES + if (NULL != max_fd) + MHD_DLOG (daemon, + "Maximum socket in select set: %d\n", + *max_fd); +#endif +#endif + return MHD_YES; +} + + +/** + * Main function of the thread that handles an individual + * connection when #MHD_USE_THREAD_PER_CONNECTION is set. + * + * @param data the `struct MHD_Connection` this thread will handle + * @return always 0 + */ +static MHD_THRD_RTRN_TYPE_ MHD_THRD_CALL_SPEC_ +MHD_handle_connection (void *data) +{ + struct MHD_Connection *con = data; + int num_ready; + fd_set rs; + fd_set ws; + MHD_socket max; + struct timeval tv; + struct timeval *tvp; + unsigned int timeout; + time_t now; +#if WINDOWS + MHD_pipe spipe = con->daemon->wpipe[0]; + char tmp; +#ifdef HAVE_POLL + int extra_slot; +#endif /* HAVE_POLL */ +#define EXTRA_SLOTS 1 +#else /* !WINDOWS */ +#define EXTRA_SLOTS 0 +#endif /* !WINDOWS */ +#ifdef HAVE_POLL + struct pollfd p[1 + EXTRA_SLOTS]; +#endif + + timeout = con->daemon->connection_timeout; + while ( (MHD_YES != con->daemon->shutdown) && + (MHD_CONNECTION_CLOSED != con->state) ) + { + tvp = NULL; + if (timeout > 0) + { + now = MHD_monotonic_time(); + if (now - con->last_activity > timeout) + tv.tv_sec = 0; + else + tv.tv_sec = timeout - (now - con->last_activity); + tv.tv_usec = 0; + tvp = &tv; + } +#if HTTPS_SUPPORT + if (MHD_YES == con->tls_read_ready) + { + /* do not block (more data may be inside of TLS buffers waiting for us) */ + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + } +#endif + if (0 == (con->daemon->options & MHD_USE_POLL)) + { + /* use select */ + int err_state = 0; + FD_ZERO (&rs); + FD_ZERO (&ws); + max = 0; + switch (con->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if (MHD_YES != + add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE)) + err_state = 1; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if (MHD_YES != + add_to_fd_set (con->socket_fd, &ws, &max, FD_SETSIZE)) + err_state = 1; + if ( (con->read_buffer_size > con->read_buffer_offset) && + (MHD_YES != + add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE)) ) + err_state = 1; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if ( (con->read_buffer_size > con->read_buffer_offset) && + (MHD_YES != + add_to_fd_set (con->socket_fd, &rs, &max, FD_SETSIZE)) ) + err_state = 1; + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* how did we get here!? */ + goto exit; + } +#if WINDOWS + if (MHD_INVALID_PIPE_ != spipe) + { + if (MHD_YES != + add_to_fd_set (spipe, &rs, &max, FD_SETSIZE)) + err_state = 1; + } +#endif + if (0 != err_state) + { +#if HAVE_MESSAGES + MHD_DLOG (con->daemon, + "Can't add FD to fd_set\n"); +#endif + goto exit; + } + + num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, NULL, tvp); + if (num_ready < 0) + { + if (EINTR == MHD_socket_errno_) + continue; +#if HAVE_MESSAGES + MHD_DLOG (con->daemon, + "Error during select (%d): `%s'\n", + MHD_socket_errno_, + MHD_socket_last_strerr_ ()); +#endif + break; + } +#if WINDOWS + /* drain signaling pipe */ + if ( (MHD_INVALID_PIPE_ != spipe) && + (FD_ISSET (spipe, &rs)) ) + (void) MHD_pipe_read_ (spipe, &tmp, sizeof (tmp)); +#endif + /* call appropriate connection handler if necessary */ + if ( (FD_ISSET (con->socket_fd, &rs)) +#if HTTPS_SUPPORT + || (MHD_YES == con->tls_read_ready) +#endif + ) + con->read_handler (con); + if (FD_ISSET (con->socket_fd, &ws)) + con->write_handler (con); + if (MHD_NO == con->idle_handler (con)) + goto exit; + } +#ifdef HAVE_POLL + else + { + /* use poll */ + memset (&p, 0, sizeof (p)); + p[0].fd = con->socket_fd; + switch (con->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + p[0].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + p[0].events |= POLLOUT; + if (con->read_buffer_size > con->read_buffer_offset) + p[0].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if (con->read_buffer_size > con->read_buffer_offset) + p[0].events |= POLLIN; + tv.tv_sec = 0; + tv.tv_usec = 0; + tvp = &tv; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* how did we get here!? */ + goto exit; + } +#if WINDOWS + extra_slot = 0; + if (MHD_INVALID_PIPE_ != spipe) + { + p[1].events |= POLLIN; + p[1].fd = spipe; + p[1].revents = 0; + extra_slot = 1; + } +#endif + if (MHD_sys_poll_ (p, +#if WINDOWS + 1 + extra_slot, +#else + 1, +#endif + (NULL == tvp) ? -1 : tv.tv_sec * 1000) < 0) + { + if (EINTR == MHD_socket_errno_) + continue; +#if HAVE_MESSAGES + MHD_DLOG (con->daemon, + "Error during poll: `%s'\n", + MHD_socket_last_strerr_ ()); +#endif + break; + } +#if WINDOWS + /* drain signaling pipe */ + if ( (MHD_INVALID_PIPE_ != spipe) && + (0 != (p[1].revents & (POLLERR | POLLHUP))) ) + (void) MHD_pipe_read_ (spipe, &tmp, sizeof (tmp)); +#endif + if ( (0 != (p[0].revents & POLLIN)) +#if HTTPS_SUPPORT + || (MHD_YES == con->tls_read_ready) +#endif + ) + con->read_handler (con); + if (0 != (p[0].revents & POLLOUT)) + con->write_handler (con); + if (0 != (p[0].revents & (POLLERR | POLLHUP))) + MHD_connection_close (con, MHD_REQUEST_TERMINATED_WITH_ERROR); + if (MHD_NO == con->idle_handler (con)) + goto exit; + } +#endif + } + if (MHD_CONNECTION_IN_CLEANUP != con->state) + { +#if DEBUG_CLOSE +#if HAVE_MESSAGES + MHD_DLOG (con->daemon, + "Processing thread terminating, closing connection\n"); +#endif +#endif + if (MHD_CONNECTION_CLOSED != con->state) + MHD_connection_close (con, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + con->idle_handler (con); + } +exit: + if (NULL != con->response) + { + MHD_destroy_response (con->response); + con->response = NULL; + } + + if (NULL != con->daemon->notify_connection) + con->daemon->notify_connection (con->daemon->notify_connection_cls, + con, + &con->socket_context, + MHD_CONNECTION_NOTIFY_CLOSED); + + return (MHD_THRD_RTRN_TYPE_)0; +} + + +/** + * Callback for receiving data from the socket. + * + * @param connection the MHD connection structure + * @param other where to write received data to + * @param i maximum size of other (in bytes) + * @return number of bytes actually received + */ +static ssize_t +recv_param_adapter (struct MHD_Connection *connection, + void *other, + size_t i) +{ + ssize_t ret; + + if ( (MHD_INVALID_SOCKET == connection->socket_fd) || + (MHD_CONNECTION_CLOSED == connection->state) ) + { + MHD_set_socket_errno_ (ENOTCONN); + return -1; + } + ret = recv (connection->socket_fd, other, i, MSG_NOSIGNAL); +#if EPOLL_SUPPORT + if (ret < (ssize_t) i) + { + /* partial read --- no longer read-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_READ_READY; + } +#endif + return ret; +} + + +/** + * Callback for writing data to the socket. + * + * @param connection the MHD connection structure + * @param other data to write + * @param i number of bytes to write + * @return actual number of bytes written + */ +static ssize_t +send_param_adapter (struct MHD_Connection *connection, + const void *other, + size_t i) +{ + ssize_t ret; +#if LINUX + MHD_socket fd; + off_t offset; + off_t left; +#endif + + if ( (MHD_INVALID_SOCKET == connection->socket_fd) || + (MHD_CONNECTION_CLOSED == connection->state) ) + { + MHD_set_socket_errno_ (ENOTCONN); + return -1; + } + if (0 != (connection->daemon->options & MHD_USE_SSL)) + return send (connection->socket_fd, other, i, MSG_NOSIGNAL); +#if LINUX + if ( (connection->write_buffer_append_offset == + connection->write_buffer_send_offset) && + (NULL != connection->response) && + (MHD_INVALID_SOCKET != (fd = connection->response->fd)) ) + { + /* can use sendfile */ + offset = (off_t) connection->response_write_position + connection->response->fd_off; + left = connection->response->total_size - connection->response_write_position; + if (left > SSIZE_MAX) + left = SSIZE_MAX; /* cap at return value limit */ + if (-1 != (ret = sendfile (connection->socket_fd, + fd, + &offset, + (size_t) left))) + { +#if EPOLL_SUPPORT + if (ret < left) + { + /* partial write --- no longer write-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; + } +#endif + return ret; + } + const int err = MHD_socket_errno_; + if ( (EINTR == err) || (EAGAIN == err) || (EWOULDBLOCK == err) ) + return 0; + if ( (EINVAL == err) || (EBADF == err) ) + return -1; + /* None of the 'usual' sendfile errors occurred, so we should try + to fall back to 'SEND'; see also this thread for info on + odd libc/Linux behavior with sendfile: + http://lists.gnu.org/archive/html/libmicrohttpd/2011-02/msg00015.html */ + } +#endif + ret = send (connection->socket_fd, other, i, MSG_NOSIGNAL); +#if EPOLL_SUPPORT + if (ret < (ssize_t) i) + { + /* partial write --- no longer write-ready */ + connection->epoll_state &= ~MHD_EPOLL_STATE_WRITE_READY; + } +#endif + /* Handle broken kernel / libc, returning -1 but not setting errno; + kill connection as that should be safe; reported on mailinglist here: + http://lists.gnu.org/archive/html/libmicrohttpd/2014-10/msg00023.html */ + if ( (-1 == ret) && (0 == errno) ) + errno = ECONNRESET; + return ret; +} + + +/** + * Signature of main function for a thread. + * + * @param cls closure argument for the function + * @return termination code from the thread + */ +typedef MHD_THRD_RTRN_TYPE_ (MHD_THRD_CALL_SPEC_ *ThreadStartRoutine)(void *cls); + + +/** + * Create a thread and set the attributes according to our options. + * + * @param thread handle to initialize + * @param daemon daemon with options + * @param start_routine main function of thread + * @param arg argument for start_routine + * @return 0 on success + */ +static int +create_thread (MHD_thread_handle_ *thread, + const struct MHD_Daemon *daemon, + ThreadStartRoutine start_routine, + void *arg) +{ +#if defined(MHD_USE_POSIX_THREADS) + pthread_attr_t attr; + pthread_attr_t *pattr; + int ret; + + if (0 != daemon->thread_stack_size) + { + if (0 != (ret = pthread_attr_init (&attr))) + goto ERR; + if (0 != (ret = pthread_attr_setstacksize (&attr, daemon->thread_stack_size))) + { + pthread_attr_destroy (&attr); + goto ERR; + } + pattr = &attr; + } + else + { + pattr = NULL; + } + ret = pthread_create (thread, pattr, + start_routine, arg); +#ifdef HAVE_PTHREAD_SETNAME_NP + (void) pthread_setname_np (*thread, "libmicrohttpd"); +#endif /* HAVE_PTHREAD_SETNAME_NP */ + if (0 != daemon->thread_stack_size) + pthread_attr_destroy (&attr); + return ret; + ERR: +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to set thread stack size\n"); +#endif + errno = EINVAL; + return ret; +#elif defined(MHD_USE_W32_THREADS) + unsigned threadID; + *thread = (HANDLE)_beginthreadex(NULL, (unsigned)daemon->thread_stack_size, start_routine, + arg, 0, &threadID); + if (NULL == (*thread)) + return errno; + + W32_SetThreadName(threadID, "libmicrohttpd"); + + return 0; +#endif +} + + +/** + * Add another client connection to the set of connections + * managed by MHD. This API is usually not needed (since + * MHD will accept inbound connections on the server socket). + * Use this API in special cases, for example if your HTTP + * server is behind NAT and needs to connect out to the + * HTTP client. + * + * The given client socket will be managed (and closed!) by MHD after + * this call and must no longer be used directly by the application + * afterwards. + * + * Per-IP connection limits are ignored when using this API. + * + * @param daemon daemon that manages the connection + * @param client_socket socket to manage (MHD will expect + * to receive an HTTP request from this socket next). + * @param addr IP address of the client + * @param addrlen number of bytes in @a addr + * @param external_add perform additional operations needed due + * to the application calling us directly + * @return #MHD_YES on success, #MHD_NO if this daemon could + * not handle the connection (i.e. malloc failed, etc). + * The socket will be closed in any case; 'errno' is + * set to indicate further details about the error. + */ +static int +internal_add_connection (struct MHD_Daemon *daemon, + MHD_socket client_socket, + const struct sockaddr *addr, + socklen_t addrlen, + int external_add) +{ + struct MHD_Connection *connection; + int res_thread_create; + unsigned int i; + int eno; + struct MHD_Daemon *worker; +#if OSX + static int on = 1; +#endif + + if (NULL != daemon->worker_pool) + { + /* have a pool, try to find a pool with capacity; we use the + socket as the initial offset into the pool for load + balancing */ + for (i=0;i<daemon->worker_pool_size;i++) + { + worker = &daemon->worker_pool[(i + client_socket) % daemon->worker_pool_size]; + if (worker->connections < worker->connection_limit) + return internal_add_connection (worker, + client_socket, + addr, addrlen, + external_add); + } + /* all pools are at their connection limit, must refuse */ + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); +#if ENFILE + errno = ENFILE; +#endif + return MHD_NO; + } + +#ifndef WINDOWS + if ( (client_socket >= FD_SETSIZE) && + (0 == (daemon->options & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Socket descriptor larger than FD_SETSIZE: %d > %d\n", + client_socket, + FD_SETSIZE); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); +#if EINVAL + errno = EINVAL; +#endif + return MHD_NO; + } +#endif + + +#if HAVE_MESSAGES +#if DEBUG_CONNECT + MHD_DLOG (daemon, + "Accepted connection on socket %d\n", + client_socket); +#endif +#endif + if ( (daemon->connections == daemon->connection_limit) || + (MHD_NO == MHD_ip_limit_add (daemon, addr, addrlen)) ) + { + /* above connection limit - reject */ +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Server reached connection limit (closing inbound connection)\n"); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); +#if ENFILE + errno = ENFILE; +#endif + return MHD_NO; + } + + /* apply connection acceptance policy if present */ + if ( (NULL != daemon->apc) && + (MHD_NO == daemon->apc (daemon->apc_cls, + addr, addrlen)) ) + { +#if DEBUG_CLOSE +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Connection rejected, closing connection\n"); +#endif +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); +#if EACCESS + errno = EACCESS; +#endif + return MHD_NO; + } + +#if OSX +#ifdef SOL_SOCKET +#ifdef SO_NOSIGPIPE + setsockopt (client_socket, + SOL_SOCKET, SO_NOSIGPIPE, + &on, sizeof (on)); +#endif +#endif +#endif + + if (NULL == (connection = malloc (sizeof (struct MHD_Connection)))) + { + eno = errno; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Error allocating memory: %s\n", + MHD_strerror_ (errno)); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + errno = eno; + return MHD_NO; + } + memset (connection, + 0, + sizeof (struct MHD_Connection)); + connection->pool = MHD_pool_create (daemon->pool_size); + if (NULL == connection->pool) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Error allocating memory: %s\n", + MHD_strerror_ (errno)); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + free (connection); +#if ENOMEM + errno = ENOMEM; +#endif + return MHD_NO; + } + + connection->connection_timeout = daemon->connection_timeout; + if (NULL == (connection->addr = malloc (addrlen))) + { + eno = errno; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Error allocating memory: %s\n", + MHD_strerror_ (errno)); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + MHD_pool_destroy (connection->pool); + free (connection); + errno = eno; + return MHD_NO; + } + memcpy (connection->addr, addr, addrlen); + connection->addr_len = addrlen; + connection->socket_fd = client_socket; + connection->daemon = daemon; + connection->last_activity = MHD_monotonic_time(); + + /* set default connection handlers */ + MHD_set_http_callbacks_ (connection); + connection->recv_cls = &recv_param_adapter; + connection->send_cls = &send_param_adapter; + + if (0 == (connection->daemon->options & MHD_USE_EPOLL_TURBO)) + { + /* non-blocking sockets are required on most systems and for GNUtls; + however, they somehow cause serious problems on CYGWIN (#1824); + in turbo mode, we assume that non-blocking was already set + by 'accept4' or whoever calls 'MHD_add_connection' */ +#ifdef CYGWIN + if (0 != (daemon->options & MHD_USE_SSL)) +#endif + { + /* make socket non-blocking */ +#if !defined(WINDOWS) || defined(CYGWIN) + int flags = fcntl (connection->socket_fd, F_GETFL); + if ( (-1 == flags) || + (0 != fcntl (connection->socket_fd, F_SETFL, flags | O_NONBLOCK)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make socket non-blocking: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } +#else + unsigned long flags = 1; + if (0 != ioctlsocket (connection->socket_fd, FIONBIO, &flags)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make socket non-blocking: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } +#endif + } + } + +#if HTTPS_SUPPORT + if (0 != (daemon->options & MHD_USE_SSL)) + { + connection->recv_cls = &recv_tls_adapter; + connection->send_cls = &send_tls_adapter; + connection->state = MHD_TLS_CONNECTION_INIT; + MHD_set_https_callbacks (connection); + gnutls_init (&connection->tls_session, GNUTLS_SERVER); + gnutls_priority_set (connection->tls_session, + daemon->priority_cache); + switch (daemon->cred_type) + { + /* set needed credentials for certificate authentication. */ + case GNUTLS_CRD_CERTIFICATE: + gnutls_credentials_set (connection->tls_session, + GNUTLS_CRD_CERTIFICATE, + daemon->x509_cred); + break; + default: +#if HAVE_MESSAGES + MHD_DLOG (connection->daemon, + "Failed to setup TLS credentials: unknown credential type %d\n", + daemon->cred_type); +#endif + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + free (connection->addr); + free (connection); + MHD_PANIC ("Unknown credential type"); +#if EINVAL + errno = EINVAL; +#endif + return MHD_NO; + } + gnutls_transport_set_ptr (connection->tls_session, + (gnutls_transport_ptr_t) connection); + gnutls_transport_set_pull_function (connection->tls_session, + (gnutls_pull_func) &recv_param_adapter); + gnutls_transport_set_push_function (connection->tls_session, + (gnutls_push_func) &send_param_adapter); + + if (daemon->https_mem_trust) + gnutls_certificate_server_set_request (connection->tls_session, + GNUTLS_CERT_REQUEST); + } +#endif + + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + DLL_insert (daemon->connections_head, + daemon->connections_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + + if (NULL != daemon->notify_connection) + daemon->notify_connection (daemon->notify_connection_cls, + connection, + &connection->socket_context, + MHD_CONNECTION_NOTIFY_STARTED); + + /* attempt to create handler thread */ + if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + { + res_thread_create = create_thread (&connection->pid, + daemon, + &MHD_handle_connection, + connection); + if (0 != res_thread_create) + { + eno = errno; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to create a thread: %s\n", + MHD_strerror_ (res_thread_create)); +#endif + goto cleanup; + } + } + else + if ( (MHD_YES == external_add) && + (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && + (1 != MHD_pipe_write_ (daemon->wpipe[1], "n", 1)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "failed to signal new connection via pipe"); +#endif + } +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + if (0 == (daemon->options & MHD_USE_EPOLL_TURBO)) + { + struct epoll_event event; + + event.events = EPOLLIN | EPOLLOUT | EPOLLET; + event.data.ptr = connection; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + client_socket, + &event)) + { + eno = errno; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto cleanup; + } + connection->epoll_state |= MHD_EPOLL_STATE_IN_EPOLL_SET; + } + else + { + connection->epoll_state |= MHD_EPOLL_STATE_READ_READY | MHD_EPOLL_STATE_WRITE_READY + | MHD_EPOLL_STATE_IN_EREADY_EDLL; + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + connection); + } + } +#endif + daemon->connections++; + return MHD_YES; + cleanup: + if (NULL != daemon->notify_connection) + daemon->notify_connection (daemon->notify_connection_cls, + connection, + &connection->socket_context, + MHD_CONNECTION_NOTIFY_CLOSED); + if (0 != MHD_socket_close_ (client_socket)) + MHD_PANIC ("close failed\n"); + MHD_ip_limit_del (daemon, addr, addrlen); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + DLL_remove (daemon->connections_head, + daemon->connections_tail, + connection); + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + MHD_pool_destroy (connection->pool); + free (connection->addr); + free (connection); +#if EINVAL + errno = eno; +#endif + return MHD_NO; +} + + +/** + * Suspend handling of network data for a given connection. This can + * be used to dequeue a connection from MHD's event loop (external + * select, internal select or thread pool; not applicable to + * thread-per-connection!) for a while. + * + * If you use this API in conjunction with a internal select or a + * thread pool, you must set the option #MHD_USE_PIPE_FOR_SHUTDOWN to + * ensure that a resumed connection is immediately processed by MHD. + * + * Suspended connections continue to count against the total number of + * connections allowed (per daemon, as well as per IP, if such limits + * are set). Suspended connections will NOT time out; timeouts will + * restart when the connection handling is resumed. While a + * connection is suspended, MHD will not detect disconnects by the + * client. + * + * The only safe time to suspend a connection is from the + * #MHD_AccessHandlerCallback. + * + * Finally, it is an API violation to call #MHD_stop_daemon while + * having suspended connections (this will at least create memory and + * socket leaks or lead to undefined behavior). You must explicitly + * resume all connections before stopping the daemon. + * + * @param connection the connection to suspend + */ +void +MHD_suspend_connection (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon; + + daemon = connection->daemon; + if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME)) + MHD_PANIC ("Cannot suspend connections without enabling MHD_USE_SUSPEND_RESUME!\n"); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + DLL_remove (daemon->connections_head, + daemon->connections_tail, + connection); + DLL_insert (daemon->suspended_connections_head, + daemon->suspended_connections_tail, + connection); + if (connection->connection_timeout == daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + connection); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + connection); +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + if (0 != (connection->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) + { + EDLL_remove (daemon->eready_head, + daemon->eready_tail, + connection); + connection->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + if (0 != (connection->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) + { + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + connection->socket_fd, + NULL)) + MHD_PANIC ("Failed to remove FD from epoll set\n"); + connection->epoll_state &= ~MHD_EPOLL_STATE_IN_EPOLL_SET; + } + connection->epoll_state |= MHD_EPOLL_STATE_SUSPENDED; + } +#endif + connection->suspended = MHD_YES; + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + +/** + * Resume handling of network data for suspended connection. It is + * safe to resume a suspended connection at any time. Calling this function + * on a connection that was not previously suspended will result + * in undefined behavior. + * + * @param connection the connection to resume + */ +void +MHD_resume_connection (struct MHD_Connection *connection) +{ + struct MHD_Daemon *daemon; + + daemon = connection->daemon; + if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME)) + MHD_PANIC ("Cannot resume connections without enabling MHD_USE_SUSPEND_RESUME!\n"); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + connection->resuming = MHD_YES; + daemon->resuming = MHD_YES; + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && + (1 != MHD_pipe_write_ (daemon->wpipe[1], "r", 1)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "failed to signal resume via pipe"); +#endif + } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + +/** + * Run through the suspended connections and move any that are no + * longer suspended back to the active state. + * + * @param daemon daemon context + * @return #MHD_YES if a connection was actually resumed + */ +static int +resume_suspended_connections (struct MHD_Daemon *daemon) +{ + struct MHD_Connection *pos; + struct MHD_Connection *next = NULL; + int ret; + + ret = MHD_NO; + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + if (MHD_YES == daemon->resuming) + next = daemon->suspended_connections_head; + + while (NULL != (pos = next)) + { + next = pos->next; + if (MHD_NO == pos->resuming) + continue; + ret = MHD_YES; + DLL_remove (daemon->suspended_connections_head, + daemon->suspended_connections_tail, + pos); + DLL_insert (daemon->connections_head, + daemon->connections_tail, + pos); + if (pos->connection_timeout == daemon->connection_timeout) + XDLL_insert (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + pos); + else + XDLL_insert (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + pos); +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + if (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) + MHD_PANIC ("Resumed connection was already in EREADY set\n"); + /* we always mark resumed connections as ready, as we + might have missed the edge poll event during suspension */ + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + pos->epoll_state &= ~MHD_EPOLL_STATE_SUSPENDED; + } +#endif + pos->suspended = MHD_NO; + pos->resuming = MHD_NO; + } + daemon->resuming = MHD_NO; + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + return ret; +} + + +/** + * Change socket options to be non-blocking, non-inheritable. + * + * @param daemon daemon context + * @param sock socket to manipulate + */ +static void +make_nonblocking_noninheritable (struct MHD_Daemon *daemon, + MHD_socket sock) +{ +#ifdef WINDOWS + DWORD dwFlags; + unsigned long flags = 1; + + if (0 != ioctlsocket (sock, FIONBIO, &flags)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make socket non-blocking: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } + if (!GetHandleInformation ((HANDLE) sock, &dwFlags) || + ((dwFlags != (dwFlags & ~HANDLE_FLAG_INHERIT)) && + !SetHandleInformation ((HANDLE) sock, HANDLE_FLAG_INHERIT, 0))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make socket non-inheritable: %u\n", + (unsigned int) GetLastError ()); +#endif + } +#else + int flags; + int nonblock; + + nonblock = O_NONBLOCK; +#ifdef CYGWIN + if (0 == (daemon->options & MHD_USE_SSL)) + nonblock = 0; +#endif + flags = fcntl (sock, F_GETFD); + if ( ( (-1 == flags) || + ( (flags != (flags | FD_CLOEXEC)) && + (0 != fcntl (sock, F_SETFD, flags | nonblock | FD_CLOEXEC)) ) ) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make socket non-inheritable: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } +#endif +} + + +/** + * Add another client connection to the set of connections managed by + * MHD. This API is usually not needed (since MHD will accept inbound + * connections on the server socket). Use this API in special cases, + * for example if your HTTP server is behind NAT and needs to connect + * out to the HTTP client, or if you are building a proxy. + * + * If you use this API in conjunction with a internal select or a + * thread pool, you must set the option + * #MHD_USE_PIPE_FOR_SHUTDOWN to ensure that the freshly added + * connection is immediately processed by MHD. + * + * The given client socket will be managed (and closed!) by MHD after + * this call and must no longer be used directly by the application + * afterwards. + * + * Per-IP connection limits are ignored when using this API. + * + * @param daemon daemon that manages the connection + * @param client_socket socket to manage (MHD will expect + * to receive an HTTP request from this socket next). + * @param addr IP address of the client + * @param addrlen number of bytes in @a addr + * @return #MHD_YES on success, #MHD_NO if this daemon could + * not handle the connection (i.e. malloc() failed, etc). + * The socket will be closed in any case; `errno` is + * set to indicate further details about the error. + * @ingroup specialized + */ +int +MHD_add_connection (struct MHD_Daemon *daemon, + MHD_socket client_socket, + const struct sockaddr *addr, + socklen_t addrlen) +{ + make_nonblocking_noninheritable (daemon, + client_socket); + return internal_add_connection (daemon, + client_socket, + addr, addrlen, + MHD_YES); +} + + +/** + * Accept an incoming connection and create the MHD_Connection object for + * it. This function also enforces policy by way of checking with the + * accept policy callback. + * + * @param daemon handle with the listen socket + * @return #MHD_YES on success (connections denied by policy or due + * to 'out of memory' and similar errors) are still considered + * successful as far as #MHD_accept_connection() is concerned); + * a return code of #MHD_NO only refers to the actual + * accept() system call. + */ +static int +MHD_accept_connection (struct MHD_Daemon *daemon) +{ +#if HAVE_INET6 + struct sockaddr_in6 addrstorage; +#else + struct sockaddr_in addrstorage; +#endif + struct sockaddr *addr = (struct sockaddr *) &addrstorage; + socklen_t addrlen; + MHD_socket s; + MHD_socket fd; + int nonblock; + + addrlen = sizeof (addrstorage); + memset (addr, 0, sizeof (addrstorage)); + if (MHD_INVALID_SOCKET == (fd = daemon->socket_fd)) + return MHD_NO; +#ifdef HAVE_SOCK_NONBLOCK + nonblock = SOCK_NONBLOCK; +#else + nonblock = 0; +#endif +#ifdef CYGWIN + if (0 == (daemon->options & MHD_USE_SSL)) + nonblock = 0; +#endif +#if HAVE_ACCEPT4 + s = accept4 (fd, addr, &addrlen, SOCK_CLOEXEC | nonblock); +#else + s = accept (fd, addr, &addrlen); +#endif + if ((MHD_INVALID_SOCKET == s) || (addrlen <= 0)) + { +#if HAVE_MESSAGES + const int err = MHD_socket_errno_; + /* This could be a common occurance with multiple worker threads */ + if ( (EINVAL == err) && + (MHD_INVALID_SOCKET == daemon->socket_fd) ) + return MHD_NO; /* can happen during shutdown */ + if ((EAGAIN != err) && (EWOULDBLOCK != err)) + MHD_DLOG (daemon, + "Error accepting connection: %s\n", + MHD_socket_last_strerr_ ()); +#endif + if (MHD_INVALID_SOCKET != s) + { + if (0 != MHD_socket_close_ (s)) + MHD_PANIC ("close failed\n"); + /* just in case */ + } + return MHD_NO; + } +#if !defined(HAVE_ACCEPT4) || HAVE_ACCEPT4+0 == 0 || !defined(HAVE_SOCK_NONBLOCK) || SOCK_CLOEXEC+0 == 0 + make_nonblocking_noninheritable (daemon, s); +#endif +#if HAVE_MESSAGES +#if DEBUG_CONNECT + MHD_DLOG (daemon, + "Accepted connection on socket %d\n", + s); +#endif +#endif + (void) internal_add_connection (daemon, s, + addr, addrlen, + MHD_NO); + return MHD_YES; +} + + +/** + * Free resources associated with all closed connections. + * (destroy responses, free buffers, etc.). All closed + * connections are kept in the "cleanup" doubly-linked list. + * + * @param daemon daemon to clean up + */ +static void +MHD_cleanup_connections (struct MHD_Daemon *daemon) +{ + struct MHD_Connection *pos; + + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + while (NULL != (pos = daemon->cleanup_head)) + { + DLL_remove (daemon->cleanup_head, + daemon->cleanup_tail, + pos); + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_NO == pos->thread_joined) ) + { + if (0 != MHD_join_thread_ (pos->pid)) + { + MHD_PANIC ("Failed to join a thread\n"); + } + } + MHD_pool_destroy (pos->pool); +#if HTTPS_SUPPORT + if (NULL != pos->tls_session) + gnutls_deinit (pos->tls_session); +#endif + if (NULL != daemon->notify_connection) + daemon->notify_connection (daemon->notify_connection_cls, + pos, + &pos->socket_context, + MHD_CONNECTION_NOTIFY_CLOSED); + MHD_ip_limit_del (daemon, pos->addr, pos->addr_len); +#if EPOLL_SUPPORT + if (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL)) + { + EDLL_remove (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (MHD_INVALID_SOCKET != daemon->epoll_fd) && + (0 != (pos->epoll_state & MHD_EPOLL_STATE_IN_EPOLL_SET)) ) + { + /* epoll documentation suggests that closing a FD + automatically removes it from the epoll set; however, + this is not true as if we fail to do manually remove it, + we are still seeing an event for this fd in epoll, + causing grief (use-after-free...) --- at least on my + system. */ + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + pos->socket_fd, + NULL)) + MHD_PANIC ("Failed to remove FD from epoll set\n"); + pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EPOLL_SET; + } +#endif + if (NULL != pos->response) + { + MHD_destroy_response (pos->response); + pos->response = NULL; + } + if (MHD_INVALID_SOCKET != pos->socket_fd) + { +#ifdef WINDOWS + shutdown (pos->socket_fd, SHUT_WR); +#endif + if (0 != MHD_socket_close_ (pos->socket_fd)) + MHD_PANIC ("close failed\n"); + } + if (NULL != pos->addr) + free (pos->addr); + free (pos); + daemon->connections--; + } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); +} + + +/** + * Obtain timeout value for `select()` for this daemon (only needed if + * connection timeout is used). The returned value is how long + * `select()` or `poll()` should at most block, not the timeout value set + * for connections. This function MUST NOT be called if MHD is + * running with #MHD_USE_THREAD_PER_CONNECTION. + * + * @param daemon daemon to query for timeout + * @param timeout set to the timeout (in milliseconds) + * @return #MHD_YES on success, #MHD_NO if timeouts are + * not used (or no connections exist that would + * necessiate the use of a timeout right now). + * @ingroup event + */ +int +MHD_get_timeout (struct MHD_Daemon *daemon, + MHD_UNSIGNED_LONG_LONG *timeout) +{ + time_t earliest_deadline; + time_t now; + struct MHD_Connection *pos; + int have_timeout; + + if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Illegal call to MHD_get_timeout\n"); +#endif + return MHD_NO; + } + +#if HTTPS_SUPPORT + if (0 != daemon->num_tls_read_ready) + { + /* if there is any TLS connection with data ready for + reading, we must not block in the event loop */ + *timeout = 0; + return MHD_YES; + } +#endif + + have_timeout = MHD_NO; + earliest_deadline = 0; /* avoid compiler warnings */ + for (pos = daemon->manual_timeout_head; NULL != pos; pos = pos->nextX) + { + if (0 != pos->connection_timeout) + { + if ( (! have_timeout) || + (earliest_deadline > pos->last_activity + pos->connection_timeout) ) + earliest_deadline = pos->last_activity + pos->connection_timeout; +#if HTTPS_SUPPORT + if ( (0 != (daemon->options & MHD_USE_SSL)) && + (0 != gnutls_record_check_pending (pos->tls_session)) ) + earliest_deadline = 0; +#endif + have_timeout = MHD_YES; + } + } + /* normal timeouts are sorted, so we only need to look at the 'head' */ + pos = daemon->normal_timeout_head; + if ( (NULL != pos) && + (0 != pos->connection_timeout) ) + { + if ( (! have_timeout) || + (earliest_deadline > pos->last_activity + pos->connection_timeout) ) + earliest_deadline = pos->last_activity + pos->connection_timeout; +#if HTTPS_SUPPORT + if ( (0 != (daemon->options & MHD_USE_SSL)) && + (0 != gnutls_record_check_pending (pos->tls_session)) ) + earliest_deadline = 0; +#endif + have_timeout = MHD_YES; + } + + if (MHD_NO == have_timeout) + return MHD_NO; + now = MHD_monotonic_time(); + if (earliest_deadline < now) + *timeout = 0; + else + *timeout = 1000 * (1 + earliest_deadline - now); + return MHD_YES; +} + + +/** + * Run webserver operations. This method should be called by clients + * in combination with #MHD_get_fdset if the client-controlled select + * method is used. + * + * You can use this function instead of #MHD_run if you called + * `select()` on the result from #MHD_get_fdset. File descriptors in + * the sets that are not controlled by MHD will be ignored. Calling + * this function instead of #MHD_run is more efficient as MHD will + * not have to call `select()` again to determine which operations are + * ready. + * + * @param daemon daemon to run select loop for + * @param read_fd_set read set + * @param write_fd_set write set + * @param except_fd_set except set (not used, can be NULL) + * @return #MHD_NO on serious errors, #MHD_YES on success + * @ingroup event + */ +int +MHD_run_from_select (struct MHD_Daemon *daemon, + const fd_set *read_fd_set, + const fd_set *write_fd_set, + const fd_set *except_fd_set) +{ + MHD_socket ds; + char tmp; + struct MHD_Connection *pos; + struct MHD_Connection *next; + +#if EPOLL_SUPPORT + if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + /* we're in epoll mode, the epoll FD stands for + the entire event set! */ + if (daemon->epoll_fd >= FD_SETSIZE) + return MHD_NO; /* poll fd too big, fail hard */ + if (FD_ISSET (daemon->epoll_fd, read_fd_set)) + return MHD_run (daemon); + return MHD_YES; + } +#endif + + /* select connection thread handling type */ + if ( (MHD_INVALID_SOCKET != (ds = daemon->socket_fd)) && + (FD_ISSET (ds, read_fd_set)) ) + (void) MHD_accept_connection (daemon); + /* drain signaling pipe to avoid spinning select */ + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && + (FD_ISSET (daemon->wpipe[0], read_fd_set)) ) + (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp)); + + if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + { + /* do not have a thread per connection, process all connections now */ + next = daemon->connections_head; + while (NULL != (pos = next)) + { + next = pos->next; + ds = pos->socket_fd; + if (MHD_INVALID_SOCKET == ds) + continue; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + if ( (FD_ISSET (ds, read_fd_set)) +#if HTTPS_SUPPORT + || (MHD_YES == pos->tls_read_ready) +#endif + ) + pos->read_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_WRITE: + if ( (FD_ISSET (ds, read_fd_set)) && + (pos->read_buffer_size > pos->read_buffer_offset) ) + pos->read_handler (pos); + if (FD_ISSET (ds, write_fd_set)) + pos->write_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if ( (FD_ISSET (ds, read_fd_set)) && + (pos->read_buffer_size > pos->read_buffer_offset) ) + pos->read_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + /* should never happen */ + break; + } + pos->idle_handler (pos); + } + } + MHD_cleanup_connections (daemon); + return MHD_YES; +} + + +/** + * Main internal select() call. Will compute select sets, call select() + * and then #MHD_run_from_select with the result. + * + * @param daemon daemon to run select() loop for + * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking + * @return #MHD_NO on serious errors, #MHD_YES on success + */ +static int +MHD_select (struct MHD_Daemon *daemon, + int may_block) +{ + int num_ready; + fd_set rs; + fd_set ws; + fd_set es; + MHD_socket max; + struct timeval timeout; + struct timeval *tv; + MHD_UNSIGNED_LONG_LONG ltimeout; + + timeout.tv_sec = 0; + timeout.tv_usec = 0; + if (MHD_YES == daemon->shutdown) + return MHD_NO; + FD_ZERO (&rs); + FD_ZERO (&ws); + FD_ZERO (&es); + max = MHD_INVALID_SOCKET; + if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + { + if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) && + (MHD_YES == resume_suspended_connections (daemon)) ) + may_block = MHD_NO; + + /* single-threaded, go over everything */ + if (MHD_NO == MHD_get_fdset2 (daemon, &rs, &ws, &es, &max, FD_SETSIZE)) + return MHD_NO; + + /* If we're at the connection limit, no need to + accept new connections; however, make sure + we do not miss the shutdown, so only do this + optimization if we have a shutdown signaling + pipe. */ + if ( (MHD_INVALID_SOCKET != daemon->socket_fd) && + (daemon->connections == daemon->connection_limit) && + (0 != (daemon->options & MHD_USE_PIPE_FOR_SHUTDOWN)) ) + FD_CLR (daemon->socket_fd, &rs); + } + else + { + /* accept only, have one thread per connection */ + if ( (MHD_INVALID_SOCKET != daemon->socket_fd) && + (MHD_YES != add_to_fd_set (daemon->socket_fd, + &rs, + &max, + FD_SETSIZE)) ) + return MHD_NO; + } + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && + (MHD_YES != add_to_fd_set (daemon->wpipe[0], + &rs, + &max, + FD_SETSIZE)) ) + return MHD_NO; + + tv = NULL; + if (MHD_NO == may_block) + { + timeout.tv_usec = 0; + timeout.tv_sec = 0; + tv = &timeout; + } + else if ( (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES == MHD_get_timeout (daemon, <imeout)) ) + { + /* ltimeout is in ms */ + timeout.tv_usec = (ltimeout % 1000) * 1000; + timeout.tv_sec = ltimeout / 1000; + tv = &timeout; + } + if (MHD_INVALID_SOCKET == max) + return MHD_YES; + num_ready = MHD_SYS_select_ (max + 1, &rs, &ws, &es, tv); + if (MHD_YES == daemon->shutdown) + return MHD_NO; + if (num_ready < 0) + { + if (EINTR == MHD_socket_errno_) + return MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "select failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + return MHD_run_from_select (daemon, &rs, &ws, &es); +} + + +#ifdef HAVE_POLL +/** + * Process all of our connections and possibly the server + * socket using poll(). + * + * @param daemon daemon to run poll loop for + * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking + * @return #MHD_NO on serious errors, #MHD_YES on success + */ +static int +MHD_poll_all (struct MHD_Daemon *daemon, + int may_block) +{ + unsigned int num_connections; + struct MHD_Connection *pos; + struct MHD_Connection *next; + + if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) && + (MHD_YES == resume_suspended_connections (daemon)) ) + may_block = MHD_NO; + + /* count number of connections and thus determine poll set size */ + num_connections = 0; + for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + num_connections++; + { + MHD_UNSIGNED_LONG_LONG ltimeout; + unsigned int i; + int timeout; + unsigned int poll_server; + int poll_listen; + int poll_pipe; + char tmp; + struct pollfd *p; + + p = malloc(sizeof (struct pollfd) * (2 + num_connections)); + if (NULL == p) + { +#if HAVE_MESSAGES + MHD_DLOG(daemon, + "Error allocating memory: %s\n", + MHD_strerror_(errno)); +#endif + return MHD_NO; + } + memset (p, 0, sizeof (struct pollfd) * (2 + num_connections)); + poll_server = 0; + poll_listen = -1; + if ( (MHD_INVALID_SOCKET != daemon->socket_fd) && + (daemon->connections < daemon->connection_limit) ) + { + /* only listen if we are not at the connection limit */ + p[poll_server].fd = daemon->socket_fd; + p[poll_server].events = POLLIN; + p[poll_server].revents = 0; + poll_listen = (int) poll_server; + poll_server++; + } + poll_pipe = -1; + if (MHD_INVALID_PIPE_ != daemon->wpipe[0]) + { + p[poll_server].fd = daemon->wpipe[0]; + p[poll_server].events = POLLIN; + p[poll_server].revents = 0; + poll_pipe = (int) poll_server; + poll_server++; + } + if (may_block == MHD_NO) + timeout = 0; + else if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || + (MHD_YES != MHD_get_timeout (daemon, <imeout)) ) + timeout = -1; + else + timeout = (ltimeout > INT_MAX) ? INT_MAX : (int) ltimeout; + + i = 0; + for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + { + p[poll_server+i].fd = pos->socket_fd; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + p[poll_server+i].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + p[poll_server+i].events |= POLLOUT; + if (pos->read_buffer_size > pos->read_buffer_offset) + p[poll_server+i].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if (pos->read_buffer_size > pos->read_buffer_offset) + p[poll_server+i].events |= POLLIN; + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + timeout = 0; /* clean up "pos" immediately */ + break; + } + i++; + } + if (0 == poll_server + num_connections) + { + free(p); + return MHD_YES; + } + if (MHD_sys_poll_(p, poll_server + num_connections, timeout) < 0) + { + if (EINTR == MHD_socket_errno_) + { + free(p); + return MHD_YES; + } +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "poll failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + free(p); + return MHD_NO; + } + /* handle shutdown */ + if (MHD_YES == daemon->shutdown) + { + free(p); + return MHD_NO; + } + i = 0; + next = daemon->connections_head; + while (NULL != (pos = next)) + { + next = pos->next; + switch (pos->event_loop_info) + { + case MHD_EVENT_LOOP_INFO_READ: + /* first, sanity checks */ + if (i >= num_connections) + break; /* connection list changed somehow, retry later ... */ + if (p[poll_server+i].fd != pos->socket_fd) + break; /* fd mismatch, something else happened, retry later ... */ + /* normal handling */ + if (0 != (p[poll_server+i].revents & POLLIN)) + pos->read_handler (pos); + pos->idle_handler (pos); + i++; + break; + case MHD_EVENT_LOOP_INFO_WRITE: + /* first, sanity checks */ + if (i >= num_connections) + break; /* connection list changed somehow, retry later ... */ + if (p[poll_server+i].fd != pos->socket_fd) + break; /* fd mismatch, something else happened, retry later ... */ + /* normal handling */ + if (0 != (p[poll_server+i].revents & POLLIN)) + pos->read_handler (pos); + if (0 != (p[poll_server+i].revents & POLLOUT)) + pos->write_handler (pos); + pos->idle_handler (pos); + i++; + break; + case MHD_EVENT_LOOP_INFO_BLOCK: + if (0 != (p[poll_server+i].revents & POLLIN)) + pos->read_handler (pos); + pos->idle_handler (pos); + break; + case MHD_EVENT_LOOP_INFO_CLEANUP: + pos->idle_handler (pos); + break; + } + } + /* handle 'listen' FD */ + if ( (-1 != poll_listen) && + (0 != (p[poll_listen].revents & POLLIN)) ) + (void) MHD_accept_connection (daemon); + + /* handle pipe FD */ + if ( (-1 != poll_pipe) && + (0 != (p[poll_pipe].revents & POLLIN)) ) + (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp)); + + free(p); + } + return MHD_YES; +} + + +/** + * Process only the listen socket using poll(). + * + * @param daemon daemon to run poll loop for + * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking + * @return #MHD_NO on serious errors, #MHD_YES on success + */ +static int +MHD_poll_listen_socket (struct MHD_Daemon *daemon, + int may_block) +{ + struct pollfd p[2]; + int timeout; + unsigned int poll_count; + int poll_listen; + + memset (&p, 0, sizeof (p)); + poll_count = 0; + poll_listen = -1; + if (MHD_INVALID_SOCKET != daemon->socket_fd) + { + p[poll_count].fd = daemon->socket_fd; + p[poll_count].events = POLLIN; + p[poll_count].revents = 0; + poll_listen = poll_count; + poll_count++; + } + if (MHD_INVALID_PIPE_ != daemon->wpipe[0]) + { + p[poll_count].fd = daemon->wpipe[0]; + p[poll_count].events = POLLIN; + p[poll_count].revents = 0; + poll_count++; + } + if (MHD_NO == may_block) + timeout = 0; + else + timeout = -1; + if (0 == poll_count) + return MHD_YES; + if (MHD_sys_poll_(p, poll_count, timeout) < 0) + { + if (EINTR == MHD_socket_errno_) + return MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "poll failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + /* handle shutdown */ + if (MHD_YES == daemon->shutdown) + return MHD_NO; + if ( (-1 != poll_listen) && + (0 != (p[poll_listen].revents & POLLIN)) ) + (void) MHD_accept_connection (daemon); + return MHD_YES; +} +#endif + + +/** + * Do poll()-based processing. + * + * @param daemon daemon to run poll()-loop for + * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking + * @return #MHD_NO on serious errors, #MHD_YES on success + */ +static int +MHD_poll (struct MHD_Daemon *daemon, + int may_block) +{ +#ifdef HAVE_POLL + if (MHD_YES == daemon->shutdown) + return MHD_NO; + if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + return MHD_poll_all (daemon, may_block); + else + return MHD_poll_listen_socket (daemon, may_block); +#else + return MHD_NO; +#endif +} + + +#if EPOLL_SUPPORT + +/** + * How many events to we process at most per epoll() call? Trade-off + * between required stack-size and number of system calls we have to + * make; 128 should be way enough to avoid more than one system call + * for most scenarios, and still be moderate in stack size + * consumption. Embedded systems might want to choose a smaller value + * --- but why use epoll() on such a system in the first place? + */ +#define MAX_EVENTS 128 + + +/** + * Do epoll()-based processing (this function is allowed to + * block if @a may_block is set to #MHD_YES). + * + * @param daemon daemon to run poll loop for + * @param may_block #MHD_YES if blocking, #MHD_NO if non-blocking + * @return #MHD_NO on serious errors, #MHD_YES on success + */ +static int +MHD_epoll (struct MHD_Daemon *daemon, + int may_block) +{ + struct MHD_Connection *pos; + struct MHD_Connection *next; + struct epoll_event events[MAX_EVENTS]; + struct epoll_event event; + int timeout_ms; + MHD_UNSIGNED_LONG_LONG timeout_ll; + int num_events; + unsigned int i; + unsigned int series_length; + char tmp; + + if (-1 == daemon->epoll_fd) + return MHD_NO; /* we're down! */ + if (MHD_YES == daemon->shutdown) + return MHD_NO; + if ( (MHD_INVALID_SOCKET != daemon->socket_fd) && + (daemon->connections < daemon->connection_limit) && + (MHD_NO == daemon->listen_socket_in_epoll) ) + { + event.events = EPOLLIN; + event.data.ptr = daemon; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->socket_fd, + &event)) + { +#if 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 = MHD_YES; + } + if ( (MHD_YES == daemon->listen_socket_in_epoll) && + (daemon->connections == daemon->connection_limit) ) + { + /* we're at the connection limit, disable listen socket + for event loop for now */ + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + daemon->socket_fd, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->listen_socket_in_epoll = MHD_NO; + } + if (MHD_YES == may_block) + { + if (MHD_YES == MHD_get_timeout (daemon, + &timeout_ll)) + { + if (timeout_ll >= (MHD_UNSIGNED_LONG_LONG) INT_MAX) + timeout_ms = INT_MAX; + else + timeout_ms = (int) timeout_ll; + } + else + timeout_ms = -1; + } + else + timeout_ms = 0; + + /* drain 'epoll' event queue; need to iterate as we get at most + MAX_EVENTS in one system call here; in practice this should + pretty much mean only one round, but better an extra loop here + than unfair behavior... */ + num_events = MAX_EVENTS; + while (MAX_EVENTS == num_events) + { + /* update event masks */ + num_events = epoll_wait (daemon->epoll_fd, + events, MAX_EVENTS, timeout_ms); + if (-1 == num_events) + { + if (EINTR == MHD_socket_errno_) + return MHD_YES; +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Call to epoll_wait failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + for (i=0;i<(unsigned int) num_events;i++) + { + if (NULL == events[i].data.ptr) + continue; /* shutdown signal! */ + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && + (daemon->wpipe[0] == events[i].data.fd) ) + { + (void) MHD_pipe_read_ (daemon->wpipe[0], &tmp, sizeof (tmp)); + continue; + } + if (daemon != events[i].data.ptr) + { + /* this is an event relating to a 'normal' connection, + remember the event and if appropriate mark the + connection as 'eready'. */ + pos = events[i].data.ptr; + if (0 != (events[i].events & EPOLLIN)) + { + pos->epoll_state |= MHD_EPOLL_STATE_READ_READY; + if ( ( (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) || + (pos->read_buffer_size > pos->read_buffer_offset) ) && + (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) ) + { + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + } + if (0 != (events[i].events & EPOLLOUT)) + { + pos->epoll_state |= MHD_EPOLL_STATE_WRITE_READY; + if ( (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) && + (0 == (pos->epoll_state & MHD_EPOLL_STATE_IN_EREADY_EDLL) ) ) + { + EDLL_insert (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state |= MHD_EPOLL_STATE_IN_EREADY_EDLL; + } + } + } + else /* must be listen socket */ + { + /* run 'accept' until it fails or we are not allowed to take + on more connections */ + series_length = 0; + while ( (MHD_YES == MHD_accept_connection (daemon)) && + (daemon->connections < daemon->connection_limit) && + (series_length < 128) ) + series_length++; + } + } + } + + /* we handle resumes here because we may have ready connections + that will not be placed into the epoll list immediately. */ + if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) && + (MHD_YES == resume_suspended_connections (daemon)) ) + may_block = MHD_NO; + + /* process events for connections */ + while (NULL != (pos = daemon->eready_tail)) + { + EDLL_remove (daemon->eready_head, + daemon->eready_tail, + pos); + pos->epoll_state &= ~MHD_EPOLL_STATE_IN_EREADY_EDLL; + if (MHD_EVENT_LOOP_INFO_READ == pos->event_loop_info) + pos->read_handler (pos); + if (MHD_EVENT_LOOP_INFO_WRITE == pos->event_loop_info) + pos->write_handler (pos); + pos->idle_handler (pos); + } + + /* Finally, handle timed-out connections; we need to do this here + as the epoll mechanism won't call the 'idle_handler' on everything, + as the other event loops do. As timeouts do not get an explicit + event, we need to find those connections that might have timed out + here. + + Connections with custom timeouts must all be looked at, as we + do not bother to sort that (presumably very short) list. */ + next = daemon->manual_timeout_head; + while (NULL != (pos = next)) + { + next = pos->nextX; + pos->idle_handler (pos); + } + /* Connections with the default timeout are sorted by prepending + them to the head of the list whenever we touch the connection; + thus it sufficies to iterate from the tail until the first + connection is NOT timed out */ + next = daemon->normal_timeout_tail; + while (NULL != (pos = next)) + { + next = pos->prevX; + pos->idle_handler (pos); + if (MHD_CONNECTION_CLOSED != pos->state) + break; /* sorted by timeout, no need to visit the rest! */ + } + return MHD_YES; +} +#endif + + +/** + * Run webserver operations (without blocking unless in client + * callbacks). This method should be called by clients in combination + * with #MHD_get_fdset if the client-controlled select method is used. + * + * This function is a convenience method, which is useful if the + * fd_sets from #MHD_get_fdset were not directly passed to `select()`; + * with this function, MHD will internally do the appropriate `select()` + * call itself again. While it is always safe to call #MHD_run (in + * external select mode), you should call #MHD_run_from_select if + * performance is important (as it saves an expensive call to + * `select()`). + * + * @param daemon daemon to run + * @return #MHD_YES on success, #MHD_NO if this + * daemon was not started with the right + * options for this call. + * @ingroup event + */ +int +MHD_run (struct MHD_Daemon *daemon) +{ + if ( (MHD_YES == daemon->shutdown) || + (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || + (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) ) + return MHD_NO; + if (0 != (daemon->options & MHD_USE_POLL)) + { + MHD_poll (daemon, MHD_NO); + MHD_cleanup_connections (daemon); + } +#if EPOLL_SUPPORT + else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + { + MHD_epoll (daemon, MHD_NO); + MHD_cleanup_connections (daemon); + } +#endif + else + { + MHD_select (daemon, MHD_NO); + /* MHD_select does MHD_cleanup_connections already */ + } + return MHD_YES; +} + + +/** + * Thread that runs the select loop until the daemon + * is explicitly shut down. + * + * @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_select_thread (void *cls) +{ + struct MHD_Daemon *daemon = cls; + + while (MHD_YES != daemon->shutdown) + { + if (0 != (daemon->options & MHD_USE_POLL)) + MHD_poll (daemon, MHD_YES); +#if EPOLL_SUPPORT + else if (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) + MHD_epoll (daemon, MHD_YES); +#endif + else + MHD_select (daemon, MHD_YES); + MHD_cleanup_connections (daemon); + } + return (MHD_THRD_RTRN_TYPE_)0; +} + + +/** + * 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 connection handle to connection, 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_Connection *connection, + char *val) +{ + return MHD_http_unescape (val); +} + + +/** + * Start a webserver on the given port. Variadic version of + * #MHD_start_daemon_va. + * + * @param flags combination of `enum MHD_FLAG` values + * @param port port to bind to + * @param apc callback to call to check which clients + * will be allowed to connect; you can pass NULL + * in which case connections from any IP will be + * accepted + * @param apc_cls extra argument to @a apc + * @param dh handler called for all requests (repeatedly) + * @param dh_cls extra argument to @a dh + * @return NULL on error, handle to daemon on success + * @ingroup event + */ +struct MHD_Daemon * +MHD_start_daemon (unsigned int flags, + uint16_t port, + MHD_AcceptPolicyCallback apc, + void *apc_cls, + MHD_AccessHandlerCallback dh, void *dh_cls, ...) +{ + struct MHD_Daemon *daemon; + va_list ap; + + va_start (ap, dh_cls); + daemon = MHD_start_daemon_va (flags, port, apc, apc_cls, dh, dh_cls, ap); + va_end (ap); + return daemon; +} + + +/** + * 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_PIPE_FOR_SHUTDOWN 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 + * @ingroup specialized + */ +MHD_socket +MHD_quiesce_daemon (struct MHD_Daemon *daemon) +{ + unsigned int i; + MHD_socket ret; + + ret = daemon->socket_fd; + if (MHD_INVALID_SOCKET == ret) + return MHD_INVALID_SOCKET; + if ( (MHD_INVALID_PIPE_ == daemon->wpipe[1]) && + (0 != (daemon->options & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Using MHD_quiesce_daemon in this mode requires MHD_USE_PIPE_FOR_SHUTDOWN\n"); +#endif + return MHD_INVALID_SOCKET; + } + + if (NULL != daemon->worker_pool) + for (i = 0; i < daemon->worker_pool_size; i++) + { + daemon->worker_pool[i].socket_fd = MHD_INVALID_SOCKET; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->worker_pool[i].epoll_fd) && + (MHD_YES == daemon->worker_pool[i].listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (daemon->worker_pool[i].epoll_fd, + EPOLL_CTL_DEL, + ret, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->worker_pool[i].listen_socket_in_epoll = MHD_NO; + } +#endif + } + daemon->socket_fd = MHD_INVALID_SOCKET; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (MHD_YES == daemon->listen_socket_in_epoll) ) + { + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_DEL, + ret, + NULL)) + MHD_PANIC ("Failed to remove listen FD from epoll set\n"); + daemon->listen_socket_in_epoll = MHD_NO; + } +#endif + return ret; +} + + +/** + * Signature of the MHD custom logger function. + * + * @param cls closure + * @param format format string + * @param va arguments to the format string (fprintf-style) + */ +typedef void (*VfprintfFunctionPointerType)(void *cls, + const char *format, + va_list va); + + +/** + * Parse a list of options given as varargs. + * + * @param daemon the daemon to initialize + * @param servaddr where to store the server's listen address + * @param ap the options + * @return #MHD_YES on success, #MHD_NO on error + */ +static int +parse_options_va (struct MHD_Daemon *daemon, + const struct sockaddr **servaddr, + va_list ap); + + +/** + * Parse a list of options given as varargs. + * + * @param daemon the daemon to initialize + * @param servaddr where to store the server's listen address + * @param ... the options + * @return #MHD_YES on success, #MHD_NO on error + */ +static int +parse_options (struct MHD_Daemon *daemon, + const struct sockaddr **servaddr, + ...) +{ + va_list ap; + int ret; + + va_start (ap, servaddr); + ret = parse_options_va (daemon, servaddr, ap); + va_end (ap); + return ret; +} + + +/** + * Parse a list of options given as varargs. + * + * @param daemon the daemon to initialize + * @param servaddr where to store the server's listen address + * @param ap the options + * @return #MHD_YES on success, #MHD_NO on error + */ +static int +parse_options_va (struct MHD_Daemon *daemon, + const struct sockaddr **servaddr, + va_list ap) +{ + enum MHD_OPTION opt; + struct MHD_OptionItem *oa; + unsigned int i; +#if HTTPS_SUPPORT + int ret; + const char *pstr; +#endif + + while (MHD_OPTION_END != (opt = (enum MHD_OPTION) va_arg (ap, int))) + { + switch (opt) + { + case MHD_OPTION_CONNECTION_MEMORY_LIMIT: + daemon->pool_size = va_arg (ap, size_t); + break; + case MHD_OPTION_CONNECTION_MEMORY_INCREMENT: + daemon->pool_increment= va_arg (ap, size_t); + break; + case MHD_OPTION_CONNECTION_LIMIT: + daemon->connection_limit = va_arg (ap, unsigned int); + break; + case MHD_OPTION_CONNECTION_TIMEOUT: + daemon->connection_timeout = va_arg (ap, unsigned int); + break; + case MHD_OPTION_NOTIFY_COMPLETED: + daemon->notify_completed = + va_arg (ap, MHD_RequestCompletedCallback); + daemon->notify_completed_cls = va_arg (ap, void *); + break; + case MHD_OPTION_NOTIFY_CONNECTION: + daemon->notify_connection = + va_arg (ap, MHD_NotifyConnectionCallback); + daemon->notify_connection_cls = va_arg (ap, void *); + break; + case MHD_OPTION_PER_IP_CONNECTION_LIMIT: + daemon->per_ip_connection_limit = va_arg (ap, unsigned int); + break; + case MHD_OPTION_SOCK_ADDR: + *servaddr = va_arg (ap, const struct sockaddr *); + break; + case MHD_OPTION_URI_LOG_CALLBACK: + daemon->uri_log_callback = + va_arg (ap, LogCallback); + daemon->uri_log_callback_cls = va_arg (ap, void *); + break; + case MHD_OPTION_THREAD_POOL_SIZE: + daemon->worker_pool_size = va_arg (ap, unsigned int); + if (daemon->worker_pool_size >= (SIZE_MAX / sizeof (struct MHD_Daemon))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Specified thread pool size (%u) too big\n", + daemon->worker_pool_size); +#endif + return MHD_NO; + } + break; +#if HTTPS_SUPPORT + case MHD_OPTION_HTTPS_MEM_KEY: + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->https_mem_key = va_arg (ap, const char *); +#if HAVE_MESSAGES + else + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", + opt); +#endif + break; + case MHD_OPTION_HTTPS_KEY_PASSWORD: + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->https_key_password = va_arg (ap, const char *); +#if HAVE_MESSAGES + else + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", + opt); +#endif + break; + case MHD_OPTION_HTTPS_MEM_CERT: + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->https_mem_cert = va_arg (ap, const char *); +#if HAVE_MESSAGES + else + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", + opt); +#endif + break; + case MHD_OPTION_HTTPS_MEM_TRUST: + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->https_mem_trust = va_arg (ap, const char *); +#if HAVE_MESSAGES + else + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", + opt); +#endif + break; + case MHD_OPTION_HTTPS_CRED_TYPE: + daemon->cred_type = (gnutls_credentials_type_t) va_arg (ap, int); + break; + case MHD_OPTION_HTTPS_MEM_DHPARAMS: + if (0 != (daemon->options & MHD_USE_SSL)) + { + const char *arg = va_arg (ap, const char *); + gnutls_datum_t dhpar; + + if (gnutls_dh_params_init (&daemon->https_mem_dhparams) < 0) + { +#if HAVE_MESSAGES + MHD_DLOG(daemon, + "Error initializing DH parameters\n"); +#endif + return MHD_NO; + } + dhpar.data = (unsigned char *) arg; + dhpar.size = strlen (arg); + if (gnutls_dh_params_import_pkcs3 (daemon->https_mem_dhparams, &dhpar, + GNUTLS_X509_FMT_PEM) < 0) + { +#if HAVE_MESSAGES + MHD_DLOG(daemon, + "Bad Diffie-Hellman parameters format\n"); +#endif + gnutls_dh_params_deinit (daemon->https_mem_dhparams); + return MHD_NO; + } + daemon->have_dhparams = MHD_YES; + } + else + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", + opt); +#endif + return MHD_NO; + } + break; + case MHD_OPTION_HTTPS_PRIORITIES: + if (0 != (daemon->options & MHD_USE_SSL)) + { + gnutls_priority_deinit (daemon->priority_cache); + ret = gnutls_priority_init (&daemon->priority_cache, + pstr = va_arg (ap, const char*), + NULL); + if (GNUTLS_E_SUCCESS != ret) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Setting priorities to `%s' failed: %s\n", + pstr, + gnutls_strerror (ret)); +#endif + daemon->priority_cache = NULL; + return MHD_NO; + } + } + break; + case MHD_OPTION_HTTPS_CERT_CALLBACK: +#if GNUTLS_VERSION_MAJOR < 3 +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD_OPTION_HTTPS_CERT_CALLBACK requires building MHD with GnuTLS >= 3.0\n"); +#endif + return MHD_NO; +#else + if (0 != (daemon->options & MHD_USE_SSL)) + daemon->cert_callback = va_arg (ap, gnutls_certificate_retrieve_function2 *); + break; +#endif +#endif +#ifdef DAUTH_SUPPORT + case MHD_OPTION_DIGEST_AUTH_RANDOM: + daemon->digest_auth_rand_size = va_arg (ap, size_t); + daemon->digest_auth_random = va_arg (ap, const char *); + break; + case MHD_OPTION_NONCE_NC_SIZE: + daemon->nonce_nc_size = va_arg (ap, unsigned int); + break; +#endif + case MHD_OPTION_LISTEN_SOCKET: + daemon->socket_fd = va_arg (ap, MHD_socket); + break; + case MHD_OPTION_EXTERNAL_LOGGER: +#if HAVE_MESSAGES + daemon->custom_error_log = + va_arg (ap, VfprintfFunctionPointerType); + daemon->custom_error_log_cls = va_arg (ap, void *); +#else + va_arg (ap, VfprintfFunctionPointerType); + va_arg (ap, void *); +#endif + break; + case MHD_OPTION_THREAD_STACK_SIZE: + daemon->thread_stack_size = va_arg (ap, size_t); + break; +#ifdef TCP_FASTOPEN + case MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE: + daemon->fastopen_queue_size = va_arg (ap, unsigned int); + break; +#endif + case MHD_OPTION_LISTENING_ADDRESS_REUSE: + daemon->listening_address_reuse = va_arg (ap, unsigned int) ? 1 : -1; + break; + case MHD_OPTION_ARRAY: + oa = va_arg (ap, struct MHD_OptionItem*); + i = 0; + while (MHD_OPTION_END != (opt = oa[i].option)) + { + switch (opt) + { + /* all options taking 'size_t' */ + case MHD_OPTION_CONNECTION_MEMORY_LIMIT: + case MHD_OPTION_CONNECTION_MEMORY_INCREMENT: + case MHD_OPTION_THREAD_STACK_SIZE: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (size_t) oa[i].value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* all options taking 'unsigned int' */ + case MHD_OPTION_NONCE_NC_SIZE: + case MHD_OPTION_CONNECTION_LIMIT: + case MHD_OPTION_CONNECTION_TIMEOUT: + case MHD_OPTION_PER_IP_CONNECTION_LIMIT: + case MHD_OPTION_THREAD_POOL_SIZE: + case MHD_OPTION_TCP_FASTOPEN_QUEUE_SIZE: + case MHD_OPTION_LISTENING_ADDRESS_REUSE: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (unsigned int) oa[i].value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* all options taking 'enum' */ + case MHD_OPTION_HTTPS_CRED_TYPE: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (int) oa[i].value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* all options taking 'MHD_socket' */ + case MHD_OPTION_LISTEN_SOCKET: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (MHD_socket) oa[i].value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* all options taking one pointer */ + case MHD_OPTION_SOCK_ADDR: + case MHD_OPTION_HTTPS_MEM_KEY: + case MHD_OPTION_HTTPS_KEY_PASSWORD: + case MHD_OPTION_HTTPS_MEM_CERT: + case MHD_OPTION_HTTPS_MEM_TRUST: + case MHD_OPTION_HTTPS_MEM_DHPARAMS: + case MHD_OPTION_HTTPS_PRIORITIES: + case MHD_OPTION_ARRAY: + case MHD_OPTION_HTTPS_CERT_CALLBACK: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + oa[i].ptr_value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* all options taking two pointers */ + case MHD_OPTION_NOTIFY_COMPLETED: + case MHD_OPTION_NOTIFY_CONNECTION: + case MHD_OPTION_URI_LOG_CALLBACK: + case MHD_OPTION_EXTERNAL_LOGGER: + case MHD_OPTION_UNESCAPE_CALLBACK: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (void *) oa[i].value, + oa[i].ptr_value, + MHD_OPTION_END)) + return MHD_NO; + break; + /* options taking size_t-number followed by pointer */ + case MHD_OPTION_DIGEST_AUTH_RANDOM: + if (MHD_YES != parse_options (daemon, + servaddr, + opt, + (size_t) oa[i].value, + oa[i].ptr_value, + MHD_OPTION_END)) + return MHD_NO; + break; + default: + return MHD_NO; + } + i++; + } + break; + case MHD_OPTION_UNESCAPE_CALLBACK: + daemon->unescape_callback = + va_arg (ap, UnescapeCallback); + daemon->unescape_callback_cls = va_arg (ap, void *); + break; + default: +#if HAVE_MESSAGES + if (((opt >= MHD_OPTION_HTTPS_MEM_KEY) && + (opt <= MHD_OPTION_HTTPS_PRIORITIES)) || (opt == MHD_OPTION_HTTPS_MEM_TRUST)) + { + MHD_DLOG (daemon, + "MHD HTTPS option %d passed to MHD compiled without HTTPS support\n", + opt); + } + else + { + MHD_DLOG (daemon, + "Invalid option %d! (Did you terminate the list with MHD_OPTION_END?)\n", + opt); + } +#endif + return MHD_NO; + } + } + return MHD_YES; +} + + +/** + * Create a listen socket, if possible with SOCK_CLOEXEC flag set. + * + * @param daemon daemon for which we create the socket + * @param domain socket domain (i.e. PF_INET) + * @param type socket type (usually SOCK_STREAM) + * @param protocol desired protocol, 0 for default + */ +static MHD_socket +create_socket (struct MHD_Daemon *daemon, + int domain, int type, int protocol) +{ + int ctype = type | SOCK_CLOEXEC; + MHD_socket fd; + + /* use SOCK_STREAM rather than ai_socktype: some getaddrinfo + * implementations do not set ai_socktype, e.g. RHL6.2. */ + fd = socket (domain, ctype, protocol); + if ( (MHD_INVALID_SOCKET == fd) && (EINVAL == MHD_socket_errno_) && (0 != SOCK_CLOEXEC) ) + { + ctype = type; + fd = socket(domain, type, protocol); + } + if (MHD_INVALID_SOCKET == fd) + return MHD_INVALID_SOCKET; + if (type == ctype) + make_nonblocking_noninheritable (daemon, fd); + return fd; +} + + +#if EPOLL_SUPPORT +/** + * Setup epoll() FD for the daemon and initialize it to listen + * on the listen FD. + * + * @param daemon daemon to initialize for epoll() + * @return #MHD_YES on success, #MHD_NO on failure + */ +static int +setup_epoll_to_listen (struct MHD_Daemon *daemon) +{ + struct epoll_event event; + +#ifdef HAVE_EPOLL_CREATE1 + daemon->epoll_fd = epoll_create1 (EPOLL_CLOEXEC); +#else /* !HAVE_EPOLL_CREATE1 */ + daemon->epoll_fd = epoll_create (MAX_EVENTS); +#endif /* !HAVE_EPOLL_CREATE1 */ + if (-1 == daemon->epoll_fd) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Call to epoll_create1 failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } +#ifndef HAVE_EPOLL_CREATE1 + else + { + int fdflags = fcntl (daemon->epoll_fd, F_GETFD); + if (0 > fdflags || 0 > fcntl (daemon->epoll_fd, F_SETFD, fdflags | FD_CLOEXEC)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to change flags on epoll fd: %s\n", + MHD_socket_last_strerr_ ()); +#endif /* HAVE_MESSAGES */ + } + } +#endif /* !HAVE_EPOLL_CREATE1 */ + if (0 == EPOLL_CLOEXEC) + make_nonblocking_noninheritable (daemon, + daemon->epoll_fd); + if (MHD_INVALID_SOCKET == daemon->socket_fd) + return MHD_YES; /* non-listening daemon */ + event.events = EPOLLIN; + event.data.ptr = daemon; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->socket_fd, + &event)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Call to epoll_ctl failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + return MHD_NO; + } + if ( (MHD_INVALID_PIPE_ != daemon->wpipe[0]) && + (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) ) + { + event.events = EPOLLIN | EPOLLET; + event.data.ptr = NULL; + event.data.fd = daemon->wpipe[0]; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->wpipe[0], + &event)) + { +#if 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 = MHD_YES; + return MHD_YES; +} +#endif + + +/** + * Start a webserver on the given port. + * + * @param flags combination of `enum MHD_FLAG` values + * @param port port to bind to (in host byte order) + * @param apc callback to call to check which clients + * will be allowed to connect; you can pass NULL + * in which case connections from any IP will be + * accepted + * @param apc_cls extra argument to @a apc + * @param dh handler called for all requests (repeatedly) + * @param dh_cls extra argument to @a dh + * @param ap list of options (type-value pairs, + * terminated with #MHD_OPTION_END). + * @return NULL on error, handle to daemon on success + * @ingroup event + */ +struct MHD_Daemon * +MHD_start_daemon_va (unsigned int flags, + uint16_t port, + MHD_AcceptPolicyCallback apc, + void *apc_cls, + MHD_AccessHandlerCallback dh, void *dh_cls, + va_list ap) +{ + const int on = 1; + struct MHD_Daemon *daemon; + MHD_socket socket_fd; + struct sockaddr_in servaddr4; +#if HAVE_INET6 + struct sockaddr_in6 servaddr6; +#endif + const struct sockaddr *servaddr = NULL; + socklen_t addrlen; + unsigned int i; + int res_thread_create; + int use_pipe; + +#ifndef HAVE_INET6 + if (0 != (flags & MHD_USE_IPv6)) + return NULL; +#endif +#ifndef HAVE_POLL + if (0 != (flags & MHD_USE_POLL)) + return NULL; +#endif +#if ! HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + return NULL; +#endif +#ifndef TCP_FASTOPEN + if (0 != (flags & MHD_USE_TCP_FASTOPEN)) + return NULL; +#endif + if (NULL == dh) + return NULL; + if (NULL == (daemon = malloc (sizeof (struct MHD_Daemon)))) + return NULL; + memset (daemon, 0, sizeof (struct MHD_Daemon)); +#if EPOLL_SUPPORT + daemon->epoll_fd = -1; +#endif + /* try to open listen socket */ +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + { + gnutls_priority_init (&daemon->priority_cache, + "NORMAL", + NULL); + } +#endif + daemon->socket_fd = MHD_INVALID_SOCKET; + daemon->listening_address_reuse = 0; + daemon->options = flags; +#if WINDOWS + /* Winsock is broken with respect to 'shutdown'; + this disables us calling 'shutdown' on W32. */ + daemon->options |= MHD_USE_EPOLL_TURBO; +#endif + daemon->port = port; + daemon->apc = apc; + daemon->apc_cls = apc_cls; + daemon->default_handler = dh; + daemon->default_handler_cls = dh_cls; + daemon->connections = 0; + daemon->connection_limit = MHD_MAX_CONNECTIONS_DEFAULT; + daemon->pool_size = MHD_POOL_SIZE_DEFAULT; + daemon->pool_increment = MHD_BUF_INC_SIZE; + daemon->unescape_callback = &unescape_wrapper; + daemon->connection_timeout = 0; /* no timeout */ + daemon->wpipe[0] = MHD_INVALID_PIPE_; + daemon->wpipe[1] = MHD_INVALID_PIPE_; +#if HAVE_MESSAGES + daemon->custom_error_log = (MHD_LogCallback) &vfprintf; + daemon->custom_error_log_cls = stderr; +#endif +#ifdef HAVE_LISTEN_SHUTDOWN + use_pipe = (0 != (daemon->options & (MHD_USE_NO_LISTEN_SOCKET | MHD_USE_PIPE_FOR_SHUTDOWN))); +#else + use_pipe = 1; /* yes, must use pipe to signal shutdown */ +#endif + if (0 == (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) + use_pipe = 0; /* useless if we are using 'external' select */ + if ( (use_pipe) && (0 != MHD_pipe_ (daemon->wpipe)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to create control pipe: %s\n", + MHD_strerror_ (errno)); +#endif + free (daemon); + return NULL; + } +#ifndef WINDOWS + if ( (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) && + (1 == use_pipe) && + (daemon->wpipe[0] >= FD_SETSIZE) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "file descriptor for control pipe exceeds maximum value\n"); +#endif + if (0 != MHD_pipe_close_ (daemon->wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != MHD_pipe_close_ (daemon->wpipe[1])) + MHD_PANIC ("close failed\n"); + free (daemon); + return NULL; + } +#endif +#ifdef DAUTH_SUPPORT + daemon->digest_auth_rand_size = 0; + daemon->digest_auth_random = NULL; + daemon->nonce_nc_size = 4; /* tiny */ +#endif +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + { + daemon->cred_type = GNUTLS_CRD_CERTIFICATE; + } +#endif + + + if (MHD_YES != parse_options_va (daemon, &servaddr, ap)) + { +#if HTTPS_SUPPORT + if ( (0 != (flags & MHD_USE_SSL)) && + (NULL != daemon->priority_cache) ) + gnutls_priority_deinit (daemon->priority_cache); +#endif + free (daemon); + return NULL; + } +#ifdef DAUTH_SUPPORT + if (daemon->nonce_nc_size > 0) + { + if ( ( (size_t) (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc))) / + sizeof(struct MHD_NonceNc) != daemon->nonce_nc_size) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Specified value for NC_SIZE too large\n"); +#endif +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + gnutls_priority_deinit (daemon->priority_cache); +#endif + free (daemon); + return NULL; + } + daemon->nnc = malloc (daemon->nonce_nc_size * sizeof (struct MHD_NonceNc)); + if (NULL == daemon->nnc) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to allocate memory for nonce-nc map: %s\n", + MHD_strerror_ (errno)); +#endif +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + gnutls_priority_deinit (daemon->priority_cache); +#endif + free (daemon); + return NULL; + } + } + + if (MHD_YES != MHD_mutex_create_ (&daemon->nnc_lock)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD failed to initialize nonce-nc mutex\n"); +#endif +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + gnutls_priority_deinit (daemon->priority_cache); +#endif + free (daemon->nnc); + free (daemon); + return NULL; + } +#endif + + /* Thread pooling currently works only with internal select thread model */ + if ( (0 == (flags & MHD_USE_SELECT_INTERNALLY)) && + (daemon->worker_pool_size > 0) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD thread pooling only works with MHD_USE_SELECT_INTERNALLY\n"); +#endif + goto free_and_fail; + } + + if ( (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) && + (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_SUSPEND_RESUME is not supported.\n"); +#endif + goto free_and_fail; + } + +#ifdef __SYMBIAN32__ + if (0 != (flags & (MHD_USE_SELECT_INTERNALLY | MHD_USE_THREAD_PER_CONNECTION))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Threaded operations are not supported on Symbian.\n"); +#endif + goto free_and_fail; + } +#endif + if ( (MHD_INVALID_SOCKET == daemon->socket_fd) && + (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) ) + { + /* try to open listen socket */ + if (0 != (flags & MHD_USE_IPv6)) + socket_fd = create_socket (daemon, + PF_INET6, SOCK_STREAM, 0); + else + socket_fd = create_socket (daemon, + PF_INET, SOCK_STREAM, 0); + if (MHD_INVALID_SOCKET == socket_fd) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Call to socket failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } + + /* Apply the socket options according to listening_address_reuse. */ + if (0 == daemon->listening_address_reuse) + { + /* No user requirement, use "traditional" default SO_REUSEADDR, + and do not fail if it doesn't work */ + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } + } + else if (daemon->listening_address_reuse > 0) + { + /* User requested to allow reusing listening address:port. + * Use SO_REUSEADDR on Windows and SO_REUSEPORT on most platforms. + * Fail if SO_REUSEPORT does not exist or setsockopt fails. + */ +#ifdef _WIN32 + /* SO_REUSEADDR on W32 has the same semantics + as SO_REUSEPORT on BSD/Linux */ + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEADDR, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else +#ifndef SO_REUSEPORT +#ifdef LINUX +/* Supported since Linux 3.9, but often not present (or commented out) + in the headers at this time; but 15 is reserved for this and + thus should be safe to use. */ +#define SO_REUSEPORT 15 +#endif +#endif +#ifdef SO_REUSEPORT + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_REUSEPORT, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else + /* we're supposed to allow address:port re-use, but + on this platform we cannot; fail hard */ +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Cannot allow listening address reuse: SO_REUSEPORT not defined\n"); +#endif + goto free_and_fail; +#endif +#endif + } + else /* if (daemon->listening_address_reuse < 0) */ + { + /* User requested to disallow reusing listening address:port. + * Do nothing except for Windows where SO_EXCLUSIVEADDRUSE + * is used. Fail if it does not exist or setsockopt fails. + */ +#ifdef _WIN32 +#ifdef SO_EXCLUSIVEADDRUSE + if (0 > setsockopt (socket_fd, + SOL_SOCKET, + SO_EXCLUSIVEADDRUSE, + (void*)&on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + goto free_and_fail; + } +#else /* SO_EXCLUSIVEADDRUSE not defined on W32? */ +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Cannot disallow listening address reuse: SO_EXCLUSIVEADDRUSE not defined\n"); +#endif + goto free_and_fail; +#endif +#endif /* _WIN32 */ + } + + /* check for user supplied sockaddr */ +#if HAVE_INET6 + if (0 != (flags & MHD_USE_IPv6)) + addrlen = sizeof (struct sockaddr_in6); + else +#endif + addrlen = sizeof (struct sockaddr_in); + if (NULL == servaddr) + { +#if HAVE_INET6 + if (0 != (flags & MHD_USE_IPv6)) + { + memset (&servaddr6, 0, sizeof (struct sockaddr_in6)); + servaddr6.sin6_family = AF_INET6; + servaddr6.sin6_port = htons (port); +#if HAVE_SOCKADDR_IN_SIN_LEN + servaddr6.sin6_len = sizeof (struct sockaddr_in6); +#endif + servaddr = (struct sockaddr *) &servaddr6; + } + else +#endif + { + memset (&servaddr4, 0, sizeof (struct sockaddr_in)); + servaddr4.sin_family = AF_INET; + servaddr4.sin_port = htons (port); +#if HAVE_SOCKADDR_IN_SIN_LEN + servaddr4.sin_len = sizeof (struct sockaddr_in); +#endif + servaddr = (struct sockaddr *) &servaddr4; + } + } + daemon->socket_fd = socket_fd; + + if (0 != (flags & MHD_USE_IPv6)) + { +#ifdef IPPROTO_IPV6 +#ifdef 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... */ +#ifndef WINDOWS + const int +#else + const char +#endif + on = (MHD_USE_DUAL_STACK != (flags & MHD_USE_DUAL_STACK)); + if (0 > setsockopt (socket_fd, + IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof (on))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } +#endif +#endif + } + if (-1 == bind (socket_fd, servaddr, addrlen)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to bind to port %u: %s\n", + (unsigned int) port, + MHD_socket_last_strerr_ ()); +#endif + if (0 != MHD_socket_close_ (socket_fd)) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } +#ifdef TCP_FASTOPEN + if (0 != (flags & MHD_USE_TCP_FASTOPEN)) + { + if (0 == daemon->fastopen_queue_size) + daemon->fastopen_queue_size = MHD_TCP_FASTOPEN_QUEUE_SIZE_DEFAULT; + if (0 != setsockopt (socket_fd, + IPPROTO_TCP, TCP_FASTOPEN, + &daemon->fastopen_queue_size, + sizeof (daemon->fastopen_queue_size))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "setsockopt failed: %s\n", + MHD_socket_last_strerr_ ()); +#endif + } + } +#endif +#if EPOLL_SUPPORT + if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) + { + int sk_flags = fcntl (socket_fd, F_GETFL); + if (0 != fcntl (socket_fd, F_SETFL, sk_flags | O_NONBLOCK)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to make listen socket non-blocking: %s\n", + MHD_socket_last_strerr_ ()); +#endif + if (0 != MHD_socket_close_ (socket_fd)) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + } +#endif + if (listen (socket_fd, 32) < 0) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to listen for connections: %s\n", + MHD_socket_last_strerr_ ()); +#endif + if (0 != MHD_socket_close_ (socket_fd)) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + } + else + { + socket_fd = daemon->socket_fd; + } +#ifndef WINDOWS + if ( (socket_fd >= FD_SETSIZE) && + (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY)) ) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Socket descriptor larger than FD_SETSIZE: %d > %d\n", + socket_fd, + FD_SETSIZE); +#endif + if (0 != MHD_socket_close_ (socket_fd)) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } +#endif + +#if EPOLL_SUPPORT + if ( (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) && + (0 == daemon->worker_pool_size) && + (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) ) + { + if (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Combining MHD_USE_THREAD_PER_CONNECTION and MHD_USE_EPOLL_LINUX_ONLY is not supported.\n"); +#endif + goto free_and_fail; + } + if (MHD_YES != setup_epoll_to_listen (daemon)) + goto free_and_fail; + } +#else + if (0 != (flags & MHD_USE_EPOLL_LINUX_ONLY)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "epoll is not supported on this platform by this build.\n"); +#endif + goto free_and_fail; + } +#endif + + if (MHD_YES != MHD_mutex_create_ (&daemon->per_ip_connection_mutex)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD failed to initialize IP connection limit mutex\n"); +#endif + if ( (MHD_INVALID_SOCKET != socket_fd) && + (0 != MHD_socket_close_ (socket_fd)) ) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + if (MHD_YES != MHD_mutex_create_ (&daemon->cleanup_connection_mutex)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD failed to initialize IP connection limit mutex\n"); +#endif + (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + if ( (MHD_INVALID_SOCKET != socket_fd) && + (0 != MHD_socket_close_ (socket_fd)) ) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + +#if HTTPS_SUPPORT + /* initialize HTTPS daemon certificate aspects & send / recv functions */ + if ((0 != (flags & MHD_USE_SSL)) && (0 != MHD_TLS_init (daemon))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to initialize TLS support\n"); +#endif + if ( (MHD_INVALID_SOCKET != socket_fd) && + (0 != MHD_socket_close_ (socket_fd)) ) + MHD_PANIC ("close failed\n"); + (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex); + goto free_and_fail; + } +#endif + if ( ( (0 != (flags & MHD_USE_THREAD_PER_CONNECTION)) || + ( (0 != (flags & MHD_USE_SELECT_INTERNALLY)) && + (0 == daemon->worker_pool_size)) ) && + (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) && + (0 != (res_thread_create = + create_thread (&daemon->pid, daemon, &MHD_select_thread, daemon)))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to create listen thread: %s\n", + MHD_strerror_ (res_thread_create)); +#endif + (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex); + if ( (MHD_INVALID_SOCKET != socket_fd) && + (0 != MHD_socket_close_ (socket_fd)) ) + MHD_PANIC ("close failed\n"); + goto free_and_fail; + } + if ( (daemon->worker_pool_size > 0) && + (0 == (daemon->options & MHD_USE_NO_LISTEN_SOCKET)) ) + { +#if !defined(WINDOWS) || defined(CYGWIN) + int sk_flags; +#else + unsigned long sk_flags; +#endif + + /* 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->worker_pool_size; + unsigned int leftover_conns = daemon->connection_limit + % daemon->worker_pool_size; + + i = 0; /* we need this in case fcntl or malloc fails */ + + /* 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. */ +#if !defined(WINDOWS) || defined(CYGWIN) + sk_flags = fcntl (socket_fd, F_GETFL); + if (sk_flags < 0) + goto thread_failed; + if (0 != fcntl (socket_fd, F_SETFL, sk_flags | O_NONBLOCK)) + goto thread_failed; +#else + sk_flags = 1; + if (SOCKET_ERROR == ioctlsocket (socket_fd, FIONBIO, &sk_flags)) + goto thread_failed; +#endif /* WINDOWS && !CYGWIN */ + + /* Allocate memory for pooled objects */ + daemon->worker_pool = malloc (sizeof (struct MHD_Daemon) + * daemon->worker_pool_size); + if (NULL == daemon->worker_pool) + goto thread_failed; + + /* Start the workers in the pool */ + for (i = 0; i < daemon->worker_pool_size; ++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_SELECT_INTERNALLY thread model into + the worker threads. */ + d->master = daemon; + d->worker_pool_size = 0; + d->worker_pool = NULL; + + if ( (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) && + (0 != MHD_pipe_ (d->wpipe)) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to create worker control pipe: %s\n", + MHD_pipe_last_strerror_() ); +#endif + goto thread_failed; + } +#ifndef WINDOWS + if ( (0 == (flags & (MHD_USE_POLL | MHD_USE_EPOLL_LINUX_ONLY))) && + (MHD_USE_SUSPEND_RESUME == (flags & MHD_USE_SUSPEND_RESUME)) && + (d->wpipe[0] >= FD_SETSIZE) ) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "file descriptor for worker control pipe exceeds maximum value\n"); +#endif + if (0 != MHD_pipe_close_ (d->wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != MHD_pipe_close_ (d->wpipe[1])) + MHD_PANIC ("close failed\n"); + goto thread_failed; + } +#endif + + /* 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 EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (MHD_YES != setup_epoll_to_listen (d)) ) + goto thread_failed; +#endif + /* Must init cleanup connection mutex for each worker */ + if (MHD_YES != MHD_mutex_create_ (&d->cleanup_connection_mutex)) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD failed to initialize cleanup connection mutex for thread worker %d\n", i); +#endif + goto thread_failed; + } + + /* Spawn the worker thread */ + if (0 != (res_thread_create = + create_thread (&d->pid, daemon, &MHD_select_thread, d))) + { +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "Failed to create pool thread: %s\n", + MHD_strerror_ (res_thread_create)); +#endif + /* Free memory for this worker; cleanup below handles + * all previously-created workers. */ + (void) MHD_mutex_destroy_ (&d->cleanup_connection_mutex); + goto thread_failed; + } + } + } +#if HTTPS_SUPPORT + /* API promises to never use the password after initialization, + so we additionally NULL it here to not deref a dangling pointer. */ + daemon->https_key_password = NULL; +#endif /* HTTPS_SUPPORT */ + + return daemon; + +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_SELECT_INTERNALLY mode. */ + if (0 == i) + { + if ( (MHD_INVALID_SOCKET != socket_fd) && + (0 != MHD_socket_close_ (socket_fd)) ) + MHD_PANIC ("close failed\n"); + (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex); + if (NULL != daemon->worker_pool) + free (daemon->worker_pool); + goto free_and_fail; + } + + /* 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; + MHD_stop_daemon (daemon); + return NULL; + + free_and_fail: + /* clean up basic memory state in 'daemon' and return NULL to + indicate failure */ +#if EPOLL_SUPPORT + if (-1 != daemon->epoll_fd) + close (daemon->epoll_fd); +#endif +#ifdef DAUTH_SUPPORT + free (daemon->nnc); + (void) MHD_mutex_destroy_ (&daemon->nnc_lock); +#endif +#if HTTPS_SUPPORT + if (0 != (flags & MHD_USE_SSL)) + gnutls_priority_deinit (daemon->priority_cache); +#endif + free (daemon); + return NULL; +} + + +/** + * Close the given connection, remove it from all of its + * DLLs and move it into the cleanup queue. + * + * @param pos connection to move to cleanup + */ +static void +close_connection (struct MHD_Connection *pos) +{ + struct MHD_Daemon *daemon = pos->daemon; + + MHD_connection_close (pos, + MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN); + if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + return; /* must let thread to the rest */ + if (pos->connection_timeout == pos->daemon->connection_timeout) + XDLL_remove (daemon->normal_timeout_head, + daemon->normal_timeout_tail, + pos); + else + XDLL_remove (daemon->manual_timeout_head, + daemon->manual_timeout_tail, + pos); + DLL_remove (daemon->connections_head, + daemon->connections_tail, + pos); + pos->event_loop_info = MHD_EVENT_LOOP_INFO_CLEANUP; + DLL_insert (daemon->cleanup_head, + daemon->cleanup_tail, + pos); +} + + +/** + * Close all connections for the daemon; must only be called after + * all of the threads have been joined and there is no more concurrent + * activity on the connection lists. + * + * @param daemon daemon to close down + */ +static void +close_all_connections (struct MHD_Daemon *daemon) +{ + struct MHD_Connection *pos; + + /* first, make sure all threads are aware of shutdown; need to + traverse DLLs in peace... */ + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to acquire cleanup mutex\n"); + for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + { + shutdown (pos->socket_fd, + (pos->read_closed == MHD_YES) ? SHUT_WR : SHUT_RDWR); +#if WINDOWS + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_INVALID_PIPE_ != daemon->wpipe[1]) && + (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1)) ) + MHD_PANIC ("failed to signal shutdown via pipe"); +#endif + } + if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && + (MHD_YES != MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) + MHD_PANIC ("Failed to release cleanup mutex\n"); + + /* now, collect threads from thread pool */ + if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) + { + for (pos = daemon->connections_head; NULL != pos; pos = pos->next) + { + if (0 != MHD_join_thread_ (pos->pid)) + MHD_PANIC ("Failed to join a thread\n"); + pos->thread_joined = MHD_YES; + } + } + + /* now that we're alone, move everyone to cleanup */ + while (NULL != (pos = daemon->connections_head)) + close_connection (pos); + MHD_cleanup_connections (daemon); +} + + +#if EPOLL_SUPPORT +/** + * Shutdown epoll()-event loop by adding 'wpipe' to its event set. + * + * @param daemon daemon of which the epoll() instance must be signalled + */ +static void +epoll_shutdown (struct MHD_Daemon *daemon) +{ + struct epoll_event event; + + if (MHD_INVALID_PIPE_ == daemon->wpipe[1]) + { + /* wpipe was required in this mode, how could this happen? */ + MHD_PANIC ("Internal error\n"); + } + event.events = EPOLLOUT; + event.data.ptr = NULL; + if (0 != epoll_ctl (daemon->epoll_fd, + EPOLL_CTL_ADD, + daemon->wpipe[1], + &event)) + MHD_PANIC ("Failed to add wpipe to epoll set to signal termination\n"); +} +#endif + + +/** + * Shutdown an HTTP daemon. + * + * @param daemon daemon to stop + * @ingroup event + */ +void +MHD_stop_daemon (struct MHD_Daemon *daemon) +{ + MHD_socket fd; + unsigned int i; + + if (NULL == daemon) + return; + daemon->shutdown = MHD_YES; + fd = daemon->socket_fd; + daemon->socket_fd = MHD_INVALID_SOCKET; + /* Prepare workers for shutdown */ + if (NULL != daemon->worker_pool) + { + /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ + for (i = 0; i < daemon->worker_pool_size; ++i) + { + daemon->worker_pool[i].shutdown = MHD_YES; + daemon->worker_pool[i].socket_fd = MHD_INVALID_SOCKET; +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->worker_pool[i].epoll_fd) && + (MHD_INVALID_SOCKET == fd) ) + epoll_shutdown (&daemon->worker_pool[i]); +#endif + } + } + if (MHD_INVALID_PIPE_ != daemon->wpipe[1]) + { + if (1 != MHD_pipe_write_ (daemon->wpipe[1], "e", 1)) + MHD_PANIC ("failed to signal shutdown via pipe"); + } +#ifdef HAVE_LISTEN_SHUTDOWN + else + { + /* fd might be MHD_INVALID_SOCKET here due to 'MHD_quiesce_daemon' */ + if (MHD_INVALID_SOCKET != fd) + (void) shutdown (fd, SHUT_RDWR); + } +#endif +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (MHD_INVALID_SOCKET == fd) ) + epoll_shutdown (daemon); +#endif + +#if DEBUG_CLOSE +#if HAVE_MESSAGES + MHD_DLOG (daemon, + "MHD listen socket shutdown\n"); +#endif +#endif + + + /* Signal workers to stop and clean them up */ + if (NULL != daemon->worker_pool) + { + /* MHD_USE_NO_LISTEN_SOCKET disables thread pools, hence we need to check */ + for (i = 0; i < daemon->worker_pool_size; ++i) + { + if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1]) + { + if (1 != MHD_pipe_write_ (daemon->worker_pool[i].wpipe[1], "e", 1)) + MHD_PANIC ("failed to signal shutdown via pipe"); + } + if (0 != MHD_join_thread_ (daemon->worker_pool[i].pid)) + MHD_PANIC ("Failed to join a thread\n"); + close_all_connections (&daemon->worker_pool[i]); + (void) MHD_mutex_destroy_ (&daemon->worker_pool[i].cleanup_connection_mutex); +#if EPOLL_SUPPORT + if ( (-1 != daemon->worker_pool[i].epoll_fd) && + (0 != MHD_socket_close_ (daemon->worker_pool[i].epoll_fd)) ) + MHD_PANIC ("close failed\n"); +#endif + if ( (MHD_USE_SUSPEND_RESUME == (daemon->options & MHD_USE_SUSPEND_RESUME)) ) + { + if (MHD_INVALID_PIPE_ != daemon->worker_pool[i].wpipe[1]) + { + if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != MHD_pipe_close_ (daemon->worker_pool[i].wpipe[1])) + MHD_PANIC ("close failed\n"); + } + } + } + free (daemon->worker_pool); + } + else + { + /* clean up master threads */ + if ((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) || + ((0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)) + && (0 == daemon->worker_pool_size))) + { + if (0 != MHD_join_thread_ (daemon->pid)) + { + MHD_PANIC ("Failed to join a thread\n"); + } + } + } + close_all_connections (daemon); + if ( (MHD_INVALID_SOCKET != fd) && + (0 != MHD_socket_close_ (fd)) ) + MHD_PANIC ("close failed\n"); + + /* TLS clean up */ +#if HTTPS_SUPPORT + if (MHD_YES == daemon->have_dhparams) + { + gnutls_dh_params_deinit (daemon->https_mem_dhparams); + daemon->have_dhparams = MHD_NO; + } + if (0 != (daemon->options & MHD_USE_SSL)) + { + gnutls_priority_deinit (daemon->priority_cache); + if (daemon->x509_cred) + gnutls_certificate_free_credentials (daemon->x509_cred); + } +#endif +#if EPOLL_SUPPORT + if ( (0 != (daemon->options & MHD_USE_EPOLL_LINUX_ONLY)) && + (-1 != daemon->epoll_fd) && + (0 != MHD_socket_close_ (daemon->epoll_fd)) ) + MHD_PANIC ("close failed\n"); +#endif + +#ifdef DAUTH_SUPPORT + free (daemon->nnc); + (void) MHD_mutex_destroy_ (&daemon->nnc_lock); +#endif + (void) MHD_mutex_destroy_ (&daemon->per_ip_connection_mutex); + (void) MHD_mutex_destroy_ (&daemon->cleanup_connection_mutex); + + if (MHD_INVALID_PIPE_ != daemon->wpipe[1]) + { + if (0 != MHD_pipe_close_ (daemon->wpipe[0])) + MHD_PANIC ("close failed\n"); + if (0 != MHD_pipe_close_ (daemon->wpipe[1])) + MHD_PANIC ("close failed\n"); + } + free (daemon); +} + + +/** + * 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, + ...) +{ + 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->socket_fd; +#if EPOLL_SUPPORT + case MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY: + return (const union MHD_DaemonInfo *) &daemon->epoll_fd; +#endif + case MHD_DAEMON_INFO_CURRENT_CONNECTIONS: + MHD_cleanup_connections (daemon); + if (daemon->worker_pool) + { + /* Collect the connection information stored in the workers. */ + unsigned int i; + + daemon->connections = 0; + for (i=0;i<daemon->worker_pool_size;i++) + { + MHD_cleanup_connections (&daemon->worker_pool[i]); + daemon->connections += daemon->worker_pool[i].connections; + } + } + return (const union MHD_DaemonInfo *) &daemon->connections; + default: + return 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; +} + + +/** + * 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_MESSGES: +#if HAVE_MESSAGES + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_SSL: +#if HTTPS_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_HTTPS_CERT_CALLBACK: +#if HTTPS_SUPPORT && GNUTLS_VERSION_MAJOR >= 3 + return MHD_YES; +#else + return MHD_NO; +#endif + 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: +#if 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_DONT_USE_PIPES + 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: +#if BAUTH_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_DIGEST_AUTH: +#if DAUTH_SUPPORT + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_POSTPROCESSOR: +#if HAVE_POSTPROCESSOR + return MHD_YES; +#else + return MHD_NO; +#endif + case MHD_FEATURE_HTTPS_KEY_PASSWORD: +#if HTTPS_SUPPORT && GNUTLS_VERSION_NUMBER >= 0x030111 + return MHD_YES; +#else + return MHD_NO; +#endif + } + return MHD_NO; +} + + +#if 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_YES != MHD_mutex_create_ ((MHD_mutex_*)*ppmtx)) + { + free (*ppmtx); + *ppmtx = NULL; + return EPERM; + } + + return 0; +} +static int gcry_w32_mutex_destroy (void **ppmtx) + { int res = (MHD_YES == MHD_mutex_destroy_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; + free (*ppmtx); return res; } +static int gcry_w32_mutex_lock (void **ppmtx) + { return (MHD_YES == MHD_mutex_lock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; } +static int gcry_w32_mutex_unlock (void **ppmtx) + { return (MHD_YES == MHD_mutex_unlock_ ((MHD_mutex_*)*ppmtx)) ? 0 : 1; } + +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 + + +/** + * Initialize do setup work. + */ +void MHD_init(void) +{ + mhd_panic = &mhd_panic_std; + mhd_panic_cls = NULL; + +#ifdef _WIN32 + WSADATA wsd; + 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 +#if HTTPS_SUPPORT +#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 + gnutls_global_init (); +#endif +} + + +void MHD_fini(void) +{ +#if HTTPS_SUPPORT + gnutls_global_deinit (); +#endif +#ifdef _WIN32 + if (mhd_winsock_inited_) + WSACleanup(); +#endif +} + +_SET_INIT_AND_DEINIT_FUNCS(MHD_init, MHD_fini); + +/* end of daemon.c */ |