static int _gnutls_buffer_insert_data (gnutls_buffer_st * dest, int pos, const void *str, size_t str_size) { size_t orig_length = dest->length; int ret; ret = _gnutls_buffer_resize (dest, dest->length + str_size); /* resize to make space */ if (ret < 0) return ret; memmove (&dest->data[pos + str_size], &dest->data[pos], orig_length - pos); memcpy (&dest->data[pos], str, str_size); dest->length += str_size; return 0; }
/* will merge the given handshake_buffer_st to the handshake_recv_buffer * list. The given hsk packet will be released in any case (success or failure). * Only used in DTLS. */ static int merge_handshake_packet(gnutls_session_t session, handshake_buffer_st * hsk) { int exists = 0, i, pos = 0; int ret; for (i = 0; i < session->internals.handshake_recv_buffer_size; i++) { if (session->internals.handshake_recv_buffer[i].htype == hsk->htype) { exists = 1; pos = i; break; } } if (!exists) pos = session->internals.handshake_recv_buffer_size; if (pos >= MAX_HANDSHAKE_MSGS) return gnutls_assert_val(GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS); if (!exists) { if (hsk->length > 0 && hsk->end_offset > 0 && hsk->end_offset - hsk->start_offset + 1 != hsk->length) { ret = _gnutls_buffer_resize(&hsk->data, hsk->length); if (ret < 0) return gnutls_assert_val(ret); hsk->data.length = hsk->length; memmove(&hsk->data.data[hsk->start_offset], hsk->data.data, hsk->end_offset - hsk->start_offset + 1); } session->internals.handshake_recv_buffer_size++; /* rewrite headers to make them look as each packet came as a single fragment */ _gnutls_write_uint24(hsk->length, &hsk->header[1]); _gnutls_write_uint24(0, &hsk->header[6]); _gnutls_write_uint24(hsk->length, &hsk->header[9]); _gnutls_handshake_buffer_move(&session->internals. handshake_recv_buffer[pos], hsk); } else { if (hsk->start_offset < session->internals.handshake_recv_buffer[pos]. start_offset && hsk->end_offset + 1 >= session->internals.handshake_recv_buffer[pos]. start_offset) { memcpy(&session->internals. handshake_recv_buffer[pos].data.data[hsk-> start_offset], hsk->data.data, hsk->data.length); session->internals.handshake_recv_buffer[pos]. start_offset = hsk->start_offset; session->internals.handshake_recv_buffer[pos]. end_offset = MIN(hsk->end_offset, session->internals. handshake_recv_buffer[pos].end_offset); } else if (hsk->end_offset > session->internals.handshake_recv_buffer[pos]. end_offset && hsk->start_offset <= session->internals.handshake_recv_buffer[pos]. end_offset + 1) { memcpy(&session->internals. handshake_recv_buffer[pos].data.data[hsk-> start_offset], hsk->data.data, hsk->data.length); session->internals.handshake_recv_buffer[pos]. end_offset = hsk->end_offset; session->internals.handshake_recv_buffer[pos]. start_offset = MIN(hsk->start_offset, session->internals. handshake_recv_buffer[pos].start_offset); } _gnutls_handshake_buffer_clear(hsk); } return 0; }
/** * gnutls_init - initialize the session to null (null encryption etc...). * @con_end: indicate if this session is to be used for server or client. * @session: is a pointer to a #gnutls_session_t structure. * * This function initializes the current session to null. Every * session must be initialized before use, so internal structures can * be allocated. This function allocates structures which can only * be free'd by calling gnutls_deinit(). Returns zero on success. * * @con_end can be one of %GNUTLS_CLIENT and %GNUTLS_SERVER. * * Returns: %GNUTLS_E_SUCCESS on success, or an error code. **/ int gnutls_init (gnutls_session_t * session, gnutls_connection_end_t con_end) { *session = gnutls_calloc (1, sizeof (struct gnutls_session_int)); if (*session == NULL) return GNUTLS_E_MEMORY_ERROR; (*session)->security_parameters.entity = con_end; /* the default certificate type for TLS */ (*session)->security_parameters.cert_type = DEFAULT_CERT_TYPE; /* Set the defaults for initial handshake */ (*session)->security_parameters.read_bulk_cipher_algorithm = (*session)->security_parameters.write_bulk_cipher_algorithm = GNUTLS_CIPHER_NULL; (*session)->security_parameters.read_mac_algorithm = (*session)->security_parameters.write_mac_algorithm = GNUTLS_MAC_NULL; (*session)->security_parameters.read_compression_algorithm = GNUTLS_COMP_NULL; (*session)->security_parameters.write_compression_algorithm = GNUTLS_COMP_NULL; (*session)->internals.enable_private = 0; /* Initialize buffers */ _gnutls_buffer_init (&(*session)->internals.application_data_buffer); _gnutls_buffer_init (&(*session)->internals.handshake_data_buffer); _gnutls_buffer_init (&(*session)->internals.handshake_hash_buffer); _gnutls_buffer_init (&(*session)->internals.ia_data_buffer); _gnutls_buffer_init (&(*session)->internals.record_send_buffer); _gnutls_buffer_init (&(*session)->internals.record_recv_buffer); _gnutls_buffer_init (&(*session)->internals.handshake_send_buffer); _gnutls_buffer_init (&(*session)->internals.handshake_recv_buffer); (*session)->key = gnutls_calloc (1, sizeof (struct gnutls_key_st)); if ((*session)->key == NULL) { cleanup_session: gnutls_free (*session); *session = NULL; return GNUTLS_E_MEMORY_ERROR; } (*session)->internals.expire_time = DEFAULT_EXPIRE_TIME; /* one hour default */ gnutls_dh_set_prime_bits ((*session), MIN_DH_BITS); gnutls_transport_set_lowat ((*session), DEFAULT_LOWAT); /* the default for tcp */ gnutls_handshake_set_max_packet_length ((*session), MAX_HANDSHAKE_PACKET_SIZE); /* Allocate a minimum size for recv_data * This is allocated in order to avoid small messages, making * the receive procedure slow. */ if (_gnutls_buffer_resize (&(*session)->internals.record_recv_buffer, INITIAL_RECV_BUFFER_SIZE)) { gnutls_free ((*session)->key); goto cleanup_session; } /* set the socket pointers to -1; */ (*session)->internals.transport_recv_ptr = (gnutls_transport_ptr_t) - 1; (*session)->internals.transport_send_ptr = (gnutls_transport_ptr_t) - 1; /* set the default maximum record size for TLS */ (*session)->security_parameters.max_record_recv_size = DEFAULT_MAX_RECORD_SIZE; (*session)->security_parameters.max_record_send_size = DEFAULT_MAX_RECORD_SIZE; /* everything else not initialized here is initialized * as NULL or 0. This is why calloc is used. */ _gnutls_handshake_internal_state_clear (*session); return 0; }
/* * Processes a heartbeat message. */ int _gnutls_heartbeat_handle(gnutls_session_t session, mbuffer_st * bufel) { int ret; unsigned type; unsigned pos; uint8_t *msg = _mbuffer_get_udata_ptr(bufel); size_t hb_len, len = _mbuffer_get_udata_size(bufel); if (gnutls_heartbeat_allowed (session, GNUTLS_HB_PEER_ALLOWED_TO_SEND) == 0) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); if (len < 4) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); pos = 0; type = msg[pos++]; hb_len = _gnutls_read_uint16(&msg[pos]); if (hb_len > len - 3) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); pos += 2; switch (type) { case HEARTBEAT_REQUEST: _gnutls_buffer_reset(&session->internals.hb_remote_data); ret = _gnutls_buffer_resize(&session->internals. hb_remote_data, hb_len); if (ret < 0) return gnutls_assert_val(ret); if (hb_len > 0) memcpy(session->internals.hb_remote_data.data, &msg[pos], hb_len); session->internals.hb_remote_data.length = hb_len; return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PING_RECEIVED); case HEARTBEAT_RESPONSE: if (hb_len != session->internals.hb_local_data.length) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); if (hb_len > 0 && memcmp(&msg[pos], session->internals.hb_local_data.data, hb_len) != 0) { if (IS_DTLS(session)) return gnutls_assert_val(GNUTLS_E_AGAIN); /* ignore it */ else return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET); } _gnutls_buffer_reset(&session->internals.hb_local_data); return gnutls_assert_val(GNUTLS_E_HEARTBEAT_PONG_RECEIVED); default: _gnutls_record_log ("REC[%p]: HB: received unknown type %u\n", session, type); return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); } }
/** * gnutls_heartbeat_ping: * @session: is a #gnutls_session_t structure. * @data_size: is the length of the ping payload. * @max_tries: if flags is %GNUTLS_HEARTBEAT_WAIT then this sets the number of retransmissions. Use zero for indefinite (until timeout). * @flags: if %GNUTLS_HEARTBEAT_WAIT then wait for pong or timeout instead of returning immediately. * * This function sends a ping to the peer. If the @flags is set * to %GNUTLS_HEARTBEAT_WAIT then it waits for a reply from the peer. * * Note that it is highly recommended to use this function with the * flag %GNUTLS_HEARTBEAT_WAIT, or you need to handle retransmissions * and timeouts manually. * * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. * * Since: 3.1.2 **/ int gnutls_heartbeat_ping(gnutls_session_t session, size_t data_size, unsigned int max_tries, unsigned int flags) { int ret; unsigned int retries = 1, diff; struct timespec now; if (data_size > MAX_HEARTBEAT_LENGTH) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET_LENGTH); if (gnutls_heartbeat_allowed (session, GNUTLS_HB_LOCAL_ALLOWED_TO_SEND) == 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); /* resume previous call if interrupted */ if (session->internals.record_send_buffer.byte_length > 0 && session->internals.record_send_buffer.head != NULL && session->internals.record_send_buffer.head->type == GNUTLS_HEARTBEAT) return _gnutls_io_write_flush(session); switch (session->internals.hb_state) { case SHB_SEND1: if (data_size > DEFAULT_PAYLOAD_SIZE) data_size -= DEFAULT_PAYLOAD_SIZE; else data_size = 0; _gnutls_buffer_reset(&session->internals.hb_local_data); ret = _gnutls_buffer_resize(&session->internals. hb_local_data, data_size); if (ret < 0) return gnutls_assert_val(ret); ret = _gnutls_rnd(GNUTLS_RND_NONCE, session->internals.hb_local_data.data, data_size); if (ret < 0) return gnutls_assert_val(ret); gettime(&session->internals.hb_ping_start); session->internals.hb_local_data.length = data_size; session->internals.hb_state = SHB_SEND2; case SHB_SEND2: session->internals.hb_actual_retrans_timeout_ms = session->internals.hb_retrans_timeout_ms; retry: ret = heartbeat_send_data(session, session->internals.hb_local_data. data, session->internals.hb_local_data. length, HEARTBEAT_REQUEST); if (ret < 0) return gnutls_assert_val(ret); gettime(&session->internals.hb_ping_sent); if (!(flags & GNUTLS_HEARTBEAT_WAIT)) { session->internals.hb_state = SHB_SEND1; break; } session->internals.hb_state = SHB_RECV; case SHB_RECV: ret = _gnutls_recv_int(session, GNUTLS_HEARTBEAT, -1, NULL, 0, NULL, session->internals. hb_actual_retrans_timeout_ms); if (ret == GNUTLS_E_HEARTBEAT_PONG_RECEIVED) { session->internals.hb_state = SHB_SEND1; break; } else if (ret == GNUTLS_E_TIMEDOUT) { retries++; if (max_tries > 0 && retries > max_tries) { session->internals.hb_state = SHB_SEND1; return gnutls_assert_val(ret); } gettime(&now); diff = timespec_sub_ms(&now, &session->internals. hb_ping_start); if (diff > session->internals.hb_total_timeout_ms) { session->internals.hb_state = SHB_SEND1; return gnutls_assert_val(GNUTLS_E_TIMEDOUT); } session->internals.hb_actual_retrans_timeout_ms *= 2; session->internals.hb_actual_retrans_timeout_ms %= MAX_DTLS_TIMEOUT; session->internals.hb_state = SHB_SEND2; goto retry; } else if (ret < 0) { session->internals.hb_state = SHB_SEND1; return gnutls_assert_val(ret); } } return 0; }
/* This function is like recv(with MSG_PEEK). But it does not return -1 on error. * It does return gnutls_errno instead. * This function reads data from the socket and keeps them in a buffer, of up to * MAX_RECV_SIZE. * * This is not a general purpose function. It returns EXACTLY the data requested, * which are stored in a local (in the session) buffer. A pointer (iptr) to this buffer is returned. * */ ssize_t _gnutls_io_read_buffered (gnutls_session_t session, opaque ** iptr, size_t sizeOfPtr, content_type_t recv_type) { ssize_t ret = 0, ret2 = 0; size_t min; int buf_pos; opaque *buf; int recvlowat; int recvdata; *iptr = session->internals.record_recv_buffer.data; if (sizeOfPtr > MAX_RECV_SIZE || sizeOfPtr == 0) { gnutls_assert (); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } /* If an external pull function is used, then do not leave * any data into the kernel buffer. */ if (session->internals._gnutls_pull_func != NULL) { recvlowat = 0; } else { /* leave peeked data to the kernel space only if application data * is received and we don't have any peeked * data in gnutls session. */ if (recv_type != GNUTLS_APPLICATION_DATA && session->internals.have_peeked_data == 0) recvlowat = 0; else recvlowat = RCVLOWAT; } /* calculate the actual size, ie. get the minimum of the * buffered data and the requested data. */ min = MIN (session->internals.record_recv_buffer.length, sizeOfPtr); if (min > 0) { /* if we have enough buffered data * then just return them. */ if (min == sizeOfPtr) { return min; } } /* min is over zero. recvdata is the data we must * receive in order to return the requested data. */ recvdata = sizeOfPtr - min; /* Check if the previously read data plus the new data to * receive are longer than the maximum receive buffer size. */ if ((session->internals.record_recv_buffer.length + recvdata) > MAX_RECV_SIZE) { gnutls_assert (); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } /* Allocate the data required to store the new packet. */ ret = _gnutls_buffer_resize( &session->internals.record_recv_buffer, recvdata + session->internals.record_recv_buffer.length); if (ret < 0) { gnutls_assert(); return ret; } buf_pos = session->internals.record_recv_buffer.length; buf = session->internals.record_recv_buffer.data; *iptr = buf; /* READ DATA - but leave RCVLOWAT bytes in the kernel buffer. */ if (recvdata - recvlowat > 0) { ret = _gnutls_read (session, &buf[buf_pos], recvdata - recvlowat, 0); /* return immediately if we got an interrupt or eagain * error. */ if (ret < 0 && gnutls_error_is_fatal (ret) == 0) { return ret; } } /* copy fresh data to our buffer. */ if (ret > 0) { _gnutls_read_log ("RB: Have %d bytes into buffer. Adding %d bytes.\n", session->internals.record_recv_buffer.length, ret); _gnutls_read_log ("RB: Requested %d bytes\n", sizeOfPtr); session->internals.record_recv_buffer.length += ret; } buf_pos = session->internals.record_recv_buffer.length; /* This is hack in order for select to work. Just leave recvlowat data, * into the kernel buffer (using a read with MSG_PEEK), thus making * select think, that the socket is ready for reading. * MSG_PEEK is only used with berkeley style sockets. */ if (ret == (recvdata - recvlowat) && recvlowat > 0) { ret2 = _gnutls_read (session, &buf[buf_pos], recvlowat, MSG_PEEK); if (ret2 < 0 && gnutls_error_is_fatal (ret2) == 0) { return ret2; } if (ret2 > 0) { _gnutls_read_log ("RB-PEEK: Read %d bytes in PEEK MODE.\n", ret2); _gnutls_read_log ("RB-PEEK: Have %d bytes into buffer. Adding %d bytes.\nRB: Requested %d bytes\n", session->internals.record_recv_buffer.length, ret2, sizeOfPtr); session->internals.have_peeked_data = 1; session->internals.record_recv_buffer.length += ret2; } } if (ret < 0 || ret2 < 0) { gnutls_assert (); /* that's because they are initialized to 0 */ return MIN (ret, ret2); } ret += ret2; if (ret > 0 && ret < recvlowat) { gnutls_assert (); return GNUTLS_E_AGAIN; } if (ret == 0) { /* EOF */ gnutls_assert (); return 0; } ret = session->internals.record_recv_buffer.length; if ((ret > 0) && ((size_t) ret < sizeOfPtr)) { /* Short Read */ gnutls_assert (); return GNUTLS_E_AGAIN; } else { return ret; } }