Exemplo n.º 1
0
Arquivo: tls.c Projeto: lavabit/magma
/**
 * @brief	Consolidate the complicated logic associated with handling SSL_read/SSL_write calls which result in 0, or a negative number.
 */
stringer_t * tls_error(TLS *tls, int_t code, stringer_t *output) {

	int tlserr = 0, syserr = 0;
	chr_t *message = MEMORYBUF(1024);
	stringer_t *result = NULL;

	// Check the SSL_get_error() function. If any of the codes listed here (0, 2 or 3) are returned, then the SSL input/output is
	// still valid, and may be used for future read and write operations.
	if ((tlserr = SSL_get_error_d(tls, code)) == SSL_ERROR_NONE || tlserr == SSL_ERROR_WANT_READ || tlserr == SSL_ERROR_WANT_WRITE) {
		return NULL;
	}

	// If we make it past here, then there should be an error message in the buffer by the time we exit this function.
	else if (!(result = st_output(output, 128))) {
		log_pedantic("Unable to record the TLS error because the output buffer is invalid.");
		return NULL;
	}

	// We need to record the errno locally to prevent any future function call from clobbering the relevant value.
	syserr = errno;

	// Note that if errno is non-zero, then the SSL error was probably just an indication we had a system level event, like a peer disconnect.
	if (syserr != 0) {
		st_sprint(result, "error = %i / errno = %i / message = %s", tlserr, syserr, strerror_r(syserr, message, 1024));
	}
	// If the operation returned a negative value, but errno indicate no particular problem, then we record that scenario here.
	else if (code < 0 && syserr == 0) {
		st_sprint(result, "error = %i / errno = 0", tlserr);
	}

	// SSL error code number 1, an SSL library error occurred, usually because of a protocol problem, and thus the OpenSSL
	// error queue should hold more information about the fault.
	else if (tlserr == SSL_ERROR_SSL) {
		ERR_error_string_n_d(ERR_get_error_d(), message, 1024);
		st_sprint(result, "error = %i / errno = 0 / message = %s", tlserr, message);
	}

	// SSL error code number 5, a non-recoverable I/O error occurred. Usually errno will provide a clue, but not always.
	else if (tlserr == SSL_ERROR_SYSCALL) {
		ERR_error_string_n_d(ERR_get_error_d(), message, 1024);
		st_sprint(result, "error = %i / errno = 0 / message = %s", tlserr, message);
	}

	// SSL error code number 6, which seems to indicate the remote host shut down the connection.
	else if (tlserr == SSL_ERROR_ZERO_RETURN) {
		st_sprint(result, "error = %i / message = Connection shut down.", tlserr);
	}

	// We end up here if the result was zero, and errno was zero, and we didn't recognize the SSL error code directly.
	else {
		st_sprint(result, "error = %i / errno = 0", tlserr);
	}

	return result;
}
Exemplo n.º 2
0
Arquivo: tls.c Projeto: lavabit/magma
/**
 * @brief	Return -1 if the connection is invalid, 0 if the operation should be retried, or a positive number indicating the
 * 			number of bytes processed.
 */
int tls_continue(TLS *tls, int result, int syserror) {

	int holder = 0;
	unsigned long tlserror = 0;
	chr_t *message = MEMORYBUF(1024);

	// Check that the daemon hasn't initiated a shutdown.
	if (!status()) return -1;

	// Data was processed, so there is no need to retry the operation.
	else if (result > 0) return result;

	// Switch statement will process neutral/negative result codes.
	switch ((holder = SSL_get_error_d(tls, result))) {

		// This result is expected when no more data is expected, such as when the end-of-file terminator ir reached.
		case SSL_ERROR_ZERO_RETURN:
			// This indicates a non-error occurred, such as a timeout lapse, or shutdown/close notifcation is reccieved.
		case SSL_ERROR_NONE:
			result = -1;
			break;

			// This indicates the operation should be retried, possibly because of a renegotiation, or other out-of-band
			// interrupted the operation.
		case SSL_ERROR_WANT_WRITE:
		case SSL_ERROR_WANT_READ:
			result = 0;
			break;

			// A TLS error ocurred, check the error stack to find out more.
		case SSL_ERROR_SSL:
			ERR_error_string_n_d((tlserror = ERR_get_error_d()), message, 1024);
			log_pedantic("A TLS error occurred. { error = %lu / message = %s", tlserror, message);
			result = -1;
			break;

			// Indicates the call returned because of a transport error. Check errno for more information.
		case SSL_ERROR_SYSCALL:
			log_pedantic("A TCP error occurred. { errno = %i / error = %s / message = %s }", syserror, errno_name(syserror),
				strerror_r(syserror, message, 1024));
			result = -1;
			break;

		default:
			log_pedantic("An unexpected TLS error result was encountered. { error = %i }", holder);
			result = 0;
			break;
	}

	return result;
}
Exemplo n.º 3
0
Arquivo: tls.c Projeto: lavabit/magma
/**
 * @brief	Establish an TLS client wrapper around a socket descriptor.
 * @param	sockd	the file descriptor of the socket to have its transport security level upgraded.
 * @return	NULL on failure or a pointer to the SSL handle of the file descriptor if SSL negotiation was successful.
 */
