diff options
Diffstat (limited to 'lib/record.c')
-rw-r--r-- | lib/record.c | 1752 |
1 files changed, 1752 insertions, 0 deletions
diff --git a/lib/record.c b/lib/record.c new file mode 100644 index 0000000000..51683e67cd --- /dev/null +++ b/lib/record.c @@ -0,0 +1,1752 @@ +/* + * Copyright (C) 2000-2013 Free Software Foundation, Inc. + * Copyright (C) 2012,2013 Nikos Mavrogiannopoulos + * + * Author: Nikos Mavrogiannopoulos + * + * This file is part of GnuTLS. + * + * The GnuTLS 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 program. If not, see <http://www.gnu.org/licenses/> + * + */ + +/* Functions that are record layer specific, are included in this file. + */ + +/* allocate this many bytes more when encrypting or decrypting, to + * compensate for broken backends such as cryptodev. + */ +#define CIPHER_SLACK_SIZE 32 + +#include "gnutls_int.h" +#include "errors.h" +#include "debug.h" +#include "compress.h" +#include "cipher.h" +#include "buffers.h" +#include "mbuffers.h" +#include "handshake.h" +#include "hash_int.h" +#include "cipher_int.h" +#include "algorithms.h" +#include "db.h" +#include "auth.h" +#include "num.h" +#include "record.h" +#include "datum.h" +#include "constate.h" +#include "ext/max_record.h" +#include <ext/heartbeat.h> +#include <state.h> +#include <dtls.h> +#include <dh.h> +#include <random.h> + +struct tls_record_st { + uint16_t header_size; + uint8_t version[2]; + uint64 sequence; /* DTLS */ + uint16_t length; + uint16_t packet_size; /* header_size + length */ + content_type_t type; + uint16_t epoch; /* valid in DTLS only */ + unsigned v2:1; /* whether an SSLv2 client hello */ + /* the data */ +}; + +/** + * gnutls_record_disable_padding: + * @session: is a #gnutls_session_t type. + * + * Used to disabled padding in TLS 1.0 and above. Normally you do not + * need to use this function, but there are buggy clients that + * complain if a server pads the encrypted data. This of course will + * disable protection against statistical attacks on the data. + * + * This functions is defunt since 3.1.7. Random padding is disabled + * by default unless requested using gnutls_range_send_message(). + * + **/ +void gnutls_record_disable_padding(gnutls_session_t session) +{ + return; +} + +/** + * gnutls_transport_set_ptr: + * @session: is a #gnutls_session_t type. + * @ptr: is the value. + * + * Used to set the first argument of the transport function (for push + * and pull callbacks). In berkeley style sockets this function will set the + * connection descriptor. + * + **/ +void +gnutls_transport_set_ptr(gnutls_session_t session, + gnutls_transport_ptr_t ptr) +{ + session->internals.transport_recv_ptr = ptr; + session->internals.transport_send_ptr = ptr; +} + +/** + * gnutls_transport_set_ptr2: + * @session: is a #gnutls_session_t type. + * @recv_ptr: is the value for the pull function + * @send_ptr: is the value for the push function + * + * Used to set the first argument of the transport function (for push + * and pull callbacks). In berkeley style sockets this function will set the + * connection descriptor. With this function you can use two different + * pointers for receiving and sending. + **/ +void +gnutls_transport_set_ptr2(gnutls_session_t session, + gnutls_transport_ptr_t recv_ptr, + gnutls_transport_ptr_t send_ptr) +{ + session->internals.transport_send_ptr = send_ptr; + session->internals.transport_recv_ptr = recv_ptr; +} + +/** + * gnutls_transport_set_int2: + * @session: is a #gnutls_session_t type. + * @recv_int: is the value for the pull function + * @send_int: is the value for the push function + * + * Used to set the first argument of the transport function (for push + * and pull callbacks), when using the berkeley style sockets. + * With this function you can set two different + * pointers for receiving and sending. + * + * Since: 3.1.9 + **/ +void +gnutls_transport_set_int2(gnutls_session_t session, + int recv_int, int send_int) +{ + session->internals.transport_send_ptr = + (gnutls_transport_ptr_t) (long) send_int; + session->internals.transport_recv_ptr = + (gnutls_transport_ptr_t) (long) recv_int; +} + +#if 0 +/* this will be a macro */ +/** + * gnutls_transport_set_int: + * @session: is a #gnutls_session_t type. + * @i: is the value. + * + * Used to set the first argument of the transport function (for push + * and pull callbacks) for berkeley style sockets. + * + * Since: 3.1.9 + * + **/ +void gnutls_transport_set_int(gnutls_session_t session, int i) +{ + session->internals.transport_recv_ptr = + (gnutls_transport_ptr_t) (long) i; + session->internals.transport_send_ptr = + (gnutls_transport_ptr_t) (long) i; +} +#endif + +/** + * gnutls_transport_get_ptr: + * @session: is a #gnutls_session_t type. + * + * Used to get the first argument of the transport function (like + * PUSH and PULL). This must have been set using + * gnutls_transport_set_ptr(). + * + * Returns: The first argument of the transport function. + **/ +gnutls_transport_ptr_t gnutls_transport_get_ptr(gnutls_session_t session) +{ + return session->internals.transport_recv_ptr; +} + +/** + * gnutls_transport_get_ptr2: + * @session: is a #gnutls_session_t type. + * @recv_ptr: will hold the value for the pull function + * @send_ptr: will hold the value for the push function + * + * Used to get the arguments of the transport functions (like PUSH + * and PULL). These should have been set using + * gnutls_transport_set_ptr2(). + **/ +void +gnutls_transport_get_ptr2(gnutls_session_t session, + gnutls_transport_ptr_t * recv_ptr, + gnutls_transport_ptr_t * send_ptr) +{ + + *recv_ptr = session->internals.transport_recv_ptr; + *send_ptr = session->internals.transport_send_ptr; +} + +/** + * gnutls_transport_get_int2: + * @session: is a #gnutls_session_t type. + * @recv_int: will hold the value for the pull function + * @send_int: will hold the value for the push function + * + * Used to get the arguments of the transport functions (like PUSH + * and PULL). These should have been set using + * gnutls_transport_set_int2(). + * + * Since: 3.1.9 + **/ +void +gnutls_transport_get_int2(gnutls_session_t session, + int *recv_int, int *send_int) +{ + + *recv_int = (long) session->internals.transport_recv_ptr; + *send_int = (long) session->internals.transport_send_ptr; +} + +/** + * gnutls_transport_get_int: + * @session: is a #gnutls_session_t type. + * + * Used to get the first argument of the transport function (like + * PUSH and PULL). This must have been set using + * gnutls_transport_set_int(). + * + * Returns: The first argument of the transport function. + * + * Since: 3.1.9 + **/ +int gnutls_transport_get_int(gnutls_session_t session) +{ + return (long) session->internals.transport_recv_ptr; +} + +/** + * gnutls_bye: + * @session: is a #gnutls_session_t type. + * @how: is an integer + * + * Terminates the current TLS/SSL connection. The connection should + * have been initiated using gnutls_handshake(). @how should be one + * of %GNUTLS_SHUT_RDWR, %GNUTLS_SHUT_WR. + * + * In case of %GNUTLS_SHUT_RDWR the TLS session gets + * terminated and further receives and sends will be disallowed. If + * the return value is zero you may continue using the underlying + * transport layer. %GNUTLS_SHUT_RDWR sends an alert containing a close + * request and waits for the peer to reply with the same message. + * + * In case of %GNUTLS_SHUT_WR the TLS session gets terminated + * and further sends will be disallowed. In order to reuse the + * connection you should wait for an EOF from the peer. + * %GNUTLS_SHUT_WR sends an alert containing a close request. + * + * Note that not all implementations will properly terminate a TLS + * connection. Some of them, usually for performance reasons, will + * terminate only the underlying transport layer, and thus not + * distinguishing between a malicious party prematurely terminating + * the connection and normal termination. + * + * This function may also return %GNUTLS_E_AGAIN or + * %GNUTLS_E_INTERRUPTED; cf. gnutls_record_get_direction(). + * + * Returns: %GNUTLS_E_SUCCESS on success, or an error code, see + * function documentation for entire semantics. + **/ +int gnutls_bye(gnutls_session_t session, gnutls_close_request_t how) +{ + int ret = 0; + + switch (STATE) { + case STATE0: + case STATE60: + ret = _gnutls_io_write_flush(session); + STATE = STATE60; + if (ret < 0) { + gnutls_assert(); + return ret; + } + /* fallthrough */ + case STATE61: + ret = + gnutls_alert_send(session, GNUTLS_AL_WARNING, + GNUTLS_A_CLOSE_NOTIFY); + STATE = STATE61; + if (ret < 0) { + gnutls_assert(); + return ret; + } + + case STATE62: + STATE = STATE62; + if (how == GNUTLS_SHUT_RDWR) { + do { + ret = + _gnutls_recv_int(session, GNUTLS_ALERT, + -1, NULL, NULL, 0, NULL, + session->internals. + record_timeout_ms); + } + while (ret == GNUTLS_E_GOT_APPLICATION_DATA); + + if (ret >= 0) + session->internals.may_not_read = 1; + + if (ret < 0) { + gnutls_assert(); + return ret; + } + } + STATE = STATE62; + + break; + default: + gnutls_assert(); + return GNUTLS_E_INTERNAL_ERROR; + } + + STATE = STATE0; + + session->internals.may_not_write = 1; + return 0; +} + +inline static void session_unresumable(gnutls_session_t session) +{ + session->internals.resumable = RESUME_FALSE; +} + +/* returns 0 if session is valid + */ +inline static int session_is_valid(gnutls_session_t session) +{ + if (session->internals.invalid_connection != 0) + return GNUTLS_E_INVALID_SESSION; + + return 0; +} + +/* Copies the record version into the headers. The + * version must have 2 bytes at least. + */ +inline static void +copy_record_version(gnutls_session_t session, + gnutls_handshake_description_t htype, + uint8_t version[2]) +{ + const version_entry_st *lver; + + if (session->internals.initial_negotiation_completed + || htype != GNUTLS_HANDSHAKE_CLIENT_HELLO + || session->internals.default_record_version[0] == 0) { + lver = get_version(session); + + version[0] = lver->major; + version[1] = lver->minor; + } else { + version[0] = session->internals.default_record_version[0]; + version[1] = session->internals.default_record_version[1]; + } +} + +/* Increments the sequence value + */ +inline static int +sequence_increment(gnutls_session_t session, uint64 * value) +{ + if (IS_DTLS(session)) { + return _gnutls_uint48pp(value); + } else { + return _gnutls_uint64pp(value); + } +} + +/* This function behaves exactly like write(). The only difference is + * that it accepts, the gnutls_session_t and the content_type_t of data to + * send (if called by the user the Content is specific) + * It is intended to transfer data, under the current session. + * + * @type: The content type to send + * @htype: If this is a handshake message then the handshake type + * @epoch_rel: %EPOCH_READ_* or %EPOCH_WRITE_* + * @data: the data to be sent + * @data_size: the size of the @data + * @min_pad: the minimum required padding + * @mflags: zero or %MBUFFER_FLUSH + * + * Oct 30 2001: Removed capability to send data more than MAX_RECORD_SIZE. + * This makes the function much easier to read, and more error resistant + * (there were cases were the old function could mess everything up). + * --nmav + * + * This function may accept a NULL pointer for data, and 0 for size, if + * and only if the previous send was interrupted for some reason. + * + */ +ssize_t +_gnutls_send_tlen_int(gnutls_session_t session, content_type_t type, + gnutls_handshake_description_t htype, + unsigned int epoch_rel, const void *_data, + size_t data_size, size_t min_pad, + unsigned int mflags) +{ + mbuffer_st *bufel; + ssize_t cipher_size; + int retval, ret; + int send_data_size; + uint8_t *headers; + int header_size; + const uint8_t *data = _data; + record_parameters_st *record_params; + size_t max_send_size; + record_state_st *record_state; + + ret = _gnutls_epoch_get(session, epoch_rel, &record_params); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Safeguard against processing data with an incomplete cipher state. */ + if (!record_params->initialized) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + record_state = &record_params->write; + + /* Do not allow null pointer if the send buffer is empty. + * If the previous send was interrupted then a null pointer is + * ok, and means to resume. + */ + if (session->internals.record_send_buffer.byte_length == 0 && + (data_size == 0 && _data == NULL)) { + gnutls_assert(); + return GNUTLS_E_INVALID_REQUEST; + } + + if (type != GNUTLS_ALERT) /* alert messages are sent anyway */ + if (session_is_valid(session) + || session->internals.may_not_write != 0) { + gnutls_assert(); + return GNUTLS_E_INVALID_SESSION; + } + + max_send_size = max_user_send_size(session, record_params); + + if (data_size > max_send_size) { + if (IS_DTLS(session)) + return gnutls_assert_val(GNUTLS_E_LARGE_PACKET); + + send_data_size = max_send_size; + } else + send_data_size = data_size; + + /* Only encrypt if we don't have data to send + * from the previous run. - probably interrupted. + */ + if (mflags != 0 + && session->internals.record_send_buffer.byte_length > 0) { + ret = _gnutls_io_write_flush(session); + if (ret > 0) + cipher_size = ret; + else + cipher_size = 0; + + retval = session->internals.record_send_buffer_user_size; + } else { + if (unlikely((send_data_size == 0 && min_pad == 0))) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* now proceed to packet encryption + */ + cipher_size = MAX_RECORD_SEND_SIZE(session); + + bufel = _mbuffer_alloc_align16(cipher_size + CIPHER_SLACK_SIZE, + get_total_headers2(session, record_params)); + if (bufel == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + headers = _mbuffer_get_uhead_ptr(bufel); + headers[0] = type; + /* Use the default record version, if it is + * set. */ + copy_record_version(session, htype, &headers[1]); + /* Adjust header length and add sequence for DTLS */ + if (IS_DTLS(session)) + memcpy(&headers[3], + record_state->sequence_number.i, 8); + + _gnutls_record_log + ("REC[%p]: Preparing Packet %s(%d) with length: %d and min pad: %d\n", + session, _gnutls_packet2str(type), type, + (int) data_size, (int) min_pad); + + header_size = RECORD_HEADER_SIZE(session); + _mbuffer_set_udata_size(bufel, cipher_size); + _mbuffer_set_uhead_size(bufel, header_size); + + ret = + _gnutls_encrypt(session, + data, send_data_size, min_pad, + bufel, type, record_params); + if (ret <= 0) { + gnutls_assert(); + if (ret == 0) + ret = GNUTLS_E_ENCRYPTION_FAILED; + gnutls_free(bufel); + return ret; /* error */ + } + + cipher_size = _mbuffer_get_udata_size(bufel); + retval = send_data_size; + session->internals.record_send_buffer_user_size = + send_data_size; + + /* increase sequence number + */ + if (sequence_increment + (session, &record_state->sequence_number) != 0) { + session_invalidate(session); + gnutls_free(bufel); + return + gnutls_assert_val + (GNUTLS_E_RECORD_LIMIT_REACHED); + } + + ret = _gnutls_io_write_buffered(session, bufel, mflags); + } + + if (ret != cipher_size) { + /* If we have sent any data then just return + * the error value. Do not invalidate the session. + */ + if (ret < 0 && gnutls_error_is_fatal(ret) == 0) + return gnutls_assert_val(ret); + + if (ret > 0) + ret = gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + session_unresumable(session); + session->internals.may_not_write = 1; + return gnutls_assert_val(ret); + } + + session->internals.record_send_buffer_user_size = 0; + + _gnutls_record_log + ("REC[%p]: Sent Packet[%d] %s(%d) in epoch %d and length: %d\n", + session, (unsigned int) + _gnutls_uint64touint32(&record_state->sequence_number), + _gnutls_packet2str(type), type, (int) record_params->epoch, + (int) cipher_size); + + return retval; +} + +inline static int +check_recv_type(gnutls_session_t session, content_type_t recv_type) +{ + switch (recv_type) { + case GNUTLS_CHANGE_CIPHER_SPEC: + case GNUTLS_ALERT: + case GNUTLS_HANDSHAKE: + case GNUTLS_HEARTBEAT: + case GNUTLS_APPLICATION_DATA: + return 0; + default: + gnutls_assert(); + _gnutls_audit_log(session, + "Received record packet of unknown type %u\n", + (unsigned int) recv_type); + return GNUTLS_E_UNEXPECTED_PACKET; + } + +} + + +/* Checks if there are pending data in the record buffers. If there are + * then it copies the data. + */ +static int +check_buffers(gnutls_session_t session, content_type_t type, + uint8_t * data, int data_size, void *seq) +{ + if ((type == GNUTLS_APPLICATION_DATA || + type == GNUTLS_HANDSHAKE || type == GNUTLS_CHANGE_CIPHER_SPEC) + && _gnutls_record_buffer_get_size(session) > 0) { + int ret; + ret = + _gnutls_record_buffer_get(type, session, data, + data_size, seq); + if (ret < 0) { + if (IS_DTLS(session)) { + if (ret == GNUTLS_E_UNEXPECTED_PACKET) { + ret = GNUTLS_E_AGAIN; + } + } + gnutls_assert(); + return ret; + } + + return ret; + } + + return 0; +} + +/* Checks and retrieves any pending data in the application data record buffers. + */ +static int +check_packet_buffers(gnutls_session_t session, content_type_t type, + gnutls_packet_t *packet) +{ + if (_gnutls_record_buffer_get_size(session) > 0) { + int ret; + ret = + _gnutls_record_buffer_get_packet(type, session, + packet); + if (ret < 0) { + if (IS_DTLS(session)) { + if (ret == GNUTLS_E_UNEXPECTED_PACKET) { + ret = GNUTLS_E_AGAIN; + } + } + gnutls_assert(); + return ret; + } + + return ret; + } + + *packet = NULL; + return 0; +} + + + +/* Here we check if the advertized version is the one we + * negotiated in the handshake. + */ +inline static int +record_check_version(gnutls_session_t session, + gnutls_handshake_description_t htype, + uint8_t version[2]) +{ + const version_entry_st *vers = get_version(session); + int diff = 0; + + if (vers->major != version[0] || vers->minor != version[1]) + diff = 1; + + if (!IS_DTLS(session)) { + if (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO || + htype == GNUTLS_HANDSHAKE_SERVER_HELLO) { + if (version[0] != 3) { + gnutls_assert(); + _gnutls_record_log + ("REC[%p]: INVALID VERSION PACKET: (%d) %d.%d\n", + session, htype, version[0], + version[1]); + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + } + } else if (diff != 0) { + /* Reject record packets that have a different version than the + * one negotiated. Note that this version is not protected by any + * mac. I don't really think that this check serves any purpose. + */ + gnutls_assert(); + _gnutls_record_log + ("REC[%p]: INVALID VERSION PACKET: (%d) %d.%d\n", + session, htype, version[0], version[1]); + + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + } + } else { /* DTLS */ + + /* In DTLS the only information we have here is whether we + * expect a handshake message or not. + */ + if (htype == (gnutls_handshake_description_t) - 1) { + if (diff) { + /* Reject record packets that have a different version than the + * one negotiated. Note that this version is not protected by any + * mac. I don't really think that this check serves any purpose. + */ + gnutls_assert(); + _gnutls_record_log + ("REC[%p]: INVALID VERSION PACKET: (%d) %d.%d\n", + session, htype, version[0], + version[1]); + + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + } + } else if (vers->id > GNUTLS_DTLS1_0 && version[0] > 254) { + gnutls_assert(); + _gnutls_record_log + ("REC[%p]: INVALID DTLS VERSION PACKET: (%d) %d.%d\n", + session, htype, version[0], version[1]); + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + } else if (vers->id == GNUTLS_DTLS0_9 && version[0] > 1) { + gnutls_assert(); + _gnutls_record_log + ("REC[%p]: INVALID DTLS VERSION PACKET: (%d) %d.%d\n", + session, htype, version[0], version[1]); + return GNUTLS_E_UNSUPPORTED_VERSION_PACKET; + } + } + + return 0; +} + +/* This function will check if the received record type is + * the one we actually expect and adds it to the proper + * buffer. The bufel will be deinitialized after calling + * this function, even if it fails. + */ +static int +record_add_to_buffers(gnutls_session_t session, + struct tls_record_st *recv, content_type_t type, + gnutls_handshake_description_t htype, + uint64 * seq, mbuffer_st * bufel) +{ + + int ret; + + if ((recv->type == type) + && (type == GNUTLS_APPLICATION_DATA || + type == GNUTLS_CHANGE_CIPHER_SPEC || + type == GNUTLS_HANDSHAKE)) { + _gnutls_record_buffer_put(session, type, seq, bufel); + + /* if we received application data as expected then we + * deactivate the async timer */ + _dtls_async_timer_delete(session); + } else { + /* if the expected type is different than the received + */ + switch (recv->type) { + case GNUTLS_ALERT: + if (bufel->msg.size < 2) { + ret = + gnutls_assert_val + (GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + goto unexpected_packet; + } + + _gnutls_record_log + ("REC[%p]: Alert[%d|%d] - %s - was received\n", + session, bufel->msg.data[0], + bufel->msg.data[1], + gnutls_alert_get_name((int) bufel->msg. + data[1])); + + session->internals.last_alert = bufel->msg.data[1]; + + /* if close notify is received and + * the alert is not fatal + */ + if (bufel->msg.data[1] == GNUTLS_A_CLOSE_NOTIFY + && bufel->msg.data[0] != GNUTLS_AL_FATAL) { + /* If we have been expecting for an alert do + */ + session->internals.read_eof = 1; + ret = GNUTLS_E_SESSION_EOF; + goto cleanup; + } else { + /* if the alert is FATAL or WARNING + * return the apropriate message + */ + + gnutls_assert(); + ret = GNUTLS_E_WARNING_ALERT_RECEIVED; + if (bufel->msg.data[0] == GNUTLS_AL_FATAL) { + session_unresumable(session); + session_invalidate(session); + ret = + gnutls_assert_val + (GNUTLS_E_FATAL_ALERT_RECEIVED); + } + goto cleanup; + } + break; + + case GNUTLS_CHANGE_CIPHER_SPEC: + if (!(IS_DTLS(session))) { + ret = gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); + goto cleanup; + } + + _gnutls_record_buffer_put(session, recv->type, seq, + bufel); + + break; + +#ifdef ENABLE_HEARTBEAT + case GNUTLS_HEARTBEAT: + ret = _gnutls_heartbeat_handle(session, bufel); + goto cleanup; +#endif + + case GNUTLS_APPLICATION_DATA: + if (session->internals. + initial_negotiation_completed == 0) { + ret = + gnutls_assert_val + (GNUTLS_E_UNEXPECTED_PACKET); + goto unexpected_packet; + } + + + /* the got_application data is only returned + * if expecting client hello (for rehandshake + * reasons). Otherwise it is an unexpected packet + */ + if (type == GNUTLS_ALERT + || ((htype == GNUTLS_HANDSHAKE_SERVER_HELLO || htype == GNUTLS_HANDSHAKE_CLIENT_HELLO) + && type == GNUTLS_HANDSHAKE)) { + /* even if data is unexpected put it into the buffer */ + _gnutls_record_buffer_put(session, recv->type, + seq, bufel); + return + gnutls_assert_val + (GNUTLS_E_GOT_APPLICATION_DATA); + } else { + ret = + gnutls_assert_val + (GNUTLS_E_UNEXPECTED_PACKET); + goto unexpected_packet; + } + + break; + + case GNUTLS_HANDSHAKE: + /* In DTLS we might receive a handshake replay from the peer to indicate + * the our last TLS handshake messages were not received. + */ + if (IS_DTLS(session)) { + if (type == GNUTLS_CHANGE_CIPHER_SPEC) { + ret = + gnutls_assert_val + (GNUTLS_E_UNEXPECTED_PACKET); + goto unexpected_packet; + } + + if (_dtls_is_async(session) + && _dtls_async_timer_active(session)) { + if (session->security_parameters. + entity == GNUTLS_SERVER + && bufel->htype == + GNUTLS_HANDSHAKE_CLIENT_HELLO) + { + /* client requested rehandshake. Delete the timer */ + _dtls_async_timer_delete + (session); + } else { + session->internals. + recv_state = + RECV_STATE_DTLS_RETRANSMIT; + ret = + _dtls_retransmit + (session); + if (ret == 0) { + session->internals. + recv_state = + RECV_STATE_0; + ret = + gnutls_assert_val + (GNUTLS_E_AGAIN); + goto unexpected_packet; + } + goto cleanup; + } + } + } + + /* This is legal if HELLO_REQUEST is received - and we are a client. + * If we are a server, a client may initiate a renegotiation at any time. + */ + if (session->security_parameters.entity == + GNUTLS_SERVER + && bufel->htype == + GNUTLS_HANDSHAKE_CLIENT_HELLO) { + gnutls_assert(); + _gnutls_record_buffer_put(session, + recv->type, + seq, bufel); + return GNUTLS_E_REHANDSHAKE; + } + + /* If we are already in a handshake then a Hello + * Request is illegal. But here we don't really care + * since this message will never make it up here. + */ + + /* So we accept it, if it is a Hello. If not, this will + * fail and trigger flight retransmissions after some time. */ + ret = + _gnutls_recv_hello_request(session, + bufel->msg.data, + bufel->msg.size); + goto unexpected_packet; + + break; + default: + + _gnutls_record_log + ("REC[%p]: Received unexpected packet %d (%s) expecting %d (%s)\n", + session, recv->type, + _gnutls_packet2str(recv->type), type, + _gnutls_packet2str(type)); + + gnutls_assert(); + ret = GNUTLS_E_UNEXPECTED_PACKET; + goto unexpected_packet; + } + } + + return 0; + + unexpected_packet: + if (IS_DTLS(session) && ret != GNUTLS_E_REHANDSHAKE) { + _mbuffer_xfree(&bufel); + RETURN_DTLS_EAGAIN_OR_TIMEOUT(session, ret); + } + + cleanup: + _mbuffer_xfree(&bufel); + return ret; + +} + + +/* Checks the record headers and returns the length, version and + * content type. + */ +static void +record_read_headers(gnutls_session_t session, + uint8_t headers[MAX_RECORD_HEADER_SIZE], + content_type_t type, + gnutls_handshake_description_t htype, + struct tls_record_st *record) +{ + + /* Read the first two bytes to determine if this is a + * version 2 message + */ + + if (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO + && type == GNUTLS_HANDSHAKE && headers[0] > 127 + && !(IS_DTLS(session))) { + + /* if msb set and expecting handshake message + * it should be SSL 2 hello + */ + record->version[0] = 3; /* assume SSL 3.0 */ + record->version[1] = 0; + + record->length = (((headers[0] & 0x7f) << 8)) | headers[1]; + + /* SSL 2.0 headers */ + record->header_size = record->packet_size = 2; + record->type = GNUTLS_HANDSHAKE; /* we accept only v2 client hello + */ + + /* in order to assist the handshake protocol. + * V2 compatibility is a mess. + */ + record->v2 = 1; + record->epoch = 0; + memset(&record->sequence, 0, sizeof(record->sequence)); + + _gnutls_record_log + ("REC[%p]: SSL 2.0 %s packet received. Length: %d\n", + session, _gnutls_packet2str(record->type), + record->length); + + } else { + /* dtls version 1.0 and TLS version 1.x */ + record->v2 = 0; + + record->type = headers[0]; + record->version[0] = headers[1]; + record->version[1] = headers[2]; + + if (IS_DTLS(session)) { + memcpy(record->sequence.i, &headers[3], 8); + record->length = _gnutls_read_uint16(&headers[11]); + record->epoch = + _gnutls_read_uint16(record->sequence.i); + } else { + memset(&record->sequence, 0, + sizeof(record->sequence)); + record->length = _gnutls_read_uint16(&headers[3]); + record->epoch = 0; + } + + _gnutls_record_log + ("REC[%p]: SSL %d.%d %s packet received. Epoch %d, length: %d\n", + session, (int) record->version[0], + (int) record->version[1], + _gnutls_packet2str(record->type), (int) record->epoch, + record->length); + + } + + record->packet_size += record->length; +} + + +static int recv_headers(gnutls_session_t session, + record_parameters_st *record_params, + content_type_t type, + gnutls_handshake_description_t htype, + struct tls_record_st *record, unsigned int *ms) +{ + int ret; + gnutls_datum_t raw; /* raw headers */ + /* Read the headers. + */ + record->header_size = record->packet_size = + RECORD_HEADER_SIZE(session); + + ret = + _gnutls_io_read_buffered(session, record->header_size, -1, ms); + if (ret != record->header_size) { + if (ret < 0 && gnutls_error_is_fatal(ret) == 0) + return ret; + + if (ret > 0) + ret = GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + else if (ret == 0) + ret = GNUTLS_E_PREMATURE_TERMINATION; + + return gnutls_assert_val(ret); + } + + ret = _mbuffer_linearize_align16(&session->internals.record_recv_buffer, + get_total_headers2(session, record_params)); + if (ret < 0) + return gnutls_assert_val(ret); + + _mbuffer_head_get_first(&session->internals.record_recv_buffer, + &raw); + if (raw.size < RECORD_HEADER_SIZE(session)) + return + gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + + record_read_headers(session, raw.data, type, htype, record); + + /* Check if the DTLS epoch is valid */ + if (IS_DTLS(session)) { + if (_gnutls_epoch_is_valid(session, record->epoch) == 0) { + _gnutls_audit_log(session, + "Discarded message[%u] with invalid epoch %u.\n", + (unsigned int) + _gnutls_uint64touint32(&record-> + sequence), + (unsigned int) record->sequence. + i[0] * 256 + + (unsigned int) record->sequence. + i[1]); + gnutls_assert(); + /* doesn't matter, just a fatal error */ + return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + } + } + + /* Here we check if the Type of the received packet is + * ok. + */ + if ((ret = check_recv_type(session, record->type)) < 0) + return gnutls_assert_val(ret); + + /* Here we check if the advertized version is the one we + * negotiated in the handshake. + */ + if ((ret = + record_check_version(session, htype, record->version)) < 0) + return gnutls_assert_val(ret); + + if (record->length > max_record_recv_size(session)) { + _gnutls_audit_log + (session, "Received packet with illegal length: %u\n", + (unsigned int) record->length); + return + gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); + } + + _gnutls_record_log + ("REC[%p]: Expected Packet %s(%d)\n", session, + _gnutls_packet2str(type), type); + _gnutls_record_log + ("REC[%p]: Received Packet %s(%d) with length: %d\n", session, + _gnutls_packet2str(record->type), record->type, + record->length); + + + return 0; +} + +/* @ms: is the number of milliseconds to wait for data. Use zero for indefinite. + * + * This will receive record layer packets and add them to + * application_data_buffer and handshake_data_buffer. + * + * If the htype is not -1 then handshake timeouts + * will be enforced. + */ +ssize_t +_gnutls_recv_in_buffers(gnutls_session_t session, content_type_t type, + gnutls_handshake_description_t htype, + unsigned int ms) +{ + uint64 *packet_sequence; + gnutls_datum_t ciphertext; + mbuffer_st *bufel = NULL, *decrypted = NULL; + gnutls_datum_t t; + int ret; + unsigned int empty_fragments = 0; + record_parameters_st *record_params; + record_state_st *record_state; + struct tls_record_st record; + + begin: + + if (empty_fragments > DEFAULT_MAX_EMPTY_RECORDS) { + gnutls_assert(); + return GNUTLS_E_TOO_MANY_EMPTY_PACKETS; + } + + if (session->internals.read_eof != 0) { + /* if we have already read an EOF + */ + return 0; + } else if (session_is_valid(session) != 0 + || session->internals.may_not_read != 0) + return gnutls_assert_val(GNUTLS_E_INVALID_SESSION); + + /* get the record state parameters */ + ret = + _gnutls_epoch_get(session, EPOCH_READ_CURRENT, &record_params); + if (ret < 0) + return gnutls_assert_val(ret); + + /* Safeguard against processing data with an incomplete cipher state. */ + if (!record_params->initialized) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + record_state = &record_params->read; + + /* receive headers */ + ret = recv_headers(session, record_params, type, htype, &record, session->internals.blocking?&ms:0); + if (ret < 0) { + ret = gnutls_assert_val_fatal(ret); + goto recv_error; + } + + if (IS_DTLS(session)) + packet_sequence = &record.sequence; + else + packet_sequence = &record_state->sequence_number; + + /* Read the packet data and insert it to record_recv_buffer. + */ + ret = + _gnutls_io_read_buffered(session, record.packet_size, + record.type, session->internals.blocking?&ms:0); + if (ret != record.packet_size) { + gnutls_assert(); + goto recv_error; + } + + /* ok now we are sure that we have read all the data - so + * move on ! + */ + ret = _mbuffer_linearize_align16(&session->internals.record_recv_buffer, + get_total_headers2(session, record_params)); + if (ret < 0) + return gnutls_assert_val(ret); + + bufel = + _mbuffer_head_get_first(&session->internals.record_recv_buffer, + NULL); + if (bufel == NULL) + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + + /* We allocate the maximum possible to allow few compressed bytes to expand to a + * full record. Moreover we add space for any pad and the MAC (in case + * they are encrypted). + */ + ret = max_decrypted_size(session) + MAX_PAD_SIZE + MAX_HASH_SIZE; + decrypted = _mbuffer_alloc_align16(ret, 0); + if (decrypted == NULL) + return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); + + _mbuffer_set_udata_size(decrypted, ret); + ciphertext.data = + (uint8_t *) _mbuffer_get_udata_ptr(bufel) + record.header_size; + ciphertext.size = record.length; + + /* decrypt the data we got. + */ + t.data = _mbuffer_get_udata_ptr(decrypted); + t.size = _mbuffer_get_udata_size(decrypted); + ret = + _gnutls_decrypt(session, &ciphertext, &t, + record.type, record_params, packet_sequence); + if (ret >= 0) + _mbuffer_set_udata_size(decrypted, ret); + + _mbuffer_head_remove_bytes(&session->internals.record_recv_buffer, + record.header_size + record.length); + if (ret < 0) { + gnutls_assert(); + _gnutls_audit_log(session, + "Discarded message[%u] due to invalid decryption\n", + (unsigned int) + _gnutls_uint64touint32(packet_sequence)); + goto sanity_check_error; + } + + /* check for duplicates. We check after the message + * is processed and authenticated to avoid someone + * messing with our windows. + */ + if (IS_DTLS(session) + && session->internals.no_replay_protection == 0) { + ret = _dtls_record_check(record_params, packet_sequence); + if (ret < 0) { + _gnutls_record_log + ("REC[%p]: Discarded duplicate message[%u.%u]: %s\n", + session, + (unsigned int) record.sequence.i[0] * 256 + + (unsigned int) record.sequence.i[1], + (unsigned int) + _gnutls_uint64touint32(packet_sequence), + _gnutls_packet2str(record.type)); + goto sanity_check_error; + } + _gnutls_record_log + ("REC[%p]: Decrypted Packet[%u.%u] %s(%d) with length: %d\n", + session, + (unsigned int) record.sequence.i[0] * 256 + + (unsigned int) record.sequence.i[1], + (unsigned int) + _gnutls_uint64touint32(packet_sequence), + _gnutls_packet2str(record.type), record.type, + (int) _mbuffer_get_udata_size(decrypted)); + } else { + _gnutls_record_log + ("REC[%p]: Decrypted Packet[%u] %s(%d) with length: %d\n", + session, + (unsigned int) + _gnutls_uint64touint32(packet_sequence), + _gnutls_packet2str(record.type), record.type, + (int) _mbuffer_get_udata_size(decrypted)); + } + + /* increase sequence number + */ + if (!IS_DTLS(session) + && sequence_increment(session, + &record_state->sequence_number) != 0) { + session_invalidate(session); + gnutls_assert(); + ret = GNUTLS_E_RECORD_LIMIT_REACHED; + goto sanity_check_error; + } + +/* (originally for) TLS 1.0 CBC protection. + * Actually this code is called if we just received + * an empty packet. An empty TLS packet is usually + * sent to protect some vulnerabilities in the CBC mode. + * In that case we go to the beginning and start reading + * the next packet. + */ + if (_mbuffer_get_udata_size(decrypted) == 0) { + _mbuffer_xfree(&decrypted); + empty_fragments++; + goto begin; + } + + if (record.v2) { + decrypted->htype = GNUTLS_HANDSHAKE_CLIENT_HELLO_V2; + } else { + uint8_t *p = _mbuffer_get_udata_ptr(decrypted); + decrypted->htype = p[0]; + } + + ret = + record_add_to_buffers(session, &record, type, htype, + packet_sequence, decrypted); + + /* decrypted is now either deinitialized or buffered somewhere else */ + + if (ret < 0) + return gnutls_assert_val(ret); + + return ret; + + discard: + session->internals.dtls.packets_dropped++; + + /* discard the whole received fragment. */ + bufel = + _mbuffer_head_pop_first(&session->internals. + record_recv_buffer); + _mbuffer_xfree(&bufel); + return gnutls_assert_val(GNUTLS_E_AGAIN); + + sanity_check_error: + if (IS_DTLS(session)) { + session->internals.dtls.packets_dropped++; + ret = gnutls_assert_val(GNUTLS_E_AGAIN); + goto cleanup; + } + + session_unresumable(session); + session_invalidate(session); + + cleanup: + _mbuffer_xfree(&decrypted); + return ret; + + recv_error: + if (ret < 0 + && (gnutls_error_is_fatal(ret) == 0 + || ret == GNUTLS_E_TIMEDOUT)) + return ret; + + if (type == GNUTLS_ALERT) { /* we were expecting close notify */ + session_invalidate(session); + gnutls_assert(); + return 0; + } + + if (IS_DTLS(session) && (ret == GNUTLS_E_DECRYPTION_FAILED || + ret == GNUTLS_E_UNSUPPORTED_VERSION_PACKET || + ret == GNUTLS_E_UNEXPECTED_PACKET_LENGTH || + ret == GNUTLS_E_UNEXPECTED_PACKET || + ret == GNUTLS_E_ERROR_IN_FINISHED_PACKET || + ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET)) { + goto discard; + } + + session_invalidate(session); + session_unresumable(session); + + if (ret == 0) + return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; + else + return ret; +} + +/* This function behaves exactly like read(). The only difference is + * that it accepts the gnutls_session_t and the content_type_t of data to + * receive (if called by the user the Content is Userdata only) + * It is intended to receive data, under the current session. + * + * The gnutls_handshake_description_t was introduced to support SSL V2.0 client hellos. + */ +ssize_t +_gnutls_recv_int(gnutls_session_t session, content_type_t type, + gnutls_handshake_description_t htype, + gnutls_packet_t *packet, + uint8_t * data, size_t data_size, void *seq, + unsigned int ms) +{ + int ret; + + if (packet == NULL && (type != GNUTLS_ALERT && type != GNUTLS_HEARTBEAT) + && (data_size == 0 || data == NULL)) + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + + if (session->internals.read_eof != 0) { + /* if we have already read an EOF + */ + return 0; + } else if (session_is_valid(session) != 0 + || session->internals.may_not_read != 0) { + gnutls_assert(); + return GNUTLS_E_INVALID_SESSION; + } + + switch (session->internals.recv_state) { + case RECV_STATE_DTLS_RETRANSMIT: + ret = _dtls_retransmit(session); + if (ret < 0) + return gnutls_assert_val(ret); + + session->internals.recv_state = RECV_STATE_0; + case RECV_STATE_0: + + _dtls_async_timer_check(session); + + if (packet == NULL) { + /* If we have enough data in the cache do not bother receiving + * a new packet. (in order to flush the cache) + */ + ret = check_buffers(session, type, data, data_size, seq); + if (ret != 0) + return ret; + + ret = _gnutls_recv_in_buffers(session, type, htype, ms); + if (ret < 0 && ret != GNUTLS_E_SESSION_EOF) + return gnutls_assert_val(ret); + + return check_buffers(session, type, data, data_size, seq); + } else { + ret = check_packet_buffers(session, type, packet); + if (ret != 0) + return ret; + + ret = _gnutls_recv_in_buffers(session, type, -1, ms); + if (ret < 0 && ret != GNUTLS_E_SESSION_EOF) + return gnutls_assert_val(ret); + + return check_packet_buffers(session, type, packet); + } + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } +} + +/** + * gnutls_packet_get: + * @packet: is a #gnutls_packet_t type. + * @data: will contain the data present in the @packet structure (may be %NULL) + * @sequence: the 8-bytes of the packet sequence number (may be %NULL) + * + * This function returns the data and sequence number associated with + * the received packet. + * + * Since: 3.3.5 + **/ + +void gnutls_packet_get(gnutls_packet_t packet, gnutls_datum_t *data, unsigned char *sequence) +{ + if (unlikely(packet == NULL)) { + gnutls_assert(); + if (data) { + data->data = NULL; + data->size = 0; + } + } + + if (sequence) { + memcpy(sequence, packet->record_sequence.i, 8); + } + + if (data) { + data->size = packet->msg.size - packet->mark; + data->data = packet->msg.data + packet->mark; + } +} + +/** + * gnutls_packet_deinit: + * @packet: is a pointer to a #gnutls_packet_st structure. + * + * This function will deinitialize all data associated with + * the received packet. + * + * Since: 3.3.5 + **/ +void gnutls_packet_deinit(gnutls_packet_t packet) +{ + gnutls_free(packet); +} + +/** + * gnutls_record_discard_queued: + * @session: is a #gnutls_session_t type. + * + * This function discards all queued to be sent packets in a TLS or DTLS session. + * These are the packets queued after an interrupted gnutls_record_send(). + * + * Returns: The number of bytes discarded. + * + * Since: 3.4.0 + **/ +size_t +gnutls_record_discard_queued(gnutls_session_t session) +{ + size_t ret = session->internals.record_send_buffer.byte_length; + _mbuffer_head_clear(&session->internals.record_send_buffer); + return ret; +} + +/** + * gnutls_record_recv_packet: + * @session: is a #gnutls_session_t type. + * @packet: the structure that will hold the packet data + * + * This is a lower-level function than gnutls_record_recv() and allows + * to directly receive the whole decrypted packet. That avoids a + * memory copy, and is intended to be used by applications seeking high + * performance. + * + * The received packet is accessed using gnutls_packet_get() and + * must be deinitialized using gnutls_packet_deinit(). The returned + * packet will be %NULL if the return value is zero (EOF). + * + * Returns: The number of bytes received and zero on EOF (for stream + * connections). A negative error code is returned in case of an error. + * + * Since: 3.3.5 + **/ +ssize_t +gnutls_record_recv_packet(gnutls_session_t session, + gnutls_packet_t *packet) +{ + return _gnutls_recv_int(session, GNUTLS_APPLICATION_DATA, -1, packet, + NULL, 0, NULL, + session->internals.record_timeout_ms); +} + +/** + * gnutls_record_send: + * @session: is a #gnutls_session_t type. + * @data: contains the data to send + * @data_size: is the length of the data + * + * This function has the similar semantics with send(). The only + * difference is that it accepts a GnuTLS session, and uses different + * error codes. + * Note that if the send buffer is full, send() will block this + * function. See the send() documentation for more information. + * + * You can replace the default push function which is send(), by using + * gnutls_transport_set_push_function(). + * + * If the EINTR is returned by the internal push function + * then %GNUTLS_E_INTERRUPTED will be returned. If + * %GNUTLS_E_INTERRUPTED or %GNUTLS_E_AGAIN is returned, you must + * call this function again, with the exact same parameters; alternatively + * you could provide a %NULL pointer for data, and 0 for + * size. cf. gnutls_record_get_direction(). + * + * Note that in DTLS this function will return the %GNUTLS_E_LARGE_PACKET + * error code if the send data exceed the data MTU value - as returned + * by gnutls_dtls_get_data_mtu(). The errno value EMSGSIZE + * also maps to %GNUTLS_E_LARGE_PACKET. + * Note that since 3.2.13 this function can be called under cork in DTLS + * mode, and will refuse to send data over the MTU size by returning + * %GNUTLS_E_LARGE_PACKET. + * + * Returns: The number of bytes sent, or a negative error code. The + * number of bytes sent might be less than @data_size. The maximum + * number of bytes this function can send in a single call depends + * on the negotiated maximum record size. + **/ +ssize_t +gnutls_record_send(gnutls_session_t session, const void *data, + size_t data_size) +{ + if (session->internals.record_flush_mode == RECORD_FLUSH) { + return _gnutls_send_int(session, GNUTLS_APPLICATION_DATA, + -1, EPOCH_WRITE_CURRENT, data, + data_size, MBUFFER_FLUSH); + } else { /* GNUTLS_CORKED */ + + int ret; + + if (IS_DTLS(session)) { + if (data_size + session->internals.record_presend_buffer.length > + gnutls_dtls_get_data_mtu(session)) { + return gnutls_assert_val(GNUTLS_E_LARGE_PACKET); + } + } + + ret = + _gnutls_buffer_append_data(&session->internals. + record_presend_buffer, data, + data_size); + if (ret < 0) + return gnutls_assert_val(ret); + + return data_size; + } +} + +/** + * gnutls_record_cork: + * @session: is a #gnutls_session_t type. + * + * If called, gnutls_record_send() will no longer send any records. + * Any sent records will be cached until gnutls_record_uncork() is called. + * + * This function is safe to use with DTLS after GnuTLS 3.3.0. + * + * Since: 3.1.9 + **/ +void gnutls_record_cork(gnutls_session_t session) +{ + session->internals.record_flush_mode = RECORD_CORKED; +} + +/** + * gnutls_record_uncork: + * @session: is a #gnutls_session_t type. + * @flags: Could be zero or %GNUTLS_RECORD_WAIT + * + * This resets the effect of gnutls_record_cork(), and flushes any pending + * data. If the %GNUTLS_RECORD_WAIT flag is specified then this + * function will block until the data is sent or a fatal error + * occurs (i.e., the function will retry on %GNUTLS_E_AGAIN and + * %GNUTLS_E_INTERRUPTED). + * + * If the flag %GNUTLS_RECORD_WAIT is not specified and the function + * is interrupted then the %GNUTLS_E_AGAIN or %GNUTLS_E_INTERRUPTED + * errors will be returned. To obtain the data left in the corked + * buffer use gnutls_record_check_corked(). + * + * Returns: On success the number of transmitted data is returned, or + * otherwise a negative error code. + * + * Since: 3.1.9 + **/ +int gnutls_record_uncork(gnutls_session_t session, unsigned int flags) +{ + int ret; + ssize_t total = 0; + + if (session->internals.record_flush_mode == RECORD_FLUSH) + return 0; /* nothing to be done */ + + session->internals.record_flush_mode = RECORD_FLUSH; + + while (session->internals.record_presend_buffer.length > 0) { + if (flags == GNUTLS_RECORD_WAIT) { + do { + ret = + gnutls_record_send(session, + session->internals. + record_presend_buffer. + data, + session->internals. + record_presend_buffer. + length); + } + while (ret < 0 && (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)); + } else { + ret = + gnutls_record_send(session, + session->internals. + record_presend_buffer.data, + session->internals. + record_presend_buffer. + length); + } + if (ret < 0) + goto fail; + + session->internals.record_presend_buffer.data += ret; + session->internals.record_presend_buffer.length -= ret; + total += ret; + } + + return total; + + fail: + session->internals.record_flush_mode = RECORD_CORKED; + return ret; +} + +/** + * gnutls_record_recv: + * @session: is a #gnutls_session_t type. + * @data: the buffer that the data will be read into + * @data_size: the number of requested bytes + * + * This function has the similar semantics with recv(). The only + * difference is that it accepts a GnuTLS session, and uses different + * error codes. + * In the special case that a server requests a renegotiation, the + * client may receive an error code of %GNUTLS_E_REHANDSHAKE. This + * message may be simply ignored, replied with an alert + * %GNUTLS_A_NO_RENEGOTIATION, or replied with a new handshake, + * depending on the client's will. + * If %EINTR is returned by the internal push function (the default + * is recv()) then %GNUTLS_E_INTERRUPTED will be returned. If + * %GNUTLS_E_INTERRUPTED or %GNUTLS_E_AGAIN is returned, you must + * call this function again to get the data. See also + * gnutls_record_get_direction(). + * A server may also receive %GNUTLS_E_REHANDSHAKE when a client has + * initiated a handshake. In that case the server can only initiate a + * handshake or terminate the connection. + * + * Returns: The number of bytes received and zero on EOF (for stream + * connections). A negative error code is returned in case of an error. + * The number of bytes received might be less than the requested @data_size. + **/ +ssize_t +gnutls_record_recv(gnutls_session_t session, void *data, size_t data_size) +{ + return _gnutls_recv_int(session, GNUTLS_APPLICATION_DATA, -1, NULL, + data, data_size, NULL, + session->internals.record_timeout_ms); +} + +/** + * gnutls_record_recv_seq: + * @session: is a #gnutls_session_t type. + * @data: the buffer that the data will be read into + * @data_size: the number of requested bytes + * @seq: is the packet's 64-bit sequence number. Should have space for 8 bytes. + * + * This function is the same as gnutls_record_recv(), except that + * it returns in addition to data, the sequence number of the data. + * This is useful in DTLS where record packets might be received + * out-of-order. The returned 8-byte sequence number is an + * integer in big-endian format and should be + * treated as a unique message identification. + * + * Returns: The number of bytes received and zero on EOF. A negative + * error code is returned in case of an error. The number of bytes + * received might be less than @data_size. + * + * Since: 3.0 + **/ +ssize_t +gnutls_record_recv_seq(gnutls_session_t session, void *data, + size_t data_size, unsigned char *seq) +{ + return _gnutls_recv_int(session, GNUTLS_APPLICATION_DATA, -1, NULL, + data, data_size, seq, + session->internals.record_timeout_ms); +} + +/** + * gnutls_record_set_timeout: + * @session: is a #gnutls_session_t type. + * @ms: is a timeout value in milliseconds + * + * This function sets the receive timeout for the record layer + * to the provided value. Use an @ms value of zero to disable + * timeout (the default). + * + * This function requires to set a pull timeout callback. See + * gnutls_transport_set_pull_timeout_function(). + * + * Since: 3.1.7 + **/ +void gnutls_record_set_timeout(gnutls_session_t session, unsigned int ms) +{ + session->internals.record_timeout_ms = ms; +} |