/** * @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; }
/** * @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; }
/** * @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; }
/** * @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; }
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); }
/** * @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); }