void * tls_client_alloc(int_t sockd) {

	BIO *bio;
	SSL *tls;
	int_t result = 0, counter = 0;
	SSL_CTX *ctx = NULL;
	long options = (SSL_OP_ALL | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_MODE_AUTO_RETRY);

	// Clear the error state, so we get accurate indications of a problem.
	errno = 0;
	ERR_clear_error_d();

	if (!(ctx = SSL_CTX_new_d(SSLv23_client_method_d()))) {
		log_pedantic("Could not create a valid TLS context. { error = %s }", ssl_error_string(MEMORYBUF(512), 512));
		return NULL;
	}
	else if ((SSL_CTX_ctrl_d(ctx, SSL_CTRL_OPTIONS, options, NULL) & options) != options) {
		log_pedantic("Could set the options mask on the TLS context. { error = %s }", ssl_error_string(MEMORYBUF(512), 512));
		SSL_CTX_free_d(ctx);
		return NULL;
	}

	/// LOW: Add requisite config options and sandbox resources to verify server TLS certificates.
	// SSL_CTX_load_verify_locations(result, SSL_CAFILE, SSL_CAPATH);
	// lookup = X509_STORE_add_lookup(SSL_CTX_get_cert_store(result.context), X509_LOOKUP_file());
	// X509_load_crl_file(lookup, SSL_CRLFILE, X509_FILETYPE_PEM);
	// X509_STORE_set_flags(SSL_CTX_get_cert_store(result.context), X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);
	// mode = SSL_VERIFY_NONE or SSL_VERIFY_PEER
	// SSL_CTX_set_verify(result.context, mode, cert_verify_callback);

	// We don't bother with server certificate verification, just yet.
	SSL_CTX_set_verify_d(ctx, SSL_VERIFY_NONE, NULL);

	if (!(tls = SSL_new_d(ctx))) {
		log_pedantic("Could create the TLS client connection context. { error = %s }", ssl_error_string(MEMORYBUF(512), 512));
		SSL_CTX_free_d(ctx);
		return NULL;
	}
	else if (!(bio = BIO_new_socket_d(sockd, BIO_NOCLOSE))) {
		log_pedantic("Could not create the TLS client BIO context. { error = %s }", ssl_error_string(MEMORYBUF(512), 512));
		SSL_CTX_free_d(ctx);
		SSL_free_d(tls);
		return NULL;
	}

	SSL_set_bio_d(tls, bio, bio);
	SSL_set_connect_state_d(tls);

	SSL_CTX_free_d(ctx);

	do {

		// Attempt the connection. Retry if the error indicates a retryable error.
		if ((result = SSL_connect_d(tls)) < 0) {
			log_pedantic("Could not establish a TLS client connection, but the result indicates we should retry. { error = %i }", SSL_get_error_d(tls, result));
		}

		else if (result < 1) {
			log_pedantic("Could not establish a TLS client connection. { error = %s }", ssl_error_string(MEMORYBUF(512), 512));
		}

	} while (result < 0 && counter++ < 10);

	if (result != 1) {
		SSL_free_d(tls);
		return NULL;
	}

	return tls;
}
Exemplo n.º 4
0
Arquivo: tls.c Projeto: lavabit/magma
/**
 * @brief	Create a TLS session for a file descriptor, and accept the client TLS/SSL handshake.
 * @see		SSL_accept()
 * @see		BIO_new_socket()
 * @param	server	a server object which contains the underlying SSL context.
 * @param	sockd	the file descriptor of the TCP connection to be made SSL-ready.
 * @param	flags	passed to BIO_new_socket(), determines whether the socket is shut down when the BIO is freed.
 */
