idevice: Route OpenSSL reads/writes through internal_connection_*

Let's not allow OpenSSL to directly access our file descriptors
diff --git a/src/idevice.c b/src/idevice.c
index ecc0418..a2a4d0b 100644
--- a/src/idevice.c
+++ b/src/idevice.c
@@ -432,6 +432,8 @@
 		new_connection->data = (void*)(long)sfd;
 		new_connection->ssl_data = NULL;
 		new_connection->device = device;
+		new_connection->ssl_recv_timeout = (unsigned int)-1;
+		new_connection->status = IDEVICE_E_SUCCESS;
 		*connection = new_connection;
 		return IDEVICE_E_SUCCESS;
 	} else if (device->conn_type == CONNECTION_NETWORK) {
@@ -478,6 +480,7 @@
 		new_connection->data = (void*)(long)sfd;
 		new_connection->ssl_data = NULL;
 		new_connection->device = device;
+		new_connection->ssl_recv_timeout = (unsigned int)-1;
 
 		*connection = new_connection;
 
@@ -558,15 +561,10 @@
 	}
 
 	if (connection->ssl_data) {
+		connection->status = IDEVICE_E_SUCCESS;
 		uint32_t sent = 0;
 		while (sent < len) {
 #ifdef HAVE_OPENSSL
-			int c = socket_check_fd((int)(long)connection->data, FDM_WRITE, 100);
-			if (c == 0 || c == -ETIMEDOUT || c == -EAGAIN) {
-				continue;
-			} else if (c < 0) {
-				break;
-			}
 			int s = SSL_write(connection->ssl_data->session, (const void*)(data+sent), (int)(len-sent));
 			if (s <= 0) {
 				int sslerr = SSL_get_error(connection->ssl_data->session, s);
@@ -586,7 +584,7 @@
 		debug_info("SSL_write %d, sent %d", len, sent);
 		if (sent < len) {
 			*sent_bytes = 0;
-			return IDEVICE_E_SSL_ERROR;
+			return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status;
 		}
 		*sent_bytes = sent;
 		return IDEVICE_E_SUCCESS;
@@ -670,30 +668,17 @@
 
 	if (connection->ssl_data) {
 		uint32_t received = 0;
-		int do_select = 1;
-		idevice_error_t error = IDEVICE_E_SSL_ERROR;
 
+		if (connection->ssl_recv_timeout != (unsigned int)-1) {
+			debug_info("WARNING: ssl_recv_timeout was not properly reset in idevice_connection_receive_timeout");
+		}
+
+		// this should be reset after the SSL_read call on all codepaths, as
+		// the supplied timeout should only apply to the current read.
+		connection->ssl_recv_timeout = timeout;
+		connection->status = IDEVICE_E_SUCCESS;
 		while (received < len) {
 #ifdef HAVE_OPENSSL
-			do_select = (SSL_pending(connection->ssl_data->session) == 0);
-#endif
-			if (do_select) {
-				int conn_error = socket_check_fd((int)(long)connection->data, FDM_READ, timeout);
-				error = socket_recv_to_idevice_error(conn_error, len, received);
-				switch (error) {
-					case IDEVICE_E_SUCCESS:
-					case IDEVICE_E_TIMEOUT:
-						break;
-					case IDEVICE_E_UNKNOWN_ERROR:
-					default:
-						debug_info("ERROR: socket_check_fd returned %d (%s)", conn_error, strerror(-conn_error));
-						return error;
-				}
-			}
-			if (error == IDEVICE_E_TIMEOUT) {
-				break;
-			}
-#ifdef HAVE_OPENSSL
 			int r = SSL_read(connection->ssl_data->session, (void*)((char*)(data+received)), (int)len-received);
 			if (r > 0) {
 				received += r;
@@ -713,11 +698,12 @@
 			}
 #endif
 		}
+		connection->ssl_recv_timeout = (unsigned int)-1;
 
 		debug_info("SSL_read %d, received %d", len, received);
 		if (received < len) {
 			*recv_bytes = received;
-			return error;
+			return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status;
 		}
 
 		*recv_bytes = received;
@@ -763,6 +749,10 @@
 	}
 
 	if (connection->ssl_data) {
+		if (connection->ssl_recv_timeout != (unsigned int)-1) {
+			debug_info("WARNING: ssl_recv_timeout was not properly reset in idevice_connection_receive_timeout");
+			connection->ssl_recv_timeout = (unsigned int)-1;
+		}
 #ifdef HAVE_OPENSSL
 		int received = SSL_read(connection->ssl_data->session, (void*)data, (int)len);
 		debug_info("SSL_read %d, received %d", len, received);
@@ -816,28 +806,36 @@
 	return IDEVICE_E_SUCCESS;
 }
 
-#ifndef HAVE_OPENSSL
 /**
- * Internally used gnutls callback function for receiving encrypted data.
+ * Internally used SSL callback function for receiving encrypted data.
  */
-static ssize_t internal_ssl_read(gnutls_transport_ptr_t transport, char *buffer, size_t length)
+static ssize_t internal_ssl_read(idevice_connection_t connection, char *buffer, size_t length)
 {
 	int bytes = 0, pos_start_fill = 0;
 	size_t tbytes = 0;
 	int this_len = length;
 	idevice_error_t res;
-	idevice_connection_t connection = (idevice_connection_t)transport;
 	char *recv_buffer;
 
 	debug_info("pre-read client wants %zi bytes", length);
 
 	recv_buffer = (char *)malloc(sizeof(char) * this_len);
 
+	unsigned int timeout = connection->ssl_recv_timeout;
+
 	/* repeat until we have the full data or an error occurs */
 	do {
-		if ((res = internal_connection_receive(connection, recv_buffer, this_len, (uint32_t*)&bytes)) != IDEVICE_E_SUCCESS) {
-			debug_info("ERROR: idevice_connection_receive returned %d", res);
-			return res;
+		if (timeout == (unsigned int)-1) {
+			res = internal_connection_receive(connection, recv_buffer, this_len, (uint32_t*)&bytes);
+		} else {
+			res = internal_connection_receive_timeout(connection, recv_buffer, this_len, (uint32_t*)&bytes, (unsigned int)timeout);
+		}
+		if (res != IDEVICE_E_SUCCESS) {
+			if (res != IDEVICE_E_TIMEOUT) {
+				debug_info("ERROR: %s returned %d", (timeout == (unsigned int)-1) ? "internal_connection_receive" : "internal_connection_receive_timeout", res);
+			}
+			connection->status = res;
+			return -1;
 		}
 		debug_info("post-read we got %i bytes", bytes);
 
@@ -863,22 +861,21 @@
 }
 
 /**
- * Internally used gnutls callback function for sending encrypted data.
+ * Internally used SSL callback function for sending encrypted data.
  */
-static ssize_t internal_ssl_write(gnutls_transport_ptr_t transport, char *buffer, size_t length)
+static ssize_t internal_ssl_write(idevice_connection_t connection, const char *buffer, size_t length)
 {
 	uint32_t bytes = 0;
 	idevice_error_t res;
-	idevice_connection_t connection = (idevice_connection_t)transport;
 	debug_info("pre-send length = %zi", length);
 	if ((res = internal_connection_send(connection, buffer, length, &bytes)) != IDEVICE_E_SUCCESS) {
 		debug_info("ERROR: internal_connection_send returned %d", res);
+		connection->status = res;
 		return -1;
 	}
 	debug_info("post-send sent %i bytes", bytes);
 	return bytes;
 }
-#endif
 
 /**
  * Internally used function for cleaning up SSL stuff.
@@ -918,6 +915,32 @@
 }
 
 #ifdef HAVE_OPENSSL
+static long ssl_idevice_bio_callback(BIO *b, int oper, const char *argp, int argi, long argl, long retvalue)
+{
+	idevice_connection_t conn = (idevice_connection_t)BIO_get_callback_arg(b);
+	size_t len = (size_t)argi;
+	switch (oper) {
+	case (BIO_CB_READ|BIO_CB_RETURN):
+		return argp ? (long)internal_ssl_read(conn, (char *)argp, len) : 0;
+	case (BIO_CB_PUTS|BIO_CB_RETURN):
+		len = strlen(argp);
+		// fallthrough
+	case (BIO_CB_WRITE|BIO_CB_RETURN):
+		return (long)internal_ssl_write(conn, argp, len);
+	default:
+		return retvalue;
+	}
+}
+
+static BIO *ssl_idevice_bio_new(idevice_connection_t conn)
+{
+	BIO *b = BIO_new(BIO_s_null());
+	if (!b) return NULL;
+	BIO_set_callback_arg(b, (char *)conn);
+	BIO_set_callback(b, ssl_idevice_bio_callback);
+	return b;
+}
+
 static int ssl_verify_callback(int ok, X509_STORE_CTX *ctx)
 {
 	return 1;
@@ -1009,12 +1032,11 @@
 	if (pair_record)
 		plist_free(pair_record);
 
-	BIO *ssl_bio = BIO_new(BIO_s_socket());
+	BIO *ssl_bio = ssl_idevice_bio_new(connection);
 	if (!ssl_bio) {
 		debug_info("ERROR: Could not create SSL bio.");
 		return ret;
 	}
-	BIO_set_fd(ssl_bio, (int)(long)connection->data, BIO_NOCLOSE);
 
 	SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method());
 	if (ssl_ctx == NULL) {
diff --git a/src/idevice.h b/src/idevice.h
index 8709c9a..4e53a7f 100644
--- a/src/idevice.h
+++ b/src/idevice.h
@@ -68,6 +68,8 @@
 	enum idevice_connection_type type;
 	void *data;
 	ssl_data_t ssl_data;
+	unsigned int ssl_recv_timeout;
+	idevice_error_t status;
 };
 
 struct idevice_private {