blob: e158b14664dbf6ff5a34f9358c590edf49ccd354 [file] [log] [blame]
/*
This file is part of libmicrohttpd
Copyright (C) 2007-2018 Daniel Pittman and Christian Grothoff
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
/**
* @file lib/upgrade_process.c
* @brief function to process upgrade activity (over TLS)
* @author Christian Grothoff
*/
#include "internal.h"
#include "upgrade_process.h"
#if defined(HTTPS_SUPPORT) && defined(UPGRADE_SUPPORT)
/**
* Performs bi-directional forwarding on upgraded HTTPS connections
* based on the readiness state stored in the @a urh handle.
* @remark To be called only from thread that process
* connection's recv(), send() and response.
*
* @param urh handle to process
*/
void
MHD_upgrade_response_handle_process_ (struct MHD_UpgradeResponseHandle *urh)
{
/* Help compiler to optimize:
* pointers to 'connection' and 'daemon' are not changed
* during this processing, so no need to chain dereference
* each time. */
struct MHD_Connection *const connection = urh->connection;
struct MHD_Daemon *const daemon = connection->daemon;
/* Prevent data races: use same value of 'was_closed' throughout
* this function. If 'was_closed' changed externally in the middle
* of processing - it will be processed on next iteration. */
bool was_closed;
struct MHD_TLS_Plugin *tls = daemon->tls_api;
if (daemon->shutdown)
{
/* Daemon shutting down, application will not receive any more data. */
#ifdef HAVE_MESSAGES
if (! urh->was_closed)
{
MHD_DLOG (daemon,
MHD_SC_DAEMON_ALREADY_SHUTDOWN,
_ (
"Initiated daemon shutdown while \"upgraded\" connection was not closed.\n"));
}
#endif
urh->was_closed = true;
}
was_closed = urh->was_closed;
if (was_closed)
{
/* Application was closed connections: no more data
* can be forwarded to application socket. */
if (0 < urh->in_buffer_used)
{
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
_ (
"Failed to forward to application "
MHD_UNSIGNED_LONG_LONG_PRINTF \
" bytes of data received from remote side: application shut down socket.\n"),
(MHD_UNSIGNED_LONG_LONG) urh->in_buffer_used);
#endif
}
/* If application signaled MHD about socket closure then
* check for any pending data even if socket is not marked
* as 'ready' (signal may arrive after poll()/select()).
* Socketpair for forwarding is always in non-blocking mode
* so no risk that recv() will block the thread. */if (0 != urh->out_buffer_size)
urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
/* Discard any data received form remote. */
urh->in_buffer_used = 0;
/* Do not try to push data to application. */
urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
/* Reading from remote client is not required anymore. */
urh->in_buffer_size = 0;
urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
connection->tls_read_ready = false;
}
/* On some platforms (W32, possibly Darwin) failed send() (send() will always
* fail after remote disconnect was detected) may discard data in system
* buffers received by system but not yet read by recv().
* So, before trying send() on any socket, recv() must be performed at first
* otherwise last part of incoming data may be lost. *//* If disconnect or error was detected - try to read from socket
* to dry data possibly pending is system buffers. */if (0 != (MHD_EPOLL_STATE_ERROR & urh->app.celi))
urh->app.celi |= MHD_EPOLL_STATE_READ_READY;
if (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi))
urh->mhd.celi |= MHD_EPOLL_STATE_READ_READY;
/*
* handle reading from remote TLS client
*/
if ( ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->app.celi)) ||
(connection->tls_read_ready) ) &&
(urh->in_buffer_used < urh->in_buffer_size) )
{
ssize_t res;
size_t buf_size;
buf_size = urh->in_buffer_size - urh->in_buffer_used;
if (buf_size > SSIZE_MAX)
buf_size = SSIZE_MAX;
connection->tls_read_ready = false;
res = tls->recv (tls->cls,
connection->tls_cs,
&urh->in_buffer[urh->in_buffer_used],
buf_size);
if (0 >= res)
{
// FIXME: define GNUTLS-independent error codes!
if (GNUTLS_E_INTERRUPTED != res)
{
urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
if (GNUTLS_E_AGAIN != res)
{
/* Unrecoverable error on socket was detected or
* socket was disconnected/shut down. */
/* Stop trying to read from this TLS socket. */
urh->in_buffer_size = 0;
}
}
}
else /* 0 < res */
{
urh->in_buffer_used += res;
if (buf_size > (size_t) res)
urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
else if (0 < tls->check_record_pending (tls->cls,
connection->tls_cs))
connection->tls_read_ready = true;
}
if (MHD_EPOLL_STATE_ERROR ==
((MHD_EPOLL_STATE_ERROR | MHD_EPOLL_STATE_READ_READY) & urh->app.celi))
{
/* Unrecoverable error on socket was detected and all
* pending data was read from system buffers. */
/* Stop trying to read from this TLS socket. */
urh->in_buffer_size = 0;
}
}
/*
* handle reading from application
*/
if ( (0 != (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
(urh->out_buffer_used < urh->out_buffer_size) )
{
ssize_t res;
size_t buf_size;
buf_size = urh->out_buffer_size - urh->out_buffer_used;
if (buf_size > MHD_SCKT_SEND_MAX_SIZE_)
buf_size = MHD_SCKT_SEND_MAX_SIZE_;
res = MHD_recv_ (urh->mhd.socket,
&urh->out_buffer[urh->out_buffer_used],
buf_size);
if (0 >= res)
{
const int err = MHD_socket_get_error_ ();
if ((0 == res) ||
((! MHD_SCKT_ERR_IS_EINTR_ (err)) &&
(! MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err))))
{
urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
if ((0 == res) ||
(was_closed) ||
(0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) ||
(! MHD_SCKT_ERR_IS_EAGAIN_ (err)))
{
/* Socket disconnect/shutdown was detected;
* Application signaled about closure of 'upgraded' socket;
* or persistent / unrecoverable error. */
/* Do not try to pull more data from application. */
urh->out_buffer_size = 0;
}
}
}
else /* 0 < res */
{
urh->out_buffer_used += res;
if (buf_size > (size_t) res)
urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
if ( (0 == (MHD_EPOLL_STATE_READ_READY & urh->mhd.celi)) &&
( (0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) ||
(was_closed) ) )
{
/* Unrecoverable error on socket was detected and all
* pending data was read from system buffers. */
/* Do not try to pull more data from application. */
urh->out_buffer_size = 0;
}
}
/*
* handle writing to remote HTTPS client
*/
if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->app.celi)) &&
(urh->out_buffer_used > 0) )
{
ssize_t res;
size_t data_size;
data_size = urh->out_buffer_used;
if (data_size > SSIZE_MAX)
data_size = SSIZE_MAX;
res = tls->send (tls->cls,
connection->tls_cs,
urh->out_buffer,
data_size);
if (0 >= res)
{
// FIXME: define GNUTLS-independent error codes!
if (GNUTLS_E_INTERRUPTED != res)
{
urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
if (GNUTLS_E_INTERRUPTED != res)
{
/* TLS connection shut down or
* persistent / unrecoverable error. */
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
_ (
"Failed to forward to remote client "
MHD_UNSIGNED_LONG_LONG_PRINTF \
" bytes of data received from application: %s\n"),
(MHD_UNSIGNED_LONG_LONG) urh->out_buffer_used,
tls->strerror (tls->cls,
res));
#endif
/* Discard any data unsent to remote. */
urh->out_buffer_used = 0;
/* Do not try to pull more data from application. */
urh->out_buffer_size = 0;
urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
}
}
else /* 0 < res */
{
const size_t next_out_buffer_used = urh->out_buffer_used - res;
if (0 != next_out_buffer_used)
{
memmove (urh->out_buffer,
&urh->out_buffer[res],
next_out_buffer_used);
if (data_size > (size_t) res)
urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
}
urh->out_buffer_used = next_out_buffer_used;
}
if ( (0 == urh->out_buffer_used) &&
(0 != (MHD_EPOLL_STATE_ERROR & urh->app.celi)) )
{
/* Unrecoverable error on socket was detected and all
* pending data was sent to remote. */
/* Do not try to send to remote anymore. */
urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
/* Do not try to pull more data from application. */
urh->out_buffer_size = 0;
urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
}
/*
* handle writing to application
*/
if ( (0 != (MHD_EPOLL_STATE_WRITE_READY & urh->mhd.celi)) &&
(urh->in_buffer_used > 0) )
{
ssize_t res;
size_t data_size;
data_size = urh->in_buffer_used;
if (data_size > MHD_SCKT_SEND_MAX_SIZE_)
data_size = MHD_SCKT_SEND_MAX_SIZE_;
res = MHD_send_ (urh->mhd.socket,
urh->in_buffer,
data_size);
if (0 >= res)
{
const int err = MHD_socket_get_error_ ();
if ( (! MHD_SCKT_ERR_IS_EINTR_ (err)) &&
(! MHD_SCKT_ERR_IS_LOW_RESOURCES_ (err)) )
{
urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
if (! MHD_SCKT_ERR_IS_EAGAIN_ (err))
{
/* Socketpair connection shut down or
* persistent / unrecoverable error. */
#ifdef HAVE_MESSAGES
MHD_DLOG (daemon,
MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
_ (
"Failed to forward to application "
MHD_UNSIGNED_LONG_LONG_PRINTF \
" bytes of data received from remote side: %s\n"),
(MHD_UNSIGNED_LONG_LONG) urh->in_buffer_used,
MHD_socket_strerr_ (err));
#endif
/* Discard any data received form remote. */
urh->in_buffer_used = 0;
/* Reading from remote client is not required anymore. */
urh->in_buffer_size = 0;
urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
connection->tls_read_ready = false;
}
}
}
else /* 0 < res */
{
const size_t next_in_buffer_used = urh->in_buffer_used - res;
if (0 != next_in_buffer_used)
{
memmove (urh->in_buffer,
&urh->in_buffer[res],
next_in_buffer_used);
if (data_size > (size_t) res)
urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
}
urh->in_buffer_used = next_in_buffer_used;
}
if ( (0 == urh->in_buffer_used) &&
(0 != (MHD_EPOLL_STATE_ERROR & urh->mhd.celi)) )
{
/* Do not try to push data to application. */
urh->mhd.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
/* Reading from remote client is not required anymore. */
urh->in_buffer_size = 0;
urh->app.celi &= ~MHD_EPOLL_STATE_READ_READY;
connection->tls_read_ready = false;
}
}
/* Check whether data is present in TLS buffers
* and incoming forward buffer have some space. */
if ( (connection->tls_read_ready) &&
(urh->in_buffer_used < urh->in_buffer_size) &&
(MHD_TM_THREAD_PER_CONNECTION != daemon->threading_mode) )
daemon->data_already_pending = true;
if ( (daemon->shutdown) &&
( (0 != urh->out_buffer_size) ||
(0 != urh->out_buffer_used) ) )
{
/* Daemon shutting down, discard any remaining forward data. */
#ifdef HAVE_MESSAGES
if (0 < urh->out_buffer_used)
MHD_DLOG (daemon,
MHD_SC_UPGRADE_FORWARD_INCOMPLETE,
_ (
"Failed to forward to remote client "
MHD_UNSIGNED_LONG_LONG_PRINTF \
" bytes of data received from application: daemon shut down.\n"),
(MHD_UNSIGNED_LONG_LONG) urh->out_buffer_used);
#endif
/* Discard any data unsent to remote. */
urh->out_buffer_used = 0;
/* Do not try to sent to remote anymore. */
urh->app.celi &= ~MHD_EPOLL_STATE_WRITE_READY;
/* Do not try to pull more data from application. */
urh->out_buffer_size = 0;
urh->mhd.celi &= ~MHD_EPOLL_STATE_READ_READY;
}
}
#endif /* HTTPS_SUPPORT && UPGRADE_SUPPORT */
/* end of upgrade_process.c */