TLS * tls_server_alloc(void *server, int sockd, int flags) {

	SSL *tls;
	BIO *bio;
	server_t *local = server;
	int_t error = 0, result = 0, counter = 0;

	// Clear the error state, so we get accurate indications of a problem.
	errno = 0;
	ERR_clear_error_d();

#ifdef MAGMA_PEDANTIC
	if (!local) {
		log_pedantic("Passed a NULL server pointer.");
	}
	else if (!local->tls.context) {
		log_pedantic("Passed a NULL SSL context pointer.");
	}
	else if (sockd < 0) {
		log_pedantic("Passed an invalid socket. { sockd = %i }", sockd);
	}
#endif

	if (!local || !local->tls.context || sockd < 0) {
		return NULL;
	}
	else if (!(tls = SSL_new_d(local->tls.context)) || !(bio = BIO_new_socket_d(sockd, flags))) {
		log_pedantic("TLS/BIO allocation error. { error = %s }", ssl_error_string(MEMORYBUF(256), 256));

		if (tls) {
			SSL_free_d(tls);
		}

		return NULL;
	}

	SSL_set_bio_d(tls, bio, bio);
	SSL_set_accept_state_d(tls);

	// If the result code indicates a handshake error, but the TCP connection is still alive, we retry the handshake.
	do {
		// Attempt the server connection setup.
		if ((result = SSL_accept_d(tls)) <= 0 && status()) {

			switch ((error = SSL_get_error_d(tls, result))) {

				// Log these errors with extra information.
				case (SSL_ERROR_SSL):
					log_pedantic("TLS accept error. { accept = %i / error = SSL_ERROR_SSL, message = %s }", result,
						ssl_error_string(MEMORYBUF(512), 512));
					break;
				case (SSL_ERROR_SYSCALL):
					log_pedantic("TLS accept error. { accept = %i / error = SSL_ERROR_SYSCALL / errno = %i / message = %s }", result,
						errno, errno_name(errno));
					break;

				// A zero return indicates a socket shutdown. The latter should never happen.
				case (SSL_ERROR_ZERO_RETURN):
				case (SSL_ERROR_NONE):
					break;

				default:
					log_pedantic("TLS accept error. { accept = %i / error = %i }", result, error);
					break;
			}
		}

	} while (result < 0 && counter++ < 10);

	if (result != 1) {
		SSL_free_d(tls);
		return NULL;
	}

	return tls;
}
Exemplo n.º 5
0
int64_t client_read(client_t *client) {

	ssize_t bytes;
	bool_t blocking;
	int sslerr;

	if (!client || client->sockd == -1) {
		client->status = -1;
		return -1;
	}

	// Check for data past the current line buffer.
	if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) {

		// Move the unused data to the front of the buffer.
		mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line));

		// Update the length.
		st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line));

		// Clear the line buffer.
		client->line = pl_null();
	}
	// Otherwise reset the buffer and line lengths to zero.
	else {
		st_length_set(client->buffer, 0);
		client->line = pl_null();
	}

	// Loop until the buffer has data or we get an error.
	do {
		blocking = st_length_get(client->buffer) ? false : true;

		// Read bytes off the network. If data is already in the buffer this should be a non-blocking read operation so we can
		// return the already buffered data without delay.
		if (client->ssl) {
			bytes = ssl_read(client->ssl, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), blocking);
			sslerr = SSL_get_error_d(client->ssl, bytes);
		}
		else {
			bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer),
				blocking ? 0 : MSG_DONTWAIT);
		}

		// Check for errors on SSL reads.
		if (client->ssl) {

			// If 0 bytes were read, and it wasn't related to a shutdown, or if < 0 was returned and there was no more data waiting, it's an error.
			if ((!bytes && sslerr != SSL_ERROR_NONE && sslerr != SSL_ERROR_ZERO_RETURN) ||
				((bytes < 0) && sslerr != SSL_ERROR_WANT_READ)) {
				client->status = -1;
				return -1;
			}
		// Check for errors on non-SSL reads in the traditional way.
		} else if (bytes < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
			client->status = -1;
			return -1;
		}

		if (bytes > 0) {
			st_length_set(client->buffer, st_length_get(client->buffer) + bytes);
		// Or break out of the loop because we've been shutdown.
		} else if (!bytes) {
			break;
		}

	} while (status() && blocking && !st_length_get(client->buffer));

	// If there is data in the buffer process it. Otherwise if the buffer is empty and the connection appears to be closed
	// (as indicated by a return value of 0), then return -1 to let the caller know the connection is dead.
	if (st_length_get(client->buffer)) {
		client->status = 1;
	}
	else if (!bytes) {
		client->status = 2;
		return -2;
	}

	return st_length_get(client->buffer);
}
Exemplo n.º 6
0
/**
 * @brief	Read a line of input from a network client session.
 *
 *
 * @return
 *
 *
 */
