| /* |
| 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 <openssl/ssl.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 |
| |
| static ssize_t |
| recv_param_adapter (struct MHD_Connection *connection, |
| void *other, |
| size_t i); |
| static ssize_t |
| send_param_adapter (struct MHD_Connection *connection, |
| const void *other, |
| size_t i); |
| |
| // Internal functions for implementing OpenSSL BIO. |
| static int |
| MHD_bio_write (BIO* bio, const char* buf, int size) |
| { |
| struct MHD_Connection* connection = (struct MHD_Connection*)bio->ptr; |
| BIO_clear_retry_flags (bio); |
| ssize_t written = send_param_adapter (connection, buf, size); |
| if (written < size) |
| { |
| BIO_set_retry_write (bio); |
| } |
| return written; |
| } |
| |
| static int |
| MHD_bio_read (BIO* bio, char* buf, int size) |
| { |
| struct MHD_Connection* connection = (struct MHD_Connection*)bio->ptr; |
| BIO_clear_retry_flags (bio); |
| ssize_t read = recv_param_adapter (connection, buf, size); |
| if (read < size) |
| { |
| BIO_set_retry_read (bio); |
| } |
| return read; |
| } |
| |
| static long |
| MHD_bio_ctrl (BIO* bio, int cmd, long num, void* ptr) |
| { |
| if (cmd == BIO_CTRL_FLUSH) |
| { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int |
| MHD_bio_new (BIO* bio) |
| { |
| bio->shutdown = 0; |
| bio->init = 0; |
| bio->num = -1; // not used. |
| return 1; |
| } |
| |
| static int |
| MHD_bio_free (BIO* bio) |
| { |
| if (!bio) |
| return 0; |
| |
| if (bio->init) |
| { |
| bio->ptr = NULL; |
| bio->init = 0; |
| } |
| return 1; |
| } |
| |
| // Describes a BIO built on [send|recv]_param_adapter(). |
| BIO_METHOD MHD_bio_method = |
| { |
| BIO_TYPE_SOURCE_SINK, |
| "mhd", // name |
| MHD_bio_write, // write function |
| MHD_bio_read, // read function |
| NULL, // puts function, not implemented |
| NULL, // gets function, not implemented |
| MHD_bio_ctrl, // control function |
| MHD_bio_new, // creation |
| MHD_bio_free, // free |
| NULL, // callback function, not used |
| }; |
| |
| |
| /** |
| * 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 = SSL_read (connection->tls_session, other, i); |
| if ( res < 0 && SSL_want_read (connection->tls_session) ) |
| { |
| 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 = SSL_write (connection->tls_session, other, i); |
| if ( res < 0 && SSL_want_write (connection->tls_session) ) |
| { |
| 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; |
| } |
| |
| |
| /** |
| * 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) |
| { |
| int ret; |
| daemon->tls_context = SSL_CTX_new (TLSv1_2_server_method()); |
| if (NULL == daemon->tls_context) |
| return -1; |
| if (NULL != daemon->https_mem_trust) |
| { |
| ret = 0; |
| BIO* mem_bio = BIO_new_mem_buf ((void*)daemon->https_mem_trust, -1); |
| X509* x509 = PEM_read_bio_X509 (mem_bio, NULL, NULL, NULL); |
| BIO_free(mem_bio); |
| if (x509 != NULL) |
| { |
| ret = SSL_CTX_add_client_CA (daemon->tls_context, x509); |
| } |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Bad trust certificate format\n"); |
| #endif |
| return -1; |
| } |
| } |
| |
| if (NULL != daemon->https_mem_dhparams) |
| { |
| ret = 0; |
| BIO* mem_bio = BIO_new_mem_buf ((void*)daemon->https_mem_dhparams, -1); |
| DH* dh = PEM_read_bio_DHparams (mem_bio, NULL, NULL, NULL); |
| BIO_free (mem_bio); |
| if (dh != NULL) |
| { |
| ret = SSL_CTX_set_tmp_dh (daemon->tls_context, dh); |
| } |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Bad DH parameters format\n"); |
| #endif |
| return -1; |
| } |
| } |
| |
| /* certificate & key loaded from memory */ |
| if ( (NULL != daemon->https_mem_cert) && |
| (NULL != daemon->https_mem_key) ) |
| { |
| ret = 0; |
| BIO* mem_bio = BIO_new_mem_buf ((void*)daemon->https_mem_key, -1); |
| EVP_PKEY* key = PEM_read_bio_PrivateKey (mem_bio, NULL, NULL, |
| (void*)daemon->https_key_password); |
| BIO_free (mem_bio); |
| if (key != NULL) |
| { |
| ret = SSL_CTX_use_PrivateKey (daemon->tls_context, key); |
| } |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Bad private key format\n"); |
| #endif |
| return -1; |
| } |
| ret = 0; |
| mem_bio = BIO_new_mem_buf ((void*)daemon->https_mem_cert, -1); |
| X509* x509 = PEM_read_bio_X509 (mem_bio, NULL, NULL, NULL); |
| BIO_free (mem_bio); |
| if (x509 != NULL) |
| { |
| ret = SSL_CTX_use_certificate (daemon->tls_context, x509); |
| } |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Bad certificate format\n"); |
| #endif |
| return -1; |
| } |
| if (1 != SSL_CTX_check_private_key (daemon->tls_context)) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Invalid key / certificate combination\n"); |
| #endif |
| return -1; |
| } |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "You need to specify a certificate and key location\n"); |
| #endif |
| return -1; |
| } |
| if (NULL != daemon->https_mem_cipher) |
| { |
| ret = SSL_CTX_set_cipher_list (daemon->tls_context, |
| daemon->https_mem_cipher); |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Bad cipher string format\n"); |
| #endif |
| return -1; |
| } |
| } |
| else |
| { |
| ret = SSL_CTX_set_cipher_list (daemon->tls_context, |
| "HIGH!SHA1!DH@STRENGTH"); |
| if (ret == 0) |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "Failed to setup default cipher string\n"); |
| #endif |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| static void |
| MHD_TLS_deinit (struct MHD_Daemon *daemon) |
| { |
| SSL_CTX_free (daemon->tls_context); |
| } |
| #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); |
| connection->tls_session = SSL_new (daemon->tls_context); |
| BIO* bio = BIO_new (&MHD_bio_method); |
| if (bio) |
| { |
| bio->ptr = connection; |
| bio->init = 1; |
| } |
| SSL_set_bio (connection->tls_session, bio, bio); |
| SSL_set_app_data (connection->tls_session, connection); |
| } |
| #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) |
| SSL_free (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 != SSL_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 != SSL_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, (fd_set*)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], (fd_set*)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, (fd_set*)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, (fd_set*)read_fd_set)) && |
| (pos->read_buffer_size > pos->read_buffer_offset) ) |
| pos->read_handler (pos); |
| if (FD_ISSET (ds, (fd_set*)write_fd_set)) |
| pos->write_handler (pos); |
| break; |
| case MHD_EVENT_LOOP_INFO_BLOCK: |
| if ( (FD_ISSET (ds, (fd_set*)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 *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| 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 *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| 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 *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| 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 *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| 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: |
| break; |
| case MHD_OPTION_HTTPS_MEM_DHPARAMS: |
| if (0 != (daemon->options & MHD_USE_SSL)) |
| { |
| daemon->https_mem_dhparams = va_arg (ap, const char *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", |
| opt); |
| #endif |
| } |
| break; |
| case MHD_OPTION_HTTPS_PRIORITIES: |
| if (0 != (daemon->options & MHD_USE_SSL)) |
| { |
| daemon->https_mem_cipher = va_arg (ap, const char *); |
| } |
| else |
| { |
| #if HAVE_MESSAGES |
| MHD_DLOG (daemon, |
| "MHD HTTPS option %d passed to MHD but MHD_USE_SSL not set\n", |
| opt); |
| #endif |
| } |
| break; |
| case MHD_OPTION_HTTPS_CERT_CALLBACK: |
| break; |
| #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 */ |
| 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 (MHD_YES != parse_options_va (daemon, &servaddr, ap)) |
| { |
| 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 |
| 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 |
| 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 |
| 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 |
| 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 (0 != (daemon->options & MHD_USE_SSL)) |
| { |
| MHD_TLS_deinit (daemon); |
| } |
| #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: |
| return MHD_NO; |
| 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 |
| return MHD_YES; |
| #else |
| return MHD_NO; |
| #endif |
| } |
| return MHD_NO; |
| } |
| |
| |
| /** |
| * 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 |
| SSL_library_init(); |
| #endif |
| } |
| |
| |
| void MHD_fini(void) |
| { |
| #ifdef _WIN32 |
| if (mhd_winsock_inited_) |
| WSACleanup(); |
| #endif |
| } |
| |
| _SET_INIT_AND_DEINIT_FUNCS(MHD_init, MHD_fini); |
| |
| /* end of daemon.c */ |