From cee65d14e2a2212b159ab228889a4029405deee0 Mon Sep 17 00:00:00 2001 From: "Evgeny Grin (Karlson2k)" Date: Sun, 24 Nov 2024 20:53:44 +0300 Subject: [PATCH] GnuTLS communication: the last part --- src/include/microhttpd2.h | 5 + src/include/microhttpd2_preamble.h.in | 5 + src/mhd2/Makefile.am | 5 +- src/mhd2/conn_data_process.c | 113 ++++---------- src/mhd2/conn_data_recv.c | 2 - src/mhd2/conn_data_send.c | 4 - src/mhd2/conn_mark_ready.h | 3 +- src/mhd2/conn_tls_check.c | 130 ++++++++++++++++ src/mhd2/conn_tls_check.h | 71 +++++++++ src/mhd2/daemon_add_conn.c | 5 +- src/mhd2/mhd_connection.h | 132 +++++++++++++++- src/mhd2/mhd_recv.c | 56 ++++++- src/mhd2/mhd_send.c | 114 +++++++++----- src/mhd2/mhd_tls_enums.h | 72 +++++++++ src/mhd2/mhd_tls_funcs.h | 61 ++++++++ src/mhd2/stream_funcs.c | 27 +++- src/mhd2/stream_process_reply.c | 4 +- src/mhd2/stream_process_states.c | 32 ++-- src/mhd2/tls_gnu_conn_data.h | 21 +++ src/mhd2/tls_gnu_funcs.c | 208 +++++++++++++++++++++++++- src/mhd2/tls_gnu_funcs.h | 76 ++++++++++ 21 files changed, 986 insertions(+), 160 deletions(-) create mode 100644 src/mhd2/conn_tls_check.c create mode 100644 src/mhd2/conn_tls_check.h create mode 100644 src/mhd2/mhd_tls_enums.h diff --git a/src/include/microhttpd2.h b/src/include/microhttpd2.h index c2e1060c..93a77fad 100644 --- a/src/include/microhttpd2.h +++ b/src/include/microhttpd2.h @@ -1291,6 +1291,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_TLS_CONNECTION_INIT_FAILED = 51201 , + /** + * Failed to perform TLS handshake + */ + MHD_SC_TLS_CONNECTION_HANDSHAKED_FAILED = 51220 + , /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. diff --git a/src/include/microhttpd2_preamble.h.in b/src/include/microhttpd2_preamble.h.in index 6698041a..e6f7b3ef 100644 --- a/src/include/microhttpd2_preamble.h.in +++ b/src/include/microhttpd2_preamble.h.in @@ -1291,6 +1291,11 @@ enum MHD_FIXED_ENUM_MHD_SET_ MHD_StatusCode */ MHD_SC_TLS_CONNECTION_INIT_FAILED = 51201 , + /** + * Failed to perform TLS handshake + */ + MHD_SC_TLS_CONNECTION_HANDSHAKED_FAILED = 51220 + , /** * Something wrong in the internal MHD logic. * This error should be never returned if MHD works as expected. diff --git a/src/mhd2/Makefile.am b/src/mhd2/Makefile.am index 972b62ba..14d8fde6 100644 --- a/src/mhd2/Makefile.am +++ b/src/mhd2/Makefile.am @@ -108,8 +108,9 @@ upgrade_files = \ tls_common_files = \ mhd_tls_choice.h \ - mhd_tls_funcs.c mhd_tls_funcs.h \ - tls_dh_params.h + mhd_tls_funcs.c mhd_tls_funcs.h mhd_tls_enums.h \ + tls_dh_params.h \ + conn_tls_check.c conn_tls_check.h tls_gnu_files = \ tls_gnu_tls_lib.h tls_gnu_daemon_data.h tls_gnu_conn_data.h \ diff --git a/src/mhd2/conn_data_process.c b/src/mhd2/conn_data_process.c index e4bb489c..a4d7c8a2 100644 --- a/src/mhd2/conn_data_process.c +++ b/src/mhd2/conn_data_process.c @@ -46,26 +46,51 @@ #include "conn_data_send.h" #include "stream_process_states.h" +#ifdef MHD_ENABLE_HTTPS +# include "conn_tls_check.h" +#endif /* MHD_ENABLE_HTTPS */ + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) { + bool send_ready_state_known; + bool has_sock_err; + bool data_processed; + +#ifdef MHD_ENABLE_HTTPS + if (mhd_C_HAS_TLS (c)) + { + switch (mhd_conn_tls_check (c)) + { + case mhd_CONN_TLS_CHECK_OK: + break; /* Process HTTP data */ + case mhd_CONN_TLS_CHECK_HANDSHAKING: + return true; /* TLS is not yet ready */ + case mhd_CONN_TLS_CHECK_BROKEN: + return false; /* Connection is broken */ + default: + mhd_assert (0 && "Impossible value"); + MHD_UNREACHABLE_; + } + } +#endif /* MHD_ENABLE_HTTPS */ + /* The "send-ready" state is known if system polling call is edge-triggered (it always checks for both send- and recv-ready) or if connection needs sending (therefore "send-ready" was explicitly checked by sockets polling call). */ - const bool send_ready_state_known = + send_ready_state_known = ((mhd_D_IS_USING_EDGE_TRIG (c->daemon)) || (0 != (MHD_EVENT_LOOP_INFO_SEND & c->event_loop_info))); - const bool has_sock_err = + has_sock_err = (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk.ready)); - bool data_processed; - data_processed = false; if (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) { bool use_recv; - use_recv = (0 != (mhd_SOCKET_NET_STATE_RECV_READY & c->sk.ready)); + use_recv = (0 != (mhd_SOCKET_NET_STATE_RECV_READY + & (c->sk.ready | mhd_C_HAS_TLS_DATA_IN (c)))); use_recv = use_recv || (has_sock_err && c->sk.props.is_nonblck); @@ -85,7 +110,7 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) * + connection is ready for sending or * + just formed send data, connection send ready status is not known and * connection socket is non-blocking - * + detected network error on the connection, to check to the error */ + * + detected network error on the connection, to check for the error */ /* Assuming that after finishing receiving phase, connection send system buffers should have some space as sending was performed before receiving or has not been performed yet. */ @@ -107,80 +132,4 @@ mhd_conn_process_recv_send_data (struct MHD_Connection *restrict c) if (! data_processed) return mhd_conn_process_data (c); return true; - -#if 0 // TODO: re-implement fasttrack as a single unified buffer sending - if (! force_close) - { - /* No need to check value of 'ret' here as closed connection - * cannot be in MHD_EVENT_LOOP_INFO_SEND state. */ - if ( (MHD_EVENT_LOOP_INFO_SEND == c->event_loop_info) && - write_ready) - { - MHD_connection_handle_write (c); - ret = MHD_connection_handle_idle (c); - states_info_processed = true; - } - } - else - { - MHD_connection_close_ (c, - MHD_REQUEST_TERMINATED_WITH_ERROR); - return MHD_connection_handle_idle (c); - } - - if (! states_info_processed) - { /* Connection is not read or write ready, but external conditions - * may be changed and need to be processed. */ - ret = MHD_connection_handle_idle (c); - } - /* Fast track for fast connections. */ - /* If full request was read by single read_handler() invocation - and headers were completely prepared by single MHD_connection_handle_idle() - then try not to wait for next sockets polling and send response - immediately. - As writeability of socket was not checked and it may have - some data pending in system buffers, use this optimization - only for non-blocking sockets. */ - /* No need to check 'ret' as connection is always in - * mhd_HTTP_STAGE_CLOSED state if 'ret' is equal 'MHD_NO'. */ - else if (on_fasttrack && c->sk.props.is_nonblck) - { - if (mhd_HTTP_STAGE_HEADERS_SENDING == c->stage) - { - MHD_connection_handle_write (c); - /* Always call 'MHD_connection_handle_idle()' after each read/write. */ - ret = MHD_connection_handle_idle (c); - } - /* If all headers were sent by single write_handler() and - * response body is prepared by single MHD_connection_handle_idle() - * call - continue. */ - if ((mhd_HTTP_STAGE_UNCHUNKED_BODY_READY == c->stage) || - (mhd_HTTP_STAGE_CHUNKED_BODY_READY == c->stage)) - { - MHD_connection_handle_write (c); - ret = MHD_connection_handle_idle (c); - } - } - - /* All connection's data and states are processed for this turn. - * If connection already has more data to be processed - use - * zero timeout for next select()/poll(). */ - /* Thread-per-connection do not need global zero timeout as - * connections are processed individually. */ - /* Note: no need to check for read buffer availability for - * TLS read-ready connection in 'read info' state as connection - * without space in read buffer will be marked as 'info block'. */ - if ( (! c->daemon->data_already_pending) && - (! mhd_D_HAS_THR_PER_CONN (c->daemon)) ) - { - if (0 != (MHD_EVENT_LOOP_INFO_PROCESS & c->event_loop_info)) - c->daemon->data_already_pending = true; -#ifdef HTTPS_SUPPORT - else if ( (c->tls_read_ready) && - (0 != (MHD_EVENT_LOOP_INFO_RECV & c->event_loop_info)) ) - c->daemon->data_already_pending = true; -#endif /* HTTPS_SUPPORT */ - } - return ret; -#endif } diff --git a/src/mhd2/conn_data_recv.c b/src/mhd2/conn_data_recv.c index a9736cbd..eaa80f40 100644 --- a/src/mhd2/conn_data_recv.c +++ b/src/mhd2/conn_data_recv.c @@ -57,8 +57,6 @@ mhd_conn_data_recv (struct MHD_Connection *restrict c, mhd_assert ((0 == (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) || \ has_err); - // TODO: TLS support: handshake/transport layer - buf = c->read_buffer + c->read_buffer_offset; buf_size = c->read_buffer_size - c->read_buffer_offset; diff --git a/src/mhd2/conn_data_send.c b/src/mhd2/conn_data_send.c index 62cad439..7b122131 100644 --- a/src/mhd2/conn_data_send.c +++ b/src/mhd2/conn_data_send.c @@ -83,10 +83,6 @@ mhd_conn_data_send (struct MHD_Connection *restrict c) // TODO: assert check suspended -#ifdef HTTPS_SUPPORT - // TODO: TLS support, handshake -#endif /* HTTPS_SUPPORT */ - // TODO: MOVE out STATES PROCESSING res = mhd_SOCKET_ERR_INTERNAL; diff --git a/src/mhd2/conn_mark_ready.h b/src/mhd2/conn_mark_ready.h index 8daa8ae8..b3211d17 100644 --- a/src/mhd2/conn_mark_ready.h +++ b/src/mhd2/conn_mark_ready.h @@ -108,7 +108,8 @@ mhd_conn_mark_ready_update3 (struct MHD_Connection *restrict c, { if (force_ready || (0 != - (((unsigned int) c->sk.ready) & ((unsigned int) c->event_loop_info) + ((((unsigned int) c->sk.ready) | mhd_C_HAS_TLS_DATA_IN (c)) + & ((unsigned int) c->event_loop_info) & (MHD_EVENT_LOOP_INFO_RECV | MHD_EVENT_LOOP_INFO_SEND)))) mhd_conn_mark_ready (c, d); else diff --git a/src/mhd2/conn_tls_check.c b/src/mhd2/conn_tls_check.c new file mode 100644 index 00000000..f8e78f61 --- /dev/null +++ b/src/mhd2/conn_tls_check.c @@ -0,0 +1,130 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/conn_tls_handshake.c + * @brief The implementation of connection TLS handling functions + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" + +#include "conn_tls_check.h" + +#include "mhd_assert.h" + +#include "mhd_daemon.h" +#include "mhd_connection.h" + +#include "mhd_socket_error_funcs.h" +#include "daemon_logger.h" + +#include "mhd_tls_funcs.h" + +#include "conn_mark_ready.h" +#include "stream_funcs.h" +#include "stream_process_states.h" + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ enum mhd_ConnTlsCheckResult +mhd_conn_tls_check (struct MHD_Connection *restrict c) +{ + mhd_assert (mhd_C_HAS_TLS (c)); + mhd_assert (mhd_D_HAS_TLS (c->daemon)); + mhd_assert ((mhd_CONN_STATE_TLS_HANDSHAKE_RECV == c->conn_state) || \ + (mhd_CONN_STATE_TLS_HANDSHAKE_SEND == c->conn_state) || \ + (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state)); + + if (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state) + return mhd_CONN_TLS_CHECK_OK; /* TLS is already connected */ + + if (0 != (mhd_SOCKET_NET_STATE_ERROR_READY & c->sk.ready)) + { + /* Some socket error has been detected. Do not try to handshake. */ + if (mhd_SOCKET_ERR_NO_ERROR == c->sk.state.discnt_err) + c->sk.state.discnt_err = mhd_socket_error_get_from_socket (c->sk.fd); + mhd_conn_start_closing_skt_err (c); + return mhd_CONN_TLS_CHECK_BROKEN; + } + /* Check whether the socket is ready for the required send/recv operation */ + if (0 == (((mhd_CONN_FLAG_RECV | mhd_CONN_FLAG_SEND) + & ((unsigned int) c->conn_state) + & ((unsigned int) c->sk.ready)))) + return mhd_CONN_TLS_CHECK_HANDSHAKING; + + switch (mhd_tls_conn_handshake (c->tls)) + { + case mhd_TLS_PROCED_SUCCESS: + c->conn_state = mhd_CONN_STATE_TLS_CONNECTED; + if (! c->sk.props.is_nonblck) + { + /* The socket is blocking, + probably all available data has been processed already. */ + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' and 'send-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); + } + /* TLS is connected now, set event loop state based on HTTP protocol. + Some early application-level data could be processing in this round. */ + mhd_conn_event_loop_state_update (c); + + return mhd_CONN_TLS_CHECK_OK; + break; + case mhd_TLS_PROCED_RECV_MORE_NEEDED: + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); + /* Intentional fall-through */ + case mhd_TLS_PROCED_RECV_INTERRUPTED: + c->conn_state = mhd_CONN_STATE_TLS_HANDSHAKE_RECV; + c->event_loop_info = MHD_EVENT_LOOP_INFO_RECV; + break; + case mhd_TLS_PROCED_SEND_MORE_NEEDED: + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + /* Intentional fall-through */ + case mhd_TLS_PROCED_SEND_INTERRUPTED: + c->conn_state = mhd_CONN_STATE_TLS_HANDSHAKE_SEND; + c->event_loop_info = MHD_EVENT_LOOP_INFO_SEND; + break; + case mhd_TLS_PROCED_FAILED: + c->conn_state = mhd_CONN_STATE_TLS_FAILED; + mhd_LOG_MSG (c->daemon, \ + MHD_SC_TLS_CONNECTION_HANDSHAKED_FAILED, \ + "Failed to perform TLS handshake on the new connection"); + c->sk.state.discnt_err = mhd_SOCKET_ERR_TLS; + mhd_conn_start_closing_skt_err (c); + return mhd_CONN_TLS_CHECK_BROKEN; + break; + default: + mhd_assert (0 && "Should be unreachable"); + MHD_UNREACHABLE_; + return mhd_CONN_TLS_CHECK_BROKEN; + } + + mhd_conn_mark_ready_update (c); + return mhd_CONN_TLS_CHECK_HANDSHAKING; +} diff --git a/src/mhd2/conn_tls_check.h b/src/mhd2/conn_tls_check.h new file mode 100644 index 00000000..7c9cc1e8 --- /dev/null +++ b/src/mhd2/conn_tls_check.h @@ -0,0 +1,71 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/conn_tls_check.h + * @brief The declarations of connection TLS handling functions + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_CONN_TLS_CHECK_H +#define MHD_CONN_TLS_CHECK_H 1 + +#include "mhd_sys_options.h" + +#include "sys_bool_type.h" + +struct MHD_Connection; /* forward declaration */ + +/** + * The results of connection TLS checking + */ +enum mhd_ConnTlsCheckResult +{ + /** + * The TLS layer is connected, the communication over TLS can be performed + */ + mhd_CONN_TLS_CHECK_OK = 0 + , + /** + * The TLS layer connection is in progress. + * Communication over TLS is not possible yet. + */ + mhd_CONN_TLS_CHECK_HANDSHAKING + , + /** + * The connection is broken and must be closed + */ + mhd_CONN_TLS_CHECK_BROKEN +}; + + +/** + * Check connection TLS status, perform TLS (re-)handshake if necessary, + * update connection's recv()/send() event loop state and connection active + * state if network operation has been performed. + * @param c the connection to process + * @return #mhd_CONN_TLS_CHECK_OK if the connection can be used, + * other enum mhd_ConnTlsCheckResult values otherwise + */ +MHD_INTERNAL enum mhd_ConnTlsCheckResult +mhd_conn_tls_check (struct MHD_Connection *restrict c) +MHD_FN_PAR_NONNULL_ALL_; + +#endif /* ! MHD_CONN_TLS_CHECK_H */ diff --git a/src/mhd2/daemon_add_conn.c b/src/mhd2/daemon_add_conn.c index c2ff2f90..6635fd5f 100644 --- a/src/mhd2/daemon_add_conn.c +++ b/src/mhd2/daemon_add_conn.c @@ -249,10 +249,13 @@ new_connection_prepare_ (struct MHD_Daemon *restrict daemon, "the new connection"); ret = MHD_SC_TLS_CONNECTION_INIT_FAILED; } -#ifndef NDEBUG else + { + c->conn_state = mhd_CONN_STATE_TLS_HANDSHAKE_RECV; +#ifndef NDEBUG c->dbg.tls_inited = true; #endif + } } if (MHD_SC_OK == ret) diff --git a/src/mhd2/mhd_connection.h b/src/mhd2/mhd_connection.h index 1f122db9..44a14b51 100644 --- a/src/mhd2/mhd_connection.h +++ b/src/mhd2/mhd_connection.h @@ -79,6 +79,106 @@ struct MHD_Connection; /* forward declaration */ +#define mhd_CONN_FLAG_RECV (1u << 0) +#define mhd_CONN_FLAG_SEND (1u << 1) +#define mhd_CONN_FLAG_TLS (1u << 2) +#define mhd_CONN_FLAG_HANDSHAKE (1u << 3) +#define mhd_CONN_FLAG_CLOSING (1u << 4) +#define mhd_CONN_FLAG_ERROR (1u << 6) +#define mhd_CONN_FLAG_CLOSED (1u << 7) + +/** + * The states of connection TLS layer + * The bits (1 << 0) | (1 << 1) in enum values match the same bits in + * enum MHD_ConnectionEventLoopInfo and in enum mhd_SocketNetState values + */ +enum MHD_FIXED_ENUM_ mhd_ConnState +{ + /** + * TLS not started / plain TCP communication + */ + mhd_CONN_STATE_TCP_CONNECTED = 0 + , + /** + * TLS handshake in progress, need to receive the data + */ + mhd_CONN_STATE_TLS_HANDSHAKE_RECV = + mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_HANDSHAKE | mhd_CONN_FLAG_RECV + , + /** + * TLS handshake in progress, need to send the data + */ + mhd_CONN_STATE_TLS_HANDSHAKE_SEND = + mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_HANDSHAKE | mhd_CONN_FLAG_SEND + , + /** + * TLS connection established, HTTP communication is performing + */ + mhd_CONN_STATE_TLS_CONNECTED = mhd_CONN_FLAG_TLS + , + /** + * Sending TLS message for shutting down TLS communication on the MHD side + */ + mhd_CONN_STATE_TLS_SHUT_WR_SENDING = + mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_CLOSING | mhd_CONN_FLAG_SEND + , + /** + * Waiting to receive message from remote for shutting down TLS communication + */ + mhd_CONN_STATE_TLS_LINGERING = + mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_CLOSING | mhd_CONN_FLAG_RECV + , + /** + * TLS communication gracefully closed. + * This state should be avoided. Use #mhd_CONN_STATE_TCP_CONNECTED or + * #mhd_CONN_STATE_CLOSED. + */ + mhd_CONN_STATE_TLS_CLOSED = mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_CLOSED + , + /** + * TLS communication broken + */ + mhd_CONN_STATE_TLS_FAILED = mhd_CONN_FLAG_TLS | mhd_CONN_FLAG_ERROR +#if 0 // TODO: Extend to TCP states + , + /** + * Setting TCP shutdown WR + */ + mhd_CONN_STATE_TCP_SHUT_WR_SENDING = + mhd_CONN_FLAG_CLOSING | mhd_CONN_FLAG_SEND + , + /** + * Waiting for EOF from the remote side + */ + mhd_CONN_STATE_TCP_LINGERING = + mhd_CONN_FLAG_CLOSING | mhd_CONN_FLAG_RECV +#endif + , + /** + * TCP communication closed + */ + mhd_CONN_STATE_CLOSED = mhd_CONN_FLAG_CLOSED +}; + +#ifdef MHD_ENABLE_HTTPS +/** + * The status of TLS buffer for incoming (receive) data + */ +enum mhd_TlsBufDataIn +{ + /** + * No data in pending in the TLS buffer + */ + mhd_TLS_BUF_NO_DATA = 0 + , + /** + * The incoming data in already pending in the TLS buffer + */ + mhd_TLS_BUF_HAS_DATA_IN = mhd_SOCKET_NET_STATE_RECV_READY +}; +#endif /* MHD_ENABLE_HTTPS */ + + /** * What is this connection waiting for? */ @@ -413,7 +513,17 @@ struct MHD_Connection * deallocated separately. */ struct mhd_TlsConnData *tls; -#endif + + /** + * The state of the communication layer + */ + enum mhd_ConnState conn_state; + + /** + * Status of TLS buffer for the incoming data + */ + enum mhd_TlsBufDataIn tls_has_data_in; +#endif /* MHD_ENABLE_HTTPS */ /** * 'true' if connection is in 'process ready' list, @@ -616,11 +726,27 @@ struct MHD_Connection * Returns non-zero if connection has TLS enabled or zero otherwise */ # define mhd_C_HAS_TLS(c) (((c)->tls) ? (! 0) : (0)) -#else +#else /* ! MHD_ENABLE_HTTPS */ /** * Returns non-zero if connection has TLS enabled or zero otherwise */ # define mhd_C_HAS_TLS(c) (0) -#endif +#endif /* ! MHD_ENABLE_HTTPS */ + + +#ifdef MHD_ENABLE_HTTPS +/** + * Returns #mhd_SOCKET_NET_STATE_RECV_READY if connection has incoming data + * pending in TLS buffers + */ +# define mhd_C_HAS_TLS_DATA_IN(c) \ + (((c)->tls) ? ((unsigned int) ((c)->tls_has_data_in)) : (0u)) +#else /* ! MHD_ENABLE_HTTPS */ +/** + * Returns #mhd_SOCKET_NET_STATE_RECV_READY if connection has incoming data + * pending in TLS buffers + */ +# define mhd_C_HAS_TLS_DATA_IN(c) (0) +#endif /* ! MHD_ENABLE_HTTPS */ #endif /* ! MHD_CONNECTION_H */ diff --git a/src/mhd2/mhd_recv.c b/src/mhd2/mhd_recv.c index f4e9c0a9..3f775d13 100644 --- a/src/mhd2/mhd_recv.c +++ b/src/mhd2/mhd_recv.c @@ -33,14 +33,19 @@ #include "mhd_socket_type.h" #include "sys_sockets_headers.h" #include "mhd_sockets_macros.h" -#include "mhd_socket_error_funcs.h" #include "mhd_limits.h" +#include "mhd_assert.h" +#include "mhd_socket_error_funcs.h" + +#ifdef MHD_ENABLE_HTTPS +# include "mhd_tls_funcs.h" +#endif static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError -mhd_plain_recv (struct MHD_Connection *restrict c, +mhd_recv_plain (struct MHD_Connection *restrict c, size_t buf_size, char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], size_t *restrict received) @@ -76,6 +81,43 @@ mhd_plain_recv (struct MHD_Connection *restrict c, } +#ifdef MHD_ENABLE_HTTPS + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_recv_tls (struct MHD_Connection *restrict c, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict received) +{ + /* TLS connection */ + enum mhd_SocketError res; + + mhd_assert (mhd_C_HAS_TLS (c)); + mhd_assert (0 != buf_size); + + res = mhd_tls_conn_recv (c->tls, + buf_size, + buf, + received); + c->tls_has_data_in = mhd_TLS_BUF_NO_DATA; /* Updated with the actual value below */ + + if (mhd_SOCKET_ERR_AGAIN == res) + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'recv-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_RECV_READY)); + else if ((mhd_SOCKET_ERR_NO_ERROR == res) && + (*received == buf_size) && + mhd_tls_conn_has_data_in (c->tls)) + c->tls_has_data_in = mhd_TLS_BUF_HAS_DATA_IN; + + return res; +} + + +#endif /* MHD_ENABLE_HTTPS */ + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4) enum mhd_SocketError mhd_recv (struct MHD_Connection *restrict c, @@ -86,7 +128,13 @@ mhd_recv (struct MHD_Connection *restrict c, mhd_assert (MHD_INVALID_SOCKET != c->sk.fd); mhd_assert (mhd_HTTP_STAGE_CLOSED != c->stage); - // TODO: implement TLS support +#ifdef MHD_ENABLE_HTTPS + if (mhd_C_HAS_TLS (c)) + return mhd_recv_tls (c, + buf_size, + buf, + received); +#endif /* MHD_ENABLE_HTTPS */ - return mhd_plain_recv (c, buf_size, buf, received); + return mhd_recv_plain (c, buf_size, buf, received); } diff --git a/src/mhd2/mhd_send.c b/src/mhd2/mhd_send.c index 3a7010fb..001463c3 100644 --- a/src/mhd2/mhd_send.c +++ b/src/mhd2/mhd_send.c @@ -71,6 +71,10 @@ #include "mhd_limits.h" +#ifdef MHD_ENABLE_HTTPS +# include "mhd_tls_funcs.h" +#endif + #if defined(HAVE_SENDMSG) || defined(HAVE_WRITEV) || \ defined(MHD_SOCKETS_KIND_WINSOCK) # define mhd_USE_VECT_SEND 1 @@ -751,7 +755,7 @@ post_send_setopt (struct MHD_Connection *connection, static MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (5) enum mhd_SocketError -mhd_plain_send (struct MHD_Connection *restrict c, +mhd_send_plain (struct MHD_Connection *restrict c, size_t buf_size, const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], bool push_data, @@ -761,6 +765,9 @@ mhd_plain_send (struct MHD_Connection *restrict c, ssize_t res; bool full_buf_sent; + mhd_assert (! mhd_C_HAS_TLS (c)); + mhd_assert (! mhd_D_HAS_TLS (c->daemon)); + if (buf_size > MHD_SCKT_SEND_MAX_SIZE_) { buf_size = MHD_SCKT_SEND_MAX_SIZE_; /* send() return value limit */ @@ -816,6 +823,52 @@ mhd_plain_send (struct MHD_Connection *restrict c, } +#ifdef MHD_ENABLE_HTTPS + +static MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (5) enum mhd_SocketError +mhd_send_tls (struct MHD_Connection *restrict c, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + bool push_data, + size_t *restrict sent) +{ + /* TLS connection */ + enum mhd_SocketError res; + + mhd_assert (mhd_C_HAS_TLS (c)); + mhd_assert (mhd_D_HAS_TLS (c->daemon)); + mhd_assert (0 != buf_size); + + pre_send_setopt (c, false, push_data); + + res = mhd_tls_conn_send (c->tls, + buf_size, + buf, + sent); + + if (mhd_SOCKET_ERR_NO_ERROR != res) + { + if (mhd_SOCKET_ERR_AGAIN == res) + c->sk.ready = (enum mhd_SocketNetState) /* Clear 'send-ready' */ + (((unsigned int) c->sk.ready) + & (~(enum mhd_SocketNetState) + mhd_SOCKET_NET_STATE_SEND_READY)); + return res; + } + + /* If there is a need to push the data from network buffers + * call post_send_setopt(). */ + if (push_data && (buf_size == *sent)) + post_send_setopt (c, false, true); + + return mhd_SOCKET_ERR_NO_ERROR; +} + + +#endif /* MHD_ENABLE_HTTPS */ + MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (5) enum mhd_SocketError @@ -825,28 +878,19 @@ mhd_send_data (struct MHD_Connection *restrict connection, bool push_data, size_t *restrict sent) { - const bool tls_conn = false; // TODO: TLS support - mhd_assert (MHD_INVALID_SOCKET != connection->sk.fd); mhd_assert (mhd_HTTP_STAGE_CLOSED != connection->stage); - if (tls_conn) - { - enum mhd_SocketError ret; +#ifdef MHD_ENABLE_HTTPS + if (mhd_C_HAS_TLS (connection)) + return mhd_send_tls (connection, + buf_size, + buf, + push_data, + sent); +#endif /* MHD_ENABLE_HTTPS */ -#ifdef HTTPS_SUPPORT - pre_send_setopt (connection, - (! tls_conn), - push_data); - ret = mhd_SOCKET_ERR_OTHER; - mhd_assert (0 && "Not implemented yet"); -#else /* ! HTTPS_SUPPORT */ - ret = mhd_SOCKET_ERR_NOTCONN; -#endif /* ! HTTPS_SUPPORT */ - return ret; - } - - return mhd_plain_send (connection, + return mhd_send_plain (connection, buf_size, buf, push_data, @@ -885,9 +929,7 @@ mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, bool no_vec; /* Is vector-send() disallowed? */ no_vec = false; -#ifdef HTTPS_SUPPORT - no_vec = no_vec || (false); // TODO: TLS support -#endif /* HTTPS_SUPPORT */ + no_vec = no_vec || (mhd_C_HAS_TLS (connection)); #if (! defined(HAVE_SENDMSG) || ! defined(MSG_NOSIGNAL) ) && \ defined(mhd_SEND_SPIPE_SUPPRESS_POSSIBLE) && \ defined(mhd_SEND_SPIPE_SUPPRESS_NEEDED) @@ -947,7 +989,6 @@ mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, push_hdr, sent); - // TODO: check 'send-ready' if ((mhd_SOCKET_ERR_NO_ERROR == ret) && (header_size == *sent) && (0 != body_size) && @@ -976,6 +1017,8 @@ mhd_send_hdr_and_body (struct MHD_Connection *restrict connection, } #ifdef mhd_USE_VECT_SEND + mhd_assert (! mhd_C_HAS_TLS (connection)); + if (header_size > (header_size + body_size)) { /* Return value limit */ @@ -1133,7 +1176,7 @@ mhd_send_sendfile (struct MHD_Connection *restrict c, mhd_assert (mhd_REPLY_CNTN_LOC_FILE == c->rp.cntn_loc); mhd_assert (MHD_SIZE_UNKNOWN != c->rp.response->cntn_size); mhd_assert (chunk_size <= (size_t) SSIZE_MAX); - // mhd_assert (0 == (connection->daemon->options & MHD_USE_TLS)); // TODO: TLS support + mhd_assert (! mhd_C_HAS_TLS (c)); send_size = 0; push_data = true; @@ -1373,7 +1416,8 @@ send_iov_nontls (struct MHD_Connection *restrict connection, DWORD cnt_w; #endif /* MHD_SOCKETS_KIND_WINSOCK */ - // TODO: assert for non-TLS + mhd_assert (! mhd_C_HAS_TLS (connection)); + mhd_assert (! mhd_D_HAS_TLS (connection->daemon)); mhd_assert (MHD_INVALID_SOCKET != connection->sk.fd); mhd_assert (mhd_HTTP_STAGE_CLOSED != connection->stage); @@ -1492,7 +1536,7 @@ send_iov_nontls (struct MHD_Connection *restrict connection, #endif /* mhd_USE_VECT_SEND */ -#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ +#if ! defined(mhd_USE_VECT_SEND) || defined(MHD_ENABLE_HTTPS) || \ defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) @@ -1574,7 +1618,7 @@ send_iov_emu (struct MHD_Connection *restrict connection, } -#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT +#endif /* !mhd_USE_VECT_SEND || MHD_ENABLE_HTTPS || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ @@ -1585,10 +1629,10 @@ mhd_send_iovec (struct MHD_Connection *restrict connection, size_t *restrict sent) { #ifdef mhd_USE_VECT_SEND -#if defined(HTTPS_SUPPORT) || \ +#if defined(MHD_ENABLE_HTTPS) || \ defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) bool use_iov_send = true; -#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* MHD_ENABLE_HTTPS || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ #endif /* mhd_USE_VECT_SEND */ mhd_assert (NULL != connection->rp.resp_iov.iov); @@ -1596,24 +1640,22 @@ mhd_send_iovec (struct MHD_Connection *restrict connection, connection->rp.response->cntn_dtype); mhd_assert (connection->rp.resp_iov.cnt > connection->rp.resp_iov.sent); #ifdef mhd_USE_VECT_SEND -#if defined(HTTPS_SUPPORT) || \ +#if defined(MHD_ENABLE_HTTPS) || \ defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) -#ifdef HTTPS_SUPPORT use_iov_send = use_iov_send && - (true); // TODO: TLS support -#endif /* HTTPS_SUPPORT */ + (! mhd_C_HAS_TLS (connection)); #ifdef mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED use_iov_send = use_iov_send && (connection->daemon->sigpipe_blocked || connection->sk.props.has_spipe_supp); #endif /* mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ if (use_iov_send) -#endif /* HTTPS_SUPPORT || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ +#endif /* MHD_ENABLE_HTTPS || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ return send_iov_nontls (connection, r_iov, push_data, sent); #endif /* mhd_USE_VECT_SEND */ -#if ! defined(mhd_USE_VECT_SEND) || defined(HTTPS_SUPPORT) || \ +#if ! defined(mhd_USE_VECT_SEND) || defined(MHD_ENABLE_HTTPS) || \ defined(mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED) return send_iov_emu (connection, r_iov, push_data, sent); -#endif /* !mhd_USE_VECT_SEND || HTTPS_SUPPORT +#endif /* !mhd_USE_VECT_SEND || MHD_ENABLE_HTTPS || mhd_VECT_SEND_NEEDS_SPIPE_SUPPRESSED */ } diff --git a/src/mhd2/mhd_tls_enums.h b/src/mhd2/mhd_tls_enums.h new file mode 100644 index 00000000..6328917c --- /dev/null +++ b/src/mhd2/mhd_tls_enums.h @@ -0,0 +1,72 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2024 Evgeny Grin (Karlson2k) + + GNU libmicrohttpd 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. + + GNU libmicrohttpd 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 src/mhd2/mhd_tls_enums.h + * @brief The definition of internal enums used for TLS communication + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHD_TLS_ENUMS_H +#define MHD_TLS_ENUMS_H 1 + +#include "mhd_sys_options.h" + +#ifndef MHD_ENABLE_HTTPS +#error This header should be used only if HTTPS is enabled +#endif + +/** + * Result of performing TLS procedure + */ +enum MHD_FIXED_ENUM_ mhd_TlsProcedureResult +{ + /** + * Completed successfully + */ + mhd_TLS_PROCED_SUCCESS = 0 + , + /** + * In progress, receive operation interrupted + */ + mhd_TLS_PROCED_RECV_INTERRUPTED + , + /** + * In progress, send operation interrupted + */ + mhd_TLS_PROCED_SEND_INTERRUPTED + , + /** + * In progress, need to receive more data + */ + mhd_TLS_PROCED_RECV_MORE_NEEDED + , + /** + * In progress, need to send more data + */ + mhd_TLS_PROCED_SEND_MORE_NEEDED + , + /** + * Procedure failed + */ + mhd_TLS_PROCED_FAILED +}; + +#endif /* ! MHD_TLS_ENUMS_H */ diff --git a/src/mhd2/mhd_tls_funcs.h b/src/mhd2/mhd_tls_funcs.h index 7967c4f1..3b99c617 100644 --- a/src/mhd2/mhd_tls_funcs.h +++ b/src/mhd2/mhd_tls_funcs.h @@ -116,6 +116,67 @@ mhd_TLS_FUNC (_conn_deinit)((c_tls)) +/* ** TLS connection establishing ** */ + +/** + * Perform TLS handshake + * @param c_tls the connection TLS handle + * @return #mhd_TLS_PROCED_SUCCESS if completed successfully + * or other enum mhd_TlsProcedureResult values + */ +#define mhd_tls_conn_handshake(c_tls) \ + mhd_TLS_FUNC (_conn_handshake)((c_tls)) + +/** + * Perform shutdown of TLS layer + * @param c_tls the connection TLS handle + * @return #mhd_TLS_PROCED_SUCCESS if completed successfully + * or other enum mhd_TlsProcedureResult values + */ +#define mhd_tls_conn_shutdown(c_tls) \ + mhd_TLS_FUNC (_conn_shutdown)((c_tls)) + +/* ** Data sending and receiving over TLS connection ** */ + +/** + * Receive the data from the remote side over TLS connection + * + * @param c_tls the connection TLS handle + * @param buf_size the size of the @a buf buffer + * @param[out] buf the buffer to fill with the received data + * @param[out] received the pointer to variable to get the size of the data + * actually put to the @a buffer + * @return mhd_SOCKET_ERR_NO_ERROR if receive succeed (the @a received gets + * the received size) or socket error + */ +#define mhd_tls_conn_recv(c_tls,buf_size,buf,received) \ + mhd_TLS_FUNC (_conn_recv)((c_tls),(buf_size),(buf),(received)) + +/** + * Check whether any incoming data is pending in the TLS buffers + * + * @param c_tls the connection TLS handle + * @return 'true' if any incoming remote data is already pending (the TLS recv() + * call can be performed), + * 'false' otherwise + */ +#define mhd_tls_conn_has_data_in(c_tls) \ + mhd_TLS_FUNC (_conn_has_data_in)((c_tls)) + +/** + * Send data to the remote side over TLS connection + * + * @param c_tls the connection TLS handle + * @param buffer_size the size of the @a buffer (in bytes) + * @param buffer content of the buffer to send + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +#define mhd_tls_conn_send(c_tls,buf_size,buf,sent) \ + mhd_TLS_FUNC (_conn_send)((c_tls),(buf_size),(buf),(sent)) + + /* ** General information function ** */ /** diff --git a/src/mhd2/stream_funcs.c b/src/mhd2/stream_funcs.c index e7826399..88081235 100644 --- a/src/mhd2/stream_funcs.c +++ b/src/mhd2/stream_funcs.c @@ -52,6 +52,10 @@ #include "conn_mark_ready.h" #include "stream_process_reply.h" +#ifdef MHD_ENABLE_HTTPS +# include "mhd_tls_funcs.h" +#endif + #include "mhd_public_api.h" @@ -822,7 +826,28 @@ mhd_conn_start_closing (struct MHD_Connection *restrict c, } else { - if (mhd_socket_shut_wr (c->sk.fd) && (! c->sk.state.rmt_shut_wr)) + bool use_graceful_closing; + + mhd_assert (! mhd_SOCKET_ERR_IS_HARD (c->sk.state.discnt_err)); + + use_graceful_closing = true; +#ifdef MHD_ENABLE_HTTPS + if (mhd_C_HAS_TLS (c)) + { + if ((0 != (((unsigned int) c->sk.ready) + & mhd_SOCKET_NET_STATE_SEND_READY)) + || c->sk.props.is_nonblck) + use_graceful_closing = + (mhd_TLS_PROCED_FAILED != mhd_tls_conn_shutdown (c->tls)); + } +#endif /* MHD_ENABLE_HTTPS */ + else if (1) + { + use_graceful_closing = mhd_socket_shut_wr (c->sk.fd); + if (use_graceful_closing) + use_graceful_closing = (! c->sk.state.rmt_shut_wr); /* Skip as already closed */ + } + if (use_graceful_closing) { (void) 0; // TODO: start local lingering phase c->stage = mhd_HTTP_STAGE_PRE_CLOSING; // TODO: start local lingering phase diff --git a/src/mhd2/stream_process_reply.c b/src/mhd2/stream_process_reply.c index 4898720d..949d54b9 100644 --- a/src/mhd2/stream_process_reply.c +++ b/src/mhd2/stream_process_reply.c @@ -302,13 +302,11 @@ setup_reply_properties (struct MHD_Connection *restrict c) c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_IOV; break; case mhd_RESPONSE_CONTENT_DATA_FILE: -#if 0 // TODO: TLS support - if (use_tls) + if (mhd_C_HAS_TLS (c)) { c->rp.cntn_loc = mhd_REPLY_CNTN_LOC_CONN_BUF; break; } -#endif #ifdef MHD_USE_SENDFILE if (r->cntn.file.use_sf) { diff --git a/src/mhd2/stream_process_states.c b/src/mhd2/stream_process_states.c index 84ed66ab..8ac0dd26 100644 --- a/src/mhd2/stream_process_states.c +++ b/src/mhd2/stream_process_states.c @@ -55,24 +55,13 @@ MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void mhd_conn_event_loop_state_update (struct MHD_Connection *restrict c) { - /* Do not update states of suspended connection */ - mhd_assert (! c->suspended); +#ifdef MHD_ENABLE_HTTPS + mhd_assert (! mhd_C_HAS_TLS (c) || \ + (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state)); + mhd_assert (mhd_C_HAS_TLS (c) || \ + (mhd_CONN_STATE_TCP_CONNECTED == c->conn_state)); +#endif /* MHD_ENABLE_HTTPS */ - if (0 != (c->sk.ready & mhd_SOCKET_NET_STATE_ERROR_READY)) - { - mhd_assert (0 && "Should be handled earlier"); - mhd_conn_start_closing_skt_err (c); - return false; - } - -#if 0 // def HTTPS_SUPPORT // TODO: implement TLS support - if (MHD_TLS_CONN_NO_TLS != connection->tls_state) - { /* HTTPS connection. */ - switch (connection->tls_state) - { - } - } -#endif /* HTTPS_SUPPORT */ switch (c->stage) { case mhd_HTTP_STAGE_INIT: @@ -288,9 +277,12 @@ mhd_conn_process_data (struct MHD_Connection *restrict c) while (! c->suspended) { -#ifdef HTTPS_SUPPORT - // TODO: support TLS, handshake -#endif /* HTTPS_SUPPORT */ +#ifdef MHD_ENABLE_HTTPS + mhd_assert (! mhd_C_HAS_TLS (c) || \ + (mhd_CONN_STATE_TLS_CONNECTED == c->conn_state)); + mhd_assert (mhd_C_HAS_TLS (c) || \ + (mhd_CONN_STATE_TCP_CONNECTED == c->conn_state)); +#endif /* MHD_ENABLE_HTTPS */ switch (c->stage) { case mhd_HTTP_STAGE_INIT: diff --git a/src/mhd2/tls_gnu_conn_data.h b/src/mhd2/tls_gnu_conn_data.h index 7f3145de..eb92cfc5 100644 --- a/src/mhd2/tls_gnu_conn_data.h +++ b/src/mhd2/tls_gnu_conn_data.h @@ -35,6 +35,16 @@ #include "tls_gnu_tls_lib.h" +#ifndef NDEBUG +struct mhd_TlsGnuConnDebug +{ + unsigned int is_inited; + unsigned int is_tls_handshake_completed; + unsigned int is_finished; + unsigned int is_failed; +}; +#endif /* ! NDEBUG */ + /** * The structure with connection-specific GnuTLS data */ @@ -44,6 +54,17 @@ struct mhd_TlsGnuConnData * GnuTLS session data */ gnutls_session_t sess; + + /** + * 'true' if received EOF (the remote side initiated shutting down) + */ + bool rmt_shut_tls_wr; +#ifndef NDEBUG + /** + * Debugging data + */ + struct mhd_TlsGnuConnDebug dbg; +#endif /* ! NDEBUG */ }; #endif /* ! MHD_TLS_GNU_CONN_DATA_H */ diff --git a/src/mhd2/tls_gnu_funcs.c b/src/mhd2/tls_gnu_funcs.c index d3f75d33..e62c9beb 100644 --- a/src/mhd2/tls_gnu_funcs.c +++ b/src/mhd2/tls_gnu_funcs.c @@ -312,7 +312,7 @@ static const struct MHD_StringNullable tlsgnulib_base_priorities[] = { {0, NULL} /* Replaced with app-defined name */ , #ifdef mhd_TLS_GNU_SUPPORTS_MULTI_KEYWORDS_PRIORITY - mhd_MSTR_INIT ("@LIBMICROHTTPD,@SYSTEM") + mhd_MSTR_INIT ("@LIBMICROHTTPD,SYSTEM") #else mhd_MSTR_INIT ("@LIBMICROHTTPD") , @@ -539,6 +539,9 @@ mhd_tls_gnu_conn_init (const struct mhd_TlsGnuDaemonData *restrict d_tls, (void) alpn_res; /* Ignore any possible ALPN set errors */ } #endif /* mhd_TLS_GNU_HAS_ALPN */ +#ifndef NDEBUG + c_tls->dbg.is_inited = true; +#endif /* ! NDEBUG */ return true; /* Success exit point */ } @@ -559,5 +562,208 @@ MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ void mhd_tls_gnu_conn_deinit (struct mhd_TlsGnuConnData *restrict c_tls) { mhd_assert (NULL != c_tls->sess); + mhd_assert (c_tls->dbg.is_inited); gnutls_deinit (c_tls->sess); } + + +/* ** TLS connection establishing ** */ + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ +enum mhd_TlsProcedureResult +mhd_tls_gnu_conn_handshake (struct mhd_TlsGnuConnData *restrict c_tls) +{ + int res; + + mhd_assert (c_tls->dbg.is_inited); + mhd_assert (! c_tls->dbg.is_tls_handshake_completed); + mhd_assert (! c_tls->dbg.is_failed); + + res = gnutls_handshake (c_tls->sess); + switch (res) + { + case GNUTLS_E_SUCCESS: +#ifndef NDEBUG + c_tls->dbg.is_tls_handshake_completed = true; +#endif /* ! NDEBUG */ + return mhd_TLS_PROCED_SUCCESS; + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: + case GNUTLS_E_WARNING_ALERT_RECEIVED: /* Ignore any warning for now */ + if (1) + { + int is_sending; + + is_sending = gnutls_record_get_direction (c_tls->sess); + if (GNUTLS_E_INTERRUPTED == res) + return is_sending ? + mhd_TLS_PROCED_SEND_INTERRUPTED : + mhd_TLS_PROCED_RECV_INTERRUPTED; + return is_sending ? + mhd_TLS_PROCED_SEND_MORE_NEEDED : + mhd_TLS_PROCED_RECV_MORE_NEEDED; + } + break; + default: + break; + } +#ifndef NDEBUG + c_tls->dbg.is_failed = true; +#endif /* ! NDEBUG */ + return mhd_TLS_PROCED_FAILED; +} + + +MHD_INTERNAL MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_ +enum mhd_TlsProcedureResult +mhd_tls_gnu_conn_shutdown (struct mhd_TlsGnuConnData *restrict c_tls) +{ + int res; + + mhd_assert (c_tls->dbg.is_inited); + mhd_assert (c_tls->dbg.is_tls_handshake_completed); + mhd_assert (! c_tls->dbg.is_failed); + + res = gnutls_bye (c_tls->sess, + c_tls->rmt_shut_tls_wr ? GNUTLS_SHUT_WR : GNUTLS_SHUT_RDWR); + switch (res) + { + case GNUTLS_E_SUCCESS: +#ifndef NDEBUG + c_tls->dbg.is_finished = true; +#endif /* ! NDEBUG */ + return mhd_TLS_PROCED_SUCCESS; + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_AGAIN: + case GNUTLS_E_WARNING_ALERT_RECEIVED: /* Ignore any warning for now */ + if (1) + { + int is_sending; + + is_sending = gnutls_record_get_direction (c_tls->sess); + if (GNUTLS_E_INTERRUPTED == res) + return is_sending ? + mhd_TLS_PROCED_SEND_INTERRUPTED : + mhd_TLS_PROCED_RECV_INTERRUPTED; + return is_sending ? + mhd_TLS_PROCED_SEND_MORE_NEEDED : + mhd_TLS_PROCED_RECV_MORE_NEEDED; + } + break; + default: + break; + } +#ifndef NDEBUG + c_tls->dbg.is_failed = true; +#endif /* ! NDEBUG */ + return mhd_TLS_PROCED_FAILED; +} + + +/* ** Data receiving and sending ** */ + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_OUT_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_tls_gnu_conn_recv (struct mhd_TlsGnuConnData *restrict c_tls, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict received) +{ + ssize_t res; + + mhd_assert (c_tls->dbg.is_inited); + mhd_assert (c_tls->dbg.is_tls_handshake_completed); + mhd_assert (! c_tls->dbg.is_failed); + + /* Check for GnuTLS return value limitation */ + if (0 > (ssize_t) buf_size) + buf_size = (ssize_t) ((~((size_t) 0u)) >> 1); /* SSIZE_MAX */ + + res = gnutls_record_recv (c_tls->sess, + buf, + buf_size); + if (0 >= res) + { + *received = 0; + switch (res) + { + case 0: /* Not an error */ + c_tls->rmt_shut_tls_wr = true; + return mhd_SOCKET_ERR_NO_ERROR; + case GNUTLS_E_AGAIN: + return mhd_SOCKET_ERR_AGAIN; + case GNUTLS_E_INTERRUPTED: + return mhd_SOCKET_ERR_INTR; + case GNUTLS_E_PREMATURE_TERMINATION: + return mhd_SOCKET_ERR_CONNRESET; + default: + break; + } + /* Treat all other kinds of errors as hard errors */ +#ifndef NDEBUG + c_tls->dbg.is_failed = true; +#endif /* ! NDEBUG */ + return mhd_SOCKET_ERR_TLS; + } + + *received = (size_t) res; + return mhd_SOCKET_ERR_NO_ERROR; +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ bool +mhd_tls_gnu_conn_has_data_in (struct mhd_TlsGnuConnData *restrict c_tls) +{ + return 0 != gnutls_record_check_pending (c_tls->sess); +} + + +MHD_INTERNAL MHD_FN_PAR_NONNULL_ALL_ +MHD_FN_PAR_IN_SIZE_ (3,2) +MHD_FN_PAR_OUT_ (4) enum mhd_SocketError +mhd_tls_gnu_conn_send (struct mhd_TlsGnuConnData *restrict c_tls, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict sent) +{ + ssize_t res; + + mhd_assert (c_tls->dbg.is_inited); + mhd_assert (c_tls->dbg.is_tls_handshake_completed); + mhd_assert (! c_tls->dbg.is_failed); + + /* Check for GnuTLS return value limitation */ + if (0 > (ssize_t) buf_size) + buf_size = (ssize_t) ((~((size_t) 0u)) >> 1); /* SSIZE_MAX */ + + res = gnutls_record_send (c_tls->sess, + buf, + buf_size); + + mhd_assert (0 != res); + + if (0 > res) + { + *sent = 0; + switch (res) + { + case GNUTLS_E_AGAIN: + return mhd_SOCKET_ERR_AGAIN; + case GNUTLS_E_INTERRUPTED: + return mhd_SOCKET_ERR_INTR; + case GNUTLS_E_PREMATURE_TERMINATION: + return mhd_SOCKET_ERR_CONNRESET; + default: + break; + } + /* Treat all other kinds of errors as hard errors */ +#ifndef NDEBUG + c_tls->dbg.is_failed = true; +#endif /* ! NDEBUG */ + return mhd_SOCKET_ERR_TLS; + } + + *sent = (size_t) res; + return mhd_SOCKET_ERR_NO_ERROR; +} diff --git a/src/mhd2/tls_gnu_funcs.h b/src/mhd2/tls_gnu_funcs.h index 954f8c91..3d593dc9 100644 --- a/src/mhd2/tls_gnu_funcs.h +++ b/src/mhd2/tls_gnu_funcs.h @@ -38,6 +38,9 @@ #include "mhd_status_code_int.h" +#include "mhd_tls_enums.h" +#include "mhd_socket_error.h" + /** * The structure with daemon-specific GnuTLS data */ @@ -140,4 +143,77 @@ MHD_INTERNAL void mhd_tls_gnu_conn_deinit (struct mhd_TlsGnuConnData *restrict c_tls) MHD_FN_PAR_NONNULL_ALL_; + +/* ** TLS connection establishing ** */ + +/** + * Perform TLS handshake + * @param c_tls the connection TLS handle + * @return #mhd_TLS_PROCED_SUCCESS if completed successfully + * or other enum mhd_TlsProcedureResult values + */ +MHD_INTERNAL enum mhd_TlsProcedureResult +mhd_tls_gnu_conn_handshake (struct mhd_TlsGnuConnData *restrict c_tls) +MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_; + +/** + * Perform shutdown of TLS layer + * @param c_tls the connection TLS handle + * @return #mhd_TLS_PROCED_SUCCESS if completed successfully + * or other enum mhd_TlsProcedureResult values + */ +MHD_INTERNAL enum mhd_TlsProcedureResult +mhd_tls_gnu_conn_shutdown (struct mhd_TlsGnuConnData *restrict c_tls) +MHD_FN_MUST_CHECK_RESULT_ MHD_FN_PAR_NONNULL_ALL_; + + +/* ** Data sending and receiving over TLS connection ** */ + +/** + * Receive the data from the remote side over TLS connection + * + * @param c_tls the connection TLS handle + * @param buf_size the size of the @a buf buffer + * @param[out] buf the buffer to fill with the received data + * @param[out] received the pointer to variable to get the size of the data + * actually put to the @a buffer + * @return mhd_SOCKET_ERR_NO_ERROR if receive succeed (the @a received gets + * the received size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_tls_gnu_conn_recv (struct mhd_TlsGnuConnData *restrict c_tls, + size_t buf_size, + char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict received) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_OUT_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4); + +/** + * Check whether any incoming data is pending in the TLS buffers + * + * @param c_tls the connection TLS handle + * @return 'true' if any incoming remote data is already pending (the TLS recv() + * call can be performed), + * 'false' otherwise + */ +MHD_INTERNAL bool +mhd_tls_gnu_conn_has_data_in (struct mhd_TlsGnuConnData *restrict c_tls) +MHD_FN_PAR_NONNULL_ALL_; + +/** + * Send data to the remote side over TLS connection + * + * @param c_tls the connection TLS handle + * @param buffer_size the size of the @a buffer (in bytes) + * @param buffer content of the buffer to send + * @param[out] sent the pointer to get amount of actually sent bytes + * @return mhd_SOCKET_ERR_NO_ERROR if send succeed (the @a sent gets + * the sent size) or socket error + */ +MHD_INTERNAL enum mhd_SocketError +mhd_tls_gnu_conn_send (struct mhd_TlsGnuConnData *restrict c_tls, + size_t buf_size, + const char buf[MHD_FN_PAR_DYN_ARR_SIZE_ (buf_size)], + size_t *restrict sent) +MHD_FN_PAR_NONNULL_ALL_ MHD_FN_PAR_IN_SIZE_ (3,2) MHD_FN_PAR_OUT_ (4); + #endif /* ! MHD_TLS_GNU_FUNCS_H */