int64_t client_read_line(client_t *client) {

	ssize_t bytes;
	bool_t line = false;
	int sslerr;

	if (!client || client->sockd == -1) {
		client->status = 1;
		return -1;
	}

	// Check for data past the current line buffer.
	if (pl_length_get(client->line) && st_length_get(client->buffer) > pl_length_get(client->line)) {

		// Move the unused data to the front of the buffer.
		mm_move(st_data_get(client->buffer), st_data_get(client->buffer) + pl_length_get(client->line), st_length_get(client->buffer) - pl_length_get(client->line));

		// Update the length.
		st_length_set(client->buffer, st_length_get(client->buffer) - pl_length_get(client->line));

		// Check whether the data we just moved contains a complete line.
		if (!pl_empty((client->line = line_pl_st(client->buffer, 0)))) {
			client->status = 1;
			return pl_length_get(client->line);
		}
	}
	// Otherwise reset the buffer and line lengths to zero.
	else {
		st_length_set(client->buffer, 0);
		client->line = pl_null();
	}

	// Loop until we get a complete line, an error, or the buffer is filled.
	do {

		// Read bytes off the network. Skip past any existing data in the buffer.
		if (client->ssl) {
			bytes = ssl_read(client->ssl, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), true);
			sslerr = SSL_get_error_d(client->ssl, bytes);
		}
		else {
			bytes = recv(client->sockd, st_char_get(client->buffer) + st_length_get(client->buffer), st_avail_get(client->buffer) - st_length_get(client->buffer), 0);
		}

		// Check for errors on SSL reads.
		if (client->ssl) {

			// If 0 bytes were read, and it wasn't related to a shutdown, or if < 0 was returned and there was no more data waiting, it's an error.
			if ((!bytes && sslerr != SSL_ERROR_NONE && sslerr != SSL_ERROR_ZERO_RETURN) ||
				((bytes < 0) && sslerr != SSL_ERROR_WANT_READ)) {
				client->status = -1;
				return -1;
			}
		}

		// Check for errors on non-SSL reads in the traditional way.
		else if (bytes < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK) {
			client->status = -1;
			return -1;
		}

		if (bytes > 0) {
			st_length_set(client->buffer, st_length_get(client->buffer) + bytes);
		}

		// Check whether we have a complete line before checking whether the connection was closed.
		if (!st_empty(client->buffer) && !pl_empty((client->line = line_pl_st(client->buffer, 0)))) {
			line = true;
		}
		// Otherwise if the connection has been closed (as indicated by a return value of 0) the line will never terminate. As such
		// the best course of action is to return an error code up the stack to indicate the disconnect.
		else if (!bytes) {
			client->status = 2;
			return -2;
		}

	} while (status() && !line && st_length_get(client->buffer) != st_avail_get(client->buffer));

	if (st_length_get(client->buffer) > 0) {
		client->status = 1;
	}

	return pl_length_get(client->line);
}