int _gnutls13_send_key_update(gnutls_session_t session, unsigned again, unsigned flags /* GNUTLS_KU_* */) { int ret; mbuffer_st *bufel = NULL; uint8_t val; if (again == 0) { if (flags & GNUTLS_KU_PEER) { /* mark that we asked a key update to prevent an * infinite ping pong when receiving the reply */ session->internals.hsk_flags |= HSK_KEY_UPDATE_ASKED; val = 0x01; } else { val = 0x00; } _gnutls_handshake_log("HSK[%p]: sending key update (%u)\n", session, (unsigned)val); bufel = _gnutls_handshake_alloc(session, 1); if (bufel == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); _mbuffer_set_udata_size(bufel, 0); ret = _mbuffer_append_data(bufel, &val, 1); if (ret < 0) { gnutls_assert(); goto cleanup; } } return _gnutls_send_handshake(session, bufel, GNUTLS_HANDSHAKE_KEY_UPDATE); cleanup: _mbuffer_xfree(&bufel); return ret; }
static ssize_t _gnutls_stream_read(gnutls_session_t session, mbuffer_st ** bufel, size_t size, gnutls_pull_func pull_func, unsigned int *ms) { size_t left; ssize_t i = 0; size_t max_size = max_record_recv_size(session); uint8_t *ptr; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; int ret; struct timespec t1, t2; unsigned int diff; session->internals.direction = 0; *bufel = _mbuffer_alloc_align16(MAX(max_size, size), get_total_headers(session)); if (!*bufel) { gnutls_assert(); return GNUTLS_E_MEMORY_ERROR; } ptr = (*bufel)->msg.data; left = size; while (left > 0) { if (ms && *ms > 0) { ret = _gnutls_io_check_recv(session, *ms); if (ret < 0) { gnutls_assert(); goto cleanup; } gnutls_gettime(&t1); } reset_errno(session); i = pull_func(fd, &ptr[size - left], left); if (i < 0) { int err = get_errno(session); _gnutls_read_log ("READ: %d returned from %p, errno=%d gerrno=%d\n", (int) i, fd, errno, session->internals.errnum); if (err == EAGAIN || err == EINTR) { if (size - left > 0) { _gnutls_read_log ("READ: returning %d bytes from %p\n", (int) (size - left), fd); goto finish; } ret = errno_to_gerr(err, 0); goto cleanup; } else { gnutls_assert(); ret = GNUTLS_E_PULL_ERROR; goto cleanup; } } else { _gnutls_read_log("READ: Got %d bytes from %p\n", (int) i, fd); if (i == 0) break; /* EOF */ } left -= i; (*bufel)->msg.size += i; if (ms && *ms > 0 && *ms != GNUTLS_INDEFINITE_TIMEOUT) { gnutls_gettime(&t2); diff = timespec_sub_ms(&t2, &t1); if (diff < *ms) *ms -= diff; else { ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto cleanup; } } } finish: _gnutls_read_log("READ: read %d bytes from %p\n", (int) (size - left), fd); if (size - left == 0) _mbuffer_xfree(bufel); return (size - left); cleanup: _mbuffer_xfree(bufel); return ret; }
static ssize_t _gnutls_dgram_read(gnutls_session_t session, mbuffer_st ** bufel, gnutls_pull_func pull_func, unsigned int *ms) { ssize_t i, ret; uint8_t *ptr; struct timespec t1, t2; size_t max_size, recv_size; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; unsigned int diff; max_size = max_record_recv_size(session); recv_size = max_size; session->internals.direction = 0; if (ms && *ms > 0) { ret = _gnutls_io_check_recv(session, *ms); if (ret < 0) return gnutls_assert_val(ret); gnutls_gettime(&t1); } *bufel = _mbuffer_alloc_align16(max_size, get_total_headers(session)); if (*bufel == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ptr = (*bufel)->msg.data; reset_errno(session); i = pull_func(fd, ptr, recv_size); if (i < 0) { int err = get_errno(session); _gnutls_read_log("READ: %d returned from %p, errno=%d\n", (int) i, fd, err); ret = errno_to_gerr(err, 1); goto cleanup; } else { _gnutls_read_log("READ: Got %d bytes from %p\n", (int) i, fd); if (i == 0) { /* If we get here, we likely have a stream socket. * That assumption may not work on DCCP. */ gnutls_assert(); ret = 0; goto cleanup; } _mbuffer_set_udata_size(*bufel, i); } if (ms && *ms > 0) { gnutls_gettime(&t2); diff = timespec_sub_ms(&t2, &t1); if (diff < *ms) *ms -= diff; else { ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto cleanup; } } _gnutls_read_log("READ: read %d bytes from %p\n", (int) i, fd); return i; cleanup: _mbuffer_xfree(bufel); return ret; }
/* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. * * htype is the next handshake packet expected. */ int _gnutls_parse_record_buffered_msgs(gnutls_session_t session) { gnutls_datum_t msg; mbuffer_st *bufel = NULL, *prev = NULL; int ret; size_t data_size; handshake_buffer_st *recv_buf = session->internals.handshake_recv_buffer; bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); if (bufel == NULL) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; if (!IS_DTLS(session)) { ssize_t append, header_size; do { if (bufel->type != GNUTLS_HANDSHAKE) return gnutls_assert_val (GNUTLS_E_UNEXPECTED_PACKET); if (unlikely (session->internals.handshake_recv_buffer_size == 0 && msg.size < HANDSHAKE_HEADER_SIZE(session) && session->internals.handshake_header_recv_buffer.byte_length < HANDSHAKE_HEADER_SIZE(session) - msg.size)) { bufel = _mbuffer_head_pop_first(&session->internals.record_buffer); _mbuffer_enqueue(&session->internals.handshake_header_recv_buffer, bufel); break; } else if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length > recv_buf[0].data.length) { /* this is the rest of a previous message */ append = MIN(msg.size, recv_buf[0].length - recv_buf[0].data.length); ret = _gnutls_buffer_append_data(&recv_buf [0].data, msg.data, append); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_head_remove_bytes(&session-> internals. record_buffer, append); } else { /* received new message */ if (unlikely (session->internals. handshake_header_recv_buffer.length > 0)) { bufel = _mbuffer_head_pop_first(&session->internals. record_buffer); _mbuffer_enqueue(&session->internals. handshake_header_recv_buffer, bufel); ret = _mbuffer_linearize_align16(&session->internals. handshake_header_recv_buffer, get_total_headers(session)); if (ret < 0) return gnutls_assert_val(ret); bufel = _mbuffer_head_pop_first(&session->internals. handshake_header_recv_buffer); _mbuffer_head_push_first(&session->internals. record_buffer, bufel); } ret = parse_handshake_header(session, bufel, &recv_buf[0]); if (ret < 0) return gnutls_assert_val(ret); header_size = ret; session->internals. handshake_recv_buffer_size = 1; _mbuffer_set_uhead_size(bufel, header_size); data_size = MIN(recv_buf[0].length, _mbuffer_get_udata_size(bufel)); ret = _gnutls_buffer_append_data(&recv_buf [0].data, _mbuffer_get_udata_ptr (bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_set_uhead_size(bufel, 0); _mbuffer_head_remove_bytes(&session-> internals. record_buffer, data_size + header_size); } /* if packet is complete then return it */ if (recv_buf[0].length == recv_buf[0].data.length) { return 0; } bufel = _mbuffer_head_get_first(&session->internals. record_buffer, &msg); } while (bufel != NULL); /* if we are here it means that the received packets were not * enough to complete the handshake packet. */ return gnutls_assert_val(GNUTLS_E_AGAIN); } else { /* DTLS */ handshake_buffer_st tmp; do { /* we now * 0. parse headers * 1. insert to handshake_recv_buffer * 2. sort handshake_recv_buffer on sequence numbers * 3. return first packet if completed or GNUTLS_E_AGAIN. */ do { if (bufel->type != GNUTLS_HANDSHAKE) { gnutls_assert(); goto next; /* ignore packet */ } _gnutls_handshake_buffer_init(&tmp); ret = parse_handshake_header(session, bufel, &tmp); if (ret < 0) { gnutls_assert(); _gnutls_audit_log(session, "Invalid handshake packet headers. Discarding.\n"); break; } _mbuffer_consume(&session->internals. record_buffer, bufel, ret); data_size = MIN(tmp.length, tmp.end_offset - tmp.start_offset + 1); ret = _gnutls_buffer_append_data(&tmp.data, _mbuffer_get_udata_ptr (bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_consume(&session->internals. record_buffer, bufel, data_size); ret = merge_handshake_packet(session, &tmp); if (ret < 0) return gnutls_assert_val(ret); } while (_mbuffer_get_udata_size(bufel) > 0); prev = bufel; bufel = _mbuffer_dequeue(&session->internals. record_buffer, bufel); _mbuffer_xfree(&prev); continue; next: bufel = _mbuffer_head_get_next(bufel, NULL); } while (bufel != NULL); /* sort in descending order */ if (session->internals.handshake_recv_buffer_size > 1) qsort(recv_buf, session->internals. handshake_recv_buffer_size, sizeof(recv_buf[0]), handshake_compare); while (session->internals.handshake_recv_buffer_size > 0 && recv_buf[LAST_ELEMENT].sequence < session->internals.dtls.hsk_read_seq) { _gnutls_audit_log(session, "Discarded replayed handshake packet with sequence %d\n", recv_buf[LAST_ELEMENT].sequence); _gnutls_handshake_buffer_clear(&recv_buf [LAST_ELEMENT]); session->internals.handshake_recv_buffer_size--; } 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))) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); _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_CLIENT_HELLO && type == GNUTLS_HANDSHAKE)) { /* even if data is unexpected put it into the buffer */ if ((ret = _gnutls_record_buffer_put (session, recv->type, seq, bufel)) < 0) { gnutls_assert (); goto cleanup; } 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 (); ret = _gnutls_record_buffer_put (session, recv->type, seq, bufel); if (ret < 0) { gnutls_assert (); goto cleanup; } 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; }
/* @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 > session->internals.priorities.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, type, htype, &record, &ms); 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, &ms); 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 (&session->internals.record_recv_buffer); 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. */ decrypted = _mbuffer_alloc(record.length, record.length); if (decrypted == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); 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_audit_log(session, "Discarded duplicate message[%u.%u]: %s\n", (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); /* bufel 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)) { goto discard; } session_invalidate (session); session_unresumable (session); if (ret == 0) return GNUTLS_E_UNEXPECTED_PACKET_LENGTH; else return ret; }
/* This is a receive function for the gnutls handshake * protocol. Makes sure that we have received all data. */ static int parse_record_buffered_msgs (gnutls_session_t session, gnutls_handshake_description_t htype, handshake_buffer_st * hsk) { gnutls_datum_t msg; mbuffer_st* bufel = NULL, *prev = NULL; int ret; size_t data_size; handshake_buffer_st* recv_buf = session->internals.handshake_recv_buffer; bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); if (bufel == NULL) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; if (!IS_DTLS(session)) { ssize_t remain, append, header_size; do { if (bufel->type != GNUTLS_HANDSHAKE) return gnutls_assert_val(GNUTLS_E_UNEXPECTED_PACKET); /* if we have a half received message the complete it. */ remain = recv_buf[0].length - recv_buf[0].data.length; /* this is the rest of a previous message */ if (session->internals.handshake_recv_buffer_size > 0 && recv_buf[0].length > 0 && remain > 0) { if (msg.size <= remain) append = msg.size; else append = remain; ret = _gnutls_buffer_append_data(&recv_buf[0].data, msg.data, append); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_head_remove_bytes(&session->internals.record_buffer, append); } else /* received new message */ { ret = parse_handshake_header(session, bufel, htype, &recv_buf[0]); if (ret < 0) return gnutls_assert_val(ret); header_size = ret; session->internals.handshake_recv_buffer_size = 1; _mbuffer_set_uhead_size(bufel, header_size); data_size = MIN(recv_buf[0].length, _mbuffer_get_udata_size(bufel)); ret = _gnutls_buffer_append_data(&recv_buf[0].data, _mbuffer_get_udata_ptr(bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_set_uhead_size(bufel, 0); _mbuffer_head_remove_bytes(&session->internals.record_buffer, data_size+header_size); if (cmp_hsk_types(htype, recv_buf[0].htype) == 0) { /* an unexpected packet */ hsk->htype = recv_buf[0].htype; return gnutls_assert_val(GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET); } } /* if packet is complete then return it */ if (recv_buf[0].length == recv_buf[0].data.length) { return get_last_packet(session, htype, hsk); } bufel = _mbuffer_head_get_first(&session->internals.record_buffer, &msg); } while(bufel != NULL); /* if we are here it means that the received packets were not * enough to complete the handshake packet. */ return gnutls_assert_val(GNUTLS_E_AGAIN); } else /* DTLS */ { handshake_buffer_st tmp; do { /* we now * 0. parse headers * 1. insert to handshake_recv_buffer * 2. sort handshake_recv_buffer on sequence numbers * 3. return first packet if completed or GNUTLS_E_AGAIN. */ do { if (bufel->type != GNUTLS_HANDSHAKE) { gnutls_assert(); goto next; /* ignore packet */ } _gnutls_handshake_buffer_init(&tmp); ret = parse_handshake_header(session, bufel, htype, &tmp); if (ret < 0) { gnutls_assert(); _gnutls_audit_log("Invalid handshake packet headers. Discarding.\n"); break; } _mbuffer_consume(&session->internals.record_buffer, bufel, ret); data_size = MIN(tmp.length, tmp.end_offset-tmp.start_offset+1); ret = _gnutls_buffer_append_data(&tmp.data, _mbuffer_get_udata_ptr(bufel), data_size); if (ret < 0) return gnutls_assert_val(ret); _mbuffer_consume(&session->internals.record_buffer, bufel, data_size); ret = merge_handshake_packet(session, &tmp); if (ret < 0) return gnutls_assert_val(ret); } while(_mbuffer_get_udata_size(bufel) > 0); prev = bufel; bufel = _mbuffer_dequeue(&session->internals.record_buffer, bufel); _mbuffer_xfree(&prev); continue; next: bufel = _mbuffer_head_get_next(bufel, NULL); } while(bufel != NULL); /* sort in descending order */ if (session->internals.handshake_recv_buffer_size > 1) qsort(recv_buf, session->internals.handshake_recv_buffer_size, sizeof(recv_buf[0]), handshake_compare); while(session->internals.handshake_recv_buffer_size > 0 && recv_buf[LAST_ELEMENT].sequence < session->internals.dtls.hsk_read_seq) { _gnutls_audit_log("Discarded replayed handshake packet with sequence %d\n", recv_buf[LAST_ELEMENT].sequence); _gnutls_handshake_buffer_clear(&recv_buf[LAST_ELEMENT]); session->internals.handshake_recv_buffer_size--; } return get_last_packet(session, htype, hsk); } }
/* 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. * */ ssize_t _gnutls_io_read_buffered (gnutls_session_t session, size_t total, content_type_t recv_type) { ssize_t ret = 0; size_t min; mbuffer_st *bufel = NULL; size_t recvdata, readsize; if (total > MAX_RECV_SIZE(session) || total == 0) { gnutls_assert (); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } /* calculate the actual size, ie. get the minimum of the * buffered data and the requested data. */ min = MIN (session->internals.record_recv_buffer.byte_length, total); if (min > 0) { /* if we have enough buffered data * then just return them. */ if (min == total) { return min; } } /* min is over zero. recvdata is the data we must * receive in order to return the requested data. */ recvdata = total - min; readsize = recvdata; /* 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.byte_length + recvdata) > MAX_RECV_SIZE(session)) { gnutls_assert (); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } if (ret < 0) { gnutls_assert (); return ret; } /* READ DATA - but leave RCVLOWAT bytes in the kernel buffer. */ if (readsize > 0) { ret = _gnutls_read (session, &bufel, readsize, session->internals.pull_func); /* return immediately if we got an interrupt or eagain * error. */ if (ret < 0 && gnutls_error_is_fatal (ret) == 0) { _mbuffer_xfree (&bufel); return ret; } } /* copy fresh data to our buffer. */ if (ret > 0) { _gnutls_read_log ("RB: Have %d bytes into buffer. Adding %d bytes.\n", (int) session->internals.record_recv_buffer.byte_length, (int) ret); _gnutls_read_log ("RB: Requested %d bytes\n", (int) total); _mbuffer_enqueue (&session->internals.record_recv_buffer, bufel); } else _mbuffer_xfree (&bufel); if (ret < 0) { gnutls_assert (); return ret; } if (ret == 0) { /* EOF */ gnutls_assert (); return 0; } if(IS_DTLS(session)) ret = MIN(total, session->internals.record_recv_buffer.byte_length); else ret = session->internals.record_recv_buffer.byte_length; if ((ret > 0) && ((size_t) ret < total)) { /* Short Read */ return gnutls_assert_val(GNUTLS_E_AGAIN); } else { return ret; } }
static ssize_t _gnutls_dgram_read (gnutls_session_t session, mbuffer_st **bufel, gnutls_pull_func pull_func) { ssize_t i, ret; char *ptr; size_t max_size = _gnutls_get_max_decrypted_data(session); size_t recv_size = MAX_RECV_SIZE(session); gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; if (recv_size > max_size) recv_size = max_size; *bufel = _mbuffer_alloc (0, max_size); if (*bufel == NULL) return gnutls_assert_val(GNUTLS_E_MEMORY_ERROR); ptr = (*bufel)->msg.data; session->internals.direction = 0; reset_errno (session); i = pull_func (fd, ptr, recv_size); if (i < 0) { int err = get_errno (session); _gnutls_read_log ("READ: %d returned from %p, errno=%d gerrno=%d\n", (int) i, fd, errno, session->internals.errnum); if (err == EAGAIN) { ret = GNUTLS_E_AGAIN; goto cleanup; } else if (err == EINTR) { ret = GNUTLS_E_INTERRUPTED; goto cleanup; } else { gnutls_assert (); ret = GNUTLS_E_PULL_ERROR; goto cleanup; } } else { _gnutls_read_log ("READ: Got %d bytes from %p\n", (int) i, fd); if (i == 0) { /* If we get here, we likely have a stream socket. * FIXME: this probably breaks DCCP. */ gnutls_assert (); ret = 0; goto cleanup; } _mbuffer_set_udata_size (*bufel, i); } _gnutls_read_log ("READ: read %d bytes from %p\n", (int) i, fd); return i; cleanup: _mbuffer_xfree(bufel); return ret; }
int _gnutls13_send_session_ticket(gnutls_session_t session, unsigned nr, unsigned again) { int ret = 0; mbuffer_st *bufel = NULL; gnutls_buffer_st buf; tls13_ticket_st ticket; unsigned i; /* Client does not send a NewSessionTicket */ if (unlikely(session->security_parameters.entity == GNUTLS_CLIENT)) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); /* Session resumption has not been enabled */ if (session->internals.flags & GNUTLS_NO_TICKETS) return gnutls_assert_val(0); /* If we received the psk_key_exchange_modes extension which * does not have overlap with the server configuration, don't * send a session ticket */ if (session->internals.hsk_flags & HSK_PSK_KE_MODE_INVALID) return gnutls_assert_val(0); if (again == 0) { for (i=0;i<nr;i++) { unsigned init_pos; memset(&ticket, 0, sizeof(tls13_ticket_st)); bufel = NULL; ret = _gnutls_buffer_init_handshake_mbuffer(&buf); if (ret < 0) return gnutls_assert_val(ret); ret = generate_session_ticket(session, &ticket); if (ret < 0) { if (ret == GNUTLS_E_INT_RET_0) { ret = gnutls_assert_val(0); goto cleanup; } gnutls_assert(); goto cleanup; } ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.lifetime); if (ret < 0) { gnutls_assert(); goto cleanup; } ret = _gnutls_buffer_append_prefix(&buf, 32, ticket.age_add); if (ret < 0) { gnutls_assert(); goto cleanup; } /* append ticket_nonce */ ret = _gnutls_buffer_append_data_prefix(&buf, 8, ticket.nonce, ticket.nonce_size); if (ret < 0) { gnutls_assert(); goto cleanup; } /* append ticket */ ret = _gnutls_buffer_append_data_prefix(&buf, 16, ticket.ticket.data, ticket.ticket.size); if (ret < 0) { gnutls_assert(); goto cleanup; } _gnutls_free_datum(&ticket.ticket); /* append extensions */ ret = _gnutls_extv_append_init(&buf); if (ret < 0) { gnutls_assert(); goto cleanup; } init_pos = ret; ret = _gnutls_extv_append(&buf, ext_mod_early_data.tls_id, session, (extv_append_func)append_nst_extension); if (ret < 0) { gnutls_assert(); goto cleanup; } ret = _gnutls_extv_append_final(&buf, init_pos, 0); if (ret < 0) { gnutls_assert(); goto cleanup; } bufel = _gnutls_buffer_to_mbuffer(&buf); ret = _gnutls_send_handshake2(session, bufel, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, 1); if (ret < 0) { gnutls_assert(); goto cleanup; } session->internals.hsk_flags |= HSK_TLS13_TICKET_SENT; } } ret = _gnutls_handshake_io_write_flush(session); return ret; cleanup: _gnutls_free_datum(&ticket.ticket); _mbuffer_xfree(&bufel); _gnutls_buffer_clear(&buf); return ret; }