Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libcfnet/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,6 @@ libcfnet_la_SOURCES = \
protocol_version.c protocol_version.h \
server_code.c server_code.h \
stat_cache.c stat_cache.h \
stream_protocol.c stream_protocol.h \
tls_client.c tls_client.h \
tls_generic.c tls_generic.h
312 changes: 1 addition & 311 deletions libcfnet/file_stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,314 +30,10 @@
#include <definitions.h>
#include <logging.h>
#include <file_lib.h>
#include <stream_protocol.h>
#include <stdint.h>
#include <stdarg.h>

/*********************************************************/
/* Network protocol */
/*********************************************************/

/**
* @brief Simple network protocol on top of SSL/TCP. Used for client-server
* communication during file stream.
*
* @details Header format:
* +----------+----------+----------+----------+
* | SDU Len. | Reserved | EOF Flag | ERR Flag |
* +----------+----------+----------+----------+
* | 12 bits | 2 bits | 1 bit | 1 bit |
* +----------+----------+----------+----------+
*
* The header consists of 16 bits and the fields are defined as follows:
* SDU Length Length of the SDU (i.e. payload) encapsulated within
* this datagram.
* Reserved 2 bits reserved for future use.
* End-of-File flag Signals whether or not the receiver should expect to
* receive more datagrams.
* Error flag Signals that the transmission must be canceled due to
* unexpected error.
*
* @note If the End-of-File flag is set, there may still be data to process in
* in the payload. If the Error flag is set, there may be an error
* message in the payload.
*/
#define PROTOCOL_HEADER_SIZE 2

/**
* @note The TLS Generic API requires that the message length is less than
* CF_BUFSIZE. Furthermore, the protocol can only handle up to 4095
* Bytes, because it's the largest unsigned integer you can represent
* with 12 bits (2^12 - 1 = 4095).
*/
#define PROTOCOL_MESSAGE_SIZE MIN(CF_BUFSIZE - 1, 4095)

/**
* @brief Send a message using the file stream protocol
* @warning You probably want to use ProtocolSendMessage() or
* ProtocolSendError() instead
*
* @param conn The SSL connection object
* @param msg The message to send
* @param len The length of the message to send (must be less or equal to
* PROTOCOL_MESSAGE_SIZE Bytes)
* @param eof Set to true if this is the last message in a transaction,
* otherwise false
* @param err Set to true if transaction must be canceled (e.g., due to an
* unexpected error), otherwise false
* @note If the err parameter is set to true, the expected return value is
* still true.
* @return true on success, otherwise false
*/
static bool __ProtocolSendMessage(
SSL *conn, const char *msg, size_t len, bool eof, bool err)
{
assert(conn != NULL);
assert(msg != NULL || len == 0);
assert(len <= PROTOCOL_MESSAGE_SIZE);

/* Set message length */
assert(sizeof(len) >= 3); /* It's probably guaranteed, but let's make sure
* to avoid potentially nasty surprises */
uint16_t header = len << 4;

/* Set Error flag */
if (err)
{
header |= (1 << 0);
}

/* Set End-of-File flag */
if (eof)
{
header |= (1 << 1);
}

/* Send header */
header = htons(header);
int ret = TLSSend(conn, (char *) &header, PROTOCOL_HEADER_SIZE);
if (ret != PROTOCOL_HEADER_SIZE)
{
Log(LOG_LEVEL_ERR,
"Failed to send message header during file stream: "
"Expected to send %d bytes, but sent %d bytes",
PROTOCOL_HEADER_SIZE,
ret);
return false;
}

if (len > 0)
{
/* Send payload */
ret = TLSSend(conn, msg, len);
if (ret != (int) len)
{
Log(LOG_LEVEL_ERR,
"Failed to send message payload during file stream: "
"Expected to send %zu bytes, but sent %d bytes",
len,
ret);
return false;
}
}

return true;
}

/**
* @brief Send a message using the file stream protocol
*
* @param conn The SSL connection object
* @param msg The message to send
* @param len The length of the message to send (must be less or equal to
* PROTOCOL_MESSAGE_SIZE Bytes)
* @param eof Set to true if this is the last message in a transaction,
* otherwise false
* @return true on success, otherwise false
*/
static inline bool ProtocolSendMessage(
SSL *conn, const char *msg, size_t len, bool eof)
{
assert(conn != NULL);
assert(msg != NULL || len == 0);

return __ProtocolSendMessage(conn, msg, len, eof, false);
}

/**
* @brief Receive a message using the file stream protocol
*
* @param conn The SSL connection object
* @param msg The message receive buffer (must be PROTOCOL_MESSAGE_SIZE bytes
* large)
* @param len The length of the received message
* @param eof Is set to true if this was the last message in the transaction
* @return true on success, otherwise false
*
* @note ProtocolRecvMessage fails if the communication is broken or if we
* received an error from the remote host. In both cases, we should not
* try to flush the stream.
*/
static bool ProtocolRecvMessage(SSL *conn, char *msg, size_t *len, bool *eof)
{
assert(conn != NULL);
assert(msg != NULL);
assert(len != NULL);
assert(eof != NULL);

/* TLSRecv() expects a buffer this size */
char recv_buffer[CF_BUFSIZE];

/* Receive header */
int ret = TLSRecv(conn, recv_buffer, PROTOCOL_HEADER_SIZE);
if (ret != PROTOCOL_HEADER_SIZE)
{
Log(LOG_LEVEL_ERR,
"Failed to receive message header during file stream: "
"Expected to receive %d bytes, but received %d bytes",
PROTOCOL_HEADER_SIZE,
ret);
return false;
}

/* Why not receive the bytes directly into header in the TLSRecv()?
* Because it actually writes a NUL-Byte after the requested bytes which
* would cause memory violations. */
uint16_t header;
memcpy(&header, recv_buffer, PROTOCOL_HEADER_SIZE);
header = ntohs(header);

/* Extract Error flag */
bool err = header & (1 << 0);

/* Extract End-of-File flag */
*eof = header & (1 << 1);

/* Extract message length */
assert(sizeof(*len) >= 2); /* It's probably guaranteed, but let's make
* sure to avoid potentially nasty surprises */
*len = header >> 4;

/* Read payload */
if (*len > 0)
{
/* The TLSRecv() function's doc string says that the returned value
* may be less than the requested length if the other side completed a
* send with less bytes. I take it that this means that there is no
* short reads/recvs. Furthermore, TLSSend() says that its return
* value is always equal to the requested length as long as TLS is
* setup correctly. I take it that the same is true for TLSRecv().
* Hence, we will interpret a shorter read than what we expect as an
* error. */
ret = TLSRecv(conn, recv_buffer, *len);
if (ret != *len)
{
Log(LOG_LEVEL_ERR,
"Failed to receive message payload during file stream: "
"Expected to receive %zu bytes, but received %d bytes",
*len,
ret);
return false;
}
memcpy(msg, recv_buffer, *len);

if (err)
{
/* If the error flag is set, then the payload contains an error
* message of 'len' bytes. */
assert(*len < sizeof(recv_buffer));
recv_buffer[*len] = '\0'; /* Set terminating null-byte */
Log(LOG_LEVEL_ERR, "Remote file stream error: %s", recv_buffer);
}
}

return !err;
}

/**
* @brief Flush the file stream
*
* It's used to prevent the remote host from blocking while sending the
* remaining data after we have experienced an unexpected error and need to
* abort the file stream. Once the stream has been successfully flushed, the
* remote host will be ready to receive our error message.
*
* @param conn The SSL connection object
* @return true on success, otherwise false
*/
static bool ProtocolFlushStream(SSL *conn)
{
assert(conn != NULL);

char msg[PROTOCOL_MESSAGE_SIZE];
size_t len;
bool eof;
while (ProtocolRecvMessage(conn, msg, &len, &eof))
{
if (eof)
{
return true;
}
}

/* Error is already logged in ProtocolRecvMessage() */
return false;
}

/**
* @brief Send an error message using the file stream protocol
*
* @param conn The SSL connection object
* @param flush Whether or not to flush the stream (see ProtocolFlushStream())
* @param fmt The format string
* @param ... The format string arguments
* @return true on success, otherwise false
*/
static bool ProtocolSendError(SSL *conn, bool flush, const char *fmt, ...)
FUNC_ATTR_PRINTF(3, 4);

static bool ProtocolSendError(SSL *conn, bool flush, const char *fmt, ...)
{
assert(conn != NULL);
assert(fmt != NULL);

va_list ap;
char msg[PROTOCOL_MESSAGE_SIZE];

va_start(ap, fmt);
int len = vsnprintf(msg, PROTOCOL_MESSAGE_SIZE, fmt, ap);
va_end(ap);

assert(len >= 0); /* Let's make sure we detect this in debug builds */
if (len < 0)
{
Log(LOG_LEVEL_ERR,
"Failed to format error message during file stream");
len = 0; /* We still want to send the header */
}
else if (len >= PROTOCOL_MESSAGE_SIZE)
{
Log(LOG_LEVEL_WARNING,
"Error message truncated during file stream: "
"Message is %d bytes, but maximum message size is %d bytes",
len,
PROTOCOL_MESSAGE_SIZE);
/* Add dots to indicate message truncation. We don't need the
* terminating NULL-byte in the buffer. Furthermore, TLSRecv() will
* append one, upon receiving the message */
msg[PROTOCOL_MESSAGE_SIZE - 1] = '.';
msg[PROTOCOL_MESSAGE_SIZE - 2] = '.';
msg[PROTOCOL_MESSAGE_SIZE - 3] = '.';
len = PROTOCOL_MESSAGE_SIZE;
}

if (flush)
{
ProtocolFlushStream(conn);
}

return __ProtocolSendMessage(conn, msg, (size_t) len, false, true);
}

/*********************************************************/
/* Common */
/*********************************************************/
Expand Down Expand Up @@ -530,9 +226,6 @@ static bool DrainOutputBufferToFile(
/* Server specific */
/*********************************************************/

#define ERROR_MSG_UNSPECIFIED_SERVER_REFUSAL "Unspecified server refusal"
#define ERROR_MSG_INTERNAL_SERVER_ERROR "Internal server error"

bool FileStreamRefuse(SSL *conn)
{
return ProtocolSendError(
Expand Down Expand Up @@ -734,9 +427,6 @@ bool FileStreamServe(SSL *conn, const char *filename)
/* Client specific */
/*********************************************************/

#define ERROR_MSG_INTERNAL_CLIENT_ERROR "Internal client error"


/**
* @brief Get the size of a file
*
Expand Down
Loading
Loading