/* Checks whether there are received data within * a timeframe. * * Returns 0 if data were received, GNUTLS_E_TIMEDOUT * on timeout and a negative error code on error. */ int _gnutls_io_check_recv(gnutls_session_t session, unsigned int ms) { gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; int ret = 0, err; if (unlikely (session->internals.pull_timeout_func == gnutls_system_recv_timeout && session->internals.pull_func != system_read)) { _gnutls_debug_log("The pull function has been replaced but not the pull timeout."); return gnutls_assert_val(GNUTLS_E_PULL_ERROR); } reset_errno(session); ret = session->internals.pull_timeout_func(fd, ms); if (ret == -1) { err = get_errno(session); _gnutls_read_log ("READ_TIMEOUT: %d returned from %p, errno=%d (timeout: %u)\n", (int) ret, fd, err, ms); return errno_to_gerr(err, IS_DTLS(session)); } if (ret > 0) return 0; else return GNUTLS_E_TIMEDOUT; }
/* * @ms: a pointer to the number of milliseconds to wait for data. Use zero or NULL for indefinite. * * 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_record_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. * * If the @ms parameter is non zero then this function will return before * the given amount of milliseconds or return GNUTLS_E_TIMEDOUT. * */ ssize_t _gnutls_io_read_buffered(gnutls_session_t session, size_t total, content_type_t recv_type, unsigned int *ms) { ssize_t ret; size_t min; mbuffer_st *bufel = NULL; size_t recvdata, readsize; if (total > max_record_recv_size(session) || total == 0) { gnutls_assert(); return GNUTLS_E_RECORD_OVERFLOW; } /* 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_record_recv_size(session)) { gnutls_assert(); /* internal error */ return GNUTLS_E_INVALID_REQUEST; } /* READ DATA */ if (readsize > 0) { ret = _gnutls_read(session, &bufel, readsize, session->internals.pull_func, ms); /* return immediately if we got an interrupt or eagain * error. */ if (ret < 0) { return gnutls_assert_val(ret); } if (ret == 0) /* EOF */ return gnutls_assert_val(0); /* copy fresh data to our buffer. */ _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); 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; } else return gnutls_assert_val(0); }
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; }
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; }
/* 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, alloc_size; *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. */ alloc_size = recvdata + session->internals.record_recv_buffer.length; session->internals.record_recv_buffer.data = gnutls_realloc_fast (session->internals.record_recv_buffer.data, alloc_size); if (session->internals.record_recv_buffer.data == NULL) { gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } 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; } }
/* This function is like read. But it does not return -1 on error. * It does return gnutls_errno instead. * * Flags are only used if the default recv() function is being used. */ static ssize_t _gnutls_read (gnutls_session_t session, void *iptr, size_t sizeOfPtr, int flags) { size_t left; ssize_t i = 0; char *ptr = iptr; unsigned j, x, sum = 0; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; session->internals.direction = 0; left = sizeOfPtr; while (left > 0) { if (session->internals._gnutls_pull_func == NULL) i = recv (GNUTLS_POINTER_TO_INT(fd), &ptr[sizeOfPtr - left], left, flags); else i = session->internals._gnutls_pull_func (fd, &ptr[sizeOfPtr - left], left); if (i < 0) { _gnutls_read_log ("READ: %d returned from %d, errno=%d\n", i, fd, errno); if (errno == EAGAIN || errno == EINTR) { if (sizeOfPtr - left > 0) { _gnutls_read_log ("READ: returning %d bytes from %d\n", sizeOfPtr - left, fd); goto finish; } gnutls_assert (); return RET (errno); } else { gnutls_assert (); return GNUTLS_E_PULL_ERROR; } } else { _gnutls_read_log ("READ: Got %d bytes from %d\n", i, fd); if (i == 0) break; /* EOF */ } left -= i; } finish: if (_gnutls_log_level >= 7) { char line[128]; char tmp[16]; _gnutls_read_log ("READ: read %d bytes from %d\n", (sizeOfPtr - left), fd); for (x = 0; x < ((sizeOfPtr - left) / 16) + 1; x++) { line[0] = 0; sprintf (tmp, "%.4x - ", x); _gnutls_str_cat (line, sizeof (line), tmp); for (j = 0; j < 16; j++) { if (sum < (sizeOfPtr - left)) { sprintf (tmp, "%.2x ", ((unsigned char *) ptr)[sum++]); _gnutls_str_cat (line, sizeof (line), tmp); } } _gnutls_read_log ("%s\n", line); } } return (sizeOfPtr - left); }
static ssize_t _gnutls_stream_read (gnutls_session_t session, mbuffer_st **bufel, size_t size, gnutls_pull_func pull_func) { size_t left; ssize_t i = 0; size_t max_size = _gnutls_get_max_decrypted_data(session); char *ptr; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; *bufel = _mbuffer_alloc (0, MAX(max_size, size)); if (!*bufel) { gnutls_assert (); return GNUTLS_E_MEMORY_ERROR; } ptr = (*bufel)->msg.data; session->internals.direction = 0; left = size; while (left > 0) { 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; } if (err == EAGAIN) return GNUTLS_E_AGAIN; return GNUTLS_E_INTERRUPTED; } else { gnutls_assert (); return GNUTLS_E_PULL_ERROR; } } 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; } finish: _gnutls_read_log ("READ: read %d bytes from %p\n", (int) (size - left), fd); return (size - left); }
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; }
/* This function is like read. But it does not return -1 on error. * It does return gnutls_errno instead. * * Flags are only used if the default recv() function is being used. */ static ssize_t _gnutls_read (gnutls_session_t session, void *iptr, size_t sizeOfPtr, int flags) { size_t left; ssize_t i = 0; char *ptr = iptr; unsigned j, x, sum = 0; gnutls_transport_ptr_t fd = session->internals.transport_recv_ptr; session->internals.direction = 0; left = sizeOfPtr; while (left > 0) { session->internals.errnum = 0; if (session->internals._gnutls_pull_func == NULL) { i = recv (GNUTLS_POINTER_TO_INT (fd), &ptr[sizeOfPtr - left], left, flags); #if HAVE_WINSOCK2_H if (i < 0) { int tmperr = WSAGetLastError (); switch (tmperr) { case WSAEWOULDBLOCK: session->internals.errnum = EAGAIN; break; case WSAEINTR: session->internals.errnum = EINTR; break; default: session->internals.errnum = EIO; break; } WSASetLastError (tmperr); } #endif } else i = session->internals._gnutls_pull_func (fd, &ptr[sizeOfPtr - left], left); if (i < 0) { int err = session->internals.errnum ? session->internals.errnum : errno; _gnutls_read_log ("READ: %d returned from %p, errno=%d gerrno=%d\n", i, fd, errno, session->internals.errnum); if (err == EAGAIN || err == EINTR) { if (sizeOfPtr - left > 0) { _gnutls_read_log ("READ: returning %d bytes from %p\n", sizeOfPtr - left, fd); goto finish; } gnutls_assert (); if (err == EAGAIN) return GNUTLS_E_AGAIN; return GNUTLS_E_INTERRUPTED; } else { gnutls_assert (); return GNUTLS_E_PULL_ERROR; } } else { _gnutls_read_log ("READ: Got %d bytes from %p\n", i, fd); if (i == 0) break; /* EOF */ } left -= i; } finish: if (_gnutls_log_level >= 7) { char line[128]; char tmp[16]; _gnutls_read_log ("READ: read %d bytes from %p\n", (sizeOfPtr - left), fd); for (x = 0; x < ((sizeOfPtr - left) / 16) + 1; x++) { line[0] = 0; sprintf (tmp, "%.4x - ", x); _gnutls_str_cat (line, sizeof (line), tmp); for (j = 0; j < 16; j++) { if (sum < (sizeOfPtr - left)) { sprintf (tmp, "%.2x ", ((unsigned char *) ptr)[sum++]); _gnutls_str_cat (line, sizeof (line), tmp); } } _gnutls_read_log ("%s\n", line); } } return (sizeOfPtr - left); }