mirror of
https://git.gnunet.org/libmicrohttpd.git
synced 2026-10-07 04:02:18 +03:00
1155 lines
32 KiB
C
1155 lines
32 KiB
C
/*
|
|
This file is part of libmicrohttpd
|
|
(C) 2007, 2008 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 daemon.c
|
|
* @brief A minimal-HTTP server library
|
|
* @author Daniel Pittman
|
|
* @author Christian Grothoff
|
|
*/
|
|
#include "platform.h"
|
|
#include "internal.h"
|
|
#include "response.h"
|
|
#include "connection.h"
|
|
#include "memorypool.h"
|
|
|
|
#if HTTPS_SUPPORT
|
|
#include "connection_https.h"
|
|
#include "gnutls_int.h"
|
|
#include "gnutls_global.h"
|
|
#endif
|
|
|
|
/**
|
|
* Default connection limit.
|
|
*/
|
|
#define MHD_MAX_CONNECTIONS_DEFAULT FD_SETSIZE -4
|
|
|
|
/**
|
|
* Default memory allowed per connection.
|
|
*/
|
|
#define MHD_POOL_SIZE_DEFAULT (1024 * 1024)
|
|
|
|
/**
|
|
* 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
|
|
|
|
#if HTTPS_SUPPORT
|
|
/**
|
|
* Note: code duplication with code in MHD_gnutls_priority.c
|
|
*
|
|
* @return 0
|
|
*/
|
|
static int
|
|
_set_priority (MHD_gtls_priority_st * st, const int *list)
|
|
{
|
|
int num = 0;
|
|
|
|
while ((list[num] != 0) && (num < MAX_ALGOS))
|
|
num++;
|
|
st->num_algorithms = num;
|
|
memcpy (st->priority, list, num * sizeof (int));
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* Callback for receiving data from the socket.
|
|
*
|
|
* @param conn 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)
|
|
{
|
|
return MHD__gnutls_record_recv (connection->tls_session, other, i);
|
|
}
|
|
|
|
/**
|
|
* Callback for writing data to the socket.
|
|
*
|
|
* @param conn 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)
|
|
{
|
|
return MHD__gnutls_record_send (connection->tls_session, other, i);
|
|
}
|
|
|
|
|
|
/**
|
|
* Read and setup our certificate and key.
|
|
*
|
|
* @return 0 on success
|
|
*/
|
|
static int
|
|
MHD_init_daemon_certificate (struct MHD_Daemon *daemon)
|
|
{
|
|
MHD_gnutls_datum_t key;
|
|
MHD_gnutls_datum_t cert;
|
|
|
|
/* certificate & key loaded from memory */
|
|
if (daemon->https_mem_cert && daemon->https_mem_key)
|
|
{
|
|
key.data = (unsigned char *) daemon->https_mem_key;
|
|
key.size = strlen (daemon->https_mem_key);
|
|
cert.data = (unsigned char *) daemon->https_mem_cert;
|
|
cert.size = strlen (daemon->https_mem_cert);
|
|
|
|
return MHD__gnutls_certificate_set_x509_key_mem (daemon->x509_cred,
|
|
&cert, &key,
|
|
GNUTLS_X509_FMT_PEM);
|
|
}
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "You need to specify a certificate and key location\n");
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Initialize security aspects of the HTTPS daemon
|
|
*
|
|
* @return 0 on success
|
|
*/
|
|
static int
|
|
MHD_TLS_init (struct MHD_Daemon *daemon)
|
|
{
|
|
switch (daemon->cred_type)
|
|
{
|
|
case MHD_GNUTLS_CRD_CERTIFICATE:
|
|
if (0 !=
|
|
MHD__gnutls_certificate_allocate_credentials (&daemon->x509_cred))
|
|
return GNUTLS_E_MEMORY_ERROR;
|
|
return MHD_init_daemon_certificate (daemon);
|
|
default:
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
"Error: invalid credentials type %d specified.\n",
|
|
daemon->cred_type);
|
|
#endif
|
|
return -1;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* Obtain the select sets for this daemon.
|
|
*
|
|
* @return MHD_YES on success, MHD_NO if this
|
|
* daemon was not started with the right
|
|
* options for this call.
|
|
*/
|
|
int
|
|
MHD_get_fdset (struct MHD_Daemon *daemon,
|
|
fd_set * read_fd_set,
|
|
fd_set * write_fd_set, fd_set * except_fd_set, int *max_fd)
|
|
{
|
|
struct MHD_Connection *con_itr;
|
|
int fd;
|
|
|
|
if ((daemon == NULL) || (read_fd_set == NULL) || (write_fd_set == NULL)
|
|
|| (except_fd_set == NULL) || (max_fd == NULL)
|
|
|| (-1 == (fd = daemon->socket_fd)) || (daemon->shutdown == MHD_YES)
|
|
|| ((daemon->options & MHD_USE_THREAD_PER_CONNECTION) != 0))
|
|
return MHD_NO;
|
|
|
|
FD_SET (fd, read_fd_set);
|
|
/* update max file descriptor */
|
|
if ((*max_fd) < fd)
|
|
*max_fd = fd;
|
|
|
|
con_itr = daemon->connections;
|
|
while (con_itr != NULL)
|
|
{
|
|
if (MHD_YES != MHD_connection_get_fdset (con_itr,
|
|
read_fd_set,
|
|
write_fd_set,
|
|
except_fd_set, max_fd))
|
|
return MHD_NO;
|
|
con_itr = con_itr->next;
|
|
}
|
|
#if DEBUG_CONNECT
|
|
MHD_DLOG (daemon, "Maximum socket in select set: %d\n", *max_fd);
|
|
#endif
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Main function of the thread that handles an individual
|
|
* connection.
|
|
*/
|
|
static void *
|
|
MHD_handle_connection (void *data)
|
|
{
|
|
struct MHD_Connection *con = data;
|
|
int num_ready;
|
|
fd_set rs;
|
|
fd_set ws;
|
|
fd_set es;
|
|
int max;
|
|
struct timeval tv;
|
|
unsigned int timeout;
|
|
unsigned int now;
|
|
|
|
if (con == NULL)
|
|
abort ();
|
|
timeout = con->daemon->connection_timeout;
|
|
while ((!con->daemon->shutdown) && (con->socket_fd != -1))
|
|
{
|
|
FD_ZERO (&rs);
|
|
FD_ZERO (&ws);
|
|
FD_ZERO (&es);
|
|
max = 0;
|
|
MHD_connection_get_fdset (con, &rs, &ws, &es, &max);
|
|
now = time (NULL);
|
|
tv.tv_usec = 0;
|
|
if (timeout > (now - con->last_activity))
|
|
tv.tv_sec = timeout - (now - con->last_activity);
|
|
else
|
|
tv.tv_sec = 0;
|
|
if ((con->state == MHD_CONNECTION_NORMAL_BODY_UNREADY) ||
|
|
(con->state == MHD_CONNECTION_CHUNKED_BODY_UNREADY))
|
|
timeout = 1; /* do not block */
|
|
num_ready = SELECT (max + 1,
|
|
&rs, &ws, &es, (timeout != 0) ? &tv : NULL);
|
|
if (num_ready < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
continue;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (con->daemon, "Error during select (%d): `%s'\n", max,
|
|
STRERROR (errno));
|
|
#endif
|
|
break;
|
|
}
|
|
/* call appropriate connection handler if necessary */
|
|
if ((con->socket_fd != -1) && (FD_ISSET (con->socket_fd, &rs)))
|
|
con->read_handler (con);
|
|
if ((con->socket_fd != -1) && (FD_ISSET (con->socket_fd, &ws)))
|
|
con->write_handler (con);
|
|
if (con->socket_fd != -1)
|
|
con->idle_handler (con);
|
|
}
|
|
if (con->socket_fd != -1)
|
|
{
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (con->daemon,
|
|
"Processing thread terminating, closing connection\n");
|
|
#endif
|
|
#endif
|
|
SHUTDOWN (con->socket_fd, SHUT_RDWR);
|
|
CLOSE (con->socket_fd);
|
|
con->socket_fd = -1;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Callback for receiving data from the socket.
|
|
*
|
|
* @param conn 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)
|
|
{
|
|
if (connection->socket_fd == -1)
|
|
return -1;
|
|
return RECV (connection->socket_fd, other, i, MSG_NOSIGNAL);
|
|
}
|
|
|
|
/**
|
|
* Callback for writing data to the socket.
|
|
*
|
|
* @param conn 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)
|
|
{
|
|
if (connection->socket_fd == -1)
|
|
return -1;
|
|
return SEND (connection->socket_fd, other, i, MSG_NOSIGNAL);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
static int
|
|
MHD_accept_connection (struct MHD_Daemon *daemon)
|
|
{
|
|
struct MHD_Connection *pos;
|
|
struct MHD_Connection *connection;
|
|
#if HAVE_INET6
|
|
struct sockaddr_in6 addrstorage;
|
|
#else
|
|
struct sockaddr_in addrstorage;
|
|
#endif
|
|
struct sockaddr *addr = (struct sockaddr *) &addrstorage;
|
|
socklen_t addrlen;
|
|
unsigned int have;
|
|
int s, res_thread_create;
|
|
#if OSX
|
|
static int on = 1;
|
|
#endif
|
|
|
|
#if HAVE_INET6
|
|
if (sizeof (struct sockaddr) > sizeof (struct sockaddr_in6))
|
|
abort (); /* fatal, serious error */
|
|
#endif
|
|
addrlen = sizeof (addrstorage);
|
|
memset (addr, 0, sizeof (addrstorage));
|
|
|
|
s = ACCEPT (daemon->socket_fd, addr, &addrlen);
|
|
|
|
if ((s < 0) || (addrlen <= 0))
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "Error accepting connection: %s\n", STRERROR (errno));
|
|
#endif
|
|
if (s != -1)
|
|
{
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
/* just in case */
|
|
}
|
|
return MHD_NO;
|
|
}
|
|
#if HAVE_MESSAGES
|
|
#if DEBUG_CONNECT
|
|
MHD_DLOG (daemon, "Accepted connection on socket %d\n", s);
|
|
#endif
|
|
#endif
|
|
have = 0;
|
|
if ((daemon->per_ip_connection_limit != 0) && (daemon->max_connections > 0))
|
|
{
|
|
pos = daemon->connections;
|
|
while (pos != NULL)
|
|
{
|
|
if ((pos->addr != NULL) && (pos->addr_len == addrlen))
|
|
{
|
|
if (addrlen == sizeof (struct sockaddr_in))
|
|
{
|
|
const struct sockaddr_in *a1 =
|
|
(const struct sockaddr_in *) &addr;
|
|
const struct sockaddr_in *a2 =
|
|
(const struct sockaddr_in *) pos->addr;
|
|
if (0 == memcmp (&a1->sin_addr, &a2->sin_addr,
|
|
sizeof (struct in_addr)))
|
|
have++;
|
|
}
|
|
#if HAVE_INET6
|
|
if (addrlen == sizeof (struct sockaddr_in6))
|
|
{
|
|
const struct sockaddr_in6 *a1 =
|
|
(const struct sockaddr_in6 *) &addr;
|
|
const struct sockaddr_in6 *a2 =
|
|
(const struct sockaddr_in6 *) pos->addr;
|
|
if (0 == memcmp (&a1->sin6_addr, &a2->sin6_addr,
|
|
sizeof (struct in6_addr)))
|
|
have++;
|
|
}
|
|
#endif
|
|
}
|
|
pos = pos->next;
|
|
}
|
|
}
|
|
|
|
if ((daemon->max_connections == 0) || ((daemon->per_ip_connection_limit
|
|
!= 0)
|
|
&& (daemon->per_ip_connection_limit
|
|
<= have)))
|
|
{
|
|
/* above connection limit - reject */
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon,
|
|
"Server reached connection limit (closing inbound connection)\n");
|
|
#endif
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
return MHD_NO;
|
|
}
|
|
|
|
/* apply connection acceptance policy if present */
|
|
if ((daemon->apc != NULL)
|
|
&& (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
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
return MHD_YES;
|
|
}
|
|
#if OSX
|
|
#ifdef SOL_SOCKET
|
|
#ifdef SO_NOSIGPIPE
|
|
setsockopt (s, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof (on));
|
|
#endif
|
|
#endif
|
|
#endif
|
|
connection = malloc (sizeof (struct MHD_Connection));
|
|
if (connection == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "Error allocating memory: %s\n", STRERROR (errno));
|
|
#endif
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
return MHD_NO;
|
|
}
|
|
memset (connection, 0, sizeof (struct MHD_Connection));
|
|
connection->pool = NULL;
|
|
connection->addr = malloc (addrlen);
|
|
if (connection->addr == NULL)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "Error allocating memory: %s\n", STRERROR (errno));
|
|
#endif
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
free (connection);
|
|
return MHD_NO;
|
|
}
|
|
memcpy (connection->addr, addr, addrlen);
|
|
connection->addr_len = addrlen;
|
|
connection->socket_fd = s;
|
|
connection->daemon = daemon;
|
|
connection->last_activity = time (NULL);
|
|
|
|
/* set default connection handlers */
|
|
MHD_set_http_calbacks (connection);
|
|
connection->recv_cls = &recv_param_adapter;
|
|
connection->send_cls = &send_param_adapter;
|
|
#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_calbacks (connection);
|
|
MHD__gnutls_init (&connection->tls_session, GNUTLS_SERVER);
|
|
MHD__gnutls_priority_set (connection->tls_session,
|
|
connection->daemon->priority_cache);
|
|
switch (connection->daemon->cred_type)
|
|
{
|
|
/* set needed credentials for certificate authentication. */
|
|
case MHD_GNUTLS_CRD_CERTIFICATE:
|
|
MHD__gnutls_credentials_set (connection->tls_session,
|
|
MHD_GNUTLS_CRD_CERTIFICATE,
|
|
connection->daemon->x509_cred);
|
|
break;
|
|
default:
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (connection->daemon,
|
|
"Failed to setup TLS credentials: unknown credential type %d\n",
|
|
connection->daemon->cred_type);
|
|
#endif
|
|
abort ();
|
|
}
|
|
MHD__gnutls_transport_set_ptr (connection->tls_session,
|
|
(MHD_gnutls_transport_ptr_t) connection);
|
|
MHD__gnutls_transport_set_pull_function (connection->tls_session,
|
|
(MHD_gtls_pull_func) &
|
|
recv_param_adapter);
|
|
MHD__gnutls_transport_set_push_function (connection->tls_session,
|
|
(MHD_gtls_push_func) &
|
|
send_param_adapter);
|
|
}
|
|
#endif
|
|
|
|
/* attempt to create handler thread */
|
|
if (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
|
|
{
|
|
res_thread_create = pthread_create (&connection->pid, NULL,
|
|
&MHD_handle_connection, connection);
|
|
if (res_thread_create != 0)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "Failed to create a thread: %s\n",
|
|
STRERROR (errno));
|
|
#endif
|
|
SHUTDOWN (s, SHUT_RDWR);
|
|
CLOSE (s);
|
|
free (connection->addr);
|
|
free (connection);
|
|
return MHD_NO;
|
|
}
|
|
}
|
|
connection->next = daemon->connections;
|
|
daemon->connections = connection;
|
|
daemon->max_connections--;
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Free resources associated with all closed connections.
|
|
* (destroy responses, free buffers, etc.). A connection
|
|
* is known to be closed if the socket_fd is -1.
|
|
*/
|
|
static void
|
|
MHD_cleanup_connections (struct MHD_Daemon *daemon)
|
|
{
|
|
struct MHD_Connection *pos;
|
|
struct MHD_Connection *prev;
|
|
void *unused;
|
|
|
|
pos = daemon->connections;
|
|
prev = NULL;
|
|
while (pos != NULL)
|
|
{
|
|
if ((pos->socket_fd == -1) ||
|
|
(((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) &&
|
|
(daemon->shutdown) && (pos->socket_fd != -1))))
|
|
{
|
|
if (prev == NULL)
|
|
daemon->connections = pos->next;
|
|
else
|
|
prev->next = pos->next;
|
|
if (0 != (pos->daemon->options & MHD_USE_THREAD_PER_CONNECTION))
|
|
{
|
|
pthread_kill (pos->pid, SIGALRM);
|
|
pthread_join (pos->pid, &unused);
|
|
}
|
|
MHD_destroy_response (pos->response);
|
|
MHD_pool_destroy (pos->pool);
|
|
#if HTTPS_SUPPORT
|
|
if (pos->tls_session != NULL)
|
|
MHD__gnutls_deinit (pos->tls_session);
|
|
#endif
|
|
free (pos->addr);
|
|
free (pos);
|
|
daemon->max_connections++;
|
|
if (prev == NULL)
|
|
pos = daemon->connections;
|
|
else
|
|
pos = prev->next;
|
|
continue;
|
|
}
|
|
prev = pos;
|
|
pos = pos->next;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obtain timeout value for select for this daemon
|
|
* (only needed if connection timeout is used). The
|
|
* returned value is how long select should at most
|
|
* block, not the timeout value set for connections.
|
|
*
|
|
* @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).
|
|
*/
|
|
int
|
|
MHD_get_timeout (struct MHD_Daemon *daemon, unsigned long long *timeout)
|
|
{
|
|
time_t earliest_deadline;
|
|
time_t now;
|
|
struct MHD_Connection *pos;
|
|
unsigned int dto;
|
|
|
|
dto = daemon->connection_timeout;
|
|
if (0 == dto)
|
|
return MHD_NO;
|
|
pos = daemon->connections;
|
|
if (pos == NULL)
|
|
return MHD_NO; /* no connections */
|
|
now = time (NULL);
|
|
/* start with conservative estimate */
|
|
earliest_deadline = now + dto;
|
|
while (pos != NULL)
|
|
{
|
|
if (earliest_deadline > pos->last_activity + dto)
|
|
earliest_deadline = pos->last_activity + dto;
|
|
pos = pos->next;
|
|
}
|
|
if (earliest_deadline < now)
|
|
*timeout = 0;
|
|
else
|
|
*timeout = (earliest_deadline - now);
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Main select call.
|
|
*
|
|
* @param may_block YES if blocking, 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)
|
|
{
|
|
struct MHD_Connection *pos;
|
|
int num_ready;
|
|
fd_set rs;
|
|
fd_set ws;
|
|
fd_set es;
|
|
int max;
|
|
struct timeval timeout;
|
|
unsigned long long ltimeout;
|
|
int ds;
|
|
time_t now;
|
|
|
|
timeout.tv_sec = 0;
|
|
timeout.tv_usec = 0;
|
|
if (daemon == NULL)
|
|
abort ();
|
|
if (daemon->shutdown == MHD_YES)
|
|
return MHD_NO;
|
|
FD_ZERO (&rs);
|
|
FD_ZERO (&ws);
|
|
FD_ZERO (&es);
|
|
max = 0;
|
|
|
|
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
|
|
{
|
|
/* single-threaded, go over everything */
|
|
if (MHD_NO == MHD_get_fdset (daemon, &rs, &ws, &es, &max))
|
|
return MHD_NO;
|
|
}
|
|
else
|
|
{
|
|
/* accept only, have one thread per connection */
|
|
max = daemon->socket_fd;
|
|
if (max == -1)
|
|
return MHD_NO;
|
|
FD_SET (max, &rs);
|
|
}
|
|
|
|
if (may_block == MHD_NO)
|
|
{
|
|
timeout.tv_usec = 0;
|
|
timeout.tv_sec = 0;
|
|
}
|
|
else
|
|
{
|
|
/* ltimeout is in ms */
|
|
if (MHD_YES == MHD_get_timeout (daemon, <imeout))
|
|
{
|
|
timeout.tv_usec = (ltimeout % 1000) * 1000;
|
|
timeout.tv_sec = ltimeout / 1000;
|
|
may_block = MHD_NO;
|
|
}
|
|
}
|
|
|
|
num_ready = select (max + 1, &rs, &ws, &es, may_block == MHD_NO ? &timeout
|
|
: NULL);
|
|
|
|
if (daemon->shutdown == MHD_YES)
|
|
return MHD_NO;
|
|
if (num_ready < 0)
|
|
{
|
|
if (errno == EINTR)
|
|
return MHD_YES;
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "select failed: %s\n", STRERROR (errno));
|
|
#endif
|
|
return MHD_NO;
|
|
}
|
|
ds = daemon->socket_fd;
|
|
if (ds == -1)
|
|
return MHD_YES;
|
|
|
|
/* select connection thread handling type */
|
|
if (FD_ISSET (ds, &rs))
|
|
MHD_accept_connection (daemon);
|
|
if (0 == (daemon->options & MHD_USE_THREAD_PER_CONNECTION))
|
|
{
|
|
/* do not have a thread per connection, process all connections now */
|
|
now = time (NULL);
|
|
pos = daemon->connections;
|
|
while (pos != NULL)
|
|
{
|
|
ds = pos->socket_fd;
|
|
if (ds != -1)
|
|
{
|
|
/* TODO call con->read handler */
|
|
if (FD_ISSET (ds, &rs))
|
|
pos->read_handler (pos);
|
|
if ((pos->socket_fd != -1) && (FD_ISSET (ds, &ws)))
|
|
pos->write_handler (pos);
|
|
if (pos->socket_fd != -1)
|
|
pos->idle_handler (pos);
|
|
}
|
|
pos = pos->next;
|
|
}
|
|
}
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @return MHD_YES on success, MHD_NO if this
|
|
* daemon was not started with the right
|
|
* options for this call.
|
|
*/
|
|
int
|
|
MHD_run (struct MHD_Daemon *daemon)
|
|
{
|
|
if ((daemon->shutdown != MHD_NO) || (0 != (daemon->options
|
|
& MHD_USE_THREAD_PER_CONNECTION))
|
|
|| (0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)))
|
|
return MHD_NO;
|
|
MHD_select (daemon, MHD_NO);
|
|
MHD_cleanup_connections (daemon);
|
|
return MHD_YES;
|
|
}
|
|
|
|
/**
|
|
* Thread that runs the select loop until the daemon
|
|
* is explicitly shut down.
|
|
*/
|
|
static void *
|
|
MHD_select_thread (void *cls)
|
|
{
|
|
struct MHD_Daemon *daemon = cls;
|
|
while (daemon->shutdown == MHD_NO)
|
|
{
|
|
MHD_select (daemon, MHD_YES);
|
|
MHD_cleanup_connections (daemon);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Start a webserver on the given port.
|
|
*
|
|
* @param port port to bind to
|
|
* @param apc callback to call to check which clients
|
|
* will be allowed to connect
|
|
* @param apc_cls extra argument to apc
|
|
* @param dh default handler for all URIs
|
|
* @param dh_cls extra argument to dh
|
|
* @return NULL on error, handle to daemon on success
|
|
*/
|
|
struct MHD_Daemon *
|
|
MHD_start_daemon (unsigned int options,
|
|
unsigned short port,
|
|
MHD_AcceptPolicyCallback apc,
|
|
void *apc_cls,
|
|
MHD_AccessHandlerCallback dh, void *dh_cls, ...)
|
|
{
|
|
struct MHD_Daemon *ret;
|
|
va_list ap;
|
|
|
|
va_start (ap, dh_cls);
|
|
ret = MHD_start_daemon_va (options, port, apc, apc_cls, dh, dh_cls, ap);
|
|
va_end (ap);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* Start a webserver on the given port.
|
|
*
|
|
* @param port port to bind to
|
|
* @param apc callback to call to check which clients
|
|
* will be allowed to connect
|
|
* @param apc_cls extra argument to apc
|
|
* @param dh default handler for all URIs
|
|
* @param dh_cls extra argument to dh
|
|
* @return NULL on error, handle to daemon on success
|
|
*/
|
|
struct MHD_Daemon *
|
|
MHD_start_daemon_va (unsigned int options,
|
|
unsigned short port,
|
|
MHD_AcceptPolicyCallback apc,
|
|
void *apc_cls,
|
|
MHD_AccessHandlerCallback dh, void *dh_cls, va_list ap)
|
|
{
|
|
const int on = 1;
|
|
struct MHD_Daemon *retVal;
|
|
int socket_fd;
|
|
struct sockaddr_in servaddr4;
|
|
#if HAVE_INET6
|
|
struct sockaddr_in6 servaddr6;
|
|
#endif
|
|
const struct sockaddr *servaddr = NULL;
|
|
socklen_t addrlen;
|
|
enum MHD_OPTION opt;
|
|
|
|
if ((port == 0) || (dh == NULL))
|
|
return NULL;
|
|
retVal = malloc (sizeof (struct MHD_Daemon));
|
|
if (retVal == NULL)
|
|
return NULL;
|
|
memset (retVal, 0, sizeof (struct MHD_Daemon));
|
|
retVal->options = options;
|
|
retVal->port = port;
|
|
retVal->apc = apc;
|
|
retVal->apc_cls = apc_cls;
|
|
retVal->default_handler = dh;
|
|
retVal->default_handler_cls = dh_cls;
|
|
retVal->max_connections = MHD_MAX_CONNECTIONS_DEFAULT;
|
|
retVal->pool_size = MHD_POOL_SIZE_DEFAULT;
|
|
retVal->connection_timeout = 0; /* no timeout */
|
|
#if HTTPS_SUPPORT
|
|
if (options & MHD_USE_SSL)
|
|
{
|
|
/* lock MHD_gnutls_global mutex since it uses reference counting */
|
|
pthread_mutex_lock (&MHD_gnutls_init_mutex);
|
|
MHD__gnutls_global_init ();
|
|
pthread_mutex_unlock (&MHD_gnutls_init_mutex);
|
|
/* set default priorities */
|
|
MHD_tls_set_default_priority (&retVal->priority_cache, "", NULL);
|
|
retVal->cred_type = MHD_GNUTLS_CRD_CERTIFICATE;
|
|
}
|
|
#endif
|
|
|
|
while (MHD_OPTION_END != (opt = va_arg (ap, enum MHD_OPTION)))
|
|
{
|
|
switch (opt)
|
|
{
|
|
case MHD_OPTION_CONNECTION_MEMORY_LIMIT:
|
|
retVal->pool_size = va_arg (ap, unsigned int);
|
|
break;
|
|
case MHD_OPTION_CONNECTION_LIMIT:
|
|
retVal->max_connections = va_arg (ap, unsigned int);
|
|
break;
|
|
case MHD_OPTION_CONNECTION_TIMEOUT:
|
|
retVal->connection_timeout = va_arg (ap, unsigned int);
|
|
break;
|
|
case MHD_OPTION_NOTIFY_COMPLETED:
|
|
retVal->notify_completed =
|
|
va_arg (ap, MHD_RequestCompletedCallback);
|
|
retVal->notify_completed_cls = va_arg (ap, void *);
|
|
break;
|
|
case MHD_OPTION_PER_IP_CONNECTION_LIMIT:
|
|
retVal->per_ip_connection_limit = va_arg (ap, unsigned int);
|
|
break;
|
|
case MHD_OPTION_SOCK_ADDR:
|
|
servaddr = va_arg (ap, struct sockaddr *);
|
|
break;
|
|
#if HTTPS_SUPPORT
|
|
case MHD_OPTION_PROTOCOL_VERSION:
|
|
_set_priority (&retVal->priority_cache->protocol,
|
|
va_arg (ap, const int *));
|
|
break;
|
|
case MHD_OPTION_HTTPS_MEM_KEY:
|
|
retVal->https_mem_key = va_arg (ap, const char *);
|
|
break;
|
|
case MHD_OPTION_HTTPS_MEM_CERT:
|
|
retVal->https_mem_cert = va_arg (ap, const char *);
|
|
break;
|
|
case MHD_OPTION_CRED_TYPE:
|
|
retVal->cred_type = va_arg (ap, const int);
|
|
break;
|
|
case MHD_OPTION_KX_PRIORITY:
|
|
_set_priority (&retVal->priority_cache->kx,
|
|
va_arg (ap, const int *));
|
|
break;
|
|
case MHD_OPTION_CIPHER_ALGORITHM:
|
|
_set_priority (&retVal->priority_cache->cipher,
|
|
va_arg (ap, const int *));
|
|
break;
|
|
case MHD_OPTION_MAC_ALGO:
|
|
_set_priority (&retVal->priority_cache->mac,
|
|
va_arg (ap, const int *));
|
|
break;
|
|
#endif
|
|
default:
|
|
#if HAVE_MESSAGES
|
|
if ((opt >= MHD_OPTION_HTTPS_KEY_PATH) &&
|
|
(opt <= MHD_OPTION_TLS_COMP_ALGO))
|
|
{
|
|
FPRINTF (stderr,
|
|
"MHD HTTPS option %d passed to MHD compiled without HTTPS support\n",
|
|
opt);
|
|
}
|
|
else
|
|
{
|
|
FPRINTF (stderr,
|
|
"Invalid option %d! (Did you terminate the list with MHD_OPTION_END?)\n",
|
|
opt);
|
|
}
|
|
#endif
|
|
abort ();
|
|
}
|
|
}
|
|
|
|
if ((options & MHD_USE_IPv6) != 0)
|
|
#if HAVE_INET6
|
|
socket_fd = SOCKET (PF_INET6, SOCK_STREAM, 0);
|
|
#else
|
|
{
|
|
#if HAVE_MESSAGES
|
|
FPRINTF (stderr, "AF_INET6 not supported\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
#endif
|
|
else
|
|
socket_fd = SOCKET (PF_INET, SOCK_STREAM, 0);
|
|
if (socket_fd < 0)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
if ((options & MHD_USE_DEBUG) != 0)
|
|
FPRINTF (stderr, "Call to socket failed: %s\n", STRERROR (errno));
|
|
#endif
|
|
free (retVal);
|
|
return NULL;
|
|
}
|
|
if ((SETSOCKOPT (socket_fd,
|
|
SOL_SOCKET,
|
|
SO_REUSEADDR,
|
|
&on, sizeof (on)) < 0) && (options & MHD_USE_DEBUG) != 0)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
FPRINTF (stderr, "setsockopt failed: %s\n", STRERROR (errno));
|
|
#endif
|
|
}
|
|
|
|
/* check for user supplied sockaddr */
|
|
#if HAVE_INET6
|
|
if ((options & MHD_USE_IPv6) != 0)
|
|
addrlen = sizeof (struct sockaddr_in6);
|
|
else
|
|
#endif
|
|
addrlen = sizeof (struct sockaddr_in);
|
|
if (NULL == servaddr)
|
|
{
|
|
#if HAVE_INET6
|
|
if ((options & MHD_USE_IPv6) != 0)
|
|
{
|
|
memset (&servaddr6, 0, sizeof (struct sockaddr_in6));
|
|
servaddr6.sin6_family = AF_INET6;
|
|
servaddr6.sin6_port = htons (port);
|
|
servaddr = (struct sockaddr *) &servaddr6;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
memset (&servaddr4, 0, sizeof (struct sockaddr_in));
|
|
servaddr4.sin_family = AF_INET;
|
|
servaddr4.sin_port = htons (port);
|
|
servaddr = (struct sockaddr *) &servaddr4;
|
|
}
|
|
}
|
|
retVal->socket_fd = socket_fd;
|
|
if (BIND (socket_fd, servaddr, addrlen) < 0)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
if ((options & MHD_USE_DEBUG) != 0)
|
|
FPRINTF (stderr,
|
|
"Failed to bind to port %u: %s\n", port, STRERROR (errno));
|
|
#endif
|
|
CLOSE (socket_fd);
|
|
free (retVal);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
if (LISTEN (socket_fd, 20) < 0)
|
|
{
|
|
#if HAVE_MESSAGES
|
|
if ((options & MHD_USE_DEBUG) != 0)
|
|
FPRINTF (stderr,
|
|
"Failed to listen for connections: %s\n", STRERROR (errno));
|
|
#endif
|
|
CLOSE (socket_fd);
|
|
free (retVal);
|
|
return NULL;
|
|
}
|
|
|
|
#if HTTPS_SUPPORT
|
|
/* initialize HTTPS daemon certificate aspects & send / recv functions */
|
|
if ((0 != (options & MHD_USE_SSL)) && (0 != MHD_TLS_init (retVal)))
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (retVal, "Failed to initialize TLS support\n");
|
|
#endif
|
|
CLOSE (socket_fd);
|
|
free (retVal);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
if (((0 != (options & MHD_USE_THREAD_PER_CONNECTION)) ||
|
|
(0 != (options & MHD_USE_SELECT_INTERNALLY)))
|
|
&& (0 !=
|
|
pthread_create (&retVal->pid, NULL, &MHD_select_thread, retVal)))
|
|
{
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (retVal,
|
|
"Failed to create listen thread: %s\n", STRERROR (errno));
|
|
#endif
|
|
free (retVal);
|
|
CLOSE (socket_fd);
|
|
return NULL;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Shutdown an http daemon.
|
|
*/
|
|
void
|
|
MHD_stop_daemon (struct MHD_Daemon *daemon)
|
|
{
|
|
void *unused;
|
|
int fd;
|
|
|
|
if (daemon == NULL)
|
|
return;
|
|
daemon->shutdown = MHD_YES;
|
|
fd = daemon->socket_fd;
|
|
daemon->socket_fd = -1;
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "MHD shutdown, closing listen socket\n");
|
|
#endif
|
|
#endif
|
|
CLOSE (fd);
|
|
if ((0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) ||
|
|
(0 != (daemon->options & MHD_USE_SELECT_INTERNALLY)))
|
|
{
|
|
pthread_kill (daemon->pid, SIGALRM);
|
|
pthread_join (daemon->pid, &unused);
|
|
}
|
|
while (daemon->connections != NULL)
|
|
{
|
|
if (-1 != daemon->connections->socket_fd)
|
|
{
|
|
#if DEBUG_CLOSE
|
|
#if HAVE_MESSAGES
|
|
MHD_DLOG (daemon, "MHD shutdown, closing active connections\n");
|
|
#endif
|
|
#endif
|
|
if (daemon->notify_completed != NULL)
|
|
daemon->notify_completed (daemon->notify_completed_cls,
|
|
daemon->connections,
|
|
&daemon->connections->client_context,
|
|
MHD_REQUEST_TERMINATED_DAEMON_SHUTDOWN);
|
|
SHUTDOWN (daemon->connections->socket_fd, SHUT_RDWR);
|
|
CLOSE (daemon->connections->socket_fd);
|
|
daemon->connections->socket_fd = -1;
|
|
}
|
|
MHD_cleanup_connections (daemon);
|
|
}
|
|
|
|
/* TLS clean up */
|
|
#if HTTPS_SUPPORT
|
|
if (daemon->options & MHD_USE_SSL)
|
|
{
|
|
MHD__gnutls_priority_deinit (daemon->priority_cache);
|
|
if (daemon->x509_cred)
|
|
MHD__gnutls_certificate_free_credentials (daemon->x509_cred);
|
|
/* lock MHD_gnutls_global mutex since it uses reference counting */
|
|
pthread_mutex_lock (&MHD_gnutls_init_mutex);
|
|
MHD__gnutls_global_deinit ();
|
|
pthread_mutex_unlock (&MHD_gnutls_init_mutex);
|
|
}
|
|
#endif
|
|
free (daemon);
|
|
}
|
|
|
|
/**
|
|
* Obtain information about the given daemon
|
|
* (not fully implemented!).
|
|
*
|
|
* @param daemon what daemon to get information about
|
|
* @param infoType what information is desired?
|
|
* @param ... depends on infoType
|
|
* @return NULL if this information is not available
|
|
* (or if the infoType is unknown)
|
|
*/
|
|
const union MHD_DaemonInfo *
|
|
MHD_get_daemon_info (struct MHD_Daemon *daemon,
|
|
enum MHD_DaemonInfoType infoType, ...)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
#ifndef WINDOWS
|
|
|
|
static struct sigaction sig;
|
|
|
|
static struct sigaction old;
|
|
|
|
static void
|
|
sigalrmHandler (int sig)
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Initialize the signal handler for SIGALRM.
|
|
*/
|
|
void __attribute__ ((constructor)) MHD_pthread_handlers_ltdl_init ()
|
|
{
|
|
/* make sure SIGALRM does not kill us */
|
|
memset (&sig, 0, sizeof (struct sigaction));
|
|
memset (&old, 0, sizeof (struct sigaction));
|
|
sig.sa_flags = SA_NODEFER;
|
|
sig.sa_handler = &sigalrmHandler;
|
|
sigaction (SIGALRM, &sig, &old);
|
|
}
|
|
|
|
void __attribute__ ((destructor)) MHD_pthread_handlers_ltdl_fini ()
|
|
{
|
|
sigaction (SIGALRM, &old, &sig);
|
|
}
|
|
|
|
#else
|
|
void __attribute__ ((constructor)) MHD_win_ltdl_init ()
|
|
{
|
|
plibc_init ("CRISP", "libmicrohttpd");
|
|
}
|
|
|
|
void __attribute__ ((destructor)) MHD_win_ltdl_fini ()
|
|
{
|
|
plibc_shutdown ();
|
|
}
|
|
#endif
|
|
|
|
/* end of daemon.c */
|