Add Reverse Proxy implementation
diff --git a/include/Makefile.am b/include/Makefile.am
index e23b2a9..2abaf49 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -26,5 +26,6 @@
libimobiledevice/mobileactivation.h \
libimobiledevice/preboard.h \
libimobiledevice/companion_proxy.h \
+ libimobiledevice/reverse_proxy.h \
libimobiledevice/property_list_service.h \
libimobiledevice/service.h
diff --git a/include/endianness.h b/include/endianness.h
index 2d6ad0e..1d414b3 100644
--- a/include/endianness.h
+++ b/include/endianness.h
@@ -31,6 +31,18 @@
#define htobe16 be16toh
#endif
+#ifndef le16toh
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define le16toh(x) ((((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8))
+#else
+#define le16toh(x) (x)
+#endif
+#endif
+
+#ifndef htole16
+#define htole16 le16toh
+#endif
+
#ifndef __bswap_32
#define __bswap_32(x) ((((x) & 0xFF000000) >> 24) \
| (((x) & 0x00FF0000) >> 8) \
diff --git a/include/libimobiledevice/reverse_proxy.h b/include/libimobiledevice/reverse_proxy.h
new file mode 100644
index 0000000..2539bd9
--- /dev/null
+++ b/include/libimobiledevice/reverse_proxy.h
@@ -0,0 +1,209 @@
+/**
+ * @file libimobiledevice/reverse_proxy.h
+ * @brief Provide a reverse proxy to allow the device to communicate through,
+ * which is used during firmware restore.
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef IREVERSE_PROXY_H
+#define IREVERSE_PROXY_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+
+#define REVERSE_PROXY_DEFAULT_PORT 1082
+
+/** Error Codes */
+typedef enum {
+ REVERSE_PROXY_E_SUCCESS = 0,
+ REVERSE_PROXY_E_INVALID_ARG = -1,
+ REVERSE_PROXY_E_PLIST_ERROR = -2,
+ REVERSE_PROXY_E_MUX_ERROR = -3,
+ REVERSE_PROXY_E_SSL_ERROR = -4,
+ REVERSE_PROXY_E_NOT_ENOUGH_DATA = -5,
+ REVERSE_PROXY_E_TIMEOUT = -6,
+ REVERSE_PROXY_E_UNKNOWN_ERROR = -256
+} reverse_proxy_error_t;
+
+typedef struct reverse_proxy_client_private reverse_proxy_client_private;
+typedef reverse_proxy_client_private *reverse_proxy_client_t; /**< The client handle. */
+
+typedef enum {
+ RP_TYPE_CTRL = 1, /**< control connection */
+ RP_TYPE_CONN /**< proxy connection */
+} reverse_proxy_client_type_t;
+
+typedef enum {
+ RP_STATUS_READY = 1, /**< proxy is ready */
+ RP_STATUS_TERMINATE, /**< proxy terminated */
+ RP_STATUS_CONNECT_REQ, /**< connection request received (only RP_TYPE_CTRL) */
+ RP_STATUS_SHUTDOWN_REQ, /**< shutdown request received (only RP_TYPE_CTRL) */
+ RP_STATUS_CONNECTED, /**< connection established (only RP_TYPE_CONN) */
+ RP_STATUS_DISCONNECTED, /**< connection closed (only RP_TYPE_CONN) */
+} reverse_proxy_status_t;
+
+typedef enum {
+ RP_DATA_DIRECTION_OUT = 1, /**< data going out to remote host */
+ RP_DATA_DIRECTION_IN /**< data coming in from remote host */
+} reverse_proxy_data_direction_t;
+
+/**
+ * Log callback function prototype.
+ *
+ * @param client The client that called the callback function
+ * @param log_msg The log message
+ * @param user_data The user_data pointer that was set when registering the callback
+ */
+typedef void (*reverse_proxy_log_cb_t) (reverse_proxy_client_t client, const char* log_msg, void* user_data);
+
+/**
+ * Data callback function prototype.
+ *
+ * @param client The client that called the callback function
+ * @param direction The direction of the data, either RP_DATA_DIRECTION_OUT or RP_DATA_DIRECTION_IN
+ * @param buffer The data buffer
+ * @param length The length of the data buffer
+ * @param user_data The user_data pointer that was set when registering the callback
+ */
+typedef void (*reverse_proxy_data_cb_t) (reverse_proxy_client_t client, reverse_proxy_data_direction_t direction, const char* buffer, uint32_t length, void* user_data);
+
+/**
+ * Status callback function prototype.
+ *
+ * @param client The client that called the callback function
+ * @param status The status the client is reporting
+ * @param status_msg A status message the client reports along with the status
+ * @param user_data The user_data pointer that was set when registering the callback
+ */
+typedef void (*reverse_proxy_status_cb_t) (reverse_proxy_client_t client, reverse_proxy_status_t status, const char* status_msg, void* user_data);
+
+/**
+ * Create a reverse proxy client using com.apple.PurpleReverseProxy.Ctrl and
+ * com.apple.PurpleReverseProxy.Conn lockdown services. This will open a port
+ * 1083 on the device that iOS apps could connect to; \b however that is
+ * only allowed if an app has the com.apple.private.PurpleReverseProxy.allowed
+ * entitlement, which currently only \c /usr/libexec/fdrhelper holds.
+ *
+ * @note This function only creates and initializes the reverse proxy;
+ * to make it operational, call reverse_proxy_client_start_proxy().
+ *
+ * @param device The device to connect to.
+ * @param client Pointer that will be set to a newly allocated #reverse_proxy_client_t
+ * upon successful return.
+ * @param label A label to pass to lockdownd when creating the service
+ * connections, usually the program name.
+ *
+ * @return REVERSE_PROXY_E_SUCCESS on success,
+ * or a REVERSE_PROXY_E_* error code otherwise.
+ */
+reverse_proxy_error_t reverse_proxy_client_create_with_service(idevice_t device, reverse_proxy_client_t* client, const char* label);
+
+/**
+ * Create a reverse proxy client using an open port on the device. This is
+ * used during firmware restores with the default port REVERSE_PROXY_DEFAULT_PORT (1082).
+ *
+ * @note This function only creates and initializes the reverse proxy;
+ * to make it operational, call reverse_proxy_client_start_proxy().
+ *
+ * @param device The device to connect to.
+ * @param client Pointer that will be set to a newly allocated reverse_proxy_client_t
+ * upon successful return.
+ * @param device_port An open port on the device. Unless it's being used for
+ * a custom implementation, pass REVERSE_PROXY_DEFAULT_PORT here.
+ *
+ * @return REVERSE_PROXY_E_SUCCESS on success,
+ * or a REVERSE_PROXY_E_* error code otherwise.
+ */
+reverse_proxy_error_t reverse_proxy_client_create_with_port(idevice_t device, reverse_proxy_client_t* client, uint16_t device_port);
+
+/**
+ * Disconnects a reverse proxy client and frees up the client data.
+ *
+ * @param client The reverse proxy client to disconnect and free.
+ */
+reverse_proxy_error_t reverse_proxy_client_free(reverse_proxy_client_t client);
+
+/**
+ * Make an initialized reverse proxy client operational, i.e. start the actual proxy.
+ *
+ * @param client The reverse proxy client to start.
+ * @param control_protocol_version The control protocol version to use.
+ * This is either 1 or 2. Recent devices use 2.
+ *
+ * @return REVERSE_PROXY_E_SUCCESS on success,
+ * or a REVERSE_PROXY_E_* error code otherwise.
+ */
+reverse_proxy_error_t reverse_proxy_client_start_proxy(reverse_proxy_client_t client, int control_protocol_version);
+
+/**
+ * Set a status callback function. This allows to report the status of the
+ * reverse proxy, like Ready, Connect Request, Connected, etc.
+ *
+ * @note Set the callback before calling reverse_proxy_client_start_proxy().
+ *
+ * @param client The reverse proxy client
+ * @param callback The status callback function that will be called
+ * when the status of the reverse proxy changes.
+ * @param user_data A pointer that will be passed to the callback function.
+ */
+void reverse_proxy_client_set_status_callback(reverse_proxy_client_t client, reverse_proxy_status_cb_t callback, void* user_data);
+
+/**
+ * Set a log callback function. Useful for debugging or verbosity.
+ *
+ * @note Set the callback before calling reverse_proxy_client_start_proxy().
+ *
+ * @param client The reverse proxy client
+ * @param callback The log callback function that will be called
+ * when the reverse proxy logs something.
+ * @param user_data A pointer that will be passed to the callback function.
+ */
+void reverse_proxy_client_set_log_callback(reverse_proxy_client_t client, reverse_proxy_log_cb_t callback, void* user_data);
+
+/**
+ * Set a data callback function. Useful for debugging or extra verbosity.
+ *
+ * @note Set the callback before calling reverse_proxy_client_start_proxy().
+ *
+ * @param client The reverse proxy client
+ * @param callback The status callback function that will be called
+ * when the status of the reverse proxy changes.
+ * @param user_data A pointer that will be passed to the callback function.
+ */
+
+void reverse_proxy_client_set_data_callback(reverse_proxy_client_t client, reverse_proxy_data_cb_t callback, void* user_data);
+
+/**
+ * Helper function to return the type of a given reverse proxy client, which
+ * is either RP_TYPE_CTRL or RP_TYPE_CONN. Useful for callback functions.
+ * @see reverse_proxy_client_type_t
+ *
+ * @param client The reverse proxy client
+ *
+ * @return The type of the rerverse proxy client
+ */
+reverse_proxy_client_type_t reverse_proxy_get_type(reverse_proxy_client_t client);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 183a745..106eef7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,6 +47,7 @@
mobileactivation.c mobileactivation.h \
preboard.c preboard.h \
companion_proxy.c companion_proxy.h \
+ reverse_proxy.c reverse_proxy.h \
syslog_relay.c syslog_relay.h
if WIN32
diff --git a/src/reverse_proxy.c b/src/reverse_proxy.c
new file mode 100644
index 0000000..fd6f1a2
--- /dev/null
+++ b/src/reverse_proxy.c
@@ -0,0 +1,799 @@
+/*
+ * reverse_proxy.c
+ * com.apple.PurpleReverseProxy service implementation.
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ * Copyright (c) 2014 BALATON Zoltan. All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <plist/plist.h>
+#include <libimobiledevice-glue/thread.h>
+#include <libimobiledevice-glue/socket.h>
+
+
+#include "reverse_proxy.h"
+#include "lockdown.h"
+#include "common/debug.h"
+#include "endianness.h"
+
+#define CTRL_PORT 1082
+#define CTRLCMD "BeginCtrl"
+#define HELLOCTRLCMD "HelloCtrl"
+#define HELLOCMD "HelloConn"
+
+#define RP_SYNC_MSG 0x1
+#define RP_PROXY_MSG 0x105
+#define RP_PLIST_MSG 0xbbaa
+
+/**
+ * Convert a service_error_t value to a reverse_proxy_error_t value.
+ * Used internally to get correct error codes.
+ *
+ * @param err A service_error_t error code
+ *
+ * @return A matching reverse_proxy_error_t error code,
+ * REVERSE_PROXY_E_UNKNOWN_ERROR otherwise.
+ */
+static reverse_proxy_error_t reverse_proxy_error(service_error_t err)
+{
+ switch (err) {
+ case SERVICE_E_SUCCESS:
+ return REVERSE_PROXY_E_SUCCESS;
+ case SERVICE_E_INVALID_ARG:
+ return REVERSE_PROXY_E_INVALID_ARG;
+ case SERVICE_E_MUX_ERROR:
+ return REVERSE_PROXY_E_MUX_ERROR;
+ case SERVICE_E_SSL_ERROR:
+ return REVERSE_PROXY_E_SSL_ERROR;
+ case SERVICE_E_NOT_ENOUGH_DATA:
+ return REVERSE_PROXY_E_NOT_ENOUGH_DATA;
+ case SERVICE_E_TIMEOUT:
+ return REVERSE_PROXY_E_TIMEOUT;
+ default:
+ break;
+ }
+ return REVERSE_PROXY_E_UNKNOWN_ERROR;
+}
+
+static void _reverse_proxy_log(reverse_proxy_client_t client, const char* format, ...)
+{
+ if (!client || !client->log_cb) {
+ return;
+ }
+ va_list args;
+ va_start(args, format);
+ char* buffer = NULL;
+ (void)vasprintf(&buffer, format, args);
+ va_end(args);
+ client->log_cb(client, buffer, client->log_cb_user_data);
+ free(buffer);
+}
+
+static void _reverse_proxy_data(reverse_proxy_client_t client, int direction, char* buffer, uint32_t length)
+{
+ if (!client || !client->data_cb) {
+ return;
+ }
+ client->data_cb(client, direction, buffer, length, client->data_cb_user_data);
+}
+
+static void _reverse_proxy_status(reverse_proxy_client_t client, int status, const char* format, ...)
+{
+ if (!client || !client->status_cb) {
+ return;
+ }
+ va_list args;
+ va_start(args, format);
+ char* buffer = NULL;
+ (void)vasprintf(&buffer, format, args);
+ va_end(args);
+ client->status_cb(client, status, buffer, client->status_cb_user_data);
+ free(buffer);
+}
+
+static int _reverse_proxy_handle_proxy_cmd(reverse_proxy_client_t client)
+{
+ reverse_proxy_error_t err = REVERSE_PROXY_E_SUCCESS;
+ char *buf = NULL;
+ size_t bufsize = 1048576;
+ uint32_t sent = 0, bytes = 0;
+ uint32_t sent_total = 0;
+ uint32_t recv_total = 0;
+ char *host = NULL;
+ uint16_t port = 0;
+
+ buf = malloc(bufsize);
+ if (!buf) {
+ _reverse_proxy_log(client, "ERROR: Failed to allocate buffer");
+ return -1;
+ }
+
+ err = reverse_proxy_receive(client, buf, bufsize, &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ free(buf);
+ _reverse_proxy_log(client, "ERROR: Unable to read data for proxy command");
+ return -1;
+ }
+ _reverse_proxy_log(client, "Handling proxy command");
+
+ /* Just return success here unconditionally because we don't know
+ * anything else and we will eventually abort on failure anyway */
+ uint16_t ack = 5;
+ err = reverse_proxy_send(client, (char *)&ack, sizeof(ack), &sent);
+ if (err != REVERSE_PROXY_E_SUCCESS || sent != sizeof(ack)) {
+ free(buf);
+ _reverse_proxy_log(client, "ERROR: Unable to send ack. Sent %u of %u bytes.", sent, (uint32_t)sizeof(ack));
+ return -1;
+ }
+
+ if (bytes < 3) {
+ free(buf);
+ _reverse_proxy_log(client, "Proxy command data too short, retrying");
+ return 0;
+ }
+
+ /* ack command data too */
+ err = reverse_proxy_send(client, buf, bytes, &sent);
+ if (err != REVERSE_PROXY_E_SUCCESS || sent != bytes) {
+ free(buf);
+ _reverse_proxy_log(client, "ERROR: Unable to send data. Sent %u of %u bytes.", sent, bytes);
+ return -1;
+ }
+
+ /* Now try to handle actual messages */
+ /* Connect: 0 3 hostlen <host> <port> */
+ if (buf[0] == 0 && buf[1] == 3) {
+ uint16_t *p = (uint16_t *)&buf[bytes - 2];
+ port = be16toh(*p);
+ buf[bytes - 2] = '\0';
+ host = strdup(&buf[3]);
+ _reverse_proxy_log(client, "Connect request to %s:%u", host, port);
+ }
+
+ if (!host || !buf[2]) {
+ /* missing or zero length host name */
+ free(buf);
+ return 0;
+ }
+
+ /* else wait for messages and forward them */
+ int sockfd = socket_connect(host, port);
+ free(host);
+ if (sockfd < 0) {
+ free(buf);
+ _reverse_proxy_log(client, "ERROR: Connection to %s:%u failed: %s", host, port, strerror(errno));
+ return -1;
+ }
+
+ _reverse_proxy_status(client, RP_STATUS_CONNECTED, "Connected to %s:%u", host, port);
+
+ int res = 0, bytes_ret;
+ while (1) {
+ bytes = 0;
+ err = reverse_proxy_receive_with_timeout(client, buf, bufsize, &bytes, 100);
+ if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && !bytes)) {
+ /* just a timeout condition */
+ }
+ else if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "Connection closed");
+ res = -1;
+ break;
+ }
+ if (bytes) {
+ _reverse_proxy_log(client, "Proxying %u bytes of data", bytes);
+ _reverse_proxy_data(client, RP_DATA_DIRECTION_OUT, buf, bytes);
+ sent = 0;
+ while (sent < bytes) {
+ int s = socket_send(sockfd, buf + sent, bytes - sent);
+ if (s < 0) {
+ break;
+ }
+ sent += s;
+ }
+ sent_total += sent;
+ if (sent != bytes) {
+ _reverse_proxy_log(client, "ERROR: Sending proxy payload failed: %s. Sent %u of %u bytes.", strerror(errno), sent, bytes);
+ socket_close(sockfd);
+ res = -1;
+ break;
+ }
+ }
+ bytes_ret = socket_receive_timeout(sockfd, buf, bufsize, 0, 100);
+ if (bytes_ret == -ETIMEDOUT) {
+ bytes_ret = 0;
+ } else if (bytes_ret == -ECONNRESET) {
+ res = 1;
+ break;
+ } else if (bytes_ret < 0) {
+ _reverse_proxy_log(client, "ERROR: Failed to receive from host: %s", strerror(-bytes_ret));
+ break;
+ }
+
+ bytes = bytes_ret;
+ if (bytes) {
+ _reverse_proxy_log(client, "Received %u bytes reply data, sending to device\n", bytes);
+ _reverse_proxy_data(client, RP_DATA_DIRECTION_IN, buf, bytes);
+ recv_total += bytes;
+ sent = 0;
+ while (sent < bytes) {
+ uint32_t s;
+ err = reverse_proxy_send(client, buf + sent, bytes - sent, &s);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ break;
+ }
+ sent += s;
+ }
+ if (err != REVERSE_PROXY_E_SUCCESS || bytes != sent) {
+ _reverse_proxy_log(client, "ERROR: Unable to send data (%d). Sent %u of %u bytes.", err, sent, bytes);
+ res = -1;
+ break;
+ }
+ }
+ }
+ socket_close(sockfd);
+ free(buf);
+
+ _reverse_proxy_status(client, RP_STATUS_DISCONNECTED, "Disconnected (out: %u / in: %u)", sent_total, recv_total);
+
+ return res;
+}
+
+static int _reverse_proxy_handle_plist_cmd(reverse_proxy_client_t client)
+{
+ plist_t dict;
+ reverse_proxy_error_t err;
+
+ err = reverse_proxy_receive_plist(client, &dict);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Unable to receive plist command, error", err);
+ return -1;
+ }
+ plist_t node = plist_dict_get_item(dict, "Command");
+ if (!node || (plist_get_node_type(node) != PLIST_STRING)) {
+ _reverse_proxy_log(client, "ERROR: No 'Command' in reply", err);
+ plist_free(dict);
+ return -1;
+ }
+ char *command = NULL;
+ plist_get_string_val(node, &command);
+ plist_free(dict);
+
+ if (!command) {
+ _reverse_proxy_log(client, "ERROR: Empty 'Command' string");
+ return -1;
+ }
+
+ if (!strcmp(command, "Ping")) {
+ _reverse_proxy_log(client, "Received Ping command, replying with Pong");
+ dict = plist_new_dict();
+ plist_dict_set_item(dict, "Pong", plist_new_bool(1));
+ err = reverse_proxy_send_plist(client, dict);
+ plist_free(dict);
+ if (err) {
+ _reverse_proxy_log(client, "ERROR: Unable to send Ping command reply");
+ free(command);
+ return -1;
+ }
+ } else {
+ _reverse_proxy_log(client, "WARNING: Received unhandled plist command '%s'", command);
+ free(command);
+ return -1;
+ }
+
+ free(command);
+ /* reverse proxy connection will be terminated remotely. Next receive will get nothing, error and terminate this worker thread. */
+ return 0;
+}
+
+static reverse_proxy_error_t reverse_proxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, reverse_proxy_client_t * client)
+{
+ *client = NULL;
+
+ if (!device || !service || service->port == 0 || !client || *client) {
+ return REVERSE_PROXY_E_INVALID_ARG;
+ }
+
+ debug_info("Creating reverse_proxy_client, port = %d.", service->port);
+
+ service_client_t sclient = NULL;
+ reverse_proxy_error_t ret = reverse_proxy_error(service_client_new(device, service, &sclient));
+ if (ret != REVERSE_PROXY_E_SUCCESS) {
+ debug_info("Creating service client failed. Error: %i", ret);
+ return ret;
+ }
+
+ reverse_proxy_client_t client_loc = (reverse_proxy_client_t) calloc(1, sizeof(struct reverse_proxy_client_private));
+ client_loc->parent = sclient;
+ client_loc->th_ctrl = THREAD_T_NULL;
+ *client = client_loc;
+
+ return 0;
+}
+
+static void* _reverse_proxy_connection_thread(void *cdata)
+{
+ reverse_proxy_client_t client = (reverse_proxy_client_t)cdata;
+ uint32_t bytes = 0;
+ reverse_proxy_client_t conn_client = NULL;
+ reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR;
+
+ if (client->conn_port == 0) {
+ service_client_factory_start_service(client->parent->connection->device, "com.apple.PurpleReverseProxy.Conn", (void**)&conn_client, client->label, SERVICE_CONSTRUCTOR(reverse_proxy_client_new), &err);
+ if (!conn_client) {
+ _reverse_proxy_log(client, "ERROR: Failed to start proxy connection service, error %d", err);
+ }
+ } else {
+ struct lockdownd_service_descriptor svc;
+ svc.port = client->conn_port;
+ svc.ssl_enabled = 0;
+ svc.identifier = NULL;
+ err = reverse_proxy_client_new(client->parent->connection->device, &svc, &conn_client);
+ if (!conn_client) {
+ _reverse_proxy_log(client, "ERROR: Failed to connect to proxy connection port %u, error %d", client->conn_port, err);
+ }
+ }
+ if (!conn_client) {
+ goto leave;
+ }
+ conn_client->type = RP_TYPE_CONN;
+ conn_client->protoversion = client->protoversion;
+ conn_client->log_cb = client->log_cb;
+ conn_client->log_cb_user_data = client->log_cb_user_data;
+ conn_client->status_cb = client->status_cb;
+ conn_client->status_cb_user_data = client->status_cb_user_data;
+
+ err = reverse_proxy_send(conn_client, HELLOCMD, sizeof(HELLOCMD), &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS || bytes != sizeof(HELLOCMD)) {
+ _reverse_proxy_log(conn_client, "ERROR: Unable to send " HELLOCMD " (sent %u/%u bytes)", bytes, sizeof(HELLOCMD));
+ goto leave;
+ }
+
+ if (conn_client->protoversion == 2) {
+ plist_t reply = NULL;
+ err = reverse_proxy_receive_plist(conn_client, &reply);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " reply, error %d", err);
+ goto leave;
+ }
+ char* identifier = NULL;
+ char* cmd = NULL;
+ plist_t node = NULL;
+ node = plist_dict_get_item(reply, "Command");
+ if (node) {
+ plist_get_string_val(node, &cmd);
+ }
+ node = plist_dict_get_item(reply, "Identifier");
+ if (node) {
+ plist_get_string_val(node, &identifier);
+ }
+ plist_free(reply);
+
+ if (!cmd || (strcmp(cmd, HELLOCMD) != 0)) {
+ free(cmd);
+ free(identifier);
+ _reverse_proxy_log(conn_client, "ERROR: Unexpected reply to " HELLOCMD " received");
+ goto leave;
+ }
+ free(cmd);
+
+ if (identifier) {
+ _reverse_proxy_log(conn_client, "Got device identifier %s", identifier);
+ free(identifier);
+ }
+ } else {
+ char buf[16];
+ memset(buf, '\0', sizeof(buf));
+ bytes = 0;
+ err = reverse_proxy_receive(conn_client, buf, sizeof(HELLOCMD), &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " reply, error %d", err);
+ goto leave;
+ }
+ if (memcmp(buf, HELLOCMD, sizeof(HELLOCMD)) != 0) {
+ _reverse_proxy_log(conn_client, "ERROR: Did not receive " HELLOCMD " as reply, but %.*s", (int)bytes, buf);
+ goto leave;
+ }
+ }
+
+ _reverse_proxy_status(conn_client, RP_STATUS_READY, "Ready");
+
+ int running = 1;
+ while (client->th_ctrl != THREAD_T_NULL && conn_client && running) {
+ uint16_t cmd = 0;
+ bytes = 0;
+ err = reverse_proxy_receive_with_timeout(conn_client, (char*)&cmd, sizeof(cmd), &bytes, 1000);
+ if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && bytes != sizeof(cmd))) {
+ continue;
+ } else if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(conn_client, "Connection closed");
+ break;
+ }
+ cmd = le16toh(cmd);
+ switch (cmd) {
+ case 0xBBAA:
+ /* plist command */
+ if (_reverse_proxy_handle_plist_cmd(conn_client) < 0) {
+ running = 0;
+ }
+ break;
+ case 0x105:
+ /* proxy command */
+ if (_reverse_proxy_handle_proxy_cmd(conn_client) < 0) {
+ running = 0;
+ }
+ break;
+ default:
+ /* unknown */
+ debug_info("ERROR: Unknown request 0x%x", cmd);
+ _reverse_proxy_log(conn_client, "ERROR: Unknown request 0x%x", cmd);
+ running = 0;
+ break;
+ }
+ }
+
+leave:
+ _reverse_proxy_status(conn_client, RP_STATUS_TERMINATE, "Terminated");
+ if (conn_client) {
+ reverse_proxy_client_free(conn_client);
+ }
+
+ return NULL;
+}
+
+static void* _reverse_proxy_control_thread(void *cdata)
+{
+ reverse_proxy_client_t client = (reverse_proxy_client_t)cdata;
+ THREAD_T th_conn = THREAD_T_NULL;
+ int running = 1;
+ _reverse_proxy_status(client, RP_STATUS_READY, "Ready");
+ while (client && client->parent && running) {
+ uint32_t cmd = 0;
+ uint32_t bytes = 0;
+ reverse_proxy_error_t err = reverse_proxy_receive_with_timeout(client, (char*)&cmd, sizeof(cmd), &bytes, 1000);
+ if (err == REVERSE_PROXY_E_TIMEOUT || (err == REVERSE_PROXY_E_SUCCESS && bytes != sizeof(cmd))) {
+ continue;
+ } else if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "Connection closed");
+ break;
+ }
+ cmd = le32toh(cmd);
+ switch (cmd) {
+ case 1:
+ /* connection request */
+ debug_info("ReverseProxy<%p> got connect request", client);
+ _reverse_proxy_status(client, RP_STATUS_CONNECT_REQ, "Connect Request");
+ if (thread_new(&th_conn, _reverse_proxy_connection_thread, client) != 0) {
+ debug_info("ERROR: Failed to start connection thread");
+ th_conn = THREAD_T_NULL;
+ running = 0;
+ }
+ break;
+ case 2:
+ /* shutdown request */
+ debug_info("ReverseProxy<%p> got shutdown request", client);
+ _reverse_proxy_status(client, RP_STATUS_SHUTDOWN_REQ, "Shutdown Request");
+ running = 0;
+ break;
+ default:
+ /* unknown */
+ debug_info("ERROR: Unknown request 0x%x", cmd);
+ _reverse_proxy_log(client, "ERROR: Unknown request 0x%x", cmd);
+ running = 0;
+ break;
+ }
+ }
+ _reverse_proxy_log(client, "Terminating");
+
+ client->th_ctrl = THREAD_T_NULL;
+ if (th_conn) {
+ debug_info("joining connection thread");
+ thread_join(th_conn);
+ thread_free(th_conn);
+ }
+
+ _reverse_proxy_status(client, RP_STATUS_TERMINATE, "Terminated");
+
+ return NULL;
+}
+
+LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_start_proxy(reverse_proxy_client_t client, int control_protocol_version)
+{
+ char buf[16] = {0, };
+ uint32_t bytes = 0;
+ reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR;
+
+ if (!client) {
+ return REVERSE_PROXY_E_INVALID_ARG;
+ }
+ if (control_protocol_version < 1 || control_protocol_version > 2) {
+ debug_info("invalid protocol version %d, must be 1 or 2", control_protocol_version);
+ return REVERSE_PROXY_E_INVALID_ARG;
+ }
+
+ if (control_protocol_version == 2) {
+ err = reverse_proxy_send(client, CTRLCMD, sizeof(CTRLCMD), &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Failed to send " CTRLCMD " to device, error %d", err);
+ return err;
+ }
+ plist_t dict = plist_new_dict();
+ plist_dict_set_item(dict, "Command", plist_new_string(CTRLCMD));
+ plist_dict_set_item(dict, "CtrlProtoVersion", plist_new_uint(client->protoversion));
+ err = reverse_proxy_send_plist(client, dict);
+ plist_free(dict);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Could not send " CTRLCMD " plist command, error %d", err);
+ return err;
+ }
+ dict = NULL;
+ err = reverse_proxy_receive_plist(client, &dict);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Could not receive " CTRLCMD " plist reply, error %d", err);
+ return err;
+ }
+ plist_t node = plist_dict_get_item(dict, "ConnPort");
+ if (node && plist_get_node_type(node) == PLIST_UINT) {
+ uint64_t u64val = 0;
+ plist_get_uint_val(node, &u64val);
+ client->conn_port = (uint16_t)u64val;
+ } else {
+ _reverse_proxy_log(client, "ERROR: Could not get ConnPort value");
+ return REVERSE_PROXY_E_UNKNOWN_ERROR;
+ }
+ client->protoversion = 2;
+ } else {
+ err = reverse_proxy_send(client, HELLOCTRLCMD, sizeof(HELLOCTRLCMD), &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Failed to send " HELLOCTRLCMD " to device, error %d", err);
+ return err;
+ }
+
+ bytes = 0;
+ err = reverse_proxy_receive(client, buf, sizeof(HELLOCTRLCMD)-1, &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Could not receive " HELLOCTRLCMD " reply, error %d", err);
+ return err;
+ }
+
+ uint16_t cport = 0;
+ bytes = 0;
+ err = reverse_proxy_receive(client, (char*)&cport, 2, &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ _reverse_proxy_log(client, "ERROR: Failed to receive connection port, error %d", err);
+ return err;
+ }
+ client->conn_port = le16toh(cport);
+ client->protoversion = 1;
+ }
+
+ if (thread_new(&(client->th_ctrl), _reverse_proxy_control_thread, client) != 0) {
+ _reverse_proxy_log(client, "ERROR: Failed to start control thread");
+ client->th_ctrl = THREAD_T_NULL; /* undefined after failure */
+ err = REVERSE_PROXY_E_UNKNOWN_ERROR;
+ }
+
+ return err;
+}
+
+LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_create_with_service(idevice_t device, reverse_proxy_client_t* client, const char* label)
+{
+ reverse_proxy_error_t err = REVERSE_PROXY_E_UNKNOWN_ERROR;
+ service_client_factory_start_service(device, "com.apple.PurpleReverseProxy.Ctrl", (void**)client, label, SERVICE_CONSTRUCTOR(reverse_proxy_client_new), &err);
+ if (!*client) {
+ return err;
+ }
+ (*client)->label = strdup(label);
+ (*client)->type = RP_TYPE_CTRL;
+
+ return REVERSE_PROXY_E_SUCCESS;
+}
+
+LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_create_with_port(idevice_t device, reverse_proxy_client_t* client, uint16_t device_port)
+{
+ reverse_proxy_client_t client_loc = NULL;
+ reverse_proxy_error_t err;
+
+ struct lockdownd_service_descriptor svc;
+ svc.port = device_port;
+ svc.ssl_enabled = 0;
+ svc.identifier = NULL;
+
+ err = reverse_proxy_client_new(device, &svc, &client_loc);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ return err;
+ }
+
+ client_loc->type = RP_TYPE_CTRL;
+ *client = client_loc;
+
+ return REVERSE_PROXY_E_SUCCESS;
+}
+
+LIBIMOBILEDEVICE_API reverse_proxy_error_t reverse_proxy_client_free(reverse_proxy_client_t client)
+{
+ if (!client)
+ return REVERSE_PROXY_E_INVALID_ARG;
+ service_client_t parent = client->parent;
+ client->parent = NULL;
+ if (client->th_ctrl) {
+ debug_info("joining control thread");
+ thread_join(client->th_ctrl);
+ thread_free(client->th_ctrl);
+ client->th_ctrl = THREAD_T_NULL;
+ }
+ reverse_proxy_error_t err = reverse_proxy_error(service_client_free(parent));
+ free(client->label);
+ free(client);
+
+ return err;
+}
+
+LIBIMOBILEDEVICE_API reverse_proxy_client_type_t reverse_proxy_get_type(reverse_proxy_client_t client)
+{
+ if (!client)
+ return 0;
+ return client->type;
+}
+
+LIBIMOBILEDEVICE_API void reverse_proxy_client_set_status_callback(reverse_proxy_client_t client, reverse_proxy_status_cb_t status_callback, void* user_data)
+{
+ if (!client) {
+ return;
+ }
+ client->status_cb = status_callback;
+ client->status_cb_user_data = user_data;
+}
+
+LIBIMOBILEDEVICE_API void reverse_proxy_client_set_log_callback(reverse_proxy_client_t client, reverse_proxy_log_cb_t log_callback, void* user_data)
+{
+ if (!client) {
+ return;
+ }
+ client->log_cb = log_callback;
+ client->log_cb_user_data = user_data;
+}
+
+LIBIMOBILEDEVICE_API void reverse_proxy_client_set_data_callback(reverse_proxy_client_t client, reverse_proxy_data_cb_t data_callback, void* user_data)
+{
+ if (!client) {
+ return;
+ }
+ client->data_cb = data_callback;
+ client->data_cb_user_data = user_data;
+}
+
+reverse_proxy_error_t reverse_proxy_send(reverse_proxy_client_t client, const char* data, uint32_t len, uint32_t* sent)
+{
+ reverse_proxy_error_t err = reverse_proxy_error(service_send(client->parent, data, len, sent));
+ return err;
+}
+
+reverse_proxy_error_t reverse_proxy_receive_with_timeout(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received, unsigned int timeout)
+{
+ if (!client)
+ return REVERSE_PROXY_E_INVALID_ARG;
+ return reverse_proxy_error(service_receive_with_timeout(client->parent, buffer, len, received, timeout));
+}
+
+reverse_proxy_error_t reverse_proxy_receive(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received)
+{
+ return reverse_proxy_receive_with_timeout(client, buffer, len, received, 20000);
+}
+
+reverse_proxy_error_t reverse_proxy_send_plist(reverse_proxy_client_t client, plist_t plist)
+{
+ reverse_proxy_error_t err;
+ uint32_t len = 0;
+ char* buf = NULL;
+ uint32_t bytes = 0;
+
+ plist_to_bin(plist, &buf, &len);
+
+ if (!buf) {
+ return REVERSE_PROXY_E_INVALID_ARG;
+ }
+
+ debug_info("Sending %u bytes", len);
+
+ uint32_t slen = htole32(len);
+ err = reverse_proxy_send(client, (char*)&slen, sizeof(slen), &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ free(buf);
+ debug_info("ERROR: Unable to send data length, error %d. Sent %u/%u bytes.", err, bytes, (uint32_t)sizeof(slen));
+ return err;
+ }
+ uint32_t done = 0;
+ do {
+ bytes = 0;
+ err = reverse_proxy_send(client, buf+done, len-done, &bytes);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ break;
+ }
+ done += bytes;
+ } while (done < len);
+ free(buf);
+ if (err != REVERSE_PROXY_E_SUCCESS || done != len) {
+ debug_info("ERROR: Unable to send data, error %d. Sent %u/%u bytes.", err, done, len);
+ return err;
+ }
+
+ debug_info("Sent %u bytes", len);
+
+ return REVERSE_PROXY_E_SUCCESS;
+}
+
+reverse_proxy_error_t reverse_proxy_receive_plist(reverse_proxy_client_t client, plist_t* plist)
+{
+ return reverse_proxy_receive_plist_with_timeout(client, plist, 20000);
+}
+
+reverse_proxy_error_t reverse_proxy_receive_plist_with_timeout(reverse_proxy_client_t client, plist_t * plist, uint32_t timeout_ms)
+{
+ uint32_t len;
+ uint32_t bytes;
+ reverse_proxy_error_t err;
+
+ err = reverse_proxy_receive_with_timeout(client, (char*)&len, sizeof(len), &bytes, timeout_ms);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ if (err != REVERSE_PROXY_E_TIMEOUT) {
+ debug_info("ERROR: Unable to receive packet length, error %d\n", err);
+ }
+ return err;
+ }
+
+ len = le32toh(len);
+ char* buf = calloc(1, len);
+ if (!buf) {
+ debug_info("ERROR: Out of memory");
+ return REVERSE_PROXY_E_UNKNOWN_ERROR;
+ }
+
+ uint32_t done = 0;
+ do {
+ bytes = 0;
+ err = reverse_proxy_receive_with_timeout(client, buf+done, len-done, &bytes, timeout_ms);
+ if (err != REVERSE_PROXY_E_SUCCESS) {
+ break;
+ }
+ done += bytes;
+ } while (done < len);
+
+ if (err != REVERSE_PROXY_E_SUCCESS || done != len) {
+ free(buf);
+ debug_info("ERROR: Unable to receive data, error %d. Received %u/%u bytes.", err, done, len);
+ return err;
+ }
+
+ debug_info("Received %u bytes", len);
+
+ plist_from_bin(buf, len, plist);
+ free(buf);
+
+ if (!(*plist)) {
+ debug_info("ERROR: Failed to convert buffer to plist");
+ return REVERSE_PROXY_E_PLIST_ERROR;
+ }
+
+ return REVERSE_PROXY_E_SUCCESS;
+}
diff --git a/src/reverse_proxy.h b/src/reverse_proxy.h
new file mode 100644
index 0000000..17eabac
--- /dev/null
+++ b/src/reverse_proxy.h
@@ -0,0 +1,50 @@
+/*
+ * reverse_proxy.h
+ * com.apple.PurpleReverseProxy service header file.
+ *
+ * Copyright (c) 2021 Nikias Bassen, All Rights Reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef __REVERSE_PROXY_H
+#define __REVERSE_PROXY_H
+
+#include "libimobiledevice/reverse_proxy.h"
+#include "service.h"
+
+struct reverse_proxy_client_private {
+ service_client_t parent;
+ char* label;
+ int type;
+ int protoversion;
+ THREAD_T th_ctrl;
+ uint16_t conn_port;
+ reverse_proxy_log_cb_t log_cb;
+ void* log_cb_user_data;
+ reverse_proxy_data_cb_t data_cb;
+ void* data_cb_user_data;
+ reverse_proxy_status_cb_t status_cb;
+ void* status_cb_user_data;
+};
+
+reverse_proxy_error_t reverse_proxy_send(reverse_proxy_client_t client, const char* data, uint32_t len, uint32_t* sent);
+reverse_proxy_error_t reverse_proxy_receive(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received);
+reverse_proxy_error_t reverse_proxy_receive_with_timeout(reverse_proxy_client_t client, char* buffer, uint32_t len, uint32_t* received, unsigned int timeout);
+reverse_proxy_error_t reverse_proxy_send_plist(reverse_proxy_client_t client, plist_t plist);
+reverse_proxy_error_t reverse_proxy_receive_plist(reverse_proxy_client_t client, plist_t* plist);
+reverse_proxy_error_t reverse_proxy_receive_plist_with_timeout(reverse_proxy_client_t client, plist_t * plist, uint32_t timeout_ms);
+
+#endif