/** * gnutls_bye: * @session: is a #gnutls_session_t structure. * @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; } 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, 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; }
/** * gnutls_heartbeat_pong: * @session: is a #gnutls_session_t structure. * @flags: should be zero * * This function replies to a ping by sending a pong to the peer. * * Returns: %GNUTLS_E_SUCCESS on success, otherwise a negative error code. * * Since: 3.1.2 **/ int gnutls_heartbeat_pong(gnutls_session_t session, unsigned int flags) { int ret; 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); if (session->internals.hb_remote_data.length == 0) return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); ret = heartbeat_send_data(session, session->internals.hb_remote_data.data, session->internals.hb_remote_data.length, HEARTBEAT_RESPONSE); _gnutls_buffer_reset(&session->internals.hb_remote_data); if (ret < 0) return gnutls_assert_val(ret); return 0; }
/* This function writes the data that are left in the * Handshake write buffer (ie. because the previous write was * interrupted. * */ ssize_t _gnutls_handshake_io_write_flush (gnutls_session_t session) { mbuffer_head_st *const send_buffer = &session->internals.handshake_send_buffer; gnutls_datum_t msg; int ret; uint16_t epoch; ssize_t total = 0; mbuffer_st *cur; _gnutls_write_log ("HWRITE FLUSH: %d bytes in buffer.\n", (int) send_buffer->byte_length); if (IS_DTLS(session)) return _dtls_transmit(session); for (cur = _mbuffer_head_get_first (send_buffer, &msg); cur != NULL; cur = _mbuffer_head_get_first (send_buffer, &msg)) { epoch = cur->epoch; ret = _gnutls_send_int (session, cur->type, cur->htype, epoch, msg.data, msg.size, 0); if (ret >= 0) { total += ret; ret = _mbuffer_head_remove_bytes (send_buffer, ret); if (ret == 1) _gnutls_epoch_refcount_dec(session, epoch); _gnutls_write_log ("HWRITE: wrote %d bytes, %d bytes left.\n", ret, (int) send_buffer->byte_length); } else { _gnutls_write_log ("HWRITE error: code %d, %d bytes left.\n", ret, (int) send_buffer->byte_length); gnutls_assert (); return ret; } } return _gnutls_io_write_flush (session); }
/* This function is to be called if the handshake was successfully * completed. This sends a Change Cipher Spec packet to the peer. */ ssize_t _gnutls_send_change_cipher_spec (gnutls_session_t session, int again) { static const opaque data[1] = { GNUTLS_TYPE_CHANGE_CIPHER_SPEC }; _gnutls_handshake_log ("REC[%x]: Sent ChangeCipherSpec\n", session); if (again == 0) return _gnutls_send_int (session, GNUTLS_CHANGE_CIPHER_SPEC, -1, data, 1); else { return _gnutls_io_write_flush (session); } }
/* This function is like write. But it does not return -1 on error. * It does return gnutls_errno instead. * * This function takes full responsibility of freeing msg->data. * * In case of E_AGAIN and E_INTERRUPTED errors, you must call * gnutls_write_flush(), until it returns ok (0). * * We need to push exactly the data in msg->size, since we cannot send * less data. In TLS the peer must receive the whole packet in order * to decrypt and verify the integrity. * */ ssize_t _gnutls_io_write_buffered (gnutls_session_t session, mbuffer_st * bufel, unsigned int mflag) { mbuffer_head_st *const send_buffer = &session->internals.record_send_buffer; _mbuffer_enqueue (send_buffer, bufel); _gnutls_write_log ("WRITE: enqueued %d bytes for %p. Total %d bytes.\n", (int) bufel->msg.size, session->internals.transport_recv_ptr, (int) send_buffer->byte_length); if (mflag == MBUFFER_FLUSH) return _gnutls_io_write_flush (session); else return bufel->msg.size; }
/* This function transmits the flight that has been previously * buffered. * * This function is called from the handshake layer and calls the * record layer. */ int _dtls_transmit (gnutls_session_t session) { int ret; uint8_t* buf = NULL; unsigned int timeout; /* PREPARING -> SENDING state transition */ mbuffer_head_st *const send_buffer = &session->internals.handshake_send_buffer; mbuffer_st *cur; gnutls_handshake_description_t last_type = 0; unsigned int diff; struct timespec now; gettime(&now); /* If we have already sent a flight and we are operating in a * non blocking way, check if it is time to retransmit or just * return. */ if (session->internals.dtls.flight_init != 0 && session->internals.dtls.blocking == 0) { /* just in case previous run was interrupted */ ret = _gnutls_io_write_flush (session); if (ret < 0) { gnutls_assert(); goto cleanup; } if (session->internals.dtls.last_flight == 0 || !_dtls_is_async(session)) { /* check for ACK */ ret = _gnutls_io_check_recv(session, 0); if (ret == GNUTLS_E_TIMEDOUT) { /* if no retransmission is required yet just return */ if (_dtls_timespec_sub_ms(&now, &session->internals.dtls.last_retransmit) < TIMER_WINDOW) { gnutls_assert(); goto nb_timeout; } } else /* received something */ { if (ret == 0) { ret = is_next_hpacket_expected(session); if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) goto nb_timeout; if (ret < 0 && ret != GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { gnutls_assert(); goto cleanup; } if (ret == 0) goto end_flight; /* if ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET retransmit */ } else goto nb_timeout; } } } do { timeout = TIMER_WINDOW; diff = _dtls_timespec_sub_ms(&now, &session->internals.dtls.handshake_start_time); if (diff >= session->internals.dtls.total_timeout_ms) { _gnutls_dtls_log("Session timeout: %u ms\n", diff); ret = gnutls_assert_val(GNUTLS_E_TIMEDOUT); goto end_flight; } diff = _dtls_timespec_sub_ms(&now, &session->internals.dtls.last_retransmit); if (session->internals.dtls.flight_init == 0 || diff >= TIMER_WINDOW) { _gnutls_dtls_log ("DTLS[%p]: %sStart of flight transmission.\n", session, (session->internals.dtls.flight_init == 0)?"":"re-"); for (cur = send_buffer->head; cur != NULL; cur = cur->next) { ret = transmit_message (session, cur, &buf); if (ret < 0) { gnutls_assert(); goto end_flight; } last_type = cur->htype; } gettime(&session->internals.dtls.last_retransmit); if (session->internals.dtls.flight_init == 0) { session->internals.dtls.flight_init = 1; RESET_TIMER; timeout = TIMER_WINDOW; if (last_type == GNUTLS_HANDSHAKE_FINISHED) { /* On the last flight we cannot ensure retransmission * from here. _dtls_wait_and_retransmit() is being called * by handshake. */ session->internals.dtls.last_flight = 1; } else session->internals.dtls.last_flight = 0; } else { UPDATE_TIMER; } } ret = _gnutls_io_write_flush (session); if (ret < 0) { ret = gnutls_assert_val(ret); goto cleanup; } /* last message in handshake -> no ack */ if (session->internals.dtls.last_flight != 0) { /* we don't wait here. We just return 0 and * if a retransmission occurs because peer didn't receive it * we rely on the record or handshake * layer calling this function again. */ ret = 0; goto cleanup; } else /* all other messages -> implicit ack (receive of next flight) */ { if (session->internals.dtls.blocking != 0) ret = _gnutls_io_check_recv(session, timeout); else { ret = _gnutls_io_check_recv(session, 0); if (ret == GNUTLS_E_TIMEDOUT) { goto nb_timeout; } } if (ret == 0) { ret = is_next_hpacket_expected(session); if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED) goto nb_timeout; if (ret == GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET) { ret = GNUTLS_E_TIMEDOUT; goto keep_up; } if (ret < 0) { gnutls_assert(); goto cleanup; } goto end_flight; } } keep_up: gettime(&now); } while(ret == GNUTLS_E_TIMEDOUT); if (ret < 0) { ret = gnutls_assert_val(ret); goto end_flight; } ret = 0; end_flight: _gnutls_dtls_log ("DTLS[%p]: End of flight transmission.\n", session); _dtls_reset_hsk_state(session); cleanup: if (buf != NULL) gnutls_free(buf); /* SENDING -> WAITING state transition */ return ret; nb_timeout: if (buf != NULL) gnutls_free(buf); RETURN_DTLS_EAGAIN_OR_TIMEOUT(session, ret); }
/* 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 * @target_length: @data_size + 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 target_length, 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; 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; } if (data_size > MAX_USER_SEND_SIZE(session)) { if (IS_DTLS(session)) return gnutls_assert_val(GNUTLS_E_LARGE_PACKET); send_data_size = MAX_USER_SEND_SIZE(session); } 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 && target_length == 0))) return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); /* now proceed to packet encryption */ cipher_size = MAX_RECORD_SEND_SIZE(session); bufel = _mbuffer_alloc (0, cipher_size+CIPHER_SLACK_SIZE); 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]); header_size = RECORD_HEADER_SIZE(session); /* 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 target length: %d\n", session, _gnutls_packet2str (type), type, (int) data_size, (int) target_length); _mbuffer_set_udata_size(bufel, cipher_size); _mbuffer_set_uhead_size(bufel, header_size); ret = _gnutls_encrypt (session, data, send_data_size, target_length, 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; }
/* 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. * * 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_int (gnutls_session_t session, content_type_t type, gnutls_handshake_description_t htype, unsigned int epoch_rel, const void *_data, size_t sizeofdata, unsigned int mflags) { mbuffer_st *bufel; ssize_t cipher_size; int retval, ret; int data2send_size; uint8_t headers[5]; const uint8_t *data = _data; record_parameters_st *record_params; record_state_st *record_state; ret = _gnutls_epoch_get (session, epoch_rel, &record_params); if (ret < 0) { gnutls_assert (); return ret; } /* Safeguard against processing data with an incomplete cipher state. */ if (!record_params->initialized) { gnutls_assert (); return 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 && (sizeofdata == 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; } headers[0] = type; /* Use the default record version, if it is * set. */ copy_record_version (session, htype, &headers[1]); _gnutls_record_log ("REC[%p]: Sending Packet[%d] %s(%d) with length: %d\n", session, (int) _gnutls_uint64touint32 (&record_state->sequence_number), _gnutls_packet2str (type), type, (int) sizeofdata); if (sizeofdata > MAX_RECORD_SEND_SIZE) data2send_size = MAX_RECORD_SEND_SIZE; else data2send_size = sizeofdata; /* 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 { /* now proceed to packet encryption */ cipher_size = data2send_size + MAX_RECORD_OVERHEAD; bufel = _mbuffer_alloc (cipher_size, cipher_size); if (bufel == NULL) { gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } cipher_size = _gnutls_encrypt (session, headers, RECORD_HEADER_SIZE, data, data2send_size, _mbuffer_get_udata_ptr (bufel), cipher_size, type, (session->internals.priorities.no_padding == 0) ? 1 : 0, record_params); if (cipher_size <= 0) { gnutls_assert (); if (cipher_size == 0) cipher_size = GNUTLS_E_ENCRYPTION_FAILED; gnutls_free (bufel); return cipher_size; /* error */ } retval = data2send_size; session->internals.record_send_buffer_user_size = data2send_size; /* increase sequence number */ if (_gnutls_uint64pp (&record_state->sequence_number) != 0) { session_invalidate (session); gnutls_assert (); gnutls_free (bufel); return GNUTLS_E_RECORD_LIMIT_REACHED; } _mbuffer_set_udata_size (bufel, cipher_size); ret = _gnutls_io_write_buffered (session, bufel, mflags); } if (ret != cipher_size) { if (ret < 0 && gnutls_error_is_fatal (ret) == 0) { /* If we have sent any data then just return * the error value. Do not invalidate the session. */ gnutls_assert (); return ret; } if (ret > 0) { gnutls_assert (); ret = GNUTLS_E_INTERNAL_ERROR; } session_unresumable (session); session->internals.may_not_write = 1; gnutls_assert (); return ret; } session->internals.record_send_buffer_user_size = 0; _gnutls_record_log ("REC[%p]: Sent Packet[%d] %s(%d) with length: %d\n", session, (int) _gnutls_uint64touint32 (&record_state->sequence_number), _gnutls_packet2str (type), type, (int) cipher_size); return retval; }
/** * 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 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. * * 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_int (gnutls_session_t session, content_type_t type, gnutls_handshake_description_t htype, const void *_data, size_t sizeofdata) { uint8_t *cipher; int cipher_size; int retval, ret; int data2send_size; uint8_t headers[5]; const uint8_t *data = _data; int erecord_size = 0; opaque *erecord = NULL; /* 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.length == 0 && (sizeofdata == 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; } headers[0] = type; /* Use the default record version, if it is * set. */ copy_record_version (session, htype, &headers[1]); _gnutls_record_log ("REC[%x]: Sending Packet[%d] %s(%d) with length: %d\n", session, (int) _gnutls_uint64touint32 (&session->connection_state. write_sequence_number), _gnutls_packet2str (type), type, sizeofdata); if (sizeofdata > MAX_RECORD_SEND_SIZE) data2send_size = MAX_RECORD_SEND_SIZE; else data2send_size = sizeofdata; /* Only encrypt if we don't have data to send * from the previous run. - probably interrupted. */ if (session->internals.record_send_buffer.length > 0) { ret = _gnutls_io_write_flush (session); if (ret > 0) cipher_size = ret; else cipher_size = 0; cipher = NULL; retval = session->internals.record_send_buffer_user_size; } else { /* now proceed to packet encryption */ cipher_size = data2send_size + MAX_RECORD_OVERHEAD; cipher = gnutls_malloc (cipher_size); if (cipher == NULL) { gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } cipher_size = _gnutls_encrypt (session, headers, RECORD_HEADER_SIZE, data, data2send_size, cipher, cipher_size, type, 1); if (cipher_size <= 0) { gnutls_assert (); if (cipher_size == 0) cipher_size = GNUTLS_E_ENCRYPTION_FAILED; gnutls_afree (erecord); gnutls_free (cipher); return cipher_size; /* error */ } retval = data2send_size; session->internals.record_send_buffer_user_size = data2send_size; /* increase sequence number */ if (_gnutls_uint64pp (&session->connection_state.write_sequence_number) != 0) { session_invalidate (session); gnutls_assert (); gnutls_afree (erecord); gnutls_free (cipher); return GNUTLS_E_RECORD_LIMIT_REACHED; } ret = _gnutls_io_write_buffered2 (session, erecord, erecord_size, cipher, cipher_size); gnutls_afree (erecord); gnutls_free (cipher); } if (ret != cipher_size + erecord_size) { if (ret < 0 && gnutls_error_is_fatal (ret) == 0) { /* If we have sent any data then just return * the error value. Do not invalidate the session. */ gnutls_assert (); return ret; } if (ret > 0) { gnutls_assert (); ret = GNUTLS_E_INTERNAL_ERROR; } session_unresumable (session); session->internals.may_not_write = 1; gnutls_assert (); return ret; } session->internals.record_send_buffer_user_size = 0; _gnutls_record_log ("REC[%x]: Sent Packet[%d] %s(%d) with length: %d\n", session, (int) _gnutls_uint64touint32 (&session-> connection_state. write_sequence_number), _gnutls_packet2str (type), type, cipher_size); return retval; }