| /* |
| 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 */ |