diff options
-rw-r--r-- | include/Makefile.am | 1 | ||||
-rw-r--r-- | include/endianness.h | 12 | ||||
-rw-r--r-- | include/libimobiledevice/reverse_proxy.h | 209 | ||||
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/reverse_proxy.c | 799 | ||||
-rw-r--r-- | src/reverse_proxy.h | 50 |
6 files changed, 1072 insertions, 0 deletions
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 @@ nobase_include_HEADERS = \ 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 @@ libimobiledevice_1_0_la_SOURCES = \ 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 |