diff options
Diffstat (limited to 'src')
63 files changed, 10923 insertions, 4680 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 70dc895..58cf07c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,24 +1,69 @@ -AM_CPPFLAGS = -I$(top_srcdir)/include +AM_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/3rd_party/libsrp6a-sha512 \ + -I$(top_srcdir)/3rd_party/ed25519 \ + -I$(top_srcdir) -AM_CFLAGS = $(GLOBAL_CFLAGS) $(libusbmuxd_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(libplist_CFLAGS) $(LFS_CFLAGS) -AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS) $(libplist_LIBS) $(libusbmuxd_LIBS) $(libgcrypt_LIBS) +AM_CFLAGS = \ + $(GLOBAL_CFLAGS) \ + $(ssl_lib_CFLAGS) \ + $(LFS_CFLAGS) \ + $(PTHREAD_CFLAGS) \ + $(libusbmuxd_CFLAGS) \ + $(libplist_CFLAGS) \ + $(limd_glue_CFLAGS) -lib_LTLIBRARIES = libimobiledevice.la -libimobiledevice_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIMOBILEDEVICE_SO_VERSION) -no-undefined -libimobiledevice_la_SOURCES = idevice.c idevice.h \ - debug.c debug.h\ - userpref.c userpref.h\ - property_list_service.c property_list_service.h\ - device_link_service.c device_link_service.h\ - lockdown.c lockdown.h\ - afc.c afc.h\ - file_relay.c file_relay.h\ - notification_proxy.c notification_proxy.h\ - installation_proxy.c installation_proxy.h\ - sbservices.c sbservices.h\ - mobile_image_mounter.c mobile_image_mounter.h\ - screenshotr.c screenshotr.h\ - mobilesync.c mobilesync.h\ - mobilebackup.c mobilebackup.h\ - house_arrest.c house_arrest.h\ - restore.c restore.h +AM_LDFLAGS = \ + $(ssl_lib_LIBS) \ + $(PTHREAD_LIBS) \ + $(libusbmuxd_LIBS) \ + $(libplist_LIBS) \ + $(limd_glue_LIBS) + +lib_LTLIBRARIES = libimobiledevice-1.0.la +libimobiledevice_1_0_la_LIBADD = $(top_builddir)/common/libinternalcommon.la +if HAVE_WIRELESS_PAIRING +libimobiledevice_1_0_la_LIBADD += $(top_builddir)/3rd_party/ed25519/libed25519.la $(top_builddir)/3rd_party/libsrp6a-sha512/libsrp6a-sha512.la +endif +libimobiledevice_1_0_la_LDFLAGS = $(AM_LDFLAGS) -version-info $(LIBIMOBILEDEVICE_SO_VERSION) -no-undefined +if DARWIN +libimobiledevice_1_0_la_LDFLAGS += -framework CoreFoundation -framework SystemConfiguration +endif +libimobiledevice_1_0_la_SOURCES = \ + idevice.c idevice.h \ + service.c service.h \ + property_list_service.c property_list_service.h \ + device_link_service.c device_link_service.h \ + lockdown.c lockdown.h \ + lockdown-cu.c \ + afc.c afc.h \ + file_relay.c file_relay.h \ + notification_proxy.c notification_proxy.h \ + installation_proxy.c installation_proxy.h \ + sbservices.c sbservices.h \ + mobile_image_mounter.c mobile_image_mounter.h \ + screenshotr.c screenshotr.h \ + mobilesync.c mobilesync.h \ + mobilebackup.c mobilebackup.h \ + house_arrest.c house_arrest.h \ + mobilebackup2.c mobilebackup2.h \ + misagent.c misagent.h \ + restore.c restore.h \ + diagnostics_relay.c diagnostics_relay.h \ + heartbeat.c heartbeat.h \ + debugserver.c debugserver.h \ + webinspector.c webinspector.h \ + 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 \ + bt_packet_logger.c bt_packet_logger.h + +if WIN32 +libimobiledevice_1_0_la_LDFLAGS += -avoid-version -static-libgcc +libimobiledevice_1_0_la_LIBADD += -lole32 -lws2_32 -lgdi32 +endif + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = libimobiledevice-1.0.pc @@ -1,308 +1,243 @@ /* - * afc.c + * afc.c * Contains functions for the built-in AFC client. - * + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. + * Copyright (c) 2009-2014 Nikias Bassen. All Rights Reserved. * Copyright (c) 2008 Zach C. 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> -#include "afc.h" #include "idevice.h" -#include "debug.h" - -/** The maximum size an AFC data packet can be */ -static const int MAXIMUM_PACKET_SIZE = (2 << 15); +#include "afc.h" +#include "common/debug.h" +#include "endianness.h" /** * Locks an AFC client, done for thread safety stuff - * + * * @param client The AFC client connection to lock */ static void afc_lock(afc_client_t client) { debug_info("Locked"); - g_mutex_lock(client->mutex); + mutex_lock(&client->mutex); } /** * Unlocks an AFC client, done for thread safety stuff. - * - * @param client The AFC + * + * @param client The AFC */ static void afc_unlock(afc_client_t client) { debug_info("Unlocked"); - g_mutex_unlock(client->mutex); + mutex_unlock(&client->mutex); } /** * Makes a connection to the AFC service on the device using the given * connection. * - * @param connection An idevice_connection_t that must have been previously - * connected using idevice_connect(). Note that this connection will - * not be closed by calling afc_client_free(). + * @param service_client A connected service client * @param client Pointer that will be set to a newly allocated afc_client_t * upon successful return. - * + * * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARG if connection is * invalid, or AFC_E_NO_MEM if there is a memory allocation problem. */ -afc_error_t afc_client_new_from_connection(idevice_connection_t connection, afc_client_t *client) +afc_error_t afc_client_new_with_service_client(service_client_t service_client, afc_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!connection) + if (!service_client) return AFC_E_INVALID_ARG; afc_client_t client_loc = (afc_client_t) malloc(sizeof(struct afc_client_private)); - client_loc->connection = connection; - client_loc->own_connection = 0; + client_loc->parent = service_client; + client_loc->free_parent = 0; /* allocate a packet */ - client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket)); + client_loc->packet_extra = 1024; + client_loc->afc_packet = (AFCPacket *) malloc(sizeof(AFCPacket) + client_loc->packet_extra); if (!client_loc->afc_packet) { free(client_loc); return AFC_E_NO_MEM; } - client_loc->afc_packet->packet_num = 0; client_loc->afc_packet->entire_length = 0; client_loc->afc_packet->this_length = 0; memcpy(client_loc->afc_packet->magic, AFC_MAGIC, AFC_MAGIC_LEN); - client_loc->file_handle = 0; - client_loc->lock = 0; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return AFC_E_SUCCESS; } -/** - * Makes a connection to the AFC service on the device. - * This function calls afc_client_new_from_connection() after creating - * a connection to the specified device and port. - * - * @see afc_client_new_from_connection - * - * @param device The device to connect to. - * @param port The destination port. - * @param client Pointer that will be set to a newly allocated afc_client_t - * upon successful return. - * - * @return AFC_E_SUCCESS on success, AFC_E_INVALID_ARG if device or port is - * invalid, AFC_E_MUX_ERROR if the connection cannot be established, - * or AFC_E_NO_MEM if there is a memory allocation problem. - */ -afc_error_t afc_client_new(idevice_t device, uint16_t port, afc_client_t * client) +afc_error_t afc_client_new(idevice_t device, lockdownd_service_descriptor_t service, afc_client_t * client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device || port==0) + if (!device || !service || service->port == 0) return AFC_E_INVALID_ARG; - /* attempt connection */ - idevice_connection_t connection = NULL; - if (idevice_connect(device, port, &connection) != IDEVICE_E_SUCCESS) { + service_client_t parent = NULL; + if (service_client_new(device, service, &parent) != SERVICE_E_SUCCESS) { return AFC_E_MUX_ERROR; } - afc_error_t err = afc_client_new_from_connection(connection, client); + afc_error_t err = afc_client_new_with_service_client(parent, client); if (err != AFC_E_SUCCESS) { - idevice_disconnect(connection); + service_client_free(parent); } else { - (*client)->own_connection = 1; + (*client)->free_parent = 1; } return err; } -/** - * Frees up an AFC client. If the connection was created by the - * client itself, the connection will be closed. - * - * @param client The client to free. - */ +afc_error_t afc_client_start_service(idevice_t device, afc_client_t * client, const char* label) +{ + afc_error_t err = AFC_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, AFC_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(afc_client_new), &err); + return err; +} + afc_error_t afc_client_free(afc_client_t client) { if (!client || !client->afc_packet) return AFC_E_INVALID_ARG; - if (client->own_connection && client->connection) { - idevice_disconnect(client->connection); - client->connection = NULL; + if (client->free_parent && client->parent) { + service_client_free(client->parent); + client->parent = NULL; } free(client->afc_packet); - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return AFC_E_SUCCESS; } /** * Dispatches an AFC packet over a client. - * + * * @param client The client to send data through. - * @param data The data to send. - * @param length The length to send. - * @param bytes_sent The number of bytes actually sent. + * @param operation The operation to perform. + * @param data The data to send together with the header. + * @param data_length The length of the data to send with the header. + * @param payload The data to send after the header has been sent. + * @param payload_length The length of data to send after the header. + * @param bytes_sent The total number of bytes actually sent. * * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - * - * @warning set client->afc_packet->this_length and - * client->afc_packet->entire_length to 0 before calling this. The - * reason is that if you set them to different values, it indicates - * you want to send the data as two packets. */ -static afc_error_t afc_dispatch_packet(afc_client_t client, const char *data, uint32_t length, uint32_t *bytes_sent) +static afc_error_t afc_dispatch_packet(afc_client_t client, uint64_t operation, uint32_t data_length, const char* payload, uint32_t payload_length, uint32_t *bytes_sent) { - uint32_t offset = 0; uint32_t sent = 0; - if (!client || !client->connection || !client->afc_packet) + if (!client || !client->parent || !client->afc_packet) return AFC_E_INVALID_ARG; *bytes_sent = 0; - if (!data || !length) - length = 0; + if (!payload || !payload_length) + payload_length = 0; client->afc_packet->packet_num++; - if (!client->afc_packet->entire_length) { - client->afc_packet->entire_length = (length) ? sizeof(AFCPacket) + length : sizeof(AFCPacket); - client->afc_packet->this_length = client->afc_packet->entire_length; - } - if (!client->afc_packet->this_length) { - client->afc_packet->this_length = sizeof(AFCPacket); - } - /* We want to send two segments; buffer+sizeof(AFCPacket) to this_length - is the parameters and everything beyond that is the next packet. - (for writing) */ - if (client->afc_packet->this_length != client->afc_packet->entire_length) { - offset = client->afc_packet->this_length - sizeof(AFCPacket); - - debug_info("Offset: %i", offset); - if ((length) < (client->afc_packet->entire_length - client->afc_packet->this_length)) { - debug_info("Length did not resemble what it was supposed to based on packet"); - debug_info("length minus offset: %i", length - offset); - debug_info("rest of packet: %i\n", client->afc_packet->entire_length - client->afc_packet->this_length); - return AFC_E_INTERNAL_ERROR; - } - - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - idevice_connection_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - AFCPacket_from_LE(client->afc_packet); - if (sent == 0) { - /* FIXME: should this be handled as success?! */ - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - /* send AFC packet data */ - sent = 0; - idevice_connection_send(client->connection, data, offset, &sent); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - - debug_info("sent the first now go with the second"); - debug_info("Length: %i", length - offset); - debug_info("Buffer: "); - debug_buffer(data + offset, length - offset); - - sent = 0; - idevice_connection_send(client->connection, data + offset, length - offset, &sent); - - *bytes_sent = sent; + client->afc_packet->operation = operation; + client->afc_packet->entire_length = sizeof(AFCPacket) + data_length + payload_length; + client->afc_packet->this_length = sizeof(AFCPacket) + data_length; + + debug_info("packet length = %i", client->afc_packet->this_length); + + /* send AFC packet header and data */ + AFCPacket_to_LE(client->afc_packet); + debug_buffer((char*)client->afc_packet, sizeof(AFCPacket) + data_length); + sent = 0; + service_send(client->parent, (void*)client->afc_packet, sizeof(AFCPacket) + data_length, &sent); + AFCPacket_from_LE(client->afc_packet); + *bytes_sent += sent; + if (sent < sizeof(AFCPacket) + data_length) { return AFC_E_SUCCESS; - } else { - debug_info("doin things the old way"); - debug_info("packet length = %i", client->afc_packet->this_length); - - debug_buffer((char*)client->afc_packet, sizeof(AFCPacket)); + } - /* send AFC packet header */ - AFCPacket_to_LE(client->afc_packet); - sent = 0; - idevice_connection_send(client->connection, (void*)client->afc_packet, sizeof(AFCPacket), &sent); - AFCPacket_from_LE(client->afc_packet); - if (sent == 0) { - return AFC_E_SUCCESS; - } - *bytes_sent += sent; - /* send AFC packet data (if there's data to send) */ - if (length > 0) { - debug_info("packet data follows"); - - debug_buffer(data, length); - idevice_connection_send(client->connection, data, length, &sent); - *bytes_sent += sent; + sent = 0; + if (payload_length > 0) { + if (payload_length > 256) { + debug_info("packet payload follows (256/%u)", payload_length); + debug_buffer(payload, 256); + } else { + debug_info("packet payload follows"); + debug_buffer(payload, payload_length); } + service_send(client->parent, payload, payload_length, &sent); + } + *bytes_sent += sent; + if (sent < payload_length) { return AFC_E_SUCCESS; } - return AFC_E_INTERNAL_ERROR; + + return AFC_E_SUCCESS; } /** * Receives data through an AFC client and sets a variable to the received data. - * + * * @param client The client to receive data on. - * @param dump_here The char* to point to the newly-received data. + * @param bytes The char* to point to the newly-received data. * @param bytes_recv How much data was received. - * + * * @return AFC_E_SUCCESS on success or an AFC_E_* error value. */ -static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint32_t *bytes_recv) +static afc_error_t afc_receive_data(afc_client_t client, char **bytes, uint32_t *bytes_recv) { AFCPacket header; uint32_t entire_len = 0; uint32_t this_len = 0; uint32_t current_count = 0; uint64_t param1 = -1; + char *buf = NULL; + uint32_t recv_len = 0; - *bytes_recv = 0; + if (bytes_recv) { + *bytes_recv = 0; + } + if (bytes) { + *bytes = NULL; + } /* first, read the AFC header */ - idevice_connection_receive(client->connection, (char*)&header, sizeof(AFCPacket), bytes_recv); + service_receive(client->parent, (char*)&header, sizeof(AFCPacket), &recv_len); AFCPacket_from_LE(&header); - if (*bytes_recv == 0) { + if (recv_len == 0) { debug_info("Just didn't get enough."); - *dump_here = NULL; return AFC_E_MUX_ERROR; - } else if (*bytes_recv < sizeof(AFCPacket)) { + } + + if (recv_len < sizeof(AFCPacket)) { debug_info("Did not even get the AFCPacket header"); - *dump_here = NULL; return AFC_E_MUX_ERROR; } /* check if it's a valid AFC header */ - if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN)) { + if (strncmp(header.magic, AFC_MAGIC, AFC_MAGIC_LEN) != 0) { debug_info("Invalid AFC packet received (magic != " AFC_MAGIC ")!"); } @@ -310,25 +245,21 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (header.packet_num != client->afc_packet->packet_num) { /* otherwise print a warning but do not abort */ debug_info("ERROR: Unexpected packet number (%lld != %lld) aborting.", header.packet_num, client->afc_packet->packet_num); - *dump_here = NULL; return AFC_E_OP_HEADER_INVALID; } /* then, read the attached packet */ if (header.this_length < sizeof(AFCPacket)) { debug_info("Invalid AFCPacket header received!"); - *dump_here = NULL; return AFC_E_OP_HEADER_INVALID; - } else if ((header.this_length == header.entire_length) - && header.entire_length == sizeof(AFCPacket)) { + } + if ((header.this_length == header.entire_length) + && header.entire_length == sizeof(AFCPacket)) { debug_info("Empty AFCPacket received!"); - *dump_here = NULL; - *bytes_recv = 0; if (header.operation == AFC_OP_DATA) { return AFC_E_SUCCESS; - } else { - return AFC_E_IO_ERROR; } + return AFC_E_IO_ERROR; } debug_info("received AFC packet, full len=%lld, this len=%lld, operation=0x%llx", header.entire_length, header.this_length, header.operation); @@ -336,22 +267,17 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 entire_len = (uint32_t)header.entire_length - sizeof(AFCPacket); this_len = (uint32_t)header.this_length - sizeof(AFCPacket); - /* this is here as a check (perhaps a different upper limit is good?) */ - if (entire_len > (uint32_t)MAXIMUM_PACKET_SIZE) { - fprintf(stderr, "%s: entire_len is larger than MAXIMUM_PACKET_SIZE, (%d > %d)!", __func__, entire_len, MAXIMUM_PACKET_SIZE); - } - - *dump_here = (char*)malloc(entire_len); + buf = (char*)malloc(entire_len); if (this_len > 0) { - idevice_connection_receive(client->connection, *dump_here, this_len, bytes_recv); - if (*bytes_recv <= 0) { - free(*dump_here); - *dump_here = NULL; + recv_len = 0; + service_receive(client->parent, buf, this_len, &recv_len); + if (recv_len <= 0) { + free(buf); debug_info("Did not get packet contents!"); return AFC_E_NOT_ENOUGH_DATA; - } else if (*bytes_recv < this_len) { - free(*dump_here); - *dump_here = NULL; + } + if (recv_len < this_len) { + free(buf); debug_info("Could not receive this_len=%d bytes", this_len); return AFC_E_NOT_ENOUGH_DATA; } @@ -361,12 +287,13 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (entire_len > this_len) { while (current_count < entire_len) { - idevice_connection_receive(client->connection, (*dump_here)+current_count, entire_len - current_count, bytes_recv); - if (*bytes_recv <= 0) { - debug_info("Error receiving data (recv returned %d)", *bytes_recv); + recv_len = 0; + service_receive(client->parent, buf+current_count, entire_len - current_count, &recv_len); + if (recv_len <= 0) { + debug_info("Error receiving data (recv returned %d)", recv_len); break; } - current_count += *bytes_recv; + current_count += recv_len; } if (current_count < entire_len) { debug_info("WARNING: could not receive full packet (read %s, size %d)", current_count, entire_len); @@ -374,12 +301,17 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 } if (current_count >= sizeof(uint64_t)) { - param1 = GUINT64_FROM_LE(*(uint64_t*)(*dump_here)); + param1 = le64toh(*(uint64_t*)(buf)); } debug_info("packet data size = %i", current_count); - debug_info("packet data follows"); - debug_buffer(*dump_here, current_count); + if (current_count > 256) { + debug_info("packet data follows (256/%u)", current_count); + debug_buffer(buf, 256); + } else { + debug_info("packet data follows"); + debug_buffer(buf, current_count); + } /* check operation types */ if (header.operation == AFC_OP_STATUS) { @@ -389,8 +321,7 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 if (param1 != AFC_E_SUCCESS) { /* error status */ /* free buffer */ - free(*dump_here); - *dump_here = NULL; + free(buf); return (afc_error_t)param1; } } else if (header.operation == AFC_OP_DATA) { @@ -404,16 +335,22 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 debug_info("got a tell response, position=%lld", param1); } else { /* unknown operation code received */ - free(*dump_here); - *dump_here = NULL; - *bytes_recv = 0; + free(buf); debug_info("WARNING: Unknown operation code received 0x%llx param1=%lld", header.operation, param1); +#ifndef WIN32 fprintf(stderr, "%s: WARNING: Unknown operation code received 0x%llx param1=%lld", __func__, (long long)header.operation, (long long)param1); +#endif return AFC_E_OP_NOT_SUPPORTED; } + if (bytes) { + *bytes = buf; + } else { + free(buf); + } + *bytes_recv = current_count; return AFC_E_SUCCESS; } @@ -421,7 +358,7 @@ static afc_error_t afc_receive_data(afc_client_t client, char **dump_here, uint3 /** * Returns counts of null characters within a string. */ -static uint32_t count_nullspaces(char *string, uint32_t number) +static uint32_t count_nullspaces(const char *string, uint32_t number) { uint32_t i = 0, nulls = 0; @@ -462,32 +399,42 @@ static char **make_strings_list(char *tokens, uint32_t length) return list; } -/** - * Gets a directory listing of the directory requested. - * - * @param client The client to get a directory listing from. - * @param dir The directory to list. (must be a fully-qualified path) - * @param list A char list of files in that directory, terminated by an empty - * string or NULL if there was an error. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***list) +static int _afc_check_packet_buffer(afc_client_t client, uint32_t data_len) +{ + if (data_len > client->packet_extra) { + client->packet_extra = (data_len & ~8) + 8; + AFCPacket* newpkt = (AFCPacket*)realloc(client->afc_packet, sizeof(AFCPacket) + client->packet_extra); + if (!newpkt) { + return -1; + } + client->afc_packet = newpkt; + } + return 0; +} + +#define AFC_PACKET_DATA_PTR ((char*)client->afc_packet + sizeof(AFCPacket)) + +afc_error_t afc_read_directory(afc_client_t client, const char *path, char ***directory_information) { uint32_t bytes = 0; char *data = NULL, **list_loc = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !dir || !list || (list && *list)) + if (!client || !path || !directory_information || (directory_information && *directory_information)) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send the command */ - client->afc_packet->operation = AFC_OP_READ_DIR; - client->afc_packet->entire_length = 0; - client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_READ_DIR, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -495,6 +442,8 @@ afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***lis /* Receive the data */ ret = afc_receive_data(client, &data, &bytes); if (ret != AFC_E_SUCCESS) { + if (data) + free(data); afc_unlock(client); return ret; } @@ -504,37 +453,24 @@ afc_error_t afc_read_directory(afc_client_t client, const char *dir, char ***lis free(data); afc_unlock(client); - *list = list_loc; + *directory_information = list_loc; return ret; } -/** - * Get device info for a client connection to phone. The device information - * returned is the device model as well as the free space, the total capacity - * and blocksize on the accessed disk partition. - * - * @param client The client to get device info for. - * @param infos A char ** list of parameters as given by AFC or NULL if there - * was an error. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_get_device_info(afc_client_t client, char ***infos) +afc_error_t afc_get_device_info(afc_client_t client, char ***device_information) { uint32_t bytes = 0; char *data = NULL, **list = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !infos) + if (!client || !device_information) return AFC_E_INVALID_ARG; afc_lock(client); /* Send the command */ - client->afc_packet->operation = AFC_OP_GET_DEVINFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, NULL, 0, &bytes); + ret = afc_dispatch_packet(client, AFC_OP_GET_DEVINFO, 0, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -542,6 +478,8 @@ afc_error_t afc_get_device_info(afc_client_t client, char ***infos) /* Receive the data */ ret = afc_receive_data(client, &data, &bytes); if (ret != AFC_E_SUCCESS) { + if (data) + free(data); afc_unlock(client); return ret; } @@ -552,22 +490,11 @@ afc_error_t afc_get_device_info(afc_client_t client, char ***infos) afc_unlock(client); - *infos = list; + *device_information = list; return ret; } -/** - * Get a specific key of the device info list for a client connection. - * Known key values are: Model, FSTotalBytes, FSFreeBytes and FSBlockSize. - * This is a helper function for afc_get_device_info(). - * - * @param client The client to get device info for. - * @param key The key to get the value of. - * @param value The value for the key if successful or NULL otherwise. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char **value) { afc_error_t ret = AFC_E_INTERNAL_ERROR; @@ -587,43 +514,40 @@ afc_error_t afc_get_device_info_key(afc_client_t client, const char *key, char * break; } } - - g_strfreev(kvps); + for (ptr = kvps; *ptr; ptr++) { + free(*ptr); + } + free(kvps); return ret; } -/** - * Deletes a file or directory. - * - * @param client The client to use. - * @param path The path to delete. (must be a fully-qualified path) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_remove_path(afc_client_t client, const char *path) { - char *response = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) + if (!client || !path || !client->afc_packet || !client->parent) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - client->afc_packet->operation = AFC_OP_REMOVE_PATH; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_REMOVE_PATH, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); /* special case; unknown error actually means directory not empty */ if (ret == AFC_E_UNKNOWN_ERROR) @@ -634,61 +558,45 @@ afc_error_t afc_remove_path(afc_client_t client, const char *path) return ret; } -/** - * Renames a file or directory on the phone. - * - * @param client The client to have rename. - * @param from The name to rename from. (must be a fully-qualified path) - * @param to The new name. (must also be a fully-qualified path) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_rename_path(afc_client_t client, const char *from, const char *to) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(from) + strlen(to) + 1 + sizeof(uint32_t))); + if (!client || !from || !to || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !from || !to || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; + size_t from_len = strlen(from); + size_t to_len = strlen(to); afc_lock(client); + uint32_t data_len = (uint32_t)(from_len+1 + to_len+1); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, from, strlen(from) + 1); - memcpy(send + strlen(from) + 1, to, strlen(to) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_RENAME_PATH; - ret = afc_dispatch_packet(client, send, strlen(to)+1 + strlen(from)+1, &bytes); - free(send); + memcpy(AFC_PACKET_DATA_PTR, from, from_len+1); + memcpy(AFC_PACKET_DATA_PTR + from_len+1, to, to_len+1); + ret = afc_dispatch_packet(client, AFC_OP_RENAME_PATH, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Creates a directory on the phone. - * - * @param client The client to use to make a directory. - * @param dir The directory's path. (must be a fully-qualified path, I assume - * all other mkdir restrictions apply as well) - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_make_directory(afc_client_t client, const char *dir) +afc_error_t afc_make_directory(afc_client_t client, const char *path) { uint32_t bytes = 0; - char *response = NULL; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client) @@ -696,50 +604,51 @@ afc_error_t afc_make_directory(afc_client_t client, const char *dir) afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - client->afc_packet->operation = AFC_OP_MAKE_DIR; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, dir, strlen(dir)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_MAKE_DIR, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Gets information about a specific file. - * - * @param client The client to use to get the information of the file. - * @param path The fully-qualified path to the file. - * @param infolist Pointer to a buffer that will be filled with a NULL-terminated - * list of strings with the file information. - * Set to NULL before calling this function. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***infolist) +afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***file_information) { char *received = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !infolist) + if (!client || !path || !file_information) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = (uint32_t)strlen(path)+1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + debug_info("We got %p and %p", client->afc_packet, AFC_PACKET_DATA_PTR); + /* Send command */ - client->afc_packet->operation = AFC_OP_GET_FILE_INFO; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, path, strlen(path)+1, &bytes); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_GET_FILE_INFO, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -748,7 +657,7 @@ afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***inf /* Receive data */ ret = afc_receive_data(client, &received, &bytes); if (received) { - *infolist = make_strings_list(received, bytes); + *file_information = make_strings_list(received, bytes); free(received); } @@ -757,51 +666,39 @@ afc_error_t afc_get_file_info(afc_client_t client, const char *path, char ***inf return ret; } -/** - * Opens a file on the phone. - * - * @param client The client to use to open the file. - * @param filename The file to open. (must be a fully-qualified path) - * @param file_mode The mode to use to open the file. Can be AFC_FILE_READ or - * AFC_FILE_WRITE; the former lets you read and write, - * however, and the second one will *create* the file, - * destroying anything previously there. - * @param handle Pointer to a uint64_t that will hold the handle of the file - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_open(afc_client_t client, const char *filename, - afc_file_mode_t file_mode, uint64_t *handle) +afc_error_t afc_file_open(afc_client_t client, const char *filename, afc_file_mode_t file_mode, uint64_t *handle) { - uint64_t file_mode_loc = GUINT64_TO_LE(file_mode); + if (!client || !client->parent || !client->afc_packet) + return AFC_E_INVALID_ARG; + + //uint64_t file_mode_loc = htole64(file_mode); uint32_t bytes = 0; - char *data = (char *) malloc(sizeof(char) * (8 + strlen(filename) + 1)); afc_error_t ret = AFC_E_UNKNOWN_ERROR; /* set handle to 0 so in case an error occurs, the handle is invalid */ *handle = 0; - if (!client || !client->connection || !client->afc_packet) - return AFC_E_INVALID_ARG; - afc_lock(client); - /* Send command */ - memcpy(data, &file_mode_loc, 8); - memcpy(data + 8, filename, strlen(filename)); - data[8 + strlen(filename)] = '\0'; - client->afc_packet->operation = AFC_OP_FILE_OPEN; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, data, 8 + strlen(filename) + 1, &bytes); - free(data); + uint32_t data_len = (uint32_t)(strlen(filename)+1 + 8); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ + //memcpy(AFC_PACKET_DATA_PTR, &file_mode_loc, 8); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(file_mode); + memcpy(AFC_PACKET_DATA_PTR + 8, filename, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_FILE_OPEN, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { debug_info("Didn't receive a response to the command"); afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive the data */ + char* data = NULL; ret = afc_receive_data(client, &data, &bytes); if ((ret == AFC_E_SUCCESS) && (bytes > 0) && data) { afc_unlock(client); @@ -811,6 +708,8 @@ afc_file_open(afc_client_t client, const char *filename, free(data); return ret; } + /* in case memory was allocated but no data received or an error occurred */ + free(data); debug_info("Didn't get any further data"); @@ -819,156 +718,81 @@ afc_file_open(afc_client_t client, const char *filename, return ret; } -/** - * Attempts to the read the given number of bytes from the given file. - * - * @param client The relevant AFC client - * @param handle File handle of a previously opened file - * @param data The pointer to the memory region to store the read data - * @param length The number of bytes to read - * @param bytes_read The number of bytes actually read. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) +afc_error_t afc_file_read(afc_client_t client, uint64_t handle, char *data, uint32_t length, uint32_t *bytes_read) { char *input = NULL; uint32_t current_count = 0, bytes_loc = 0; - const uint32_t MAXIMUM_READ_SIZE = 1 << 16; + struct readinfo { + uint64_t handle; + uint64_t size; + }; afc_error_t ret = AFC_E_SUCCESS; - if (!client || !client->afc_packet || !client->connection || handle == 0) + if (!client || !client->afc_packet || !client->parent || handle == 0) return AFC_E_INVALID_ARG; debug_info("called for length %i", length); + //uint32_t data_len = 8 + 8; + afc_lock(client); - /* Looping here to get around the maximum amount of data that - afc_receive_data can handle */ - while (current_count < length) { - debug_info("current count is %i but length is %i", current_count, length); - - /* Send the read command */ - AFCFilePacket *packet = (AFCFilePacket *) malloc(sizeof(AFCFilePacket)); - packet->filehandle = handle; - packet->size = GUINT64_TO_LE(((length - current_count) < MAXIMUM_READ_SIZE) ? (length - current_count) : MAXIMUM_READ_SIZE); - client->afc_packet->operation = AFC_OP_READ; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, (char *) packet, sizeof(AFCFilePacket), &bytes_loc); - free(packet); - - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - /* Receive the data */ - ret = afc_receive_data(client, &input, &bytes_loc); - debug_info("afc_receive_data returned error: %d", ret); - debug_info("bytes returned: %i", bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else if (bytes_loc == 0) { - if (input) - free(input); - afc_unlock(client); - *bytes_read = current_count; - /* FIXME: check that's actually a success */ - return ret; - } else { - if (input) { - debug_info("%d", bytes_loc); - memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); - free(input); - input = NULL; - current_count += (bytes_loc > length) ? length : bytes_loc; - } - } + /* Send the read command */ + struct readinfo* readinfo = (struct readinfo*)(AFC_PACKET_DATA_PTR); + readinfo->handle = handle; + readinfo->size = htole64(length); + ret = afc_dispatch_packet(client, AFC_OP_FILE_READ, sizeof(struct readinfo), NULL, 0, &bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + /* Receive the data */ + ret = afc_receive_data(client, &input, &bytes_loc); + debug_info("afc_receive_data returned error: %d", ret); + debug_info("bytes returned: %i", bytes_loc); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return ret; + } + if (bytes_loc == 0) { + if (input) + free(input); + afc_unlock(client); + *bytes_read = current_count; + /* FIXME: check that's actually a success */ + return ret; + } + if (input) { + debug_info("%d", bytes_loc); + memcpy(data + current_count, input, (bytes_loc > length) ? length : bytes_loc); + free(input); + input = NULL; + current_count += (bytes_loc > length) ? length : bytes_loc; } - debug_info("returning current_count as %i", current_count); afc_unlock(client); *bytes_read = current_count; return ret; } -/** - * Writes a given number of bytes to a file. - * - * @param client The client to use to write to the file. - * @param handle File handle of previously opened file. - * @param data The data to write to the file. - * @param length How much data to write. - * @param bytes_written The number of bytes actually written to the file. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ -idevice_error_t -afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) +afc_error_t afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t length, uint32_t *bytes_written) { - char *acknowledgement = NULL; - const uint32_t MAXIMUM_WRITE_SIZE = 1 << 15; - uint32_t current_count = 0, i = 0; - uint32_t segments = (length / MAXIMUM_WRITE_SIZE); + uint32_t current_count = 0; uint32_t bytes_loc = 0; - char *out_buffer = NULL; afc_error_t ret = AFC_E_SUCCESS; - if (!client || !client->afc_packet || !client->connection || !bytes_written || (handle == 0)) + if (!client || !client->afc_packet || !client->parent || !bytes_written || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); debug_info("Write length: %i", length); - /* Divide the file into segments. */ - for (i = 0; i < segments; i++) { - /* Send the segment */ - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + MAXIMUM_WRITE_SIZE; - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *)&handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, MAXIMUM_WRITE_SIZE); - ret = afc_dispatch_packet(client, out_buffer, MAXIMUM_WRITE_SIZE + 8, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return AFC_E_NOT_ENOUGH_DATA; - } - free(out_buffer); - out_buffer = NULL; - - current_count += bytes_loc; - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); - if (ret != AFC_E_SUCCESS) { - afc_unlock(client); - return ret; - } else { - free(acknowledgement); - } - } - - /* By this point, we should be at the end. i.e. the last segment that didn't - get sent in the for loop. This length is fine because it's always - sizeof(AFCPacket) + 8, but to be sure we do it again */ - if (current_count == length) { - afc_unlock(client); - *bytes_written = current_count; - return ret; - } - - client->afc_packet->this_length = sizeof(AFCPacket) + 8; - client->afc_packet->entire_length = client->afc_packet->this_length + (length - current_count); - client->afc_packet->operation = AFC_OP_WRITE; - out_buffer = (char *) malloc(sizeof(char) * client->afc_packet->entire_length - sizeof(AFCPacket)); - memcpy(out_buffer, (char *) &handle, sizeof(uint64_t)); - memcpy(out_buffer + 8, data + current_count, (length - current_count)); - ret = afc_dispatch_packet(client, out_buffer, (length - current_count) + 8, &bytes_loc); - free(out_buffer); - out_buffer = NULL; + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_WRITE, data_len, data, length, &bytes_loc); - current_count += bytes_loc; + current_count += bytes_loc - (sizeof(AFCPacket) + 8); if (ret != AFC_E_SUCCESS) { afc_unlock(client); @@ -976,43 +800,32 @@ afc_file_write(afc_client_t client, uint64_t handle, const char *data, uint32_t return AFC_E_SUCCESS; } - ret = afc_receive_data(client, &acknowledgement, &bytes_loc); + ret = afc_receive_data(client, NULL, &bytes_loc); afc_unlock(client); if (ret != AFC_E_SUCCESS) { - debug_info("uh oh?"); - } else { - free(acknowledgement); + debug_info("Failed to receive reply (%d)", ret); } *bytes_written = current_count; return ret; } -/** - * Closes a file on the phone. - * - * @param client The client to close the file with. - * @param handle File handle of a previously opened file. - */ afc_error_t afc_file_close(afc_client_t client, uint64_t handle) { - char *buffer = malloc(sizeof(char) * 8); uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); debug_info("File handle %i", handle); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); - client->afc_packet->operation = AFC_OP_FILE_CLOSE; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_CLOSE, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); @@ -1020,32 +833,20 @@ afc_error_t afc_file_close(afc_client_t client, uint64_t handle) } /* Receive the response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Locks or unlocks a file on the phone. - * - * makes use of flock on the device, see - * http://developer.apple.com/documentation/Darwin/Reference/ManPages/man2/flock.2.html - * - * @param client The client to lock the file with. - * @param handle File handle of a previously opened file. - * @param operation the lock or unlock operation to perform, this is one of - * AFC_LOCK_SH (shared lock), AFC_LOCK_EX (exclusive lock), - * or AFC_LOCK_UN (unlock). - */ afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t operation) { - char *buffer = malloc(16); uint32_t bytes = 0; - uint64_t op = GUINT64_TO_LE(operation); + struct lockinfo { + uint64_t handle; + uint64_t op; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1056,47 +857,31 @@ afc_error_t afc_file_lock(afc_client_t client, uint64_t handle, afc_lock_op_t op debug_info("file handle %i", handle); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); - memcpy(buffer + 8, &op, 8); - - client->afc_packet->operation = AFC_OP_FILE_LOCK; - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; - + struct lockinfo* lockinfo = (struct lockinfo*)(AFC_PACKET_DATA_PTR); + lockinfo->handle = handle; + lockinfo->op = htole64(operation); + ret = afc_dispatch_packet(client, AFC_OP_FILE_LOCK, sizeof(struct lockinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); debug_info("could not send lock command"); return AFC_E_UNKNOWN_ERROR; } /* Receive the response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) { - debug_buffer(buffer, bytes); - free(buffer); - } + ret = afc_receive_data(client, NULL, &bytes); + afc_unlock(client); return ret; } -/** - * Seeks to a given position of a pre-opened file on the phone. - * - * @param client The client to use to seek to the position. - * @param handle File handle of a previously opened. - * @param offset Seek offset. - * @param whence Seeking direction, one of SEEK_SET, SEEK_CUR, or SEEK_END. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, int whence) { - char *buffer = (char *) malloc(sizeof(char) * 24); - int64_t offset_loc = (int64_t)GUINT64_TO_LE(offset); - uint64_t whence_loc = GUINT64_TO_LE(whence); uint32_t bytes = 0; + struct seekinfo { + uint64_t handle; + uint64_t whence; + int64_t offset; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1105,57 +890,40 @@ afc_error_t afc_file_seek(afc_client_t client, uint64_t handle, int64_t offset, afc_lock(client); /* Send the command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - memcpy(buffer + 8, &whence_loc, sizeof(uint64_t)); /* fromwhere */ - memcpy(buffer + 16, &offset_loc, sizeof(uint64_t)); /* offset */ - client->afc_packet->operation = AFC_OP_FILE_SEEK; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 24, &bytes); - free(buffer); - buffer = NULL; + struct seekinfo* seekinfo = (struct seekinfo*)(AFC_PACKET_DATA_PTR); + seekinfo->handle = handle; + seekinfo->whence = htole64(whence); + seekinfo->offset = (int64_t)htole64(offset); + ret = afc_dispatch_packet(client, AFC_OP_FILE_SEEK, sizeof(struct seekinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Returns current position in a pre-opened file on the phone. - * - * @param client The client to use. - * @param handle File handle of a previously opened file. - * @param position Position in bytes of indicator - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *position) { - char *buffer = (char *) malloc(sizeof(char) * 8); + char *buffer = NULL; uint32_t bytes = 0; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) return AFC_E_INVALID_ARG; + uint32_t data_len = 8; + afc_lock(client); /* Send the command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - client->afc_packet->operation = AFC_OP_FILE_TELL; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 8, &bytes); - free(buffer); - buffer = NULL; - + *(uint64_t*)(AFC_PACKET_DATA_PTR) = handle; + ret = afc_dispatch_packet(client, AFC_OP_FILE_TELL, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; @@ -1166,33 +934,22 @@ afc_error_t afc_file_tell(afc_client_t client, uint64_t handle, uint64_t *positi if (bytes > 0 && buffer) { /* Get the position */ memcpy(position, buffer, sizeof(uint64_t)); - *position = GUINT64_FROM_LE(*position); + *position = le64toh(*position); } - if (buffer) - free(buffer); + free(buffer); afc_unlock(client); return ret; } -/** - * Sets the size of a file on the phone. - * - * @param client The client to use to set the file size. - * @param handle File handle of a previously opened file. - * @param newsize The size to set the file to. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - * - * @note This function is more akin to ftruncate than truncate, and truncate - * calls would have to open the file before calling this, sadly. - */ afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t newsize) { - char *buffer = (char *) malloc(sizeof(char) * 16); uint32_t bytes = 0; - uint64_t newsize_loc = GUINT64_TO_LE(newsize); + struct truncinfo { + uint64_t handle; + uint64_t newsize; + }; afc_error_t ret = AFC_E_UNKNOWN_ERROR; if (!client || (handle == 0)) @@ -1201,160 +958,240 @@ afc_error_t afc_file_truncate(afc_client_t client, uint64_t handle, uint64_t new afc_lock(client); /* Send command */ - memcpy(buffer, &handle, sizeof(uint64_t)); /* handle */ - memcpy(buffer + 8, &newsize_loc, sizeof(uint64_t)); /* newsize */ - client->afc_packet->operation = AFC_OP_FILE_SET_SIZE; - client->afc_packet->this_length = client->afc_packet->entire_length = 0; - ret = afc_dispatch_packet(client, buffer, 16, &bytes); - free(buffer); - buffer = NULL; + struct truncinfo* truncinfo = (struct truncinfo*)(AFC_PACKET_DATA_PTR); + truncinfo->handle = handle; + truncinfo->newsize = htole64(newsize); + ret = afc_dispatch_packet(client, AFC_OP_FILE_SET_SIZE, sizeof(struct truncinfo), NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &buffer, &bytes); - if (buffer) - free(buffer); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Sets the size of a file on the phone without prior opening it. - * - * @param client The client to use to set the file size. - * @param path The path of the file to be truncated. - * @param newsize The size to set the file to. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_truncate(afc_client_t client, const char *path, uint64_t newsize) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + if (!client || !path || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t size_requested = GUINT64_TO_LE(newsize); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; - afc_lock(client); + uint32_t data_len = 8 + (uint32_t)(strlen(path)+1); + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, &size_requested, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_TRUNCATE; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(newsize); + memcpy(AFC_PACKET_DATA_PTR + 8, path, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_TRUNCATE, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Creates a hard link or symbolic link on the device. - * - * @param client The client to use for making a link - * @param linktype 1 = hard link, 2 = symlink - * @param target The file to be linked. - * @param linkname The name of link. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_make_link(afc_client_t client, afc_link_type_t linktype, const char *target, const char *linkname) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(target)+1 + strlen(linkname)+1 + 8)); + if (!client || !target || !linkname || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t type = GUINT64_TO_LE(linktype); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !target || !linkname || !client->afc_packet || !client->connection) - return AFC_E_INVALID_ARG; + size_t target_len = strlen(target); + size_t link_len = strlen(linkname); afc_lock(client); - debug_info("link type: %lld", type); - debug_info("target: %s, length:%d", target, strlen(target)); - debug_info("linkname: %s, length:%d", linkname, strlen(linkname)); + uint32_t data_len = 8 + target_len + 1 + link_len + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + debug_info("link type: %lld", htole64(linktype)); + debug_info("target: %s, length:%d", target, target_len); + debug_info("linkname: %s, length:%d", linkname, link_len); /* Send command */ - memcpy(send, &type, 8); - memcpy(send + 8, target, strlen(target) + 1); - memcpy(send + 8 + strlen(target) + 1, linkname, strlen(linkname) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_MAKE_LINK; - ret = afc_dispatch_packet(client, send, 8 + strlen(linkname) + 1 + strlen(target) + 1, &bytes); - free(send); + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(linktype); + memcpy(AFC_PACKET_DATA_PTR + 8, target, target_len + 1); + memcpy(AFC_PACKET_DATA_PTR + 8 + target_len + 1, linkname, link_len + 1); + ret = afc_dispatch_packet(client, AFC_OP_MAKE_LINK, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } -/** - * Sets the modification time of a file on the phone. - * - * @param client The client to use to set the file size. - * @param path Path of the file for which the modification time should be set. - * @param mtime The modification time to set in nanoseconds since epoch. - * - * @return AFC_E_SUCCESS on success or an AFC_E_* error value. - */ afc_error_t afc_set_file_time(afc_client_t client, const char *path, uint64_t mtime) { - char *response = NULL; - char *send = (char *) malloc(sizeof(char) * (strlen(path) + 1 + 8)); + if (!client || !path || !client->afc_packet || !client->parent) + return AFC_E_INVALID_ARG; + uint32_t bytes = 0; - uint64_t mtime_loc = GUINT64_TO_LE(mtime); afc_error_t ret = AFC_E_UNKNOWN_ERROR; - if (!client || !path || !client->afc_packet || !client->connection) + afc_lock(client); + + uint32_t data_len = 8 + strlen(path) + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + + /* Send command */ + *(uint64_t*)(AFC_PACKET_DATA_PTR) = htole64(mtime); + memcpy(AFC_PACKET_DATA_PTR + 8, path, data_len-8); + ret = afc_dispatch_packet(client, AFC_OP_SET_FILE_MOD_TIME, data_len, NULL, 0, &bytes); + if (ret != AFC_E_SUCCESS) { + afc_unlock(client); + return AFC_E_NOT_ENOUGH_DATA; + } + /* Receive response */ + ret = afc_receive_data(client, NULL, &bytes); + + afc_unlock(client); + + return ret; +} + +afc_error_t afc_remove_path_and_contents(afc_client_t client, const char *path) +{ + uint32_t bytes = 0; + afc_error_t ret = AFC_E_UNKNOWN_ERROR; + + if (!client || !path || !client->afc_packet || !client->parent) return AFC_E_INVALID_ARG; afc_lock(client); + uint32_t data_len = strlen(path) + 1; + if (_afc_check_packet_buffer(client, data_len) < 0) { + afc_unlock(client); + debug_info("Failed to realloc packet buffer"); + return AFC_E_NO_MEM; + } + /* Send command */ - memcpy(send, &mtime_loc, 8); - memcpy(send + 8, path, strlen(path) + 1); - client->afc_packet->entire_length = client->afc_packet->this_length = 0; - client->afc_packet->operation = AFC_OP_SET_FILE_TIME; - ret = afc_dispatch_packet(client, send, 8 + strlen(path) + 1, &bytes); - free(send); + memcpy(AFC_PACKET_DATA_PTR, path, data_len); + ret = afc_dispatch_packet(client, AFC_OP_REMOVE_PATH_AND_CONTENTS, data_len, NULL, 0, &bytes); if (ret != AFC_E_SUCCESS) { afc_unlock(client); return AFC_E_NOT_ENOUGH_DATA; } /* Receive response */ - ret = afc_receive_data(client, &response, &bytes); - if (response) - free(response); + ret = afc_receive_data(client, NULL, &bytes); afc_unlock(client); return ret; } +afc_error_t afc_dictionary_free(char **dictionary) +{ + int i = 0; + + if (!dictionary) + return AFC_E_INVALID_ARG; + + for (i = 0; dictionary[i]; i++) { + free(dictionary[i]); + } + free(dictionary); + + return AFC_E_SUCCESS; +} + +const char* afc_strerror(afc_error_t err) +{ + switch (err) { + case AFC_E_SUCCESS: + return "Success"; + case AFC_E_UNKNOWN_ERROR: + return "Unknown Error"; + case AFC_E_OP_HEADER_INVALID: + return "Operation header invalid"; + case AFC_E_NO_RESOURCES: + return "No resources"; + case AFC_E_READ_ERROR: + return "Read error"; + case AFC_E_WRITE_ERROR: + return "Write error"; + case AFC_E_UNKNOWN_PACKET_TYPE: + return "Unknown packet type"; + case AFC_E_INVALID_ARG: + return "Invalid argument"; + case AFC_E_OBJECT_NOT_FOUND: + return "Not found"; + case AFC_E_OBJECT_IS_DIR: + return "Object is a directory"; + case AFC_E_PERM_DENIED: + return "Permission denied"; + case AFC_E_SERVICE_NOT_CONNECTED: + return "Service not connected"; + case AFC_E_OP_TIMEOUT: + return "Timeout"; + case AFC_E_TOO_MUCH_DATA: + return "Too much data"; + case AFC_E_END_OF_DATA: + return "End of data"; + case AFC_E_OP_NOT_SUPPORTED: + return "Operation not supported"; + case AFC_E_OBJECT_EXISTS: + return "Object exists"; + case AFC_E_OBJECT_BUSY: + return "Object busy"; + case AFC_E_NO_SPACE_LEFT: + return "No space left on device"; + case AFC_E_OP_WOULD_BLOCK: + return "Operation would block"; + case AFC_E_IO_ERROR: + return "I/O error"; + case AFC_E_OP_INTERRUPTED: + return "Operation interrupted"; + case AFC_E_OP_IN_PROGRESS: + return "Operation on progress"; + case AFC_E_INTERNAL_ERROR: + return "Internal error"; + case AFC_E_MUX_ERROR: + return "MUX error"; + case AFC_E_NO_MEM: + return "Out of memory"; + case AFC_E_NOT_ENOUGH_DATA: + return "Not enough data"; + case AFC_E_DIR_NOT_EMPTY: + return "Directory not empty"; + case AFC_E_FORCE_SIGNED_TYPE: + return "Force signed type"; + default: + break; + } + return "Unknown Error"; +} @@ -1,28 +1,34 @@ -/* +/* * afc.h * Defines and structs and the like for the built-in AFC client - * + * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <glib.h> +#ifndef __AFC_H +#define __AFC_H + #include <stdint.h> #include "libimobiledevice/afc.h" +#include "service.h" +#include "endianness.h" +#include <libimobiledevice-glue/thread.h> #define AFC_MAGIC "CFA6LPAA" #define AFC_MAGIC_LEN (8) @@ -33,60 +39,72 @@ typedef struct { } AFCPacket; #define AFCPacket_to_LE(x) \ - (x)->entire_length = GUINT64_TO_LE((x)->entire_length); \ - (x)->this_length = GUINT64_TO_LE((x)->this_length); \ - (x)->packet_num = GUINT64_TO_LE((x)->packet_num); \ - (x)->operation = GUINT64_TO_LE((x)->operation); + (x)->entire_length = htole64((x)->entire_length); \ + (x)->this_length = htole64((x)->this_length); \ + (x)->packet_num = htole64((x)->packet_num); \ + (x)->operation = htole64((x)->operation); #define AFCPacket_from_LE(x) \ - (x)->entire_length = GUINT64_FROM_LE((x)->entire_length); \ - (x)->this_length = GUINT64_FROM_LE((x)->this_length); \ - (x)->packet_num = GUINT64_FROM_LE((x)->packet_num); \ - (x)->operation = GUINT64_FROM_LE((x)->operation); - -typedef struct { - uint64_t filehandle, size; -} AFCFilePacket; + (x)->entire_length = le64toh((x)->entire_length); \ + (x)->this_length = le64toh((x)->this_length); \ + (x)->packet_num = le64toh((x)->packet_num); \ + (x)->operation = le64toh((x)->operation); struct afc_client_private { - idevice_connection_t connection; + service_client_t parent; AFCPacket *afc_packet; - int file_handle; - int lock; - GMutex *mutex; - int own_connection; + uint32_t packet_extra; + mutex_t mutex; + int free_parent; }; /* AFC Operations */ enum { - AFC_OP_STATUS = 0x00000001, /* Status */ - AFC_OP_DATA = 0x00000002, /* Data */ - AFC_OP_READ_DIR = 0x00000003, /* ReadDir */ - AFC_OP_READ_FILE = 0x00000004, /* ReadFile */ - AFC_OP_WRITE_FILE = 0x00000005, /* WriteFile */ - AFC_OP_WRITE_PART = 0x00000006, /* WritePart */ - AFC_OP_TRUNCATE = 0x00000007, /* TruncateFile */ - AFC_OP_REMOVE_PATH = 0x00000008, /* RemovePath */ - AFC_OP_MAKE_DIR = 0x00000009, /* MakeDir */ - AFC_OP_GET_FILE_INFO = 0x0000000a, /* GetFileInfo */ - AFC_OP_GET_DEVINFO = 0x0000000b, /* GetDeviceInfo */ - AFC_OP_WRITE_FILE_ATOM = 0x0000000c, /* WriteFileAtomic (tmp file+rename) */ - AFC_OP_FILE_OPEN = 0x0000000d, /* FileRefOpen */ - AFC_OP_FILE_OPEN_RES = 0x0000000e, /* FileRefOpenResult */ - AFC_OP_READ = 0x0000000f, /* FileRefRead */ - AFC_OP_WRITE = 0x00000010, /* FileRefWrite */ - AFC_OP_FILE_SEEK = 0x00000011, /* FileRefSeek */ - AFC_OP_FILE_TELL = 0x00000012, /* FileRefTell */ - AFC_OP_FILE_TELL_RES = 0x00000013, /* FileRefTellResult */ - AFC_OP_FILE_CLOSE = 0x00000014, /* FileRefClose */ - AFC_OP_FILE_SET_SIZE = 0x00000015, /* FileRefSetFileSize (ftruncate) */ - AFC_OP_GET_CON_INFO = 0x00000016, /* GetConnectionInfo */ - AFC_OP_SET_CON_OPTIONS = 0x00000017, /* SetConnectionOptions */ - AFC_OP_RENAME_PATH = 0x00000018, /* RenamePath */ - AFC_OP_SET_FS_BS = 0x00000019, /* SetFSBlockSize (0x800000) */ - AFC_OP_SET_SOCKET_BS = 0x0000001A, /* SetSocketBlockSize (0x800000) */ - AFC_OP_FILE_LOCK = 0x0000001B, /* FileRefLock */ - AFC_OP_MAKE_LINK = 0x0000001C, /* MakeLink */ - AFC_OP_SET_FILE_TIME = 0x0000001E /* set st_mtime */ + AFC_OP_INVALID = 0x00000000, /* Invalid */ + AFC_OP_STATUS = 0x00000001, /* Status */ + AFC_OP_DATA = 0x00000002, /* Data */ + AFC_OP_READ_DIR = 0x00000003, /* ReadDir */ + AFC_OP_READ_FILE = 0x00000004, /* ReadFile */ + AFC_OP_WRITE_FILE = 0x00000005, /* WriteFile */ + AFC_OP_WRITE_PART = 0x00000006, /* WritePart */ + AFC_OP_TRUNCATE = 0x00000007, /* TruncateFile */ + AFC_OP_REMOVE_PATH = 0x00000008, /* RemovePath */ + AFC_OP_MAKE_DIR = 0x00000009, /* MakeDir */ + AFC_OP_GET_FILE_INFO = 0x0000000A, /* GetFileInfo */ + AFC_OP_GET_DEVINFO = 0x0000000B, /* GetDeviceInfo */ + AFC_OP_WRITE_FILE_ATOM = 0x0000000C, /* WriteFileAtomic (tmp file+rename) */ + AFC_OP_FILE_OPEN = 0x0000000D, /* FileRefOpen */ + AFC_OP_FILE_OPEN_RES = 0x0000000E, /* FileRefOpenResult */ + AFC_OP_FILE_READ = 0x0000000F, /* FileRefRead */ + AFC_OP_FILE_WRITE = 0x00000010, /* FileRefWrite */ + AFC_OP_FILE_SEEK = 0x00000011, /* FileRefSeek */ + AFC_OP_FILE_TELL = 0x00000012, /* FileRefTell */ + AFC_OP_FILE_TELL_RES = 0x00000013, /* FileRefTellResult */ + AFC_OP_FILE_CLOSE = 0x00000014, /* FileRefClose */ + AFC_OP_FILE_SET_SIZE = 0x00000015, /* FileRefSetFileSize (ftruncate) */ + AFC_OP_GET_CON_INFO = 0x00000016, /* GetConnectionInfo */ + AFC_OP_SET_CON_OPTIONS = 0x00000017, /* SetConnectionOptions */ + AFC_OP_RENAME_PATH = 0x00000018, /* RenamePath */ + AFC_OP_SET_FS_BS = 0x00000019, /* SetFSBlockSize (0x800000) */ + AFC_OP_SET_SOCKET_BS = 0x0000001A, /* SetSocketBlockSize (0x800000) */ + AFC_OP_FILE_LOCK = 0x0000001B, /* FileRefLock */ + AFC_OP_MAKE_LINK = 0x0000001C, /* MakeLink */ + AFC_OP_GET_FILE_HASH = 0x0000001D, /* GetFileHash */ + AFC_OP_SET_FILE_MOD_TIME = 0x0000001E, /* SetModTime */ + AFC_OP_GET_FILE_HASH_RANGE = 0x0000001F, /* GetFileHashWithRange */ + /* iOS 6+ */ + AFC_OP_FILE_SET_IMMUTABLE_HINT = 0x00000020, /* FileRefSetImmutableHint */ + AFC_OP_GET_SIZE_OF_PATH_CONTENTS = 0x00000021, /* GetSizeOfPathContents */ + AFC_OP_REMOVE_PATH_AND_CONTENTS = 0x00000022, /* RemovePathAndContents */ + AFC_OP_DIR_OPEN = 0x00000023, /* DirectoryEnumeratorRefOpen */ + AFC_OP_DIR_OPEN_RESULT = 0x00000024, /* DirectoryEnumeratorRefOpenResult */ + AFC_OP_DIR_READ = 0x00000025, /* DirectoryEnumeratorRefRead */ + AFC_OP_DIR_CLOSE = 0x00000026, /* DirectoryEnumeratorRefClose */ + /* iOS 7+ */ + AFC_OP_FILE_READ_OFFSET = 0x00000027, /* FileRefReadWithOffset */ + AFC_OP_FILE_WRITE_OFFSET = 0x00000028 /* FileRefWriteWithOffset */ }; +afc_error_t afc_client_new_with_service_client(service_client_t service_client, afc_client_t *client); + +#endif diff --git a/src/bt_packet_logger.c b/src/bt_packet_logger.c new file mode 100644 index 0000000..937747c --- /dev/null +++ b/src/bt_packet_logger.c @@ -0,0 +1,231 @@ +/* + * bt_packet_logger.c + * com.apple.bluetooth.BTPacketLogger service implementation. + * + * Copyright (c) 2021 Geoffrey Kruse, 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 "bt_packet_logger.h" +#include "lockdown.h" +#include "common/debug.h" + +struct bt_packet_logger_worker_thread { + bt_packet_logger_client_t client; + bt_packet_logger_receive_cb_t cbfunc; + void *user_data; + uint8_t rxbuff[BT_MAX_PACKET_SIZE]; +}; + +#define SZ_READ_TIMEOUT 100 +#define PAYLOAD_READ_TIMEOUT 500 + +/** + * Convert a service_error_t value to a bt_packet_logger_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching bt_packet_logger_error_t error code, + * BT_PACKET_LOGGER_E_UNKNOWN_ERROR otherwise. + */ +static bt_packet_logger_error_t bt_packet_logger_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return BT_PACKET_LOGGER_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return BT_PACKET_LOGGER_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return BT_PACKET_LOGGER_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return BT_PACKET_LOGGER_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return BT_PACKET_LOGGER_E_TIMEOUT; + default: + break; + } + return BT_PACKET_LOGGER_E_UNKNOWN_ERROR; +} + +bt_packet_logger_error_t bt_packet_logger_client_new(idevice_t device, lockdownd_service_descriptor_t service, bt_packet_logger_client_t * client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to bt_packet_logger_client_new."); + return BT_PACKET_LOGGER_E_INVALID_ARG; + } + + debug_info("Creating bt_packet_logger_client, port = %d.", service->port); + + service_client_t parent = NULL; + bt_packet_logger_error_t ret = bt_packet_logger_error(service_client_new(device, service, &parent)); + if (ret != BT_PACKET_LOGGER_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + bt_packet_logger_client_t client_loc = (bt_packet_logger_client_t) malloc(sizeof(struct bt_packet_logger_client_private)); + client_loc->parent = parent; + client_loc->worker = THREAD_T_NULL; + + *client = client_loc; + + debug_info("bt_packet_logger_client successfully created."); + return 0; +} + +bt_packet_logger_error_t bt_packet_logger_client_start_service(idevice_t device, bt_packet_logger_client_t * client, const char* label) +{ + bt_packet_logger_error_t err = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, BT_PACKETLOGGER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(bt_packet_logger_client_new), &err); + return err; +} + +bt_packet_logger_error_t bt_packet_logger_client_free(bt_packet_logger_client_t client) +{ + if (!client) + return BT_PACKET_LOGGER_E_INVALID_ARG; + bt_packet_logger_stop_capture(client); + bt_packet_logger_error_t err = bt_packet_logger_error(service_client_free(client->parent)); + free(client); + + return err; +} + +bt_packet_logger_error_t bt_packet_logger_receive_with_timeout(bt_packet_logger_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + bt_packet_logger_error_t res = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return BT_PACKET_LOGGER_E_INVALID_ARG; + } + + res = bt_packet_logger_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (res != BT_PACKET_LOGGER_E_SUCCESS && res != BT_PACKET_LOGGER_E_TIMEOUT && res != BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return res; +} + +void *bt_packet_logger_worker(void *arg) +{ + bt_packet_logger_error_t ret = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + struct bt_packet_logger_worker_thread *btwt = (struct bt_packet_logger_worker_thread*)arg; + + if (!btwt) { + return NULL; + } + + debug_info("Running"); + + while (btwt->client->parent) { + uint32_t bytes = 0; + uint16_t len; + + ret = bt_packet_logger_receive_with_timeout(btwt->client, (char*)&len, 2, &bytes, SZ_READ_TIMEOUT); + + if (ret == BT_PACKET_LOGGER_E_TIMEOUT || ret == BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == BT_PACKET_LOGGER_E_SUCCESS))) { + continue; + } else if (ret < 0) { + debug_info("Connection to bt packet logger interrupted"); + break; + } + + // sanity check received length + if(bytes > 0 && len > sizeof(bt_packet_logger_header_t)) { + debug_info("Reading %u bytes\n", len); + ret = bt_packet_logger_receive_with_timeout(btwt->client, (char *)btwt->rxbuff, len, &bytes, PAYLOAD_READ_TIMEOUT); + + if(len != bytes) { + debug_info("Failed Read Expected %u, Received %u\n", len, bytes); + continue; + } + + if (ret == BT_PACKET_LOGGER_E_TIMEOUT || ret == BT_PACKET_LOGGER_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == BT_PACKET_LOGGER_E_SUCCESS))) { + continue; + } else if (ret < 0) { + debug_info("Connection to bt packet logger interrupted"); + break; + } + + btwt->cbfunc(btwt->rxbuff, len, btwt->user_data); + } + } + + // null check performed above + free(btwt); + + debug_info("Exiting"); + + return NULL; +} + +bt_packet_logger_error_t bt_packet_logger_start_capture(bt_packet_logger_client_t client, bt_packet_logger_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return BT_PACKET_LOGGER_E_INVALID_ARG; + + bt_packet_logger_error_t res = BT_PACKET_LOGGER_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct bt_packet_logger_worker_thread *btwt = (struct bt_packet_logger_worker_thread*)malloc(sizeof(struct bt_packet_logger_worker_thread)); + if (btwt) { + btwt->client = client; + btwt->cbfunc = callback; + btwt->user_data = user_data; + + if (thread_new(&client->worker, bt_packet_logger_worker, btwt) == 0) { + res = BT_PACKET_LOGGER_E_SUCCESS; + } + } + + return res; +} + + +bt_packet_logger_error_t bt_packet_logger_stop_capture(bt_packet_logger_client_t client) +{ + if (client->worker) { + /* notify thread to finish */ + service_client_t parent = client->parent; + client->parent = NULL; + /* join thread to make it exit */ + thread_join(client->worker); + thread_free(client->worker); + client->worker = THREAD_T_NULL; + client->parent = parent; + } + + return BT_PACKET_LOGGER_E_SUCCESS; +} diff --git a/src/bt_packet_logger.h b/src/bt_packet_logger.h new file mode 100644 index 0000000..620555e --- /dev/null +++ b/src/bt_packet_logger.h @@ -0,0 +1,37 @@ +/* + * bt_packet_logger.h + * com.apple.bluetooth.BTPacketLogger service header file. + * + * Copyright (c) 2021 Geoffrey Kruse, 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 _BR_PACKET_LOGGER_H +#define _BR_PACKET_LOGGER_H + +#include "idevice.h" +#include "libimobiledevice/bt_packet_logger.h" +#include "service.h" +#include <libimobiledevice-glue/thread.h> + +struct bt_packet_logger_client_private { + service_client_t parent; + THREAD_T worker; +}; + +void *bt_packet_logger_worker(void *arg); + +#endif diff --git a/src/companion_proxy.c b/src/companion_proxy.c new file mode 100644 index 0000000..421fa9a --- /dev/null +++ b/src/companion_proxy.c @@ -0,0 +1,380 @@ +/* + * companion_proxy.c + * com.apple.companion_proxy service implementation. + * + * Copyright (c) 2019-2020 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "companion_proxy.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a companion_proxy_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching companion_proxy_error_t error code, + * COMPANION_PROXY_E_UNKNOWN_ERROR otherwise. + */ +static companion_proxy_error_t companion_proxy_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return COMPANION_PROXY_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return COMPANION_PROXY_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return COMPANION_PROXY_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return COMPANION_PROXY_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return COMPANION_PROXY_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return COMPANION_PROXY_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return COMPANION_PROXY_E_TIMEOUT; + default: + break; + } + return COMPANION_PROXY_E_UNKNOWN_ERROR; +} + +companion_proxy_error_t companion_proxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, companion_proxy_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to companion_proxy_client_new."); + return COMPANION_PROXY_E_INVALID_ARG; + } + + debug_info("Creating companion_proxy_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + companion_proxy_error_t ret = companion_proxy_error(property_list_service_client_new(device, service, &plclient)); + if (ret != COMPANION_PROXY_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + companion_proxy_client_t client_loc = (companion_proxy_client_t) malloc(sizeof(struct companion_proxy_client_private)); + client_loc->parent = plclient; + client_loc->event_thread = THREAD_T_NULL; + + *client = client_loc; + + debug_info("Created companion_proxy_client successfully."); + return COMPANION_PROXY_E_SUCCESS; +} + +companion_proxy_error_t companion_proxy_client_start_service(idevice_t device, companion_proxy_client_t * client, const char* label) +{ + companion_proxy_error_t err = COMPANION_PROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, COMPANION_PROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(companion_proxy_client_new), &err); + return err; +} + +companion_proxy_error_t companion_proxy_client_free(companion_proxy_client_t client) +{ + if (!client) + return COMPANION_PROXY_E_INVALID_ARG; + + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->event_thread) { + debug_info("joining event thread"); + thread_join(client->event_thread); + thread_free(client->event_thread); + client->event_thread = THREAD_T_NULL; + } + companion_proxy_error_t err = companion_proxy_error(property_list_service_client_free(parent)); + free(client); + + return err; +} + +companion_proxy_error_t companion_proxy_send(companion_proxy_client_t client, plist_t plist) +{ + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + + res = companion_proxy_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != COMPANION_PROXY_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + + return res; +} + +companion_proxy_error_t companion_proxy_receive(companion_proxy_client_t client, plist_t * plist) +{ + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + res = companion_proxy_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, 10000)); + if (res != COMPANION_PROXY_E_SUCCESS && res != COMPANION_PROXY_E_TIMEOUT) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + } else if (res == COMPANION_PROXY_E_SUCCESS) { + *plist = outplist; + } + return res; +} + +companion_proxy_error_t companion_proxy_get_device_registry(companion_proxy_client_t client, plist_t* paired_devices) +{ + if (!client || !paired_devices) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("GetDeviceRegistry")); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + if (!dict || !PLIST_IS_DICT(dict)) { + return COMPANION_PROXY_E_PLIST_ERROR; + } + plist_t val = plist_dict_get_item(dict, "PairedDevicesArray"); + if (val) { + *paired_devices = plist_copy(val); + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + val = plist_dict_get_item(dict, "Error"); + if (val) { + if (plist_string_val_compare(val, "NoPairedWatches")) { + res = COMPANION_PROXY_E_NO_DEVICES; + } + } + } + plist_free(dict); + return res; +} + +struct companion_proxy_cb_data { + companion_proxy_client_t client; + companion_proxy_device_event_cb_t cbfunc; + void* user_data; +}; + +static void* companion_proxy_event_thread(void* arg) +{ + struct companion_proxy_cb_data* data = (struct companion_proxy_cb_data*)arg; + companion_proxy_client_t client = data->client; + companion_proxy_error_t res; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("StartListeningForDevices")); + res = companion_proxy_send(client, command); + plist_free(command); + + if (res != COMPANION_PROXY_E_SUCCESS) { + free(data); + client->event_thread = THREAD_T_NULL; + return NULL; + } + + while (client && client->parent) { + plist_t node = NULL; + res = companion_proxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); + if (res != COMPANION_PROXY_E_SUCCESS && res != COMPANION_PROXY_E_TIMEOUT) { + debug_info("could not receive plist, error %d", res); + break; + } + + if (node) { + data->cbfunc(node, data->user_data); + } + plist_free(node); + } + + client->event_thread = THREAD_T_NULL; + free(data); + + return NULL; +} + +companion_proxy_error_t companion_proxy_start_listening_for_devices(companion_proxy_client_t client, companion_proxy_device_event_cb_t callback, void* userdata) +{ + if (!client || !client->parent || !callback) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + if (client->event_thread) { + return COMPANION_PROXY_E_OP_IN_PROGRESS; + } + + companion_proxy_error_t res = COMPANION_PROXY_E_UNKNOWN_ERROR; + struct companion_proxy_cb_data *data = (struct companion_proxy_cb_data*)malloc(sizeof(struct companion_proxy_cb_data)); + if (data) { + data->client = client; + data->cbfunc = callback; + data->user_data = userdata; + + if (thread_new(&client->event_thread, companion_proxy_event_thread, data) == 0) { + res = COMPANION_PROXY_E_SUCCESS; + } else { + free(data); + } + } + return res; +} + +companion_proxy_error_t companion_proxy_stop_listening_for_devices(companion_proxy_client_t client) +{ + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->event_thread) { + debug_info("joining event thread"); + thread_join(client->event_thread); + thread_free(client->event_thread); + client->event_thread = THREAD_T_NULL; + } + client->parent = parent; + return COMPANION_PROXY_E_SUCCESS; +} + +companion_proxy_error_t companion_proxy_get_value_from_registry(companion_proxy_client_t client, const char* companion_udid, const char* key, plist_t* value) +{ + if (!client || !companion_udid || !key || !value) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("GetValueFromRegistry")); + plist_dict_set_item(dict, "GetValueGizmoUDIDKey", plist_new_string(companion_udid)); + plist_dict_set_item(dict, "GetValueKeyKey", plist_new_string(key)); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + if (!dict || !PLIST_IS_DICT(dict)) { + return COMPANION_PROXY_E_PLIST_ERROR; + } + plist_t val = plist_dict_get_item(dict, "RetrievedValueDictionary"); + if (val) { + *value = plist_copy(val); + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + val = plist_dict_get_item(dict, "Error"); + if (val) { + if (!plist_string_val_compare(val, "UnsupportedWatchKey")) { + res = COMPANION_PROXY_E_UNSUPPORTED_KEY; + } else if (plist_string_val_compare(val, "TimeoutReply")) { + res = COMPANION_PROXY_E_TIMEOUT_REPLY; + } + } + } + plist_free(dict); + return res; +} + +companion_proxy_error_t companion_proxy_start_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port, const char* service_name, uint16_t* forward_port, plist_t options) +{ + if (!client) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("StartForwardingServicePort")); + plist_dict_set_item(dict, "GizmoRemotePortNumber", plist_new_uint(remote_port)); + if (service_name) { + plist_dict_set_item(dict, "ForwardedServiceName", plist_new_string(service_name)); + } + plist_dict_set_item(dict, "IsServiceLowPriority", plist_new_bool(0)); + plist_dict_set_item(dict, "PreferWifi", plist_new_bool(0)); + if (options) { + plist_dict_merge(&dict, options); + } + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + plist_t val = plist_dict_get_item(dict, "CompanionProxyServicePort"); + if (val) { + uint64_t u64val = 0; + plist_get_uint_val(val, &u64val); + *forward_port = (uint16_t)u64val; + res = COMPANION_PROXY_E_SUCCESS; + } else { + res = COMPANION_PROXY_E_UNKNOWN_ERROR; + } + plist_free(dict); + + return res; +} + +companion_proxy_error_t companion_proxy_stop_forwarding_service_port(companion_proxy_client_t client, uint16_t remote_port) +{ + if (!client) { + return COMPANION_PROXY_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("StopForwardingServicePort")); + plist_dict_set_item(dict, "GizmoRemotePortNumber", plist_new_uint(remote_port)); + + companion_proxy_error_t res = companion_proxy_send(client, dict); + plist_free(dict); + dict = NULL; + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + + res = companion_proxy_receive(client, &dict); + if (res != COMPANION_PROXY_E_SUCCESS) { + return res; + } + plist_free(dict); + + return res; +} diff --git a/src/companion_proxy.h b/src/companion_proxy.h new file mode 100644 index 0000000..e36932a --- /dev/null +++ b/src/companion_proxy.h @@ -0,0 +1,35 @@ +/* + * companion_proxy.h + * com.apple.companion_proxy service header file. + * + * Copyright (c) 2019-2020 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 __COMPANION_PROXY_H +#define __COMPANION_PROXY_H + +#include "idevice.h" +#include "libimobiledevice/companion_proxy.h" +#include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> + +struct companion_proxy_client_private { + property_list_service_client_t parent; + THREAD_T event_thread; +}; + +#endif diff --git a/src/debug.c b/src/debug.c deleted file mode 100644 index 26a9678..0000000 --- a/src/debug.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * debug.c - * contains utilitary functions for debugging - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * Copyright (c) 2010 Martin S. 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 <stdarg.h> -#define _GNU_SOURCE 1 -#define __USE_GNU 1 -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> - -#include "debug.h" -#include "libimobiledevice/libimobiledevice.h" - -int debug_level = 0; - -/** - * Sets the level of debugging. Currently the only acceptable values are 0 and - * 1. - * - * @param level Set to 0 for no debugging or 1 for debugging. - */ -void idevice_set_debug_level(int level) -{ - debug_level = level; -} - -#ifndef STRIP_DEBUG_CODE -static void debug_print_line(const char *func, const char *file, int line, const char *buffer) -{ - char *str_time = NULL; - char *header = NULL; - time_t the_time; - - time(&the_time); - str_time = g_new0 (gchar, 255); - strftime(str_time, 254, "%H:%M:%S", localtime (&the_time)); - - /* generate header text */ - (void)asprintf(&header, "%s %s:%d %s()", str_time, file, line, func); - free (str_time); - - /* trim ending newlines */ - - /* print header */ - printf ("%s: ", header); - - /* print actual debug content */ - printf ("%s\n", buffer); - - /* flush this output, as we need to debug */ - fflush (stdout); - - free (header); -} -#endif - -inline void debug_info_real(const char *func, const char *file, int line, const char *format, ...) -{ -#ifndef STRIP_DEBUG_CODE - va_list args; - char *buffer = NULL; - - if (!debug_level) - return; - - /* run the real fprintf */ - va_start(args, format); - (void)vasprintf(&buffer, format, args); - va_end(args); - - debug_print_line(func, file, line, buffer); - - free(buffer); -#endif -} - -inline void debug_buffer(const char *data, const int length) -{ -#ifndef STRIP_DEBUG_CODE - int i; - int j; - unsigned char c; - - if (debug_level) { - for (i = 0; i < length; i += 16) { - fprintf(stderr, "%04x: ", i); - for (j = 0; j < 16; j++) { - if (i + j >= length) { - fprintf(stderr, " "); - continue; - } - fprintf(stderr, "%02hhx ", *(data + i + j)); - } - fprintf(stderr, " | "); - for (j = 0; j < 16; j++) { - if (i + j >= length) - break; - c = *(data + i + j); - if ((c < 32) || (c > 127)) { - fprintf(stderr, "."); - continue; - } - fprintf(stderr, "%c", c); - } - fprintf(stderr, "\n"); - } - fprintf(stderr, "\n"); - } -#endif -} - -inline void debug_buffer_to_file(const char *file, const char *data, const int length) -{ -#ifndef STRIP_DEBUG_CODE - if (debug_level) { - FILE *f = fopen(file, "w+"); - fwrite(data, 1, length, f); - fflush(f); - fclose(f); - } -#endif -} - -inline void debug_plist_real(const char *func, const char *file, int line, plist_t plist) -{ -#ifndef STRIP_DEBUG_CODE - if (!plist) - return; - - char *buffer = NULL; - uint32_t length = 0; - plist_to_xml(plist, &buffer, &length); - - /* get rid of ending newline as one is already added in the debug line */ - if (buffer[length-1] == '\n') - buffer[length-1] = '\0'; - - debug_info_real(func, file, line, "printing %i bytes plist:\n%s", length, buffer); - free(buffer); -#endif -} - diff --git a/src/debug.h b/src/debug.h deleted file mode 100644 index 2fd0960..0000000 --- a/src/debug.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * debug.h - * contains utilitary functions for debugging - * - * Copyright (c) 2008 Jonathan Beck All Rights Reserved. - * Copyright (c) 2010 Martin S. 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 DEBUG_H -#define DEBUG_H - -#include <plist/plist.h> -#include <glib.h> - -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L && !defined(STRIP_DEBUG_CODE) -#define debug_info(...) debug_info_real (__func__, __FILE__, __LINE__, __VA_ARGS__) -#define debug_plist(a) debug_plist_real (__func__, __FILE__, __LINE__, a) -#elif defined(__GNUC__) && __GNUC__ >= 3 && !defined(STRIP_DEBUG_CODE) -#define debug_info(...) debug_info_real (__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__) -#define debug_plist(a) debug_plist_real (__FUNCTION__, __FILE__, __LINE__, a) -#else -#define debug_info(...) -#define debug_plist(a) -#endif - -G_GNUC_INTERNAL inline void debug_info_real(const char *func, - const char *file, - int line, - const char *format, ...); - -G_GNUC_INTERNAL inline void debug_buffer(const char *data, const int length); -G_GNUC_INTERNAL inline void debug_buffer_to_file(const char *file, const char *data, const int length); -G_GNUC_INTERNAL inline void debug_plist_real(const char *func, - const char *file, - int line, - plist_t plist); - -#endif diff --git a/src/debugserver.c b/src/debugserver.c new file mode 100644 index 0000000..74ade8a --- /dev/null +++ b/src/debugserver.c @@ -0,0 +1,657 @@ +/* + * debugserver.c + * com.apple.debugserver service implementation. + * + * Copyright (c) 2019 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2014-2015 Martin Szulecki 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> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.h> + +#include <libimobiledevice-glue/utils.h> + +#include "debugserver.h" +#include "lockdown.h" +#include "common/debug.h" +#include "asprintf.h" + +/** + * Convert a service_error_t value to a debugserver_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching debugserver_error_t error code, + * DEBUGSERVER_E_UNKNOWN_ERROR otherwise. + */ +static debugserver_error_t debugserver_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return DEBUGSERVER_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return DEBUGSERVER_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return DEBUGSERVER_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return DEBUGSERVER_E_SSL_ERROR; + case SERVICE_E_TIMEOUT: + return DEBUGSERVER_E_TIMEOUT; + default: + break; + } + return DEBUGSERVER_E_UNKNOWN_ERROR; +} + +debugserver_error_t debugserver_client_new(idevice_t device, lockdownd_service_descriptor_t service, debugserver_client_t* client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to debugserver_client_new."); + return DEBUGSERVER_E_INVALID_ARG; + } + + debug_info("Creating debugserver_client, port = %d.", service->port); + + service_client_t parent = NULL; + debugserver_error_t ret = debugserver_error(service_client_new(device, service, &parent)); + if (ret != DEBUGSERVER_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + if (service->identifier && (strcmp(service->identifier, DEBUGSERVER_SECURE_SERVICE_NAME) != 0)) { + service_disable_bypass_ssl(parent, 1); + } + + debugserver_client_t client_loc = (debugserver_client_t) malloc(sizeof(struct debugserver_client_private)); + client_loc->parent = parent; + client_loc->noack_mode = 0; + client_loc->cancel_receive = NULL; + client_loc->receive_loop_timeout = 1000; + + *client = client_loc; + + debug_info("debugserver_client successfully created."); + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_client_start_service(idevice_t device, debugserver_client_t * client, const char* label) +{ + debugserver_error_t err = DEBUGSERVER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DEBUGSERVER_SECURE_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(debugserver_client_new), &err); + if (err != DEBUGSERVER_E_SUCCESS) { + err = DEBUGSERVER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DEBUGSERVER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(debugserver_client_new), &err); + } + return err; +} + +debugserver_error_t debugserver_client_free(debugserver_client_t client) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t err = debugserver_error(service_client_free(client->parent)); + client->parent = NULL; + free(client); + + return err; +} + +debugserver_error_t debugserver_client_send(debugserver_client_t client, const char* data, uint32_t size, uint32_t *sent) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return DEBUGSERVER_E_INVALID_ARG; + } + + debug_info("sending %d bytes", size); + res = debugserver_error(service_send(client->parent, data, size, (uint32_t*)&bytes)); + if (bytes <= 0) { + debug_info("ERROR: sending to device failed."); + } + if (sent) { + *sent = (uint32_t)bytes; + } + + return res; +} + +debugserver_error_t debugserver_client_receive_with_timeout(debugserver_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return DEBUGSERVER_E_INVALID_ARG; + } + + res = debugserver_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (bytes <= 0 && res != DEBUGSERVER_E_TIMEOUT) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return (bytes > 0) ? DEBUGSERVER_E_SUCCESS : res; +} + +debugserver_error_t debugserver_client_receive(debugserver_client_t client, char* data, uint32_t size, uint32_t *received) +{ + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + do { + /* Is this allowed to return DEBUGSERVER_E_TIMEOUT and also set data and received? */ + res = debugserver_client_receive_with_timeout(client, data, size, received, client->receive_loop_timeout); + } while (res == DEBUGSERVER_E_TIMEOUT && client->cancel_receive != NULL && !client->cancel_receive()); + return res; +} + +debugserver_error_t debugserver_command_new(const char* name, int argc, char* argv[], debugserver_command_t* command) +{ + int i; + debugserver_command_t tmp = (debugserver_command_t) malloc(sizeof(struct debugserver_command_private)); + + /* copy name */ + tmp->name = strdup(name); + + /* copy arguments */ + tmp->argc = argc; + tmp->argv = NULL; + if (argc > 0) { + tmp->argv = malloc(sizeof(char*) * (argc + 2)); + for (i = 0; i < argc; i++) { + tmp->argv[i] = strdup(argv[i]); + } + tmp->argv[i+1] = NULL; + } + + /* return */ + *command = tmp; + + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_command_free(debugserver_command_t command) +{ + int i; + debugserver_error_t res = DEBUGSERVER_E_UNKNOWN_ERROR; + + if (!command) + return DEBUGSERVER_E_INVALID_ARG; + + if (command) { + if (command->name) + free(command->name); + if (command->argv && command->argc) { + for (i = 0; i < command->argc; i++) { + free(command->argv[i]); + } + free(command->argv); + } + free(command); + res = DEBUGSERVER_E_SUCCESS; + } + + return res; +} + +static int debugserver_hex2int(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + else if (c >= 'a' && c <= 'f') + return 10 + c - 'a'; + else if (c >= 'A' && c <= 'F') + return 10 + c - 'A'; + else + return c; +} + +static char debugserver_int2hex(int x) +{ + const char *hexchars = "0123456789ABCDEF"; + return hexchars[x]; +} + +#define DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(byte) debugserver_int2hex(((byte) >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(byte) debugserver_int2hex((byte) & 0xf) +#define DEBUGSERVER_HEX_DECODE_FIRST_BYTE(byte) (((byte) >> 0x4) & 0xf) +#define DEBUGSERVER_HEX_DECODE_SECOND_BYTE(byte) ((byte) & 0xf) + +static uint32_t debugserver_get_checksum_for_buffer(const char* buffer, uint32_t size) +{ + uint32_t checksum = 0; + uint32_t i; + + for (i = 0; i < size; i++) { + checksum += buffer[i]; + } + + return checksum; +} + +static int debugserver_response_is_checksum_valid(const char* response, uint32_t size) +{ + uint32_t checksum = 0; + if ((size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1) > 0) + checksum = debugserver_get_checksum_for_buffer(&response[1], size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + + debug_info("checksum: 0x%x", checksum); + + if ((unsigned)debugserver_hex2int(response[size - 2]) != DEBUGSERVER_HEX_DECODE_FIRST_BYTE(checksum)) + return 0; + + if ((unsigned)debugserver_hex2int(response[size - 1]) != DEBUGSERVER_HEX_DECODE_SECOND_BYTE(checksum)) + return 0; + + debug_info("valid checksum"); + + return 1; +} + +void debugserver_encode_string(const char* buffer, char** encoded_buffer, uint32_t* encoded_length) +{ + uint32_t position; + uint32_t index; + uint32_t length = strlen(buffer); + *encoded_length = (2 * length) + DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1; + + *encoded_buffer = malloc(sizeof(char) * (*encoded_length)); + memset(*encoded_buffer, '\0', *encoded_length); + for (position = 0, index = 0; index < length; index++) { + position = (index * (2 * sizeof(char))); + (*encoded_buffer)[position] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(buffer[index]); + (*encoded_buffer)[position + 1] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(buffer[index]); + } +} + +void debugserver_decode_string(const char *encoded_buffer, size_t encoded_length, char** buffer) +{ + *buffer = malloc(sizeof(char) * ((encoded_length / 2)+1)); + char* t = *buffer; + const char *f = encoded_buffer; + const char *fend = f + encoded_length; + while (f < fend) { + *t++ = debugserver_hex2int(*f) << 4 | debugserver_hex2int(f[1]); + f += 2; + } + *t = '\0'; +} + +static void debugserver_format_command(const char* prefix, const char* command, const char* arguments, int calculate_checksum, char** buffer, uint32_t* size) +{ + char checksum_hash[DEBUGSERVER_CHECKSUM_HASH_LENGTH + 1] = {'#', '0', '0', '\0'}; + char* encoded = NULL; + uint32_t encoded_length = 0; + + if (arguments) { + /* arguments must be hex encoded */ + debugserver_encode_string(arguments, &encoded, &encoded_length); + } else { + encoded = NULL; + } + + char* encoded_command = string_concat(command, encoded, NULL); + encoded_length = strlen(encoded_command); + + if (calculate_checksum) { + uint32_t checksum = debugserver_get_checksum_for_buffer(encoded_command, encoded_length); + checksum_hash[1] = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(checksum); + checksum_hash[2] = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(checksum); + } + + *buffer = string_concat(prefix, encoded_command, checksum_hash, NULL); + *size = strlen(prefix) + strlen(encoded_command) + DEBUGSERVER_CHECKSUM_HASH_LENGTH; + + debug_info("formatted command: %s size: %d checksum: 0x%s", *buffer, *size, checksum_hash); + + if (encoded_command) + free(encoded_command); + + if (encoded) + free(encoded); +} + +static debugserver_error_t debugserver_client_send_ack(debugserver_client_t client) +{ + debug_info("sending ACK"); + return debugserver_client_send(client, "+", sizeof(char), NULL); +} + +static debugserver_error_t debugserver_client_send_noack(debugserver_client_t client) +{ + debug_info("sending !ACK"); + return debugserver_client_send(client, "-", sizeof(char), NULL); +} + +debugserver_error_t debugserver_client_set_ack_mode(debugserver_client_t client, int enabled) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + client->noack_mode = (enabled == 0)? 1: 0; + + debug_info("ack mode: %s", client->noack_mode == 0 ? "on": "off"); + + return DEBUGSERVER_E_SUCCESS; +} + +debugserver_error_t debugserver_client_set_receive_params(debugserver_client_t client, int (*cancel_receive)(), int receive_loop_timeout) +{ + if (!client) + return DEBUGSERVER_E_INVALID_ARG; + + client->cancel_receive = cancel_receive; + client->receive_loop_timeout = receive_loop_timeout; + + debug_info("receive params: cancel_receive %s, receive_loop_timeout %dms", (client->cancel_receive == NULL ? "unset": "set"), client->receive_loop_timeout); + + return DEBUGSERVER_E_SUCCESS; +} + +static debugserver_error_t debugserver_client_receive_internal_char(debugserver_client_t client, char* received_char) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + uint32_t bytes = 0; + + /* we loop here as we expect an answer */ + res = debugserver_client_receive(client, received_char, sizeof(char), &bytes); + if (res != DEBUGSERVER_E_SUCCESS) { + return res; + } + if (bytes != 1) { + debug_info("received %d bytes when asking for %d!", bytes, sizeof(char)); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + return res; +} + +debugserver_error_t debugserver_client_receive_response(debugserver_client_t client, char** response, size_t* response_size) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + + char data = '\0'; + int skip_prefix = 0; + + char* buffer = malloc(1024); + uint32_t buffer_size = 0; + uint32_t buffer_capacity = 1024; + + if (response) + *response = NULL; + + if (!client->noack_mode) { + debug_info("attempting to receive ACK (+)"); + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '+') { + debug_info("received ACK (+)"); + } else if (data == '$') { + debug_info("received prefix ($)"); + buffer[0] = '$'; + buffer_size = 1; + skip_prefix = 1; + } else { + debug_info("unrecognized response when looking for ACK: %c", data); + goto cleanup; + } + } + + debug_info("skip_prefix: %d", skip_prefix); + + if (!skip_prefix) { + debug_info("attempting to receive prefix ($)"); + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '$') { + debug_info("received prefix ($)"); + buffer[0] = '$'; + buffer_size = 1; + } else { + debug_info("unrecognized response when looking for prefix: %c", data); + goto cleanup; + } + } + + uint32_t checksum_length = DEBUGSERVER_CHECKSUM_HASH_LENGTH; + int receiving_checksum_response = 0; + debug_info("attempting to read up response until checksum"); + + while ((checksum_length > 0)) { + res = debugserver_client_receive_internal_char(client, &data); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + if (data == '#') { + receiving_checksum_response = 1; + } + if (receiving_checksum_response) { + checksum_length--; + } + if (buffer_size + 1 >= buffer_capacity) { + char* newbuffer = realloc(buffer, buffer_capacity+1024); + if (!newbuffer) { + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + buffer = newbuffer; + buffer_capacity += 1024; + } + buffer[buffer_size] = data; + buffer_size += sizeof(char); + } + debug_info("validating response checksum..."); + if (client->noack_mode || debugserver_response_is_checksum_valid(buffer, buffer_size)) { + if (response) { + /* assemble response string */ + uint32_t resp_size = sizeof(char) * (buffer_size - DEBUGSERVER_CHECKSUM_HASH_LENGTH - 1); + *response = (char*)malloc(resp_size + 1); + memcpy(*response, buffer + 1, resp_size); + (*response)[resp_size] = '\0'; + if (response_size) *response_size = resp_size; + } + if (!client->noack_mode) { + /* confirm valid command */ + debugserver_client_send_ack(client); + } + } else { + /* response was invalid */ + res = DEBUGSERVER_E_RESPONSE_ERROR; + if (!client->noack_mode) { + /* report invalid command */ + debugserver_client_send_noack(client); + } + } + +cleanup: + if (response) { + debug_info("response: %s", *response); + } + + if (buffer) + free(buffer); + + return res; +} + +debugserver_error_t debugserver_client_send_command(debugserver_client_t client, debugserver_command_t command, char** response, size_t* response_size) +{ + debugserver_error_t res = DEBUGSERVER_E_SUCCESS; + int i; + uint32_t bytes = 0; + + char* send_buffer = NULL; + uint32_t send_buffer_size = 0; + + char* command_arguments = NULL; + + /* concat all arguments */ + for (i = 0; i < command->argc; i++) { + debug_info("argv[%d]: %s", i, command->argv[i]); + command_arguments = string_append(command_arguments, command->argv[i], NULL); + } + + debug_info("command_arguments(%d): %s", command->argc, command_arguments); + + /* encode command arguments, add checksum if required and assemble entire command */ + debugserver_format_command("$", command->name, command_arguments, 1, &send_buffer, &send_buffer_size); + + debug_info("sending encoded command: %s", send_buffer); + + res = debugserver_client_send(client, send_buffer, send_buffer_size, &bytes); + debug_info("command result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + /* receive response */ + res = debugserver_client_receive_response(client, response, response_size); + debug_info("response result: %d", res); + if (res != DEBUGSERVER_E_SUCCESS) { + goto cleanup; + } + + if (response) { + debug_info("received response: %s", *response); + } + + /* disable sending ack on the client */ + if (!strncmp(command->name, "QStartNoAckMode", 16)) { + debugserver_client_set_ack_mode(client, 0); + } + +cleanup: + if (command_arguments) + free(command_arguments); + + if (send_buffer) + free(send_buffer); + + return res; +} + +debugserver_error_t debugserver_client_set_environment_hex_encoded(debugserver_client_t client, const char* env, char** response) +{ + if (!client || !env) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + char* env_tmp = strdup(env); + char* env_arg[2] = { env_tmp, NULL }; + + debugserver_command_t command = NULL; + debugserver_command_new("QEnvironmentHexEncoded:", 1, env_arg, &command); + result = debugserver_client_send_command(client, command, response, NULL); + debugserver_command_free(command); + + free(env_tmp); + + return result; +} + +debugserver_error_t debugserver_client_set_argv(debugserver_client_t client, int argc, char* argv[], char** response) +{ + if (!client || !argc) + return DEBUGSERVER_E_INVALID_ARG; + + debugserver_error_t result = DEBUGSERVER_E_UNKNOWN_ERROR; + char *pkt = NULL; + size_t pkt_len = 0; + int i = 0; + + /* calculate total length */ + while (i < argc && argv && argv[i]) { + char *prefix = NULL; + int ret = asprintf(&prefix, ",%zu,%d,", strlen(argv[i]) * 2, i); + if (ret < 0 || prefix == NULL) { + debug_info("asprintf failed, out of memory?"); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + pkt_len += strlen(prefix) + strlen(argv[i]) * 2; + free(prefix); + i++; + } + + /* allocate packet and initialize it */ + pkt = (char *) malloc(pkt_len + 1); + memset(pkt, 0, pkt_len + 1); + + char *pktp = pkt; + + i = 0; + while (i < argc && argv && argv[i]) { + debug_info("argv[%d] = \"%s\"", i, argv[i]); + + char *prefix = NULL; + char *m = NULL; + size_t arg_len = strlen(argv[i]); + size_t arg_hexlen = arg_len * 2; + + int ret = asprintf(&prefix, ",%zu,%d,", arg_hexlen, i); + if (ret < 0 || prefix == NULL) { + debug_info("asprintf failed, out of memory?"); + return DEBUGSERVER_E_UNKNOWN_ERROR; + } + + m = (char *) malloc(arg_hexlen); + char *p = m; + char *q = (char*)argv[i]; + while (*q) { + *p++ = DEBUGSERVER_HEX_ENCODE_FIRST_BYTE(*q); + *p++ = DEBUGSERVER_HEX_ENCODE_SECOND_BYTE(*q); + q++; + } + + memcpy(pktp, prefix, strlen(prefix)); + pktp += strlen(prefix); + + memcpy(pktp, m, arg_hexlen); + pktp += arg_hexlen; + + free(prefix); + free(m); + + i++; + } + + pkt[0] = 'A'; + + debugserver_command_t command = NULL; + debugserver_command_new(pkt, 0, NULL, &command); + result = debugserver_client_send_command(client, command, response, NULL); + debugserver_command_free(command); + + if (pkt) + free(pkt); + + return result; +} diff --git a/src/debugserver.h b/src/debugserver.h new file mode 100644 index 0000000..ce9c255 --- /dev/null +++ b/src/debugserver.h @@ -0,0 +1,44 @@ +/* + * debugserver.h + * com.apple.debugserver service header file. + * + * Copyright (c) 2014 Martin Szulecki 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 _DEBUGSERVER_H +#define _DEBUGSERVER_H + +#include "idevice.h" +#include "libimobiledevice/debugserver.h" +#include "service.h" + +#define DEBUGSERVER_CHECKSUM_HASH_LENGTH 0x3 + +struct debugserver_client_private { + service_client_t parent; + int noack_mode; + int (*cancel_receive)(); + int receive_loop_timeout; +}; + +struct debugserver_command_private { + char* name; + int argc; + char** argv; +}; + +#endif diff --git a/src/device_link_service.c b/src/device_link_service.c index 6083d80..66c2461 100644 --- a/src/device_link_service.c +++ b/src/device_link_service.c @@ -1,28 +1,53 @@ - /* +/* * device_link_service.c * DeviceLink service implementation. - * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * + * Copyright (c) 2010-2019 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 + * 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 "device_link_service.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +static device_link_service_error_t device_link_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return DEVICE_LINK_SERVICE_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return DEVICE_LINK_SERVICE_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return DEVICE_LINK_SERVICE_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return DEVICE_LINK_SERVICE_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return DEVICE_LINK_SERVICE_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT; + default: + break; + } + return DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; +} /** * Internally used function to extract the message string from a DL* message @@ -58,7 +83,7 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) return 0; } - if ((strlen(cmd_str) < 9) || (strncmp(cmd_str, "DL", 2))) { + if ((strlen(cmd_str) < 9) || (strncmp(cmd_str, "DL", 2) != 0)) { free(cmd_str); return 0; } @@ -74,7 +99,7 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) * Creates a new device link service client. * * @param device The device to connect to. - * @param port Port on device to connect to. + * @param service The service descriptor returned by lockdownd_start_service. * @param client Reference that will point to a newly allocated * device_link_service_client_t upon successful return. * @@ -82,15 +107,16 @@ static int device_link_service_get_message(plist_t dl_msg, char **message) * DEVICE_LINK_SERVICE_E_INVALID_ARG when one of the parameters is invalid, * or DEVICE_LINK_SERVICE_E_MUX_ERROR when the connection failed. */ -device_link_service_error_t device_link_service_client_new(idevice_t device, uint16_t port, device_link_service_client_t *client) +device_link_service_error_t device_link_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, device_link_service_client_t *client) { - if (!device || port == 0 || !client || *client) { + if (!device || !service || service->port == 0 || !client || *client) { return DEVICE_LINK_SERVICE_E_INVALID_ARG; } property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_client_new(device, service, &plistclient)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } /* create client object */ @@ -117,11 +143,10 @@ device_link_service_error_t device_link_service_client_free(device_link_service_ if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; - if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_client_free(client->parent)); free(client); - return DEVICE_LINK_SERVICE_E_SUCCESS; + + return err; } /** @@ -145,7 +170,7 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser { if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; - + device_link_service_error_t err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; /* perform version exchange */ @@ -153,13 +178,13 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser char *msg = NULL; /* receive DLMessageVersionExchange from device */ - if (property_list_service_receive_plist(client->parent, &array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_receive_plist(client->parent, &array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Did not receive initial message from device!"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } device_link_service_get_message(array, &msg); - if (!msg || strcmp(msg, "DLMessageVersionExchange")) { + if (!msg || strcmp(msg, "DLMessageVersionExchange") != 0) { debug_info("Did not receive DLMessageVersionExchange from device!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -199,22 +224,22 @@ device_link_service_error_t device_link_service_version_exchange(device_link_ser plist_array_append_item(array, plist_new_string("DLMessageVersionExchange")); plist_array_append_item(array, plist_new_string("DLVersionsOk")); plist_array_append_item(array, plist_new_uint(version_major)); - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Error when sending DLVersionsOk"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } plist_free(array); /* receive DeviceReady message */ array = NULL; - if (property_list_service_receive_plist(client->parent, &array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + err = device_link_error(property_list_service_receive_plist(client->parent, &array)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { debug_info("Error when receiving DLMessageDeviceReady!"); - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; goto leave; } device_link_service_get_message(array, &msg); - if (!msg || strcmp(msg, "DLMessageDeviceReady")) { + if (!msg || strcmp(msg, "DLMessageDeviceReady") != 0) { debug_info("Did not get DLMessageDeviceReady!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -235,26 +260,28 @@ leave: * Performs a disconnect with the connected device link service client. * * @param client The device link service client to disconnect. - * + * @param message Optional message to send send to the device or NULL. + * * @return DEVICE_LINK_SERVICE_E_SUCCESS on success, * DEVICE_LINK_SERVICE_E_INVALID_ARG if client is NULL, * or DEVICE_LINK_SERVICE_E_MUX_ERROR when there's an error when sending * the the disconnect message. */ -device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client) +device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client, const char *message) { if (!client) return DEVICE_LINK_SERVICE_E_INVALID_ARG; plist_t array = plist_new_array(); plist_array_append_item(array, plist_new_string("DLMessageDisconnect")); - plist_array_append_item(array, plist_new_string("All done, thanks for the memories")); + if (message) + plist_array_append_item(array, plist_new_string(message)); + else + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -278,11 +305,9 @@ device_link_service_error_t device_link_service_send_ping(device_link_service_cl plist_array_append_item(array, plist_new_string("DLMessagePing")); plist_array_append_item(array, plist_new_string(message)); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -309,11 +334,9 @@ device_link_service_error_t device_link_service_send_process_message(device_link plist_array_append_item(array, plist_new_string("DLMessageProcessMessage")); plist_array_append_item(array, plist_copy(message)); - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_SUCCESS; - if (property_list_service_send_binary_plist(client->parent, array) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - err = DEVICE_LINK_SERVICE_E_MUX_ERROR; - } + device_link_service_error_t err = device_link_error(property_list_service_send_binary_plist(client->parent, array)); plist_free(array); + return err; } @@ -340,8 +363,9 @@ device_link_service_error_t device_link_service_receive_message(device_link_serv return DEVICE_LINK_SERVICE_E_INVALID_ARG; *msg_plist = NULL; - if (property_list_service_receive_plist(client->parent, msg_plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, msg_plist)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } if (!device_link_service_get_message(*msg_plist, dlmessage)) { @@ -370,15 +394,16 @@ device_link_service_error_t device_link_service_receive_process_message(device_l return DEVICE_LINK_SERVICE_E_INVALID_ARG; plist_t pmsg = NULL; - if (property_list_service_receive_plist(client->parent, &pmsg) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; + device_link_service_error_t err = device_link_error(property_list_service_receive_plist(client->parent, &pmsg)); + if (err != DEVICE_LINK_SERVICE_E_SUCCESS) { + return err; } - device_link_service_error_t err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; + err = DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR; char *msg = NULL; device_link_service_get_message(pmsg, &msg); - if (!msg || strcmp(msg, "DLMessageProcessMessage")) { + if (!msg || strcmp(msg, "DLMessageProcessMessage") != 0) { debug_info("Did not receive DLMessageProcessMessage as expected!"); err = DEVICE_LINK_SERVICE_E_PLIST_ERROR; goto leave; @@ -424,10 +449,7 @@ device_link_service_error_t device_link_service_send(device_link_service_client_ if (!client || !plist) { return DEVICE_LINK_SERVICE_E_INVALID_ARG; } - if (property_list_service_send_binary_plist(client->parent, plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; - } - return DEVICE_LINK_SERVICE_E_SUCCESS; + return device_link_error(property_list_service_send_binary_plist(client->parent, plist)); } /* Generic device link service receive function. @@ -447,9 +469,6 @@ device_link_service_error_t device_link_service_receive(device_link_service_clie return DEVICE_LINK_SERVICE_E_INVALID_ARG; } - if (property_list_service_receive_plist(client->parent, plist) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return DEVICE_LINK_SERVICE_E_MUX_ERROR; - } - return DEVICE_LINK_SERVICE_E_SUCCESS; + return device_link_error(property_list_service_receive_plist(client->parent, plist)); } diff --git a/src/device_link_service.h b/src/device_link_service.h index 9953f77..0255b21 100644 --- a/src/device_link_service.h +++ b/src/device_link_service.h @@ -1,39 +1,41 @@ - /* +/* * device_link_service.h * Definitions for the DeviceLink service - * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * + * Copyright (c) 2010-2019 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef DEVICE_LINK_SERVICE_H -#define DEVICE_LINK_SERVICE_H +#ifndef __DEVICE_LINK_SERVICE_H +#define __DEVICE_LINK_SERVICE_H + +#include "idevice.h" #include "property_list_service.h" /* Error Codes */ -#define DEVICE_LINK_SERVICE_E_SUCCESS 0 -#define DEVICE_LINK_SERVICE_E_INVALID_ARG -1 -#define DEVICE_LINK_SERVICE_E_PLIST_ERROR -2 -#define DEVICE_LINK_SERVICE_E_MUX_ERROR -3 -#define DEVICE_LINK_SERVICE_E_BAD_VERSION -4 - -#define DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR -256 - -/** Represents an error code. */ -typedef int16_t device_link_service_error_t; +typedef enum { + DEVICE_LINK_SERVICE_E_SUCCESS = 0, + DEVICE_LINK_SERVICE_E_INVALID_ARG = -1, + DEVICE_LINK_SERVICE_E_PLIST_ERROR = -2, + DEVICE_LINK_SERVICE_E_MUX_ERROR = -3, + DEVICE_LINK_SERVICE_E_SSL_ERROR = -4, + DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT = -5, + DEVICE_LINK_SERVICE_E_BAD_VERSION = -6, + DEVICE_LINK_SERVICE_E_UNKNOWN_ERROR = -256 +} device_link_service_error_t; struct device_link_service_client_private { property_list_service_client_t parent; @@ -41,14 +43,14 @@ struct device_link_service_client_private { typedef struct device_link_service_client_private *device_link_service_client_t; -device_link_service_error_t device_link_service_client_new(idevice_t device, uint16_t port, device_link_service_client_t *client); +device_link_service_error_t device_link_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, device_link_service_client_t *client); device_link_service_error_t device_link_service_client_free(device_link_service_client_t client); device_link_service_error_t device_link_service_version_exchange(device_link_service_client_t client, uint64_t version_major, uint64_t version_minor); device_link_service_error_t device_link_service_send_ping(device_link_service_client_t client, const char *message); device_link_service_error_t device_link_service_receive_message(device_link_service_client_t client, plist_t *msg_plist, char **dlmessage); device_link_service_error_t device_link_service_send_process_message(device_link_service_client_t client, plist_t message); device_link_service_error_t device_link_service_receive_process_message(device_link_service_client_t client, plist_t *message); -device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client); +device_link_service_error_t device_link_service_disconnect(device_link_service_client_t client, const char *message); device_link_service_error_t device_link_service_send(device_link_service_client_t client, plist_t plist); device_link_service_error_t device_link_service_receive(device_link_service_client_t client, plist_t *plist); diff --git a/src/diagnostics_relay.c b/src/diagnostics_relay.c new file mode 100644 index 0000000..6ee3150 --- /dev/null +++ b/src/diagnostics_relay.c @@ -0,0 +1,467 @@ +/* + * diagnostics_relay.c + * com.apple.mobile.diagnostics_relay service implementation. + * + * Copyright (c) 2012 Martin Szulecki, 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 "diagnostics_relay.h" +#include "property_list_service.h" +#include "common/debug.h" + +#define RESULT_SUCCESS 0 +#define RESULT_FAILURE 1 +#define RESULT_UNKNOWN_REQUEST 2 + +/** + * Internally used function for checking the result from a service response + * plist to a previously sent request. + * + * @param dict The plist to evaluate. + * + * @return RESULT_SUCCESS when the result is 'Success', + * RESULT_FAILURE when the result is 'Failure', + * or a negative value if an error occurred during evaluation. + */ +static int diagnostics_relay_check_result(plist_t dict) +{ + int ret = -1; + + plist_t result_node = plist_dict_get_item(dict, "Status"); + if (!result_node) + return ret; + + plist_type result_type = plist_get_node_type(result_node); + if (result_type == PLIST_STRING) { + char *result_value = NULL; + + plist_get_string_val(result_node, &result_value); + + if (result_value) { + if (!strcmp(result_value, "Success")) { + ret = RESULT_SUCCESS; + } else if (!strcmp(result_value, "Failure")) { + ret = RESULT_FAILURE; + } else if (!strcmp(result_value, "UnknownRequest")) { + ret = RESULT_UNKNOWN_REQUEST; + } else { + debug_info("ERROR: unknown result value '%s'", result_value); + } + } + if (result_value) + free(result_value); + } + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, diagnostics_relay_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + } + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return DIAGNOSTICS_RELAY_E_MUX_ERROR; + } + + /* create client object */ + diagnostics_relay_client_t client_loc = (diagnostics_relay_client_t) malloc(sizeof(struct diagnostics_relay_client_private)); + client_loc->parent = plistclient; + + /* all done, return success */ + *client = client_loc; + return DIAGNOSTICS_RELAY_E_SUCCESS; +} + +diagnostics_relay_error_t diagnostics_relay_client_start_service(idevice_t device, diagnostics_relay_client_t * client, const char* label) +{ + diagnostics_relay_error_t err = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, DIAGNOSTICS_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(diagnostics_relay_client_new), &err); + return err; +} + +diagnostics_relay_error_t diagnostics_relay_client_free(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + free(client); + return DIAGNOSTICS_RELAY_E_SUCCESS; +} + +/** + * Receives a plist from the service. + * + * @param client The diagnostics_relay client + * @param plist The plist to store the received data + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +static diagnostics_relay_error_t diagnostics_relay_receive(diagnostics_relay_client_t client, plist_t *plist) +{ + if (!client || !plist || (plist && *plist)) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_SUCCESS; + property_list_service_error_t err; + + err = property_list_service_receive_plist(client->parent, plist); + if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (!*plist) + ret = DIAGNOSTICS_RELAY_E_PLIST_ERROR; + + return ret; +} + +/** + * Sends a plist to the service. + * + * @note This function is low-level and should only be used if you need to send + * a new type of message. + * + * @param client The diagnostics_relay client + * @param plist The plist to send + * + * @return DIAGNOSTICS_RELAY_E_SUCCESS on success, + * DIAGNOSTICS_RELAY_E_INVALID_ARG when client or plist is NULL + */ +static diagnostics_relay_error_t diagnostics_relay_send(diagnostics_relay_client_t client, plist_t plist) +{ + if (!client || !plist) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_SUCCESS; + property_list_service_error_t err; + + err = property_list_service_send_xml_plist(client->parent, plist); + if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_goodbye(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Request", plist_new_string("Goodbye")); + + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + debug_info("did not get goodbye response back"); + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + dict = NULL; + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_sleep(diagnostics_relay_client_t client) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + + plist_dict_set_item(dict,"Request", plist_new_string("Sleep")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + return ret; +} + +static diagnostics_relay_error_t internal_diagnostics_relay_action(diagnostics_relay_client_t client, const char* name, diagnostics_relay_action_t flags) +{ + if (!client) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(name)); + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_WAIT_FOR_DISCONNECT) { + plist_dict_set_item(dict, "WaitForDisconnect", plist_new_bool(1)); + } + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_PASS) { + plist_dict_set_item(dict, "DisplayPass", plist_new_bool(1)); + } + + if (flags & DIAGNOSTICS_RELAY_ACTION_FLAG_DISPLAY_FAIL) { + plist_dict_set_item(dict, "DisplayFail", plist_new_bool(1)); + } + + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_restart(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) +{ + return internal_diagnostics_relay_action(client, "Restart", flags); +} + +diagnostics_relay_error_t diagnostics_relay_shutdown(diagnostics_relay_client_t client, diagnostics_relay_action_t flags) +{ + return internal_diagnostics_relay_action(client, "Shutdown", flags); +} + +diagnostics_relay_error_t diagnostics_relay_request_diagnostics(diagnostics_relay_client_t client, const char* type, plist_t* diagnostics) +{ + if (!client || diagnostics == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(type)); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *diagnostics = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_mobilegestalt(diagnostics_relay_client_t client, plist_t keys, plist_t* result) +{ + if (!client || plist_get_node_type(keys) != PLIST_ARRAY || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"MobileGestaltKeys", plist_copy(keys)); + plist_dict_set_item(dict,"Request", plist_new_string("MobileGestalt")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_ioregistry_entry(diagnostics_relay_client_t client, const char* entry_name, const char* entry_class, plist_t* result) +{ + if (!client || (entry_name == NULL && entry_class == NULL) || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + if (entry_name) + plist_dict_set_item(dict,"EntryName", plist_new_string(entry_name)); + if (entry_class) + plist_dict_set_item(dict,"EntryClass", plist_new_string(entry_class)); + plist_dict_set_item(dict,"Request", plist_new_string("IORegistry")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + return ret; + } + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} + +diagnostics_relay_error_t diagnostics_relay_query_ioregistry_plane(diagnostics_relay_client_t client, const char* plane, plist_t* result) +{ + if (!client || plane == NULL || result == NULL) + return DIAGNOSTICS_RELAY_E_INVALID_ARG; + + diagnostics_relay_error_t ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"CurrentPlane", plist_new_string(plane)); + plist_dict_set_item(dict,"Request", plist_new_string("IORegistry")); + ret = diagnostics_relay_send(client, dict); + plist_free(dict); + dict = NULL; + + ret = diagnostics_relay_receive(client, &dict); + if (!dict) { + return DIAGNOSTICS_RELAY_E_PLIST_ERROR; + } + + int check = diagnostics_relay_check_result(dict); + if (check == RESULT_SUCCESS) { + ret = DIAGNOSTICS_RELAY_E_SUCCESS; + } else if (check == RESULT_UNKNOWN_REQUEST) { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_REQUEST; + } else { + ret = DIAGNOSTICS_RELAY_E_UNKNOWN_ERROR; + } + + if (ret != DIAGNOSTICS_RELAY_E_SUCCESS) { + plist_free(dict); + return ret; + } + + plist_t value_node = plist_dict_get_item(dict, "Diagnostics"); + if (value_node) { + *result = plist_copy(value_node); + } + + plist_free(dict); + return ret; +} diff --git a/src/diagnostics_relay.h b/src/diagnostics_relay.h new file mode 100644 index 0000000..3bb543a --- /dev/null +++ b/src/diagnostics_relay.h @@ -0,0 +1,33 @@ +/* + * diagnostics_relay.h + * com.apple.mobile.diagnostics_relay service header file. + * + * Copyright (c) 2012 Martin Szulecki, 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 __DIAGNOSTICS_RELAY_H +#define __DIAGNOSTICS_RELAY_H + +#include "idevice.h" +#include "libimobiledevice/diagnostics_relay.h" +#include "property_list_service.h" + +struct diagnostics_relay_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/file_relay.c b/src/file_relay.c index 680e28d..fbe7cbf 100644 --- a/src/file_relay.c +++ b/src/file_relay.c @@ -1,49 +1,41 @@ - /* +/* * file_relay.c * com.apple.mobile.file_relay service implementation. - * + * * Copyright (c) 2010 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 + * 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 "file_relay.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" -/** - * Connects to the file_relay service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Reference that will point to a newly allocated - * file_relay_client_t upon successful return. - * - * @return FILE_RELAY_E_SUCCESS on success, - * FILE_RELAY_E_INVALID_ARG when one of the parameters is invalid, - * or FILE_RELAY_E_MUX_ERROR when the connection failed. - */ -file_relay_error_t file_relay_client_new(idevice_t device, uint16_t port, file_relay_client_t *client) +file_relay_error_t file_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, file_relay_client_t *client) { - if (!device || port == 0 || !client || *client) { + if (!device || !service || service->port == 0 || !client || *client) { return FILE_RELAY_E_INVALID_ARG; } property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { return FILE_RELAY_E_MUX_ERROR; } @@ -56,17 +48,13 @@ file_relay_error_t file_relay_client_new(idevice_t device, uint16_t port, file_r return FILE_RELAY_E_SUCCESS; } -/** - * Disconnects a file_relay client from the device and frees up the file_relay - * client data. - * - * @param client The file_relay client to disconnect and free. - * - * @return FILE_RELAY_E_SUCCESS on success, - * FILE_RELAY_E_INVALID_ARG when one of client or client->parent - * is invalid, or FILE_RELAY_E_UNKNOWN_ERROR when the was an error - * freeing the parent property_list_service client. - */ +file_relay_error_t file_relay_client_start_service(idevice_t device, file_relay_client_t * client, const char* label) +{ + file_relay_error_t err = FILE_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, FILE_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(file_relay_client_new), &err); + return err; +} + file_relay_error_t file_relay_client_free(file_relay_client_t client) { if (!client) @@ -75,40 +63,11 @@ file_relay_error_t file_relay_client_free(file_relay_client_t client) if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { return FILE_RELAY_E_UNKNOWN_ERROR; } + free(client); return FILE_RELAY_E_SUCCESS; } -/** - * Request data for the given sources. - * - * @param client The connected file_relay client. - * @param sources A NULL-terminated list of sources to retrieve. - * Valid sources are: - * - AppleSupport - * - Network - * - VPN - * - WiFi - * - UserDatabases - * - CrashReporter - * - tmp - * - SystemConfiguration - * @param connection The connection that has to be used for receiving the - * data using idevice_connection_receive(). The connection will be closed - * automatically by the device, but use file_relay_client_free() to clean - * up properly. - * - * @note WARNING: Don't call this function without reading the data afterwards. - * A directory mobile_file_relay.XXXX used for creating the archive will - * remain in the /tmp directory otherwise. - * - * @return FILE_RELAY_E_SUCCESS on succes, FILE_RELAY_E_INVALID_ARG when one or - * more parameters are invalid, FILE_RELAY_E_MUX_ERROR if a communication - * error occurs, FILE_RELAY_E_PLIST_ERROR when the received result is NULL - * or is not a valid plist, FILE_RELAY_E_INVALID_SOURCE if one or more - * sources are invalid, FILE_RELAY_E_STAGING_EMPTY if no data is available - * for the given sources, or FILE_RELAY_E_UNKNOWN_ERROR otherwise. - */ -file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection) +file_relay_error_t file_relay_request_sources_timeout(file_relay_client_t client, const char **sources, idevice_connection_t *connection, unsigned int timeout) { if (!client || !client->parent || !sources || !sources[0]) { return FILE_RELAY_E_INVALID_ARG; @@ -121,9 +80,9 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const while (sources[i]) { plist_array_append_item(array, plist_new_string(sources[i])); i++; - } + } plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Sources", array); + plist_dict_set_item(dict, "Sources", array); if (property_list_service_send_xml_plist(client->parent, dict) != PROPERTY_LIST_SERVICE_E_SUCCESS) { debug_info("ERROR: Could not send request to device!"); @@ -133,7 +92,7 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const plist_free(dict); dict = NULL; - if (property_list_service_receive_plist_with_timeout(client->parent, &dict, 60000) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + if (property_list_service_receive_plist_with_timeout(client->parent, &dict, timeout) != PROPERTY_LIST_SERVICE_E_SUCCESS) { debug_info("ERROR: Could not receive answer from device!"); err = FILE_RELAY_E_MUX_ERROR; goto leave; @@ -156,6 +115,9 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const } else if (!strcmp(errmsg, "StagingEmpty")) { debug_info("ERROR: StagingEmpty - No data available!"); err = FILE_RELAY_E_STAGING_EMPTY; + } else if (!strcmp(errmsg, "PermissionDenied")) { + debug_info("ERROR: Permission denied."); + err = FILE_RELAY_E_PERMISSION_DENIED; } else { debug_info("ERROR: Unknown error '%s'", errmsg); } @@ -181,14 +143,14 @@ file_relay_error_t file_relay_request_sources(file_relay_client_t client, const goto leave; } - if (strcmp(ack, "Acknowledged")) { + if (strcmp(ack, "Acknowledged") != 0) { debug_info("ERROR: Did not receive 'Acknowledged' but '%s'", ack); goto leave; } free(ack); err = FILE_RELAY_E_SUCCESS; - *connection = client->parent->connection; + *connection = client->parent->parent->connection; leave: if (dict) { @@ -196,3 +158,8 @@ leave: } return err; } + +file_relay_error_t file_relay_request_sources(file_relay_client_t client, const char **sources, idevice_connection_t *connection) +{ + return file_relay_request_sources_timeout(client, sources, connection, 60000); +} diff --git a/src/file_relay.h b/src/file_relay.h index 60cc32f..65bf460 100644 --- a/src/file_relay.h +++ b/src/file_relay.h @@ -1,38 +1,31 @@ - /* +/* * file_relay.h * com.apple.mobile.file_relay service header file. - * + * * Copyright (c) 2010 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef FILE_RELAY_H -#define FILE_RELAY_H +#ifndef __FILE_RELAY_H +#define __FILE_RELAY_H + +#include "idevice.h" #include "libimobiledevice/file_relay.h" #include "property_list_service.h" -/* Error Codes */ -#define FILE_RELAY_E_SUCCESS 0 -#define FILE_RELAY_E_INVALID_ARG -1 -#define FILE_RELAY_E_PLIST_ERROR -2 -#define FILE_RELAY_E_MUX_ERROR -3 - -#define FILE_RELAY_E_UNKNOWN_ERROR -256 - - struct file_relay_client_private { property_list_service_client_t parent; }; diff --git a/src/heartbeat.c b/src/heartbeat.c new file mode 100644 index 0000000..3945d73 --- /dev/null +++ b/src/heartbeat.c @@ -0,0 +1,147 @@ +/* + * heartbeat.c + * com.apple.mobile.heartbeat service implementation. + * + * Copyright (c) 2013 Martin Szulecki 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 <plist/plist.h> + +#include "heartbeat.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a heartbeat_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching heartbeat_error_t error code, + * HEARTBEAT_E_UNKNOWN_ERROR otherwise. + */ +static heartbeat_error_t heartbeat_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return HEARTBEAT_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return HEARTBEAT_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return HEARTBEAT_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return HEARTBEAT_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return HEARTBEAT_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return HEARTBEAT_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return HEARTBEAT_E_TIMEOUT; + default: + break; + } + return HEARTBEAT_E_UNKNOWN_ERROR; +} + +heartbeat_error_t heartbeat_client_new(idevice_t device, lockdownd_service_descriptor_t service, heartbeat_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to heartbeat_client_new."); + return HEARTBEAT_E_INVALID_ARG; + } + + debug_info("Creating heartbeat_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + heartbeat_error_t ret = heartbeat_error(property_list_service_client_new(device, service, &plclient)); + if (ret != HEARTBEAT_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + heartbeat_client_t client_loc = (heartbeat_client_t) malloc(sizeof(struct heartbeat_client_private)); + client_loc->parent = plclient; + + *client = client_loc; + + debug_info("heartbeat_client successfully created."); + return 0; +} + +heartbeat_error_t heartbeat_client_start_service(idevice_t device, heartbeat_client_t * client, const char* label) +{ + heartbeat_error_t err = HEARTBEAT_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, HEARTBEAT_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(heartbeat_client_new), &err); + return err; +} + +heartbeat_error_t heartbeat_client_free(heartbeat_client_t client) +{ + if (!client) + return HEARTBEAT_E_INVALID_ARG; + + heartbeat_error_t err = heartbeat_error(property_list_service_client_free(client->parent)); + free(client); + + return err; +} + +heartbeat_error_t heartbeat_send(heartbeat_client_t client, plist_t plist) +{ + heartbeat_error_t res = HEARTBEAT_E_UNKNOWN_ERROR; + + res = heartbeat_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != HEARTBEAT_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + + debug_plist(plist); + + return res; +} + +heartbeat_error_t heartbeat_receive(heartbeat_client_t client, plist_t * plist) +{ + return heartbeat_receive_with_timeout(client, plist, 1000); +} + +heartbeat_error_t heartbeat_receive_with_timeout(heartbeat_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + heartbeat_error_t res = HEARTBEAT_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + + res = heartbeat_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, timeout_ms)); + if (res != HEARTBEAT_E_SUCCESS || !outplist) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + return HEARTBEAT_E_MUX_ERROR; + } + + *plist = outplist; + + debug_plist(*plist); + + return res; +} diff --git a/src/heartbeat.h b/src/heartbeat.h new file mode 100644 index 0000000..379ecc1 --- /dev/null +++ b/src/heartbeat.h @@ -0,0 +1,33 @@ +/* + * heartbeat.h + * com.apple.mobile.heartbeat service header file. + * + * Copyright (c) 2013 Martin Szulecki 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 __HEARTBEAT_H +#define __HEARTBEAT_H + +#include "idevice.h" +#include "libimobiledevice/heartbeat.h" +#include "property_list_service.h" + +struct heartbeat_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/house_arrest.c b/src/house_arrest.c index 5baa76e..caad731 100644 --- a/src/house_arrest.c +++ b/src/house_arrest.c @@ -8,17 +8,20 @@ * 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 + * 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 <unistd.h> @@ -27,7 +30,7 @@ #include "house_arrest.h" #include "property_list_service.h" #include "afc.h" -#include "debug.h" +#include "common/debug.h" /** * Convert a property_list_service_error_t value to a house_arrest_error_t @@ -40,39 +43,25 @@ */ static house_arrest_error_t house_arrest_error(property_list_service_error_t err) { - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return HOUSE_ARREST_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return HOUSE_ARREST_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return HOUSE_ARREST_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return HOUSE_ARREST_E_CONN_FAILED; - default: - break; - } - return HOUSE_ARREST_E_UNKNOWN_ERROR; + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return HOUSE_ARREST_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return HOUSE_ARREST_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return HOUSE_ARREST_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return HOUSE_ARREST_E_CONN_FAILED; + default: + break; + } + return HOUSE_ARREST_E_UNKNOWN_ERROR; } -/** - * Connects to the house_arrest service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will point to a newly allocated - * housearrest_client_t upon successful return. - * - * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when - * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. - */ -house_arrest_error_t house_arrest_client_new(idevice_t device, uint16_t port, house_arrest_client_t *client) +house_arrest_error_t house_arrest_client_new(idevice_t device, lockdownd_service_descriptor_t service, house_arrest_client_t *client) { - if (!device) - return HOUSE_ARREST_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - house_arrest_error_t err = house_arrest_error(property_list_service_client_new(device, port, &plistclient)); + house_arrest_error_t err = house_arrest_error(property_list_service_client_new(device, service, &plistclient)); if (err != HOUSE_ARREST_E_SUCCESS) { return err; } @@ -85,27 +74,20 @@ house_arrest_error_t house_arrest_client_new(idevice_t device, uint16_t port, ho return HOUSE_ARREST_E_SUCCESS; } -/** - * Disconnects an house_arrest client from the device and frees up the - * house_arrest client data. - * - * @note After using afc_client_new_from_house_arrest_client(), make sure - * you call afc_client_free() before calling this function to ensure - * a proper cleanup. Do not call this function if you still need to - * perform AFC operations since it will close the connection. - * - * @param client The house_arrest client to disconnect and free. - * - * @return HOUSE_ARREST_E_SUCCESS on success, HOUSE_ARREST_E_INVALID_ARG when - * client is NULL, or an HOUSE_ARREST_E_* error code otherwise. - */ +house_arrest_error_t house_arrest_client_start_service(idevice_t device, house_arrest_client_t * client, const char* label) +{ + house_arrest_error_t err = HOUSE_ARREST_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, HOUSE_ARREST_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(house_arrest_client_new), &err); + return err; +} + house_arrest_error_t house_arrest_client_free(house_arrest_client_t client) { if (!client) return HOUSE_ARREST_E_INVALID_ARG; house_arrest_error_t err = HOUSE_ARREST_E_SUCCESS; - if (client->parent && client->parent->connection) { + if (client->parent && client->parent->parent->connection) { house_arrest_error(property_list_service_client_free(client->parent)); } client->parent = NULL; @@ -114,70 +96,34 @@ house_arrest_error_t house_arrest_client_free(house_arrest_client_t client) return err; } -/** - * Sends a generic request to the connected house_arrest service. - * - * @param client The house_arrest client to use. - * @param dict The request to send as a plist of type PLIST_DICT. - * - * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean - * that the request was successful. To check for success or failure you - * need to call house_arrest_get_result(). - * @see house_arrest_get_result - * - * @return HOUSE_ARREST_E_SUCCESS if the request was successfully sent, - * HOUSE_ARREST_E_INVALID_ARG if client or dict is invalid, - * HOUSE_ARREST_E_PLIST_ERROR if dict is not a plist of type PLIST_DICT, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_send_request(house_arrest_client_t client, plist_t dict) { if (!client || !client->parent || !dict) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (plist_get_node_type(dict) != PLIST_DICT) return HOUSE_ARREST_E_PLIST_ERROR; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = house_arrest_error(property_list_service_send_xml_plist(client->parent, dict)); - if (res != HOUSE_ARREST_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - } + if (res != HOUSE_ARREST_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + } return res; } -/** - * Send a command to the connected house_arrest service. - * Calls house_arrest_send_request() internally. - * - * @param client The house_arrest client to use. - * @param command The command to send. Currently, only VendContainer and - * VendDocuments are known. - * @param appid The application identifier to pass along with the . - * - * @note If this function returns HOUSE_ARREST_E_SUCCESS it does not mean - * that the command was successful. To check for success or failure you - * need to call house_arrest_get_result(). - * @see house_arrest_get_result - * - * @return HOUSE_ARREST_E_SUCCESS if the command was successfully sent, - * HOUSE_ARREST_E_INVALID_ARG if client, command, or appid is invalid, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, const char *command, const char *appid) { if (!client || !client->parent || !command || !appid) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = HOUSE_ARREST_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string(command)); - plist_dict_insert_item(dict, "Identifier", plist_new_string(appid)); + plist_dict_set_item(dict, "Command", plist_new_string(command)); + plist_dict_set_item(dict, "Identifier", plist_new_string(appid)); res = house_arrest_send_request(client, dict); @@ -186,63 +132,30 @@ house_arrest_error_t house_arrest_send_command(house_arrest_client_t client, con return res; } -/** - * Retrieves the result of a previously sent house_arrest_request_* request. - * - * @param client The house_arrest client to use - * @param dict Pointer that will be set to a plist containing the result to - * the last performed operation. It holds a key 'Status' with the value - * 'Complete' on success or a key 'Error' with an error description as - * value. The caller is responsible for freeing the returned plist. - * - * @return HOUSE_ARREST_E_SUCCESS if a result plist was retrieved, - * HOUSE_ARREST_E_INVALID_ARG if client is invalid, - * HOUSE_ARREST_E_INVALID_MODE if the client is not in the correct mode, - * or HOUSE_ARREST_E_CONN_FAILED if a connection error occured. - */ house_arrest_error_t house_arrest_get_result(house_arrest_client_t client, plist_t *dict) { if (!client || !client->parent) - return HOUSE_ARREST_E_INVALID_ARG; + return HOUSE_ARREST_E_INVALID_ARG; if (client->mode != HOUSE_ARREST_CLIENT_MODE_NORMAL) return HOUSE_ARREST_E_INVALID_MODE; house_arrest_error_t res = house_arrest_error(property_list_service_receive_plist(client->parent, dict)); - if (res != HOUSE_ARREST_E_SUCCESS) { - debug_info("could not get result, error %d", res); - if (*dict) { - plist_free(*dict); - *dict = NULL; - } - } + if (res != HOUSE_ARREST_E_SUCCESS) { + debug_info("could not get result, error %d", res); + if (*dict) { + plist_free(*dict); + *dict = NULL; + } + } return res; } -/** - * Creates an AFC client using the given house_arrest client's connection - * allowing file access to a specific application directory requested by - * functions like house_arrest_request_vendor_documents(). - * - * @param client The house_arrest client to use. - * @param afc_client Pointer that will be set to a newly allocated afc_client_t - * upon successful return. - * - * @note After calling this function the house_arrest client will go in an - * AFC mode that will only allow calling house_arrest_client_free(). - * Only call house_arrest_client_free() if all AFC operations have - * completed since it will close the connection. - * - * @return AFC_E_SUCCESS if the afc client was successfully created, - * AFC_E_INVALID_ARG if client is invalid or was already used to create - * an afc client, or an AFC_E_* error code returned by - * afc_client_new_from_connection(). - */ afc_error_t afc_client_new_from_house_arrest_client(house_arrest_client_t client, afc_client_t *afc_client) { if (!client || !client->parent || (client->mode == HOUSE_ARREST_CLIENT_MODE_AFC)) { return AFC_E_INVALID_ARG; } - afc_error_t err = afc_client_new_from_connection(client->parent->connection, afc_client); + afc_error_t err = afc_client_new_with_service_client(client->parent->parent, afc_client); if (err == AFC_E_SUCCESS) { client->mode = HOUSE_ARREST_CLIENT_MODE_AFC; } diff --git a/src/house_arrest.h b/src/house_arrest.h index 6d13a88..5612a29 100644 --- a/src/house_arrest.h +++ b/src/house_arrest.h @@ -8,21 +8,21 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IHOUSE_ARREST_H -#define IHOUSE_ARREST_H -#include <glib.h> +#ifndef __HOUSE_ARREST_H +#define __HOUSE_ARREST_H +#include "idevice.h" #include "libimobiledevice/house_arrest.h" #include "property_list_service.h" diff --git a/src/idevice.c b/src/idevice.c index 5a9d49b..b9bbb1f 100644 --- a/src/idevice.c +++ b/src/idevice.c @@ -1,98 +1,387 @@ -/* +/* * idevice.c * Device discovery and communication interface. * + * Copyright (c) 2009-2021 Nikias Bassen. All Rights Reserved. + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. - * Copyright (c) 2009 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + #include <stdlib.h> #include <string.h> #include <errno.h> +#include <time.h> + +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#else +#include <sys/socket.h> +#include <netinet/in.h> +#endif #include <usbmuxd.h> + +#if defined(HAVE_OPENSSL) +#include <openssl/err.h> +#include <openssl/rsa.h> +#include <openssl/ssl.h> +#elif defined(HAVE_GNUTLS) #include <gnutls/gnutls.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/rsa.h> +#include <mbedtls/ssl.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#include <mbedtls/debug.h> +#else +#error No supported TLS/SSL library enabled +#endif + +#include <libimobiledevice-glue/socket.h> +#include <libimobiledevice-glue/thread.h> + #include "idevice.h" -#include "userpref.h" -#include "debug.h" +#include "lockdown.h" +#include "common/userpref.h" +#include "common/debug.h" + +#ifndef ECONNREFUSED +#define ECONNREFUSED 107 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + + +#ifdef HAVE_OPENSSL + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x20020000L)) +#define TLS_method TLSv1_method +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10002000L || defined(LIBRESSL_VERSION_NUMBER) +static void SSL_COMP_free_compression_methods(void) +{ + sk_SSL_COMP_free(SSL_COMP_get_compression_methods()); +} +#endif + +static void openssl_remove_thread_state(void) +{ +/* ERR_remove_thread_state() is available since OpenSSL 1.0.0-beta1, but + * deprecated in OpenSSL 1.1.0 */ +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10000001L + ERR_remove_thread_state(NULL); +#else + ERR_remove_state(0); +#endif +#endif +} + +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) +static mutex_t *mutex_buf = NULL; +static void locking_function(int mode, int n, const char* file, int line) +{ + if (mode & CRYPTO_LOCK) + mutex_lock(&mutex_buf[n]); + else + mutex_unlock(&mutex_buf[n]); +} + +#if OPENSSL_VERSION_NUMBER < 0x10000000L +static unsigned long id_function(void) +{ + return ((unsigned long)THREAD_ID); +} +#else +static void id_function(CRYPTO_THREADID *thread) +{ + CRYPTO_THREADID_set_numeric(thread, (unsigned long)THREAD_ID); +} +#endif +#endif +#endif /* HAVE_OPENSSL */ + +static void internal_idevice_init(void) +{ +#if defined(HAVE_OPENSSL) +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + int i; + SSL_library_init(); + + mutex_buf = malloc(CRYPTO_num_locks() * sizeof(mutex_t)); + if (!mutex_buf) + return; + for (i = 0; i < CRYPTO_num_locks(); i++) + mutex_init(&mutex_buf[i]); + +#if OPENSSL_VERSION_NUMBER < 0x10000000L + CRYPTO_set_id_callback(id_function); +#else + CRYPTO_THREADID_set_callback(id_function); +#endif + CRYPTO_set_locking_callback(locking_function); +#endif +#elif defined(HAVE_GNUTLS) + gnutls_global_init(); +#elif defined(HAVE_MBEDTLS) + // NO-OP +#endif +} + +static void internal_idevice_deinit(void) +{ +#if defined(HAVE_OPENSSL) +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + int i; + if (mutex_buf) { +#if OPENSSL_VERSION_NUMBER < 0x10000000L + CRYPTO_set_id_callback(NULL); +#else + CRYPTO_THREADID_set_callback(NULL); +#endif + CRYPTO_set_locking_callback(NULL); + for (i = 0; i < CRYPTO_num_locks(); i++) + mutex_destroy(&mutex_buf[i]); + free(mutex_buf); + mutex_buf = NULL; + } + + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + SSL_COMP_free_compression_methods(); + openssl_remove_thread_state(); +#endif +#elif defined(HAVE_GNUTLS) + gnutls_global_deinit(); +#elif defined(HAVE_MBEDTLS) + // NO-OP +#endif +} + +static thread_once_t init_once = THREAD_ONCE_INIT; +static thread_once_t deinit_once = THREAD_ONCE_INIT; + +#ifndef HAVE_ATTRIBUTE_CONSTRUCTOR + #if defined(__llvm__) || defined(__GNUC__) + #define HAVE_ATTRIBUTE_CONSTRUCTOR + #endif +#endif + +#ifdef HAVE_ATTRIBUTE_CONSTRUCTOR +static void __attribute__((constructor)) libimobiledevice_initialize(void) +{ + thread_once(&init_once, internal_idevice_init); +} + +static void __attribute__((destructor)) libimobiledevice_deinitialize(void) +{ + thread_once(&deinit_once, internal_idevice_deinit); +} +#elif defined(WIN32) +BOOL WINAPI DllMain(HINSTANCE hModule, DWORD dwReason, LPVOID lpReserved) +{ + switch (dwReason) { + case DLL_PROCESS_ATTACH: + thread_once(&init_once, internal_idevice_init); + break; + case DLL_PROCESS_DETACH: + thread_once(&deinit_once, internal_idevice_deinit); + break; + default: + break; + } + return 1; +} +#else +#warning No compiler support for constructor/destructor attributes, some features might not be available. +#endif + +const char* libimobiledevice_version() +{ +#ifndef PACKAGE_VERSION +#error PACKAGE_VERSION is not defined! +#endif + return PACKAGE_VERSION; +} + +struct idevice_subscription_context { + idevice_event_cb_t callback; + void *user_data; + usbmuxd_subscription_context_t ctx; +}; -static idevice_event_cb_t event_cb = NULL; +static idevice_subscription_context_t event_ctx = NULL; static void usbmux_event_cb(const usbmuxd_event_t *event, void *user_data) { + idevice_subscription_context_t context = (idevice_subscription_context_t)user_data; idevice_event_t ev; ev.event = event->event; - ev.uuid = event->device.uuid; - ev.conn_type = CONNECTION_USBMUXD; + ev.udid = event->device.udid; + ev.conn_type = 0; + if (event->device.conn_type == CONNECTION_TYPE_USB) { + ev.conn_type = CONNECTION_USBMUXD; + } else if (event->device.conn_type == CONNECTION_TYPE_NETWORK) { + ev.conn_type = CONNECTION_NETWORK; + } else { + debug_info("Unknown connection type %d", event->device.conn_type); + } - if (event_cb) { - event_cb(&ev, user_data); + if (context->callback) { + context->callback(&ev, context->user_data); } } -/** - * Register a callback function that will be called when device add/remove - * events occur. - * - * @param callback Callback function to call. - * @param user_data Application-specific data passed as parameter - * to the registered callback function. - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ -idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data) +idevice_error_t idevice_events_subscribe(idevice_subscription_context_t *context, idevice_event_cb_t callback, void *user_data) { - event_cb = callback; - int res = usbmuxd_subscribe(usbmux_event_cb, user_data); - if (res != 0) { - event_cb = NULL; - debug_info("Error %d when subscribing usbmux event callback!", res); + if (!context || !callback) { + return IDEVICE_E_INVALID_ARG; + } + *context = malloc(sizeof(struct idevice_subscription_context)); + if (!*context) { + debug_info("ERROR: %s: Failed to allocate subscription context\n", __func__); + return IDEVICE_E_UNKNOWN_ERROR; + } + (*context)->callback = callback; + (*context)->user_data = user_data; + int res = usbmuxd_events_subscribe(&(*context)->ctx, usbmux_event_cb, *context); + if (res != 0) { + free(*context); + *context = NULL; + debug_info("ERROR: usbmuxd_subscribe() returned %d!", res); return IDEVICE_E_UNKNOWN_ERROR; } return IDEVICE_E_SUCCESS; } -/** - * Release the event callback function that has been registered with - * idevice_event_subscribe(). - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ -idevice_error_t idevice_event_unsubscribe() +idevice_error_t idevice_events_unsubscribe(idevice_subscription_context_t context) { - event_cb = NULL; - int res = usbmuxd_unsubscribe(); + if (!context) { + return IDEVICE_E_INVALID_ARG; + } + int res = usbmuxd_events_unsubscribe(context->ctx); if (res != 0) { - debug_info("Error %d when unsubscribing usbmux event callback!", res); + debug_info("ERROR: usbmuxd_unsubscribe() returned %d!", res); return IDEVICE_E_UNKNOWN_ERROR; } + if (context == event_ctx) { + event_ctx = NULL; + } + free(context); + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_event_subscribe(idevice_event_cb_t callback, void *user_data) +{ + if (event_ctx) { + idevice_events_unsubscribe(event_ctx); + } + return idevice_events_subscribe(&event_ctx, callback, user_data); +} + +idevice_error_t idevice_event_unsubscribe(void) +{ + if (!event_ctx) { + return IDEVICE_E_SUCCESS; + } + event_ctx->callback = NULL; + return idevice_events_unsubscribe(event_ctx); +} + +idevice_error_t idevice_get_device_list_extended(idevice_info_t **devices, int *count) +{ + usbmuxd_device_info_t *dev_list; + + *devices = NULL; + *count = 0; + + if (usbmuxd_get_device_list(&dev_list) < 0) { + debug_info("ERROR: usbmuxd is not running!", __func__); + return IDEVICE_E_NO_DEVICE; + } + + idevice_info_t *newlist = NULL; + int i, newcount = 0; + + for (i = 0; dev_list[i].handle > 0; i++) { + newlist = realloc(*devices, sizeof(idevice_info_t) * (newcount+1)); + newlist[newcount] = malloc(sizeof(struct idevice_info)); + newlist[newcount]->udid = strdup(dev_list[i].udid); + if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { + newlist[newcount]->conn_type = CONNECTION_USBMUXD; + newlist[newcount]->conn_data = NULL; + } else if (dev_list[i].conn_type == CONNECTION_TYPE_NETWORK) { + newlist[newcount]->conn_type = CONNECTION_NETWORK; + struct sockaddr* saddr = (struct sockaddr*)(dev_list[i].conn_data); + size_t addrlen = 0; + switch (saddr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef AF_INET6 + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + debug_info("Unsupported address family 0x%02x\n", saddr->sa_family); + continue; + } + newlist[newcount]->conn_data = malloc(addrlen); + memcpy(newlist[newcount]->conn_data, dev_list[i].conn_data, addrlen); + } + newcount++; + *devices = newlist; + } + usbmuxd_device_list_free(&dev_list); + + *count = newcount; + newlist = realloc(*devices, sizeof(idevice_info_t) * (newcount+1)); + newlist[newcount] = NULL; + *devices = newlist; + + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_device_list_extended_free(idevice_info_t *devices) +{ + if (devices) { + int i = 0; + while (devices[i]) { + free(devices[i]->udid); + free(devices[i]->conn_data); + free(devices[i]); + i++; + } + free(devices); + } return IDEVICE_E_SUCCESS; } -/** - * Get a list of currently available devices. - * - * @param devices List of uuids of devices that are currently available. - * This list is terminated by a NULL pointer. - * @param count Number of devices found. - * - * @return IDEVICE_E_SUCCESS on success or an error value when an error occured. - */ idevice_error_t idevice_get_device_list(char ***devices, int *count) { usbmuxd_device_info_t *dev_list; @@ -101,7 +390,7 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) *count = 0; if (usbmuxd_get_device_list(&dev_list) < 0) { - debug_info("ERROR: usbmuxd is not running!\n", __func__); + debug_info("ERROR: usbmuxd is not running!", __func__); return IDEVICE_E_NO_DEVICE; } @@ -109,9 +398,11 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) int i, newcount = 0; for (i = 0; dev_list[i].handle > 0; i++) { - newlist = realloc(*devices, sizeof(char*) * (newcount+1)); - newlist[newcount++] = strdup(dev_list[i].uuid); - *devices = newlist; + if (dev_list[i].conn_type == CONNECTION_TYPE_USB) { + newlist = realloc(*devices, sizeof(char*) * (newcount+1)); + newlist[newcount++] = strdup(dev_list[i].udid); + *devices = newlist; + } } usbmuxd_device_list_free(&dev_list); @@ -123,62 +414,101 @@ idevice_error_t idevice_get_device_list(char ***devices, int *count) return IDEVICE_E_SUCCESS; } -/** - * Free a list of device uuids. - * - * @param devices List of uuids to free. - * - * @return Always returnes IDEVICE_E_SUCCESS. - */ idevice_error_t idevice_device_list_free(char **devices) { if (devices) { int i = 0; - while (devices[i++]) { + while (devices[i]) { free(devices[i]); + i++; } free(devices); } return IDEVICE_E_SUCCESS; } -/** - * Creates an idevice_t structure for the device specified by uuid, - * if the device is available. - * - * @note The resulting idevice_t structure has to be freed with - * idevice_free() if it is no longer used. - * - * @param device Upon calling this function, a pointer to a location of type - * idevice_t. On successful return, this location will be populated. - * @param uuid The UUID to match. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ -idevice_error_t idevice_new(idevice_t * device, const char *uuid) +void idevice_set_debug_level(int level) +{ + internal_set_debug_level(level); +} + +static idevice_t idevice_from_mux_device(usbmuxd_device_info_t *muxdev) +{ + if (!muxdev) + return NULL; + + idevice_t device = (idevice_t)malloc(sizeof(struct idevice_private)); + if (!device) + return NULL; + + device->udid = strdup(muxdev->udid); + device->mux_id = muxdev->handle; + device->version = 0; + device->device_class = 0; + switch (muxdev->conn_type) { + case CONNECTION_TYPE_USB: + device->conn_type = CONNECTION_USBMUXD; + device->conn_data = NULL; + break; + case CONNECTION_TYPE_NETWORK: + device->conn_type = CONNECTION_NETWORK; + struct sockaddr* saddr = (struct sockaddr*)(muxdev->conn_data); + size_t addrlen = 0; + switch (saddr->sa_family) { + case AF_INET: + addrlen = sizeof(struct sockaddr_in); + break; +#ifdef AF_INET6 + case AF_INET6: + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + default: + debug_info("Unsupported address family 0x%02x\n", saddr->sa_family); + free(device->udid); + free(device); + return NULL; + } + device->conn_data = malloc(addrlen); + memcpy(device->conn_data, muxdev->conn_data, addrlen); + break; + default: + device->conn_type = 0; + device->conn_data = NULL; + break; + } + return device; +} + +idevice_error_t idevice_new_with_options(idevice_t * device, const char *udid, enum idevice_options options) { usbmuxd_device_info_t muxdev; - int res = usbmuxd_get_device_by_uuid(uuid, &muxdev); + int usbmux_options = 0; + if (options & IDEVICE_LOOKUP_USBMUX) { + usbmux_options |= DEVICE_LOOKUP_USBMUX; + } + if (options & IDEVICE_LOOKUP_NETWORK) { + usbmux_options |= DEVICE_LOOKUP_NETWORK; + } + if (options & IDEVICE_LOOKUP_PREFER_NETWORK) { + usbmux_options |= DEVICE_LOOKUP_PREFER_NETWORK; + } + int res = usbmuxd_get_device(udid, &muxdev, usbmux_options); if (res > 0) { - idevice_t phone = (idevice_t) malloc(sizeof(struct idevice_private)); - phone->uuid = strdup(muxdev.uuid); - phone->conn_type = CONNECTION_USBMUXD; - phone->conn_data = (void*)(long)muxdev.handle; - *device = phone; + *device = idevice_from_mux_device(&muxdev); + if (!*device) { + return IDEVICE_E_UNKNOWN_ERROR; + } return IDEVICE_E_SUCCESS; } - /* other connection types could follow here */ - return IDEVICE_E_NO_DEVICE; } -/** - * Cleans up an idevice structure, then frees the structure itself. - * This is a library-level function; deals directly with the device to tear - * down relations, but otherwise is mostly internal. - * - * @param device idevice_t to free. - */ +idevice_error_t idevice_new(idevice_t * device, const char *udid) +{ + return idevice_new_with_options(device, udid, 0); +} + idevice_error_t idevice_free(idevice_t device) { if (!device) @@ -187,11 +517,8 @@ idevice_error_t idevice_free(idevice_t device) ret = IDEVICE_E_SUCCESS; - free(device->uuid); + free(device->udid); - if (device->conn_type == CONNECTION_USBMUXD) { - device->conn_data = 0; - } if (device->conn_data) { free(device->conn_data); } @@ -199,16 +526,6 @@ idevice_error_t idevice_free(idevice_t device) return ret; } -/** - * Set up a connection to the given device. - * - * @param device The device to connect to. - * @param port The destination port to connect to. - * @param connection Pointer to an idevice_connection_t that will be filled - * with the necessary data of the connection. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connection_t *connection) { if (!device) { @@ -216,31 +533,80 @@ idevice_error_t idevice_connect(idevice_t device, uint16_t port, idevice_connect } if (device->conn_type == CONNECTION_USBMUXD) { - int sfd = usbmuxd_connect((uint32_t)(long)device->conn_data, port); + int sfd = usbmuxd_connect(device->mux_id, port); if (sfd < 0) { - debug_info("ERROR: Connecting to usbmuxd failed: %d (%s)", sfd, strerror(-sfd)); + debug_info("ERROR: Connecting to usbmux device failed: %d (%s)", sfd, strerror(-sfd)); + switch (-sfd) { + case ECONNREFUSED: + return IDEVICE_E_CONNREFUSED; + case ENODEV: + return IDEVICE_E_NO_DEVICE; + default: + break; + } return IDEVICE_E_UNKNOWN_ERROR; } idevice_connection_t new_connection = (idevice_connection_t)malloc(sizeof(struct idevice_connection_private)); new_connection->type = CONNECTION_USBMUXD; 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 { - debug_info("Unknown connection type %d", device->conn_type); + } + if (device->conn_type == CONNECTION_NETWORK) { + struct sockaddr* saddr = (struct sockaddr*)(device->conn_data); + switch (saddr->sa_family) { + case AF_INET: +#ifdef AF_INET6 + case AF_INET6: +#endif + break; + default: + debug_info("Unsupported address family 0x%02x", saddr->sa_family); + return IDEVICE_E_UNKNOWN_ERROR; + } + + char addrtxt[48]; + addrtxt[0] = '\0'; + + if (!socket_addr_to_string(saddr, addrtxt, sizeof(addrtxt))) { + debug_info("Failed to convert network address: %d (%s)", errno, strerror(errno)); + } + + debug_info("Connecting to %s port %d...", addrtxt, port); + + int sfd = socket_connect_addr(saddr, port); + if (sfd < 0) { + int result = errno; + debug_info("ERROR: Connecting to network device failed: %d (%s)", result, strerror(result)); + switch (result) { + case ECONNREFUSED: + return IDEVICE_E_CONNREFUSED; + default: + break; + } + return IDEVICE_E_NO_DEVICE; + } + + idevice_connection_t new_connection = (idevice_connection_t)malloc(sizeof(struct idevice_connection_private)); + new_connection->type = CONNECTION_NETWORK; + 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; + + return IDEVICE_E_SUCCESS; } + debug_info("Unknown connection type %d", device->conn_type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Disconnect from the device and clean up the connection structure. - * - * @param connection The connection to close. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_disconnect(idevice_connection_t connection) { if (!connection) { @@ -253,11 +619,19 @@ idevice_error_t idevice_disconnect(idevice_connection_t connection) idevice_error_t result = IDEVICE_E_UNKNOWN_ERROR; if (connection->type == CONNECTION_USBMUXD) { usbmuxd_disconnect((int)(long)connection->data); + connection->data = NULL; + result = IDEVICE_E_SUCCESS; + } else if (connection->type == CONNECTION_NETWORK) { + socket_close((int)(long)connection->data); + connection->data = NULL; result = IDEVICE_E_SUCCESS; } else { debug_info("Unknown connection type %d", connection->type); } + free(connection); + connection = NULL; + return result; } @@ -271,46 +645,111 @@ static idevice_error_t internal_connection_send(idevice_connection_t connection, } if (connection->type == CONNECTION_USBMUXD) { - int res = usbmuxd_send((int)(long)connection->data, data, len, sent_bytes); + int res; + do { + res = usbmuxd_send((int)(long)connection->data, data, len, sent_bytes); + } while (res == -EAGAIN); if (res < 0) { debug_info("ERROR: usbmuxd_send returned %d (%s)", res, strerror(-res)); return IDEVICE_E_UNKNOWN_ERROR; } return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); } + if (connection->type == CONNECTION_NETWORK) { + int s = socket_send((int)(long)connection->data, (void*)data, len); + if (s < 0) { + *sent_bytes = 0; + return IDEVICE_E_UNKNOWN_ERROR; + } + *sent_bytes = s; + return IDEVICE_E_SUCCESS; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Send data to a device via the given connection. - * - * @param connection The connection to send data over. - * @param data Buffer with data to send. - * @param len Size of the buffer to send. - * @param sent_bytes Pointer to an uint32_t that will be filled - * with the number of bytes actually sent. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_send(idevice_connection_t connection, const char *data, uint32_t len, uint32_t *sent_bytes) { - if (!connection || !data || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection || !data +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + ) { return IDEVICE_E_INVALID_ARG; } if (connection->ssl_data) { - ssize_t sent = gnutls_record_send(connection->ssl_data->session, (void*)data, (size_t)len); - if ((uint32_t)sent == (uint32_t)len) { - *sent_bytes = sent; - return IDEVICE_E_SUCCESS; + connection->status = IDEVICE_E_SUCCESS; + uint32_t sent = 0; + while (sent < len) { +#if defined(HAVE_OPENSSL) + 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); + if (sslerr == SSL_ERROR_WANT_WRITE) { + continue; + } + break; + } +#elif defined(HAVE_GNUTLS) + ssize_t s = gnutls_record_send(connection->ssl_data->session, (void*)(data+sent), (size_t)(len-sent)); +#elif defined(HAVE_MBEDTLS) + int s = mbedtls_ssl_write(&connection->ssl_data->ctx, (const unsigned char*)(data+sent), (size_t)(len-sent)); +#endif + if (s < 0) { + break; + } + sent += s; + } + debug_info("SSL_write %d, sent %d", len, sent); + if (sent < len) { + *sent_bytes = 0; + return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status; + } + *sent_bytes = sent; + return IDEVICE_E_SUCCESS; + } + uint32_t sent = 0; + while (sent < len) { + uint32_t bytes = 0; + int s = internal_connection_send(connection, data+sent, len-sent, &bytes); + if (s < 0) { + break; + } + sent += bytes; + } + debug_info("internal_connection_send %d, sent %d", len, sent); + if (sent < len) { + *sent_bytes = sent; + if (sent == 0) { + return IDEVICE_E_UNKNOWN_ERROR; + } + return IDEVICE_E_NOT_ENOUGH_DATA; + } + *sent_bytes = sent; + return IDEVICE_E_SUCCESS; +} + +static inline idevice_error_t socket_recv_to_idevice_error(int conn_error, uint32_t len, uint32_t received) +{ + if (conn_error < 0) { + switch (conn_error) { + case -EAGAIN: + if (len) { + debug_info("ERROR: received partial data %d/%d (%s)", received, len, strerror(-conn_error)); + } else { + debug_info("ERROR: received partial data (%s)", strerror(-conn_error)); + } + return IDEVICE_E_NOT_ENOUGH_DATA; + case -ETIMEDOUT: + return IDEVICE_E_TIMEOUT; + default: + return IDEVICE_E_UNKNOWN_ERROR; } - *sent_bytes = 0; - return IDEVICE_E_SSL_ERROR; } - return internal_connection_send(connection, data, len, sent_bytes); + return IDEVICE_E_SUCCESS; } /** @@ -324,47 +763,92 @@ static idevice_error_t internal_connection_receive_timeout(idevice_connection_t } if (connection->type == CONNECTION_USBMUXD) { - int res = usbmuxd_recv_timeout((int)(long)connection->data, data, len, recv_bytes, timeout); - if (res < 0) { - debug_info("ERROR: usbmuxd_recv_timeout returned %d (%s)", res, strerror(-res)); - return IDEVICE_E_UNKNOWN_ERROR; + int conn_error = usbmuxd_recv_timeout((int)(long)connection->data, data, len, recv_bytes, timeout); + idevice_error_t error = socket_recv_to_idevice_error(conn_error, len, *recv_bytes); + if (error == IDEVICE_E_UNKNOWN_ERROR) { + debug_info("ERROR: usbmuxd_recv_timeout returned %d (%s)", conn_error, strerror(-conn_error)); } - return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); + return error; } + if (connection->type == CONNECTION_NETWORK) { + int res = socket_receive_timeout((int)(long)connection->data, data, len, 0, timeout); + idevice_error_t error = socket_recv_to_idevice_error(res, 0, 0); + if (error == IDEVICE_E_SUCCESS) { + *recv_bytes = (uint32_t)res; + } else if (error == IDEVICE_E_UNKNOWN_ERROR) { + debug_info("ERROR: socket_receive_timeout returned %d (%s)", res, strerror(-res)); + } + return error; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Receive data from a device via the given connection. - * This function will return after the given timeout even if no data has been - * received. - * - * @param connection The connection to receive data from. - * @param data Buffer that will be filled with the received data. - * This buffer has to be large enough to hold len bytes. - * @param len Buffer size or number of bytes to receive. - * @param recv_bytes Number of bytes actually received. - * @param timeout Timeout in milliseconds after which this function should - * return even if no data has been received. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_receive_timeout(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes, unsigned int timeout) { - if (!connection || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + || len == 0 + ) { return IDEVICE_E_INVALID_ARG; } if (connection->ssl_data) { - ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len); - if (received > 0) { + uint32_t received = 0; + + 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) { +#if defined(HAVE_OPENSSL) + int r = SSL_read(connection->ssl_data->session, (void*)((char*)(data+received)), (int)len-received); + if (r > 0) { + received += r; + } else { + int sslerr = SSL_get_error(connection->ssl_data->session, r); + if (sslerr == SSL_ERROR_WANT_READ) { + continue; + } else if (sslerr == SSL_ERROR_ZERO_RETURN) { + if (connection->status == IDEVICE_E_TIMEOUT) { + SSL_set_shutdown(connection->ssl_data->session, 0); + } + } + break; + } +#elif defined(HAVE_GNUTLS) + ssize_t r = gnutls_record_recv(connection->ssl_data->session, (void*)(data+received), (size_t)len-received); + if (r > 0) { + received += r; + } else { + break; + } +#elif defined(HAVE_MBEDTLS) + int r = mbedtls_ssl_read(&connection->ssl_data->ctx, (void*)(data+received), (size_t)len-received); + if (r > 0) { + received += r; + } else { + break; + } +#endif + } + connection->ssl_recv_timeout = (unsigned int)-1; + + debug_info("SSL_read %d, received %d", len, received); + if (received < len) { *recv_bytes = received; - return IDEVICE_E_SUCCESS; + return connection->status == IDEVICE_E_SUCCESS ? IDEVICE_E_SSL_ERROR : connection->status; } - *recv_bytes = 0; - return IDEVICE_E_SSL_ERROR; + + *recv_bytes = received; + return IDEVICE_E_SUCCESS; } return internal_connection_receive_timeout(connection, data, len, recv_bytes, timeout); } @@ -384,35 +868,45 @@ static idevice_error_t internal_connection_receive(idevice_connection_t connecti debug_info("ERROR: usbmuxd_recv returned %d (%s)", res, strerror(-res)); return IDEVICE_E_UNKNOWN_ERROR; } - return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", connection->type); } + if (connection->type == CONNECTION_NETWORK) { + int res = socket_receive((int)(long)connection->data, data, len); + if (res < 0) { + debug_info("ERROR: socket_receive returned %d (%s)", res, strerror(-res)); + return IDEVICE_E_UNKNOWN_ERROR; + } + *recv_bytes = (uint32_t)res; + return IDEVICE_E_SUCCESS; + } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Receive data from a device via the given connection. - * This function is like idevice_connection_receive_timeout, but with a - * predefined reasonable timeout. - * - * @param connection The connection to receive data from. - * @param data Buffer that will be filled with the received data. - * This buffer has to be large enough to hold len bytes. - * @param len Buffer size or number of bytes to receive. - * @param recv_bytes Number of bytes actually received. - * - * @return IDEVICE_E_SUCCESS if ok, otherwise an error code. - */ idevice_error_t idevice_connection_receive(idevice_connection_t connection, char *data, uint32_t len, uint32_t *recv_bytes) { - if (!connection || (connection->ssl_data && !connection->ssl_data->session)) { + if (!connection +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) + || (connection->ssl_data && !connection->ssl_data->session) +#endif + ) { return IDEVICE_E_INVALID_ARG; } 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; + } +#if defined(HAVE_OPENSSL) + int received = SSL_read(connection->ssl_data->session, (void*)data, (int)len); + debug_info("SSL_read %d, received %d", len, received); +#elif defined(HAVE_GNUTLS) ssize_t received = gnutls_record_recv(connection->ssl_data->session, (void*)data, (size_t)len); +#elif defined(HAVE_MBEDTLS) + int received = mbedtls_ssl_read(&connection->ssl_data->ctx, (unsigned char*)data, (size_t)len); +#endif if (received > 0) { *recv_bytes = received; return IDEVICE_E_SUCCESS; @@ -423,89 +917,105 @@ idevice_error_t idevice_connection_receive(idevice_connection_t connection, char return internal_connection_receive(connection, data, len, recv_bytes); } -/** - * Gets the handle of the device. Depends on the connection type. - */ -idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle) +idevice_error_t idevice_connection_get_fd(idevice_connection_t connection, int *fd) { - if (!device) + if (!connection || !fd) { return IDEVICE_E_INVALID_ARG; + } - if (device->conn_type == CONNECTION_USBMUXD) { - *handle = (uint32_t)(long)device->conn_data; + if (connection->type == CONNECTION_USBMUXD) { + *fd = (int)(long)connection->data; + return IDEVICE_E_SUCCESS; + } + if (connection->type == CONNECTION_NETWORK) { + *fd = (int)(long)connection->data; return IDEVICE_E_SUCCESS; - } else { - debug_info("Unknown connection type %d", device->conn_type); } + + debug_info("Unknown connection type %d", connection->type); return IDEVICE_E_UNKNOWN_ERROR; } -/** - * Gets the unique id for the device. - */ -idevice_error_t idevice_get_uuid(idevice_t device, char **uuid) +idevice_error_t idevice_get_handle(idevice_t device, uint32_t *handle) +{ + if (!device || !handle) + return IDEVICE_E_INVALID_ARG; + + *handle = device->mux_id; + return IDEVICE_E_SUCCESS; +} + +idevice_error_t idevice_get_udid(idevice_t device, char **udid) { - if (!device || !uuid) + if (!device || !udid) return IDEVICE_E_INVALID_ARG; - *uuid = strdup(device->uuid); + if (device->udid) { + *udid = strdup(device->udid); + } return IDEVICE_E_SUCCESS; } +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) +typedef ssize_t ssl_cb_ret_type_t; +#elif defined(HAVE_MBEDTLS) +typedef int ssl_cb_ret_type_t; +#endif + /** - * 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 ssl_cb_ret_type_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; + uint32_t bytes = 0; + uint32_t pos = 0; idevice_error_t res; - idevice_connection_t connection = (idevice_connection_t)transport; - char *recv_buffer; - - debug_info("pre-read client wants %zi bytes", length); + unsigned int timeout = connection->ssl_recv_timeout; - recv_buffer = (char *) malloc(sizeof(char) * this_len); + debug_info("pre-read length = %zi bytes", length); /* 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; + bytes = 0; + if (timeout == (unsigned int)-1) { + res = internal_connection_receive(connection, buffer + pos, (uint32_t)length - pos, &bytes); + } else { + res = internal_connection_receive_timeout(connection, buffer + pos, (uint32_t)length - pos, &bytes, (unsigned int)timeout); } - debug_info("post-read we got %i bytes", bytes); + 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("read %i bytes", bytes); /* increase read count */ - tbytes += bytes; - - /* fill the buffer with what we got right now */ - memcpy(buffer + pos_start_fill, recv_buffer, bytes); - pos_start_fill += bytes; - - if (tbytes >= length) { - break; + pos += bytes; + if (pos < (uint32_t)length) { + debug_info("re-read trying to read missing %i bytes", (uint32_t)length - pos); } + } while (pos < (uint32_t)length); - this_len = length - tbytes; - debug_info("re-read trying to read missing %i bytes", this_len); - } while (tbytes < length); + debug_info("post-read received %i bytes", pos); - if (recv_buffer) { - free(recv_buffer); - } - return tbytes; + return pos; } /** - * 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 ssl_cb_ret_type_t internal_ssl_write(idevice_connection_t connection, const char *buffer, size_t length) { uint32_t bytes = 0; - idevice_connection_t connection = (idevice_connection_t)transport; - debug_info("pre-send length = %zi", length); - internal_connection_send(connection, buffer, length, &bytes); + idevice_error_t res; + debug_info("pre-send length = %zi bytes", 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; } @@ -518,6 +1028,14 @@ static void internal_ssl_cleanup(ssl_data_t ssl_data) if (!ssl_data) return; +#if defined(HAVE_OPENSSL) + if (ssl_data->session) { + SSL_free(ssl_data->session); + } + if (ssl_data->ctx) { + SSL_CTX_free(ssl_data->ctx); + } +#elif defined(HAVE_GNUTLS) if (ssl_data->session) { gnutls_deinit(ssl_data->session); } @@ -536,20 +1054,118 @@ static void internal_ssl_cleanup(ssl_data_t ssl_data) if (ssl_data->host_privkey) { gnutls_x509_privkey_deinit(ssl_data->host_privkey); } +#elif defined(HAVE_MBEDTLS) + mbedtls_pk_free(&ssl_data->root_privkey); + mbedtls_x509_crt_free(&ssl_data->certificate); + mbedtls_entropy_free(&ssl_data->entropy); + mbedtls_ctr_drbg_free(&ssl_data->ctr_drbg); + mbedtls_ssl_config_free(&ssl_data->config); + mbedtls_ssl_free(&ssl_data->ctx); +#endif +} + +#ifdef HAVE_OPENSSL +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +static long ssl_idevice_bio_callback(BIO *b, int oper, const char *argp, size_t len, int argi, long argl, int retvalue, size_t *processed) +#else +static long ssl_idevice_bio_callback(BIO *b, int oper, const char *argp, int argi, long argl, long retvalue) +#endif +{ + ssize_t bytes = 0; + idevice_connection_t conn = (idevice_connection_t)BIO_get_callback_arg(b); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + size_t len = (size_t)argi; + size_t *processed = (size_t*)&bytes; +#endif + switch (oper) { + case (BIO_CB_READ|BIO_CB_RETURN): + if (argp) { + bytes = internal_ssl_read(conn, (char *)argp, len); + *processed = bytes; + return (long)bytes; + } + return 0; + case (BIO_CB_PUTS|BIO_CB_RETURN): + len = strlen(argp); + // fallthrough + case (BIO_CB_WRITE|BIO_CB_RETURN): + bytes = internal_ssl_write(conn, argp, len); + *processed = bytes; + return (long)bytes; + 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); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIO_set_callback_ex(b, ssl_idevice_bio_callback); +#else + BIO_set_callback(b, ssl_idevice_bio_callback); +#endif + return b; +} + +static int ssl_verify_callback(int ok, X509_STORE_CTX *ctx) +{ + return 1; +} + +#ifndef STRIP_DEBUG_CODE +static const char *ssl_error_to_string(int e) +{ + switch(e) { + case SSL_ERROR_NONE: + return "SSL_ERROR_NONE"; + case SSL_ERROR_SSL: + return ERR_error_string(ERR_get_error(), NULL); + case SSL_ERROR_WANT_READ: + return "SSL_ERROR_WANT_READ"; + case SSL_ERROR_WANT_WRITE: + return "SSL_ERROR_WANT_WRITE"; + case SSL_ERROR_WANT_X509_LOOKUP: + return "SSL_ERROR_WANT_X509_LOOKUP"; + case SSL_ERROR_SYSCALL: + return "SSL_ERROR_SYSCALL"; + case SSL_ERROR_ZERO_RETURN: + return "SSL_ERROR_ZERO_RETURN"; + case SSL_ERROR_WANT_CONNECT: + return "SSL_ERROR_WANT_CONNECT"; + case SSL_ERROR_WANT_ACCEPT: + return "SSL_ERROR_WANT_ACCEPT"; + default: + return "UNKOWN_ERROR_VALUE"; + } } +#endif +#endif +#if defined(HAVE_GNUTLS) /** * Internally used gnutls callback function that gets called during handshake. */ -static int internal_cert_callback (gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) +#if GNUTLS_VERSION_NUMBER >= 0x020b07 +static int internal_cert_callback(gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr2_st * st) +#else +static int internal_cert_callback(gnutls_session_t session, const gnutls_datum_t * req_ca_rdn, int nreqs, const gnutls_pk_algorithm_t * sign_algos, int sign_algos_length, gnutls_retr_st * st) +#endif { int res = -1; - gnutls_certificate_type_t type = gnutls_certificate_type_get (session); + gnutls_certificate_type_t type = gnutls_certificate_type_get(session); if (type == GNUTLS_CRT_X509) { - ssl_data_t ssl_data = (ssl_data_t)gnutls_session_get_ptr (session); + ssl_data_t ssl_data = (ssl_data_t)gnutls_session_get_ptr(session); if (ssl_data && ssl_data->host_privkey && ssl_data->host_cert) { debug_info("Passing certificate"); +#if GNUTLS_VERSION_NUMBER >= 0x020b07 + st->cert_type = type; + st->key_type = GNUTLS_PRIVKEY_X509; +#else st->type = type; +#endif st->ncerts = 1; st->cert.x509 = &ssl_data->host_cert; st->key.x509 = ssl_data->host_privkey; @@ -559,46 +1175,204 @@ static int internal_cert_callback (gnutls_session_t session, const gnutls_datum_ } return res; } +#elif defined(HAVE_MBEDTLS) +static void _mbedtls_log_cb(void* ctx, int level, const char* filename, int line, const char* message) +{ + fprintf(stderr, "[mbedtls][%d] %s:%d => %s", level, filename, line, message); +} + +static int cert_verify_cb(void* ctx, mbedtls_x509_crt* cert, int depth, uint32_t *flags) +{ + *flags = 0; + return 0; +} + +static int _mbedtls_f_rng(void* p_rng, unsigned char* buf, size_t len) +{ + memset(buf, 4, len); + return 0; +} +#endif -/** - * Enables SSL for the given connection. - * - * @param connection The connection to enable SSL for. - * - * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection - * is NULL or connection->ssl_data is non-NULL, or IDEVICE_E_SSL_ERROR when - * SSL initialization, setup, or handshake fails. - */ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) { if (!connection || connection->ssl_data) return IDEVICE_E_INVALID_ARG; idevice_error_t ret = IDEVICE_E_SSL_ERROR; - uint32_t return_me = 0; + plist_t pair_record = NULL; + + userpref_error_t uerr = userpref_read_pair_record(connection->device->udid, &pair_record); + if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed enabling SSL. Unable to read pair record for udid %s (%d)", connection->device->udid, uerr); + return ret; + } + +#if defined(HAVE_OPENSSL) + key_data_t root_cert = { NULL, 0 }; + key_data_t root_privkey = { NULL, 0 }; + + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, &root_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, &root_privkey); + if (pair_record) + plist_free(pair_record); + + BIO *ssl_bio = ssl_idevice_bio_new(connection); + if (!ssl_bio) { + debug_info("ERROR: Could not create SSL bio."); + return ret; + } + + SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_method()); + if (ssl_ctx == NULL) { + debug_info("ERROR: Could not create SSL context."); + BIO_free(ssl_bio); + return ret; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER >= 0x3060000fL)) + SSL_CTX_set_security_level(ssl_ctx, 0); +#endif + +#if OPENSSL_VERSION_NUMBER < 0x10100002L || \ + (defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2060000fL)) + /* force use of TLSv1 for older devices */ + if (connection->device->version < DEVICE_VERSION(10,0,0)) { +#ifdef SSL_OP_NO_TLSv1_1 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_1); +#endif +#ifdef SSL_OP_NO_TLSv1_2 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_2); +#endif +#ifdef SSL_OP_NO_TLSv1_3 + SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_TLSv1_3); +#endif + } +#else + SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_VERSION); + if (connection->device->version < DEVICE_VERSION(10,0,0)) { + SSL_CTX_set_max_proto_version(ssl_ctx, TLS1_VERSION); + if (connection->device->version == 0) { + /* + iOS 1 doesn't understand TLS1_VERSION, it can only speak SSL3_VERSION. + However, modern OpenSSL is usually compiled without SSLv3 support. + So if we set min_proto_version to SSL3_VERSION on an OpenSSL instance which doesn't support it, + it will just ignore min_proto_version altogether and fall back to an even higher version. + To avoid accidentally breaking iOS 2.0+, we set min version to 0 instead. + Here is what documentation says: + Setting the minimum or maximum version to 0, + will enable protocol versions down to the lowest version, + or up to the highest version supported by the library, respectively. + */ + SSL_CTX_set_min_proto_version(ssl_ctx, 0); + } + } +#endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#if defined(SSL_OP_IGNORE_UNEXPECTED_EOF) + /* + * For OpenSSL 3 and later, mark close_notify alerts as optional. + * For prior versions of OpenSSL we check for SSL_ERROR_SYSCALL when + * reading instead (this error changes to SSL_ERROR_SSL in OpenSSL 3). + */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_IGNORE_UNEXPECTED_EOF); +#endif +#if defined(SSL_OP_LEGACY_SERVER_CONNECT) + /* + * Without setting SSL_OP_LEGACY_SERVER_CONNECT, OpenSSL 3 fails with + * error "unsafe legacy renegotiation disabled" when talking to iOS 5 + */ + SSL_CTX_set_options(ssl_ctx, SSL_OP_LEGACY_SERVER_CONNECT); +#endif +#endif + + BIO* membp; + X509* rootCert = NULL; + membp = BIO_new_mem_buf(root_cert.data, root_cert.size); + PEM_read_bio_X509(membp, &rootCert, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_certificate(ssl_ctx, rootCert) != 1) { + debug_info("WARNING: Could not load RootCertificate"); + } + X509_free(rootCert); + free(root_cert.data); + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY* rootPrivKey = NULL; + membp = BIO_new_mem_buf(root_privkey.data, root_privkey.size); + PEM_read_bio_PrivateKey(membp, &rootPrivKey, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_PrivateKey(ssl_ctx, rootPrivKey) != 1) { + debug_info("WARNING: Could not load RootPrivateKey"); + } + EVP_PKEY_free(rootPrivKey); +#else + RSA* rootPrivKey = NULL; + membp = BIO_new_mem_buf(root_privkey.data, root_privkey.size); + PEM_read_bio_RSAPrivateKey(membp, &rootPrivKey, NULL, NULL); + BIO_free(membp); + if (SSL_CTX_use_RSAPrivateKey(ssl_ctx, rootPrivKey) != 1) { + debug_info("WARNING: Could not load RootPrivateKey"); + } + RSA_free(rootPrivKey); +#endif + free(root_privkey.data); + + SSL *ssl = SSL_new(ssl_ctx); + if (!ssl) { + debug_info("ERROR: Could not create SSL object"); + BIO_free(ssl_bio); + SSL_CTX_free(ssl_ctx); + return ret; + } + SSL_set_connect_state(ssl); + SSL_set_verify(ssl, 0, ssl_verify_callback); + SSL_set_bio(ssl, ssl_bio, ssl_bio); + + debug_info("Performing SSL handshake"); + int ssl_error = 0; + do { + ssl_error = SSL_get_error(ssl, SSL_do_handshake(ssl)); + if (ssl_error == 0 || ssl_error != SSL_ERROR_WANT_READ) { + break; + } +#ifdef WIN32 + Sleep(100); +#else + struct timespec ts = { 0, 100000000 }; + nanosleep(&ts, NULL); +#endif + } while (1); + if (ssl_error != 0) { + debug_info("ERROR during SSL handshake: %s", ssl_error_to_string(ssl_error)); + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + } else { + ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); + ssl_data_loc->session = ssl; + ssl_data_loc->ctx = ssl_ctx; + connection->ssl_data = ssl_data_loc; + ret = IDEVICE_E_SUCCESS; + debug_info("SSL mode enabled, %s, cipher: %s", SSL_get_version(ssl), SSL_get_cipher(ssl)); + } + /* required for proper multi-thread clean up to prevent leaks */ + openssl_remove_thread_state(); +#elif defined(HAVE_GNUTLS) ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); /* Set up GnuTLS... */ debug_info("enabling SSL mode"); errno = 0; - gnutls_global_init(); gnutls_certificate_allocate_credentials(&ssl_data_loc->certificate); - gnutls_certificate_client_set_retrieve_function (ssl_data_loc->certificate, internal_cert_callback); +#if GNUTLS_VERSION_NUMBER >= 0x020b07 + gnutls_certificate_set_retrieve_function(ssl_data_loc->certificate, internal_cert_callback); +#else + gnutls_certificate_client_set_retrieve_function(ssl_data_loc->certificate, internal_cert_callback); +#endif gnutls_init(&ssl_data_loc->session, GNUTLS_CLIENT); - { - int protocol_priority[16] = { GNUTLS_SSL3, 0 }; - int kx_priority[16] = { GNUTLS_KX_ANON_DH, GNUTLS_KX_RSA, 0 }; - int cipher_priority[16] = { GNUTLS_CIPHER_AES_128_CBC, GNUTLS_CIPHER_AES_256_CBC, 0 }; - int mac_priority[16] = { GNUTLS_MAC_SHA1, GNUTLS_MAC_MD5, 0 }; - int comp_priority[16] = { GNUTLS_COMP_NULL, 0 }; - - gnutls_cipher_set_priority(ssl_data_loc->session, cipher_priority); - gnutls_compression_set_priority(ssl_data_loc->session, comp_priority); - gnutls_kx_set_priority(ssl_data_loc->session, kx_priority); - gnutls_protocol_set_priority(ssl_data_loc->session, protocol_priority); - gnutls_mac_set_priority(ssl_data_loc->session, mac_priority); - } + gnutls_priority_set_direct(ssl_data_loc->session, "NONE:+VERS-TLS1.0:+ANON-DH:+RSA:+AES-128-CBC:+AES-256-CBC:+SHA1:+MD5:+COMP-NULL", NULL); gnutls_credentials_set(ssl_data_loc->session, GNUTLS_CRD_CERTIFICATE, ssl_data_loc->certificate); gnutls_session_set_ptr(ssl_data_loc->session, ssl_data_loc); @@ -607,10 +1381,13 @@ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) gnutls_x509_privkey_init(&ssl_data_loc->root_privkey); gnutls_x509_privkey_init(&ssl_data_loc->host_privkey); - userpref_error_t uerr = userpref_get_keys_and_certs(ssl_data_loc->root_privkey, ssl_data_loc->root_cert, ssl_data_loc->host_privkey, ssl_data_loc->host_cert); - if (uerr != USERPREF_E_SUCCESS) { - debug_info("Error %d when loading keys and certificates! %d", uerr); - } + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, ssl_data_loc->root_cert); + pair_record_import_crt_with_name(pair_record, USERPREF_HOST_CERTIFICATE_KEY, ssl_data_loc->host_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, ssl_data_loc->root_privkey); + pair_record_import_key_with_name(pair_record, USERPREF_HOST_PRIVATE_KEY_KEY, ssl_data_loc->host_privkey); + + if (pair_record) + plist_free(pair_record); debug_info("GnuTLS step 1..."); gnutls_transport_set_ptr(ssl_data_loc->session, (gnutls_transport_ptr_t)connection); @@ -619,46 +1396,146 @@ idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection) debug_info("GnuTLS step 3..."); gnutls_transport_set_pull_function(ssl_data_loc->session, (gnutls_pull_func) & internal_ssl_read); debug_info("GnuTLS step 4 -- now handshaking..."); - if (errno) - debug_info("WARN: errno says %s before handshake!", strerror(errno)); - return_me = gnutls_handshake(ssl_data_loc->session); + if (errno) { + debug_info("WARNING: errno says %s before handshake!", strerror(errno)); + } + + int return_me = 0; + do { + return_me = gnutls_handshake(ssl_data_loc->session); + } while(return_me == GNUTLS_E_AGAIN || return_me == GNUTLS_E_INTERRUPTED); + debug_info("GnuTLS handshake done..."); if (return_me != GNUTLS_E_SUCCESS) { internal_ssl_cleanup(ssl_data_loc); free(ssl_data_loc); - debug_info("GnuTLS reported something wrong."); - gnutls_perror(return_me); + debug_info("GnuTLS reported something wrong: %s", gnutls_strerror(return_me)); debug_info("oh.. errno says %s", strerror(errno)); } else { connection->ssl_data = ssl_data_loc; ret = IDEVICE_E_SUCCESS; debug_info("SSL mode enabled"); } +#elif defined(HAVE_MBEDTLS) + key_data_t root_cert = { NULL, 0 }; + key_data_t root_privkey = { NULL, 0 }; + + pair_record_import_crt_with_name(pair_record, USERPREF_ROOT_CERTIFICATE_KEY, &root_cert); + pair_record_import_key_with_name(pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY, &root_privkey); + + plist_free(pair_record); + + ssl_data_t ssl_data_loc = (ssl_data_t)malloc(sizeof(struct ssl_data_private)); + + mbedtls_ssl_init(&ssl_data_loc->ctx); + mbedtls_ssl_config_init(&ssl_data_loc->config); + mbedtls_entropy_init(&ssl_data_loc->entropy); + mbedtls_ctr_drbg_init(&ssl_data_loc->ctr_drbg); + + int r = mbedtls_ctr_drbg_seed(&ssl_data_loc->ctr_drbg, mbedtls_entropy_func, &ssl_data_loc->entropy, NULL, 0); + if (r != 0) { + debug_info("ERROR: [mbedtls] mbedtls_ctr_drbg_seed failed: %d", r); + return ret; + } + + if (mbedtls_ssl_config_defaults(&ssl_data_loc->config, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_PRESET_DEFAULT) != 0) { + debug_info("ERROR: [mbedtls] Failed to set config defaults"); + return ret; + } + + mbedtls_ssl_conf_rng(&ssl_data_loc->config, mbedtls_ctr_drbg_random, &ssl_data_loc->ctr_drbg); + + mbedtls_ssl_conf_dbg(&ssl_data_loc->config, _mbedtls_log_cb, NULL); + + mbedtls_ssl_conf_verify(&ssl_data_loc->config, cert_verify_cb, NULL); + + mbedtls_ssl_setup(&ssl_data_loc->ctx, &ssl_data_loc->config); + + mbedtls_ssl_set_bio(&ssl_data_loc->ctx, connection, (mbedtls_ssl_send_t*)&internal_ssl_write, (mbedtls_ssl_recv_t*)&internal_ssl_read, NULL); + + mbedtls_x509_crt_init(&ssl_data_loc->certificate); + + int crterr = mbedtls_x509_crt_parse(&ssl_data_loc->certificate, root_cert.data, root_cert.size); + if (crterr < 0) { + debug_info("ERROR: [mbedtls] parsing root cert failed: %d", crterr); + return ret; + } + + mbedtls_ssl_conf_ca_chain(&ssl_data_loc->config, &ssl_data_loc->certificate, NULL); + + mbedtls_pk_init(&ssl_data_loc->root_privkey); + +#if MBEDTLS_VERSION_NUMBER >= 0x03000000 + int pkerr = mbedtls_pk_parse_key(&ssl_data_loc->root_privkey, root_privkey.data, root_privkey.size, NULL, 0, &_mbedtls_f_rng, NULL); +#else + int pkerr = mbedtls_pk_parse_key(&ssl_data_loc->root_privkey, root_privkey.data, root_privkey.size, NULL, 0); +#endif + if (pkerr < 0) { + debug_info("ERROR: [mbedtls] parsing private key failed: %d (size=%d)", pkerr, root_privkey.size); + return ret; + } + + mbedtls_ssl_conf_own_cert(&ssl_data_loc->config, &ssl_data_loc->certificate, &ssl_data_loc->root_privkey); + + int return_me = 0; + do { + return_me = mbedtls_ssl_handshake(&ssl_data_loc->ctx); + } while (return_me == MBEDTLS_ERR_SSL_WANT_READ || return_me == MBEDTLS_ERR_SSL_WANT_WRITE); + + if (return_me != 0) { + debug_info("ERROR during SSL handshake: %d", return_me); + internal_ssl_cleanup(ssl_data_loc); + free(ssl_data_loc); + } else { + connection->ssl_data = ssl_data_loc; + ret = IDEVICE_E_SUCCESS; + debug_info("SSL mode enabled, %s, cipher: %s", mbedtls_ssl_get_version(&ssl_data_loc->ctx), mbedtls_ssl_get_ciphersuite(&ssl_data_loc->ctx)); + debug_info("SSL mode enabled"); + } +#endif return ret; } -/** - * Disable SSL for the given connection. - * - * @param connection The connection to disable SSL for. - * - * @return IDEVICE_E_SUCCESS on success, IDEVICE_E_INVALID_ARG when connection - * is NULL. This function also returns IDEVICE_E_SUCCESS when SSL is not - * enabled and does no further error checking on cleanup. - */ idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection) { + return idevice_connection_disable_bypass_ssl(connection, 0); +} + +idevice_error_t idevice_connection_disable_bypass_ssl(idevice_connection_t connection, uint8_t sslBypass) +{ if (!connection) return IDEVICE_E_INVALID_ARG; if (!connection->ssl_data) { - /* ignore if ssl is not enabled */ + /* ignore if ssl is not enabled */ return IDEVICE_E_SUCCESS; } - if (connection->ssl_data->session) { - gnutls_bye(connection->ssl_data->session, GNUTLS_SHUT_RDWR); + // some services require plain text communication after SSL handshake + // sending out SSL_shutdown will cause bytes + if (!sslBypass) { +#if defined(HAVE_OPENSSL) + if (connection->ssl_data->session) { + /* see: https://www.openssl.org/docs/ssl/SSL_shutdown.html#RETURN_VALUES */ + if (SSL_shutdown(connection->ssl_data->session) == 0) { + /* Only try bidirectional shutdown if we know it can complete */ + int ssl_error; + if ((ssl_error = SSL_get_error(connection->ssl_data->session, 0)) == SSL_ERROR_NONE) { + SSL_shutdown(connection->ssl_data->session); + } else { + debug_info("Skipping bidirectional SSL shutdown. SSL error code: %i", ssl_error); + } + } + } +#elif defined(HAVE_GNUTLS) + if (connection->ssl_data->session) { + gnutls_bye(connection->ssl_data->session, GNUTLS_SHUT_RDWR); + } +#elif defined(HAVE_MBEDTLS) + mbedtls_ssl_close_notify(&connection->ssl_data->ctx); +#endif } + internal_ssl_cleanup(connection->ssl_data); free(connection->ssl_data); connection->ssl_data = NULL; @@ -667,4 +1544,3 @@ idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection) return IDEVICE_E_SUCCESS; } - diff --git a/src/idevice.h b/src/idevice.h index 231b3ab..dd72f9d 100644 --- a/src/idevice.h +++ b/src/idevice.h @@ -8,51 +8,97 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IDEVICE_H -#define IDEVICE_H +#ifndef __DEVICE_H +#define __DEVICE_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#if defined(HAVE_OPENSSL) +#include <openssl/ssl.h> +#elif defined(HAVE_GNUTLS) #include <gnutls/gnutls.h> #include <gnutls/x509.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/ssl.h> +#include <mbedtls/entropy.h> +#include <mbedtls/ctr_drbg.h> +#endif +#ifdef LIBIMOBILEDEVICE_STATIC + #define LIBIMOBILEDEVICE_API +#elif defined(_WIN32) + #define LIBIMOBILEDEVICE_API __declspec( dllexport ) +#else + #if __GNUC__ >= 4 + #define LIBIMOBILEDEVICE_API __attribute__((visibility("default"))) + #else + #define LIBIMOBILEDEVICE_API + #endif +#endif + +#include "common/userpref.h" #include "libimobiledevice/libimobiledevice.h" -enum connection_type { - CONNECTION_USBMUXD = 1 -}; +#define DEVICE_VERSION(maj, min, patch) (((maj & 0xFF) << 16) | ((min & 0xFF) << 8) | (patch & 0xFF)) + +#define DEVICE_CLASS_IPHONE 1 +#define DEVICE_CLASS_IPAD 2 +#define DEVICE_CLASS_IPOD 3 +#define DEVICE_CLASS_APPLETV 4 +#define DEVICE_CLASS_WATCH 5 +#define DEVICE_CLASS_UNKNOWN 255 struct ssl_data_private { +#if defined(HAVE_OPENSSL) + SSL *session; + SSL_CTX *ctx; +#elif defined(HAVE_GNUTLS) gnutls_certificate_credentials_t certificate; gnutls_session_t session; gnutls_x509_privkey_t root_privkey; gnutls_x509_crt_t root_cert; gnutls_x509_privkey_t host_privkey; gnutls_x509_crt_t host_cert; +#elif defined(HAVE_MBEDTLS) + mbedtls_ssl_context ctx; + mbedtls_ssl_config config; + mbedtls_entropy_context entropy; + mbedtls_ctr_drbg_context ctr_drbg; + mbedtls_x509_crt certificate; + mbedtls_pk_context root_privkey; +#endif }; typedef struct ssl_data_private *ssl_data_t; struct idevice_connection_private { - enum connection_type type; + idevice_t device; + enum idevice_connection_type type; void *data; ssl_data_t ssl_data; + unsigned int ssl_recv_timeout; + idevice_error_t status; }; struct idevice_private { - char *uuid; - enum connection_type conn_type; + char *udid; + uint32_t mux_id; + enum idevice_connection_type conn_type; void *conn_data; + int version; + int device_class; }; -idevice_error_t idevice_connection_enable_ssl(idevice_connection_t connection); -idevice_error_t idevice_connection_disable_ssl(idevice_connection_t connection); - #endif diff --git a/src/installation_proxy.c b/src/installation_proxy.c index 4a76dd2..ec19da0 100644 --- a/src/installation_proxy.c +++ b/src/installation_proxy.c @@ -2,63 +2,210 @@ * installation_proxy.c * com.apple.mobile.installation_proxy service implementation. * - * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2013 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 + * 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 <inttypes.h> #include <unistd.h> #include <plist/plist.h> #include "installation_proxy.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +typedef enum { + INSTPROXY_COMMAND_TYPE_ASYNC, + INSTPROXY_COMMAND_TYPE_SYNC +} instproxy_command_type_t; struct instproxy_status_data { instproxy_client_t client; + plist_t command; instproxy_status_cb_t cbfunc; - char *operation; void *user_data; }; /** + * Converts an error string identifier to a instproxy_error_t value. + * Used internally to get correct error codes from a response. + * + * @param name The error name to convert. + * @param error_detail Pointer to store error detail text if available. The + * caller is reponsible for freeing the allocated buffer after use. If NULL + * is passed no error detail will be returned. + * + * @return A matching instproxy_error_t error code or + * INSTPROXY_E_UNKNOWN_ERROR otherwise. + */ +static instproxy_error_t instproxy_strtoerr(const char* name) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + + if (strcmp(name, "AlreadyArchived") == 0) { + err = INSTPROXY_E_ALREADY_ARCHIVED; + } else if (strcmp(name, "APIInternalError") == 0) { + err = INSTPROXY_E_API_INTERNAL_ERROR; + } else if (strcmp(name, "ApplicationAlreadyInstalled") == 0) { + err = INSTPROXY_E_APPLICATION_ALREADY_INSTALLED; + } else if (strcmp(name, "ApplicationMoveFailed") == 0) { + err = INSTPROXY_E_APPLICATION_MOVE_FAILED; + } else if (strcmp(name, "ApplicationSINFCaptureFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SINF_CAPTURE_FAILED; + } else if (strcmp(name, "ApplicationSandboxFailed") == 0) { + err = INSTPROXY_E_APPLICATION_SANDBOX_FAILED; + } else if (strcmp(name, "ApplicationVerificationFailed") == 0) { + err = INSTPROXY_E_APPLICATION_VERIFICATION_FAILED; + } else if (strcmp(name, "ArchiveDestructionFailed") == 0) { + err = INSTPROXY_E_ARCHIVE_DESTRUCTION_FAILED; + } else if (strcmp(name, "BundleVerificationFailed") == 0) { + err = INSTPROXY_E_BUNDLE_VERIFICATION_FAILED; + } else if (strcmp(name, "CarrierBundleCopyFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_COPY_FAILED; + } else if (strcmp(name, "CarrierBundleDirectoryCreationFailed") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_DIRECTORY_CREATION_FAILED; + } else if (strcmp(name, "CarrierBundleMissingSupportedSIMs") == 0) { + err = INSTPROXY_E_CARRIER_BUNDLE_MISSING_SUPPORTED_SIMS; + } else if (strcmp(name, "CommCenterNotificationFailed") == 0) { + err = INSTPROXY_E_COMM_CENTER_NOTIFICATION_FAILED; + } else if (strcmp(name, "ContainerCreationFailed") == 0) { + err = INSTPROXY_E_CONTAINER_CREATION_FAILED; + } else if (strcmp(name, "ContainerP0wnFailed") == 0) { + err = INSTPROXY_E_CONTAINER_P0WN_FAILED; + } else if (strcmp(name, "ContainerRemovalFailed") == 0) { + err = INSTPROXY_E_CONTAINER_REMOVAL_FAILED; + } else if (strcmp(name, "EmbeddedProfileInstallFailed") == 0) { + err = INSTPROXY_E_EMBEDDED_PROFILE_INSTALL_FAILED; + } else if (strcmp(name, "ExecutableTwiddleFailed") == 0) { + err = INSTPROXY_E_EXECUTABLE_TWIDDLE_FAILED; + } else if (strcmp(name, "ExistenceCheckFailed") == 0) { + err = INSTPROXY_E_EXISTENCE_CHECK_FAILED; + } else if (strcmp(name, "InstallMapUpdateFailed") == 0) { + err = INSTPROXY_E_INSTALL_MAP_UPDATE_FAILED; + } else if (strcmp(name, "ManifestCaptureFailed") == 0) { + err = INSTPROXY_E_MANIFEST_CAPTURE_FAILED; + } else if (strcmp(name, "MapGenerationFailed") == 0) { + err = INSTPROXY_E_MAP_GENERATION_FAILED; + } else if (strcmp(name, "MissingBundleExecutable") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_EXECUTABLE; + } else if (strcmp(name, "MissingBundleIdentifier") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_IDENTIFIER; + } else if (strcmp(name, "MissingBundlePath") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_PATH; + } else if (strcmp(name, "MissingContainer") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER; + } else if (strcmp(name, "NotificationFailed") == 0) { + err = INSTPROXY_E_NOTIFICATION_FAILED; + } else if (strcmp(name, "PackageExtractionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_EXTRACTION_FAILED; + } else if (strcmp(name, "PackageInspectionFailed") == 0) { + err = INSTPROXY_E_PACKAGE_INSPECTION_FAILED; + } else if (strcmp(name, "PackageMoveFailed") == 0) { + err = INSTPROXY_E_PACKAGE_MOVE_FAILED; + } else if (strcmp(name, "PathConversionFailed") == 0) { + err = INSTPROXY_E_PATH_CONVERSION_FAILED; + } else if (strcmp(name, "RestoreContainerFailed") == 0) { + err = INSTPROXY_E_RESTORE_CONTAINER_FAILED; + } else if (strcmp(name, "SeatbeltProfileRemovalFailed") == 0) { + err = INSTPROXY_E_SEATBELT_PROFILE_REMOVAL_FAILED; + } else if (strcmp(name, "StageCreationFailed") == 0) { + err = INSTPROXY_E_STAGE_CREATION_FAILED; + } else if (strcmp(name, "SymlinkFailed") == 0) { + err = INSTPROXY_E_SYMLINK_FAILED; + } else if (strcmp(name, "UnknownCommand") == 0) { + err = INSTPROXY_E_UNKNOWN_COMMAND; + } else if (strcmp(name, "iTunesArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "iTunesMetadataCaptureFailed") == 0) { + err = INSTPROXY_E_ITUNES_METADATA_CAPTURE_FAILED; + } else if (strcmp(name, "DeviceOSVersionTooLow") == 0) { + err = INSTPROXY_E_DEVICE_OS_VERSION_TOO_LOW; + } else if (strcmp(name, "DeviceFamilyNotSupported") == 0) { + err = INSTPROXY_E_DEVICE_FAMILY_NOT_SUPPORTED; + } else if (strcmp(name, "PackagePatchFailed") == 0) { + err = INSTPROXY_E_PACKAGE_PATCH_FAILED; + } else if (strcmp(name, "IncorrectArchitecture") == 0) { + err = INSTPROXY_E_INCORRECT_ARCHITECTURE; + } else if (strcmp(name, "PluginCopyFailed") == 0) { + err = INSTPROXY_E_PLUGIN_COPY_FAILED; + } else if (strcmp(name, "BreadcrumbFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_FAILED; + } else if (strcmp(name, "BreadcrumbUnlockFailed") == 0) { + err = INSTPROXY_E_BREADCRUMB_UNLOCK_FAILED; + } else if (strcmp(name, "GeoJSONCaptureFailed") == 0) { + err = INSTPROXY_E_GEOJSON_CAPTURE_FAILED; + } else if (strcmp(name, "NewsstandArtworkCaptureFailed") == 0) { + err = INSTPROXY_E_NEWSSTAND_ARTWORK_CAPTURE_FAILED; + } else if (strcmp(name, "MissingCommand") == 0) { + err = INSTPROXY_E_MISSING_COMMAND; + } else if (strcmp(name, "NotEntitled") == 0) { + err = INSTPROXY_E_NOT_ENTITLED; + } else if (strcmp(name, "MissingPackagePath") == 0) { + err = INSTPROXY_E_MISSING_PACKAGE_PATH; + } else if (strcmp(name, "MissingContainerPath") == 0) { + err = INSTPROXY_E_MISSING_CONTAINER_PATH; + } else if (strcmp(name, "MissingApplicationIdentifier") == 0) { + err = INSTPROXY_E_MISSING_APPLICATION_IDENTIFIER; + } else if (strcmp(name, "MissingAttributeValue") == 0) { + err = INSTPROXY_E_MISSING_ATTRIBUTE_VALUE; + } else if (strcmp(name, "LookupFailed") == 0) { + err = INSTPROXY_E_LOOKUP_FAILED; + } else if (strcmp(name, "DictCreationFailed") == 0) { + err = INSTPROXY_E_DICT_CREATION_FAILED; + } else if (strcmp(name, "InstallProhibited") == 0) { + err = INSTPROXY_E_INSTALL_PROHIBITED; + } else if (strcmp(name, "UninstallProhibited") == 0) { + err = INSTPROXY_E_UNINSTALL_PROHIBITED; + } else if (strcmp(name, "MissingBundleVersion") == 0) { + err = INSTPROXY_E_MISSING_BUNDLE_VERSION; + } + + return err; +} + +/** * Locks an installation_proxy client, used for thread safety. * * @param client The installation_proxy client to lock */ static void instproxy_lock(instproxy_client_t client) { - debug_info("InstallationProxy: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks an installation_proxy client, used for thread safety. - * + * * @param client The installation_proxy client to lock */ static void instproxy_unlock(instproxy_client_t client) { - debug_info("InstallationProxy: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** - * Convert a property_list_service_error_t value to an instproxy_error_t value. + * Converts a property_list_service_error_t value to an instproxy_error_t value. * Used internally to get correct error codes. * * @param err A property_list_service_error_t error code @@ -77,186 +224,80 @@ static instproxy_error_t instproxy_error(property_list_service_error_t err) return INSTPROXY_E_PLIST_ERROR; case PROPERTY_LIST_SERVICE_E_MUX_ERROR: return INSTPROXY_E_CONN_FAILED; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return INSTPROXY_E_RECEIVE_TIMEOUT; default: break; } return INSTPROXY_E_UNKNOWN_ERROR; } -/** - * Connects to the installation_proxy service on the specified device. - * - * @param device The device to connect to - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * instproxy_client_t upon successful return. - * - * @return INSTPROXY_E_SUCCESS on success, or an INSTPROXY_E_* error value - * when an error occured. - */ -instproxy_error_t instproxy_client_new(idevice_t device, uint16_t port, instproxy_client_t *client) +instproxy_error_t instproxy_client_new(idevice_t device, lockdownd_service_descriptor_t service, instproxy_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return INSTPROXY_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return INSTPROXY_E_CONN_FAILED; + instproxy_error_t err = instproxy_error(property_list_service_client_new(device, service, &plistclient)); + if (err != INSTPROXY_E_SUCCESS) { + return err; } instproxy_client_t client_loc = (instproxy_client_t) malloc(sizeof(struct instproxy_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - client_loc->status_updater = NULL; + mutex_init(&client_loc->mutex); + client_loc->receive_status_thread = THREAD_T_NULL; *client = client_loc; return INSTPROXY_E_SUCCESS; } -/** - * Disconnects an installation_proxy client from the device and frees up the - * installation_proxy client data. - * - * @param client The installation_proxy client to disconnect and free. - * - * @return INSTPROXY_E_SUCCESS on success - * or INSTPROXY_E_INVALID_ARG if client is NULL. - */ +instproxy_error_t instproxy_client_start_service(idevice_t device, instproxy_client_t * client, const char* label) +{ + instproxy_error_t err = INSTPROXY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, INSTPROXY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(instproxy_client_new), &err); + return err; +} + instproxy_error_t instproxy_client_free(instproxy_client_t client) { if (!client) return INSTPROXY_E_INVALID_ARG; - property_list_service_client_free(client->parent); + property_list_service_client_t parent = client->parent; client->parent = NULL; - if (client->status_updater) { - debug_info("joining status_updater"); - g_thread_join(client->status_updater); - } - if (client->mutex) { - g_mutex_free(client->mutex); + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; } + property_list_service_client_free(parent); + mutex_destroy(&client->mutex); free(client); return INSTPROXY_E_SUCCESS; } /** - * Send a command with specified options to the device. + * Sends a command to the device. * Only used internally. * * @param client The connected installation_proxy client. * @param command The command to execute. Required. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param appid The ApplicationIdentifier to add or NULL if not required. - * @param package_path The installation package path or NULL if not required. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static instproxy_error_t instproxy_send_command(instproxy_client_t client, const char *command, plist_t client_options, const char *appid, const char *package_path) +static instproxy_error_t instproxy_send_command(instproxy_client_t client, plist_t command) { - if (!client || !command || (client_options && (plist_get_node_type(client_options) != PLIST_DICT))) + if (!client || !command) return INSTPROXY_E_INVALID_ARG; - plist_t dict = plist_new_dict(); - if (appid) { - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - } - if (client_options && (plist_dict_get_size(client_options) > 0)) { - plist_dict_insert_item(dict, "ClientOptions", plist_copy(client_options)); - } - plist_dict_insert_item(dict, "Command", plist_new_string(command)); - if (package_path) { - plist_dict_insert_item(dict, "PackagePath", plist_new_string(package_path)); - } - - instproxy_error_t err = instproxy_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - return err; -} + instproxy_error_t res = instproxy_error(property_list_service_send_xml_plist(client->parent, command)); -/** - * List installed applications. This function runs synchronously. - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid client options include: - * "ApplicationType" -> "User" - * "ApplicationType" -> "System" - * @param result Pointer that will be set to a plist that will hold an array - * of PLIST_DICT holding information about the applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) -{ - if (!client || !client->parent || !result) - return INSTPROXY_E_INVALID_ARG; - - instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - - instproxy_lock(client); - res = instproxy_send_command(client, "Browse", client_options, NULL, NULL); if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist"); - goto leave_unlock; - } - - int browsing = 0; - plist_t apps_array = plist_new_array(); - plist_t dict = NULL; - - do { - browsing = 0; - dict = NULL; - res = instproxy_error(property_list_service_receive_plist(client->parent, &dict)); - if (res != INSTPROXY_E_SUCCESS) { - break; - } - if (dict) { - uint64_t i; - uint64_t current_amount = 0; - char *status = NULL; - plist_t camount = plist_dict_get_item(dict, "CurrentAmount"); - plist_t pstatus = plist_dict_get_item(dict, "Status"); - if (camount) { - plist_get_uint_val(camount, ¤t_amount); - } - if (current_amount > 0) { - plist_t current_list = plist_dict_get_item(dict, "CurrentList"); - for (i = 0; current_list && (i < current_amount); i++) { - plist_t item = plist_array_get_item(current_list, i); - plist_array_append_item(apps_array, plist_copy(item)); - } - } - if (pstatus) { - plist_get_string_val(pstatus, &status); - } - if (status) { - if (!strcmp(status, "BrowsingApplications")) { - browsing = 1; - } else if (!strcmp(status, "Complete")) { - debug_info("Browsing applications completed"); - res = INSTPROXY_E_SUCCESS; - } - free(status); - } - plist_free(dict); - } - } while (browsing); - - if (res == INSTPROXY_E_SUCCESS) { - *result = apps_array; + debug_info("could not send command plist, error %d", res); + return res; } -leave_unlock: - instproxy_unlock(client); return res; } @@ -269,78 +310,99 @@ leave_unlock: * * @param client The connected installation proxy client * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function - * in async mode or shown in debug messages in sync mode. + * @param command Operation specificiation in plist. Will be passed to the + * status_cb callback. * @param user_data Callback data passed to status_cb. */ -static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static instproxy_error_t instproxy_receive_status_loop(instproxy_client_t client, plist_t command, instproxy_status_cb_t status_cb, void *user_data) { instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - int ok = 1; - plist_t dict = NULL; + int complete = 0; + plist_t node = NULL; + char* command_name = NULL; + char* status_name = NULL; + char* error_name = NULL; + char* error_description = NULL; + uint64_t error_code = 0; +#ifndef STRIP_DEBUG_CODE + int percent_complete = 0; +#endif + + instproxy_command_get_name(command, &command_name); do { + /* receive status response */ instproxy_lock(client); - res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &dict, 30000)); + res = instproxy_error(property_list_service_receive_plist_with_timeout(client->parent, &node, 1000)); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { + + /* break out if we have a communication problem */ + if (res != INSTPROXY_E_SUCCESS && res != INSTPROXY_E_RECEIVE_TIMEOUT) { debug_info("could not receive plist, error %d", res); break; } - if (dict) { - /* invoke callback function */ - if (status_cb) { - status_cb(operation, dict, user_data); + + /* parse status response */ + if (node) { + /* check status for possible error to allow reporting it and aborting it gracefully */ + res = instproxy_status_get_error(node, &error_name, &error_description, &error_code); + if (res != INSTPROXY_E_SUCCESS) { + debug_info("command: %s, error %d, code 0x%08"PRIx64", name: %s, description: \"%s\"", command_name, res, error_code, error_name, error_description ? error_description: "N/A"); + complete = 1; + } + + if (error_name) { + free(error_name); + error_name = NULL; } - /* check for 'Error', so we can abort cleanly */ - plist_t err = plist_dict_get_item(dict, "Error"); - if (err) { + + if (error_description) { + free(error_description); + error_description = NULL; + } + + /* check status from response */ + instproxy_status_get_name(node, &status_name); + if (!status_name) { + debug_info("ignoring message without Status key:"); + debug_plist(node); + } else { + if (!strcmp(status_name, "Complete")) { + complete = 1; + } else { + res = INSTPROXY_E_OP_IN_PROGRESS; + } #ifndef STRIP_DEBUG_CODE - char *err_msg = NULL; - plist_get_string_val(err, &err_msg); - if (err_msg) { - debug_info("(%s): ERROR: %s", operation, err_msg); - free(err_msg); + percent_complete = -1; + instproxy_status_get_percent_complete(node, &percent_complete); + if (percent_complete >= 0) { + debug_info("command: %s, status: %s, percent (%d%%)", command_name, status_name, percent_complete); + } else { + debug_info("command: %s, status: %s", command_name, status_name); } #endif - ok = 0; - res = INSTPROXY_E_OP_FAILED; + free(status_name); + status_name = NULL; } - /* get 'Status' */ - plist_t status = plist_dict_get_item(dict, "Status"); - if (status) { - char *status_msg = NULL; - plist_get_string_val(status, &status_msg); - if (status_msg) { - if (!strcmp(status_msg, "Complete")) { - ok = 0; - res = INSTPROXY_E_SUCCESS; - } -#ifndef STRIP_DEBUG_CODE - plist_t npercent = plist_dict_get_item(dict, "PercentComplete"); - if (npercent) { - uint64_t val = 0; - int percent; - plist_get_uint_val(npercent, &val); - percent = val; - debug_info("(%s): %s (%d%%)", operation, status_msg, percent); - } else { - debug_info("(%s): %s", operation, status_msg); - } -#endif - free(status_msg); - } + + /* invoke status callback function */ + if (status_cb) { + status_cb(command, node, user_data); } - plist_free(dict); - dict = NULL; + + plist_free(node); + node = NULL; } - } while (ok && client->parent); + } while (!complete && client->parent); + + if (command_name) + free(command_name); return res; } /** - * Internally used status updater thread function that will call the specified + * Internally used "receive status" thread function that will call the specified * callback function when status update messages (or error messages) are * received. * @@ -349,20 +411,27 @@ static instproxy_error_t instproxy_perform_operation(instproxy_client_t client, * * @return Always NULL. */ -static gpointer instproxy_status_updater(gpointer arg) -{ +static void* instproxy_receive_status_loop_thread(void* arg) +{ struct instproxy_status_data *data = (struct instproxy_status_data*)arg; - /* run until the operation is complete or an error occurs */ - (void)instproxy_perform_operation(data->client, data->cbfunc, data->operation, data->user_data); + /* run until the command is complete or an error occurs */ + (void)instproxy_receive_status_loop(data->client, data->command, data->cbfunc, data->user_data); /* cleanup */ instproxy_lock(data->client); + debug_info("done, cleaning up."); - if (data->operation) { - free(data->operation); + + if (data->command) { + plist_free(data->command); + } + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; } - data->client->status_updater = NULL; + instproxy_unlock(data->client); free(data); @@ -370,379 +439,493 @@ static gpointer instproxy_status_updater(gpointer arg) } /** - * Internally used helper function that creates a status updater thread which - * will call the passed callback function when status updates occur. - * If status_cb is NULL no thread will be created, but the operation will - * run synchronously until it completes or an error occurs. + * Internally used helper function that creates a "receive status" thread which + * will call the passed callback function when a status is received. + * + * If async is 0 no thread will be created and the command will run + * synchronously until it completes or an error occurs. * * @param client The connected installation proxy client - * @param status_cb Pointer to a callback function or NULL - * @param operation Operation name. Will be passed to the callback function + * @param command Operation name. Will be passed to the callback function * in async mode or shown in debug messages in sync mode. + * @param async A boolean indicating if receive loop should be run + * asynchronously or block. + * @param status_cb Pointer to a callback function or NULL. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS when the thread was created (async mode), or - * when the operation completed successfully (sync). - * An INSTPROXY_E_* error value is returned if an error occured. + * when the command completed successfully (sync). + * An INSTPROXY_E_* error value is returned if an error occurred. */ -static instproxy_error_t instproxy_create_status_updater(instproxy_client_t client, instproxy_status_cb_t status_cb, const char *operation, void *user_data) +static instproxy_error_t instproxy_receive_status_loop_with_callback(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) { + if (!client || !client->parent || !command) { + return INSTPROXY_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return INSTPROXY_E_OP_IN_PROGRESS; + } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - if (status_cb) { + if (async == INSTPROXY_COMMAND_TYPE_ASYNC) { /* async mode */ struct instproxy_status_data *data = (struct instproxy_status_data*)malloc(sizeof(struct instproxy_status_data)); if (data) { data->client = client; + data->command = plist_copy(command); data->cbfunc = status_cb; - data->operation = strdup(operation); data->user_data = user_data; - client->status_updater = g_thread_create(instproxy_status_updater, data, TRUE, NULL); - if (client->status_updater) { + if (thread_new(&client->receive_status_thread, instproxy_receive_status_loop_thread, data) == 0) { res = INSTPROXY_E_SUCCESS; } } } else { - /* sync mode */ - res = instproxy_perform_operation(client, NULL, operation, NULL); + /* sync mode as a fallback */ + res = instproxy_receive_status_loop(client, command, status_cb, user_data); } + return res; } - /** - * Internal function used by instproxy_install and instproxy_upgrade. + * Internal core function to send a command and process the response. * * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param command The command to execute. + * @param command The command specification dictionary. + * @param async A boolean indicating whether the receive loop should be run + * asynchronously or block until completing the command. + * @param status_cb Callback function to call if a command status is received. * @param user_data Callback data passed to status_cb. * * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. + * an error occurred. */ -static instproxy_error_t instproxy_install_or_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, const char *command, void *user_data) +static instproxy_error_t instproxy_perform_command(instproxy_client_t client, plist_t command, instproxy_command_type_t async, instproxy_status_cb_t status_cb, void *user_data) { - if (!client || !client->parent || !pkg_path) { + if (!client || !client->parent || !command) { return INSTPROXY_E_INVALID_ARG; } - if (client->status_updater) { + + if (client->receive_status_thread) { return INSTPROXY_E_OP_IN_PROGRESS; } + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + /* send command */ instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, command, client_options, NULL, pkg_path); + res = instproxy_send_command(client, command); instproxy_unlock(client); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; - } + /* loop until status or error is received */ + res = instproxy_receive_status_loop_with_callback(client, command, async, status_cb, user_data); - return instproxy_create_status_updater(client, status_cb, command, user_data); + return res; } -/** - * Install an application on the device. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_browse_with_callback(instproxy_client_t client, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Install", user_data); + if (!client || !client->parent || !status_cb) + return INSTPROXY_E_INVALID_ARG; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, (void*)user_data); + + plist_free(command); + + return res; } -/** - * Upgrade an application on the device. This function is nearly the same as - * instproxy_install; the difference is that the installation progress on the - * device is faster if the application is already installed. - * - * @param client The connected installation_proxy client - * @param pkg_path Path of the installation package (inside the AFC jail) - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "iTunesMetadata" -> PLIST_DATA - * "ApplicationSINF" -> PLIST_DATA - * "PackageType" -> "Developer" - * If PackageType -> Developer is specified, then pkg_path points to - * an .app directory instead of an install package. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +static void instproxy_append_current_list_to_result_cb(plist_t command, plist_t status, void *user_data) { - return instproxy_install_or_upgrade(client, pkg_path, client_options, status_cb, "Upgrade", user_data); + plist_t *result_array = (plist_t*)user_data; + uint64_t current_amount = 0; + plist_t current_list = NULL; + uint64_t i; + + instproxy_status_get_current_list(status, NULL, NULL, ¤t_amount, ¤t_list); + + debug_info("current_amount: %d", current_amount); + + if (current_amount > 0) { + for (i = 0; current_list && (i < current_amount); i++) { + plist_t item = plist_array_get_item(current_list, i); + plist_array_append_item(*result_array, plist_copy(item)); + } + } + + if (current_list) + plist_free(current_list); } -/** - * Uninstall an application from the device. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to uninstall - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_browse(instproxy_client_t client, plist_t client_options, plist_t *result) { - if (!client || !client->parent || !appid) { + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - } - - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; - } instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "ApplicationIdentifier", plist_new_string(appid)); - plist_dict_insert_item(dict, "Command", plist_new_string("Uninstall")); - instproxy_lock(client); - res = instproxy_send_command(client, "Uninstall", client_options, appid, NULL); - instproxy_unlock(client); + plist_t result_array = plist_new_array(); - plist_free(dict); + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Browse")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_append_current_list_to_result_cb, (void*)&result_array); + + if (res == INSTPROXY_E_SUCCESS) { + *result = result_array; + } else { + plist_free(result_array); } - return instproxy_create_status_updater(client, status_cb, "Uninstall", user_data); + plist_free(command); + + return res; } -/** - * List archived applications. This function runs synchronously. - * - * @see instproxy_archive - * - * @param client The connected installation_proxy client - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param result Pointer that will be set to a plist containing a PLIST_DICT - * holding information about the archived applications found. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - */ -instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +static void instproxy_copy_lookup_result_cb(plist_t command, plist_t status, void *user_data) +{ + plist_t* result = (plist_t*)user_data; + + plist_t node = plist_dict_get_item(status, "LookupResult"); + if (node) { + *result = plist_copy(node); + } +} + +instproxy_error_t instproxy_lookup(instproxy_client_t client, const char** appids, plist_t client_options, plist_t *result) { + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + int i = 0; + plist_t lookup_result = NULL; + plist_t command = NULL; + plist_t appid_array = NULL; + plist_t node = NULL; + if (!client || !client->parent || !result) return INSTPROXY_E_INVALID_ARG; - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "LookupArchives", client_options, NULL, NULL); + command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Lookup")); + if (client_options) { + node = plist_copy(client_options); + } else if (appids) { + node = plist_new_dict(); + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - goto leave_unlock; + /* add bundle identifiers to client options */ + if (appids) { + appid_array = plist_new_array(); + while (appids[i]) { + plist_array_append_item(appid_array, plist_new_string(appids[i])); + i++; + } + plist_dict_set_item(node, "BundleIDs", appid_array); } - res = instproxy_error(property_list_service_receive_plist(client->parent, result)); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not receive plist, error %d", res); - goto leave_unlock; + if (node) { + plist_dict_set_item(command, "ClientOptions", node); } - res = INSTPROXY_E_SUCCESS; + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); + + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); + } + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_install(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Install")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_upgrade(instproxy_client_t client, const char *pkg_path, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Upgrade")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "PackagePath", plist_new_string(pkg_path)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_uninstall(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Uninstall")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_lookup_archives(instproxy_client_t client, plist_t client_options, plist_t *result) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("LookupArchives")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)result); + + plist_free(command); -leave_unlock: - instproxy_unlock(client); return res; } -/** - * Archive an application on the device. - * This function tells the device to make an archive of the specified - * application. This results in the device creating a ZIP archive in the - * 'ApplicationArchives' directory and uninstalling the application. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to archive. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Valid options include: - * "SkipUninstall" -> Boolean - * "ArchiveType" -> "ApplicationOnly" - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ instproxy_error_t instproxy_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Archive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("Restore")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +{ + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("RemoveArchive")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + plist_dict_set_item(command, "ApplicationIdentifier", plist_new_string(appid)); + + res = instproxy_perform_command(client, command, status_cb == NULL ? INSTPROXY_COMMAND_TYPE_SYNC : INSTPROXY_COMMAND_TYPE_ASYNC, status_cb, user_data); + + plist_free(command); + + return res; +} + +instproxy_error_t instproxy_check_capabilities_match(instproxy_client_t client, const char** capabilities, plist_t client_options, plist_t *result) +{ + if (!client || !capabilities || !result) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t lookup_result = NULL; + + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + plist_t command = plist_new_dict(); + plist_dict_set_item(command, "Command", plist_new_string("CheckCapabilitiesMatch")); + if (client_options) + plist_dict_set_item(command, "ClientOptions", plist_copy(client_options)); + + if (capabilities) { + int i = 0; + plist_t capabilities_array = plist_new_array(); + while (capabilities[i]) { + plist_array_append_item(capabilities_array, plist_new_string(capabilities[i])); + i++; + } + plist_dict_set_item(command, "Capabilities", capabilities_array); } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Archive", client_options, appid, NULL); - instproxy_unlock(client); + res = instproxy_perform_command(client, command, INSTPROXY_COMMAND_TYPE_SYNC, instproxy_copy_lookup_result_cb, (void*)&lookup_result); - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (res == INSTPROXY_E_SUCCESS) { + *result = lookup_result; + } else { + plist_free(lookup_result); } - return instproxy_create_status_updater(client, status_cb, "Archive", user_data); + + plist_free(command); + + return res; } -/** - * Restore a previously archived application on the device. - * This function is the counterpart to instproxy_archive. - * @see instproxy_archive - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the app to restore. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so pass NULL here. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_restore(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +instproxy_error_t instproxy_status_get_error(plist_t status, char **name, char** description, uint64_t* code) { - if (!client || !client->parent || !appid) + instproxy_error_t res = INSTPROXY_E_UNKNOWN_ERROR; + + if (!status || !name) return INSTPROXY_E_INVALID_ARG; - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; + plist_t node = plist_dict_get_item(status, "Error"); + if (node) { + plist_get_string_val(node, name); + } else { + /* no error here */ + res = INSTPROXY_E_SUCCESS; } - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "Restore", client_options, appid, NULL); - instproxy_unlock(client); + if (code != NULL) { + *code = 0; + node = plist_dict_get_item(status, "ErrorDetail"); + if (node) { + plist_get_uint_val(node, code); + *code &= 0xffffffff; + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (description != NULL) { + node = plist_dict_get_item(status, "ErrorDescription"); + if (node) { + plist_get_string_val(node, description); + } + } + + if (*name) { + res = instproxy_strtoerr(*name); } - return instproxy_create_status_updater(client, status_cb, "Restore", user_data); + + return res; } -/** - * Removes a previously archived application from the device. - * This function removes the ZIP archive from the 'ApplicationArchives' - * directory. - * - * @param client The connected installation proxy client - * @param appid ApplicationIdentifier of the archived app to remove. - * @param client_options The client options to use, as PLIST_DICT, or NULL. - * Currently there are no known client options, so passing NULL is fine. - * @param status_cb Callback function for progress and status information. If - * NULL is passed, this function will run synchronously. - * @param user_data Callback data passed to status_cb. - * - * @return INSTPROXY_E_SUCCESS on success or an INSTPROXY_E_* error value if - * an error occured. - * - * @note If a callback function is given (async mode), this function returns - * INSTPROXY_E_SUCCESS immediately if the status updater thread has been - * created successfully; any error occuring during the operation has to be - * handled inside the specified callback function. - */ -instproxy_error_t instproxy_remove_archive(instproxy_client_t client, const char *appid, plist_t client_options, instproxy_status_cb_t status_cb, void *user_data) +void instproxy_status_get_name(plist_t status, char **name) { - if (!client || !client->parent || !appid) - return INSTPROXY_E_INVALID_ARG; + if (name) { + plist_t node = plist_dict_get_item(status, "Status"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} - if (client->status_updater) { - return INSTPROXY_E_OP_IN_PROGRESS; +void instproxy_status_get_percent_complete(plist_t status, int *percent) +{ + uint64_t val = 0; + if (percent) { + plist_t node = plist_dict_get_item(status, "PercentComplete"); + if (node) { + plist_get_uint_val(node, &val); + *percent = val; + } } +} - instproxy_lock(client); - instproxy_error_t res = instproxy_send_command(client, "RemoveArchive", client_options, appid, NULL); - instproxy_unlock(client); +void instproxy_status_get_current_list(plist_t status, uint64_t* total, uint64_t* current_index, uint64_t* current_amount, plist_t* list) +{ + plist_t node = NULL; + + if (status && plist_get_node_type(status) == PLIST_DICT) { + /* command specific logic: parse browsed list */ + if (list != NULL) { + node = plist_dict_get_item(status, "CurrentList"); + if (node) { + *current_amount = plist_array_get_size(node); + *list = plist_copy(node); + } + } - if (res != INSTPROXY_E_SUCCESS) { - debug_info("could not send plist, error %d", res); - return res; + if (total != NULL) { + node = plist_dict_get_item(status, "Total"); + if (node) { + plist_get_uint_val(node, total); + } + } + + if (current_amount != NULL) { + node = plist_dict_get_item(status, "CurrentAmount"); + if (node) { + plist_get_uint_val(node, current_amount); + } + } + + if (current_index != NULL) { + node = plist_dict_get_item(status, "CurrentIndex"); + if (node) { + plist_get_uint_val(node, current_index); + } + } } - return instproxy_create_status_updater(client, status_cb, "RemoveArchive", user_data); } -/** - * Create a new client_options plist. - * - * @return A new plist_t of type PLIST_DICT. - */ -plist_t instproxy_client_options_new() +void instproxy_command_get_name(plist_t command, char** name) +{ + if (name) { + plist_t node = plist_dict_get_item(command, "Command"); + if (node) { + plist_get_string_val(node, name); + } else { + *name = NULL; + } + } +} + +plist_t instproxy_client_options_new(void) { return plist_new_dict(); } -/** - * Add one or more new key:value pairs to the given client_options. - * - * @param client_options The client options to modify. - * @param ... KEY, VALUE, [KEY, VALUE], NULL - * - * @note The keys and values passed are expected to be strings, except for - * "ApplicationSINF" and "iTunesMetadata" expecting a plist node of type - * PLIST_DATA as value, or "SkipUninstall" needing int as value. - */ void instproxy_client_options_add(plist_t client_options, ...) { if (!client_options) return; + va_list args; va_start(args, client_options); char *arg = va_arg(args, char*); @@ -750,21 +933,21 @@ void instproxy_client_options_add(plist_t client_options, ...) char *key = strdup(arg); if (!strcmp(key, "SkipUninstall")) { int intval = va_arg(args, int); - plist_dict_insert_item(client_options, key, plist_new_bool(intval)); - } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata")) { + plist_dict_set_item(client_options, key, plist_new_bool(intval)); + } else if (!strcmp(key, "ApplicationSINF") || !strcmp(key, "iTunesMetadata") || !strcmp(key, "ReturnAttributes") || !strcmp(key, "BundleIDs")) { plist_t plistval = va_arg(args, plist_t); if (!plistval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_copy(plistval)); + plist_dict_set_item(client_options, key, plist_copy(plistval)); } else { char *strval = va_arg(args, char*); if (!strval) { free(key); break; } - plist_dict_insert_item(client_options, key, plist_new_string(strval)); + plist_dict_set_item(client_options, key, plist_new_string(strval)); } free(key); arg = va_arg(args, char*); @@ -772,15 +955,106 @@ void instproxy_client_options_add(plist_t client_options, ...) va_end(args); } -/** - * Free client_options plist. - * - * @param client_options The client options plist to free. Does nothing if NULL - * is passed. - */ +void instproxy_client_options_set_return_attributes(plist_t client_options, ...) +{ + if (!client_options) + return; + + plist_t return_attributes = plist_new_array(); + + va_list args; + va_start(args, client_options); + char *arg = va_arg(args, char*); + while (arg) { + char *attribute = strdup(arg); + plist_array_append_item(return_attributes, plist_new_string(attribute)); + free(attribute); + arg = va_arg(args, char*); + } + va_end(args); + + plist_dict_set_item(client_options, "ReturnAttributes", return_attributes); +} + void instproxy_client_options_free(plist_t client_options) { if (client_options) { plist_free(client_options); } } + +instproxy_error_t instproxy_client_get_path_for_bundle_identifier(instproxy_client_t client, const char* bundle_id, char** path) +{ + if (!client || !client->parent || !bundle_id) + return INSTPROXY_E_INVALID_ARG; + + plist_t apps = NULL; + + // create client options for any application types + plist_t client_opts = instproxy_client_options_new(); + instproxy_client_options_add(client_opts, "ApplicationType", "Any", NULL); + + // only return attributes we need + instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "CFBundleExecutable", "Path", NULL); + + // only query for specific appid + const char* appids[] = {bundle_id, NULL}; + + // query device for list of apps + instproxy_error_t ierr = instproxy_lookup(client, appids, client_opts, &apps); + + instproxy_client_options_free(client_opts); + + if (ierr != INSTPROXY_E_SUCCESS) { + return ierr; + } + + plist_t app_found = plist_access_path(apps, 1, bundle_id); + if (!app_found) { + if (apps) + plist_free(apps); + *path = NULL; + return INSTPROXY_E_OP_FAILED; + } + + char* path_str = NULL; + plist_t path_p = plist_dict_get_item(app_found, "Path"); + if (path_p) { + plist_get_string_val(path_p, &path_str); + } + + char* exec_str = NULL; + plist_t exec_p = plist_dict_get_item(app_found, "CFBundleExecutable"); + if (exec_p) { + plist_get_string_val(exec_p, &exec_str); + } + + if (!path_str) { + debug_info("app path not found"); + return INSTPROXY_E_OP_FAILED; + } + + if (!exec_str) { + debug_info("bundle executable not found"); + return INSTPROXY_E_OP_FAILED; + } + + plist_free(apps); + + char* ret = (char*)malloc(strlen(path_str) + 1 + strlen(exec_str) + 1); + strcpy(ret, path_str); + strcat(ret, "/"); + strcat(ret, exec_str); + + *path = ret; + + if (path_str) { + free(path_str); + } + + if (exec_str) { + free(exec_str); + } + + return INSTPROXY_E_SUCCESS; +} diff --git a/src/installation_proxy.h b/src/installation_proxy.h index b497d62..5bdbb71 100644 --- a/src/installation_proxy.h +++ b/src/installation_proxy.h @@ -2,34 +2,36 @@ * installation_proxy.h * com.apple.mobile.installation_proxy service header file. * - * Copyright (c) 2009 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2010-2013 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IINSTALLATION_PROXY_H -#define IINSTALLATION_PROXY_H -#include <glib.h> +#ifndef __INSTALLATION_PROXY_H +#define __INSTALLATION_PROXY_H +#include "idevice.h" #include "libimobiledevice/installation_proxy.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct instproxy_client_private { property_list_service_client_t parent; - GMutex *mutex; - GThread *status_updater; + mutex_t mutex; + THREAD_T receive_status_thread; }; #endif diff --git a/src/libimobiledevice-1.0.pc.in b/src/libimobiledevice-1.0.pc.in new file mode 100644 index 0000000..f00c392 --- /dev/null +++ b/src/libimobiledevice-1.0.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: @PACKAGE_NAME@ +Description: A library to communicate with services running on Apple iOS devices. +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -limobiledevice-1.0 +Cflags: -I${includedir} +Requires: libplist-2.0 >= @LIBPLIST_VERSION@ +Requires.private: libusbmuxd-2.0 >= @LIBUSBMUXD_VERSION@ libimobiledevice-glue-1.0 >= @LIMD_GLUE_VERSION@ @ssl_requires@ diff --git a/src/lockdown-cu.c b/src/lockdown-cu.c new file mode 100644 index 0000000..1afc2c5 --- /dev/null +++ b/src/lockdown-cu.c @@ -0,0 +1,1193 @@ +/* + * lockdown-cu.c + * com.apple.mobile.lockdownd service CU additions + * + * 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <stdlib.h> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.h> +#include <ctype.h> +#include <unistd.h> +#include <plist/plist.h> + +#include "idevice.h" +#include "lockdown.h" +#include "common/debug.h" + +#ifdef HAVE_WIRELESS_PAIRING + +#include <libimobiledevice-glue/utils.h> +#include <libimobiledevice-glue/socket.h> +#include <libimobiledevice-glue/opack.h> +#include <libimobiledevice-glue/tlv.h> + +#if defined(HAVE_OPENSSL) +#include <openssl/hmac.h> +#include <openssl/evp.h> +#include <openssl/rand.h> +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2030200fL) +#include <openssl/chacha.h> +#include <openssl/poly1305.h> +#endif +#elif defined(HAVE_GCRYPT) +#include <gcrypt.h> +#elif defined(HAVE_MBEDTLS) +#include <mbedtls/md.h> +#include <mbedtls/chachapoly.h> +#endif + +#ifdef __APPLE__ +#include <sys/sysctl.h> +#include <SystemConfiguration/SystemConfiguration.h> +#include <CoreFoundation/CoreFoundation.h> +#include <TargetConditionals.h> +#endif + +#include "property_list_service.h" +#include "common/userpref.h" + +#include "endianness.h" + +#include "srp.h" +#include "ed25519.h" + +/* {{{ SRP6a parameters */ +static const unsigned char kSRPModulus3072[384] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, + 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, + 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, + 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, + 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, + 0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, 0x1f, 0xe6, + 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, + 0x98, 0xda, 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, 0x69, 0x16, 0x3f, 0xa8, 0xfd, 0x24, 0xcf, 0x5f, + 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, 0xad, 0x96, 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, 0x67, 0x0c, 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, + 0xf1, 0x74, 0x6c, 0x08, 0xca, 0x18, 0x21, 0x7c, 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, 0xce, 0x3b, + 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, + 0xb5, 0xc5, 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, 0xde, 0x2b, 0xcb, 0xf6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, 0x6a, 0xe5, 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xaa, 0xc4, 0x2d, 0xad, 0x33, 0x17, 0x0d, 0x04, 0x50, 0x7a, 0x33, + 0xa8, 0x55, 0x21, 0xab, 0xdf, 0x1c, 0xba, 0x64, 0xec, 0xfb, 0x85, 0x04, 0x58, 0xdb, 0xef, 0x0a, + 0x8a, 0xea, 0x71, 0x57, 0x5d, 0x06, 0x0c, 0x7d, 0xb3, 0x97, 0x0f, 0x85, 0xa6, 0xe1, 0xe4, 0xc7, + 0xab, 0xf5, 0xae, 0x8c, 0xdb, 0x09, 0x33, 0xd7, 0x1e, 0x8c, 0x94, 0xe0, 0x4a, 0x25, 0x61, 0x9d, + 0xce, 0xe3, 0xd2, 0x26, 0x1a, 0xd2, 0xee, 0x6b, 0xf1, 0x2f, 0xfa, 0x06, 0xd9, 0x8a, 0x08, 0x64, + 0xd8, 0x76, 0x02, 0x73, 0x3e, 0xc8, 0x6a, 0x64, 0x52, 0x1f, 0x2b, 0x18, 0x17, 0x7b, 0x20, 0x0c, + 0xbb, 0xe1, 0x17, 0x57, 0x7a, 0x61, 0x5d, 0x6c, 0x77, 0x09, 0x88, 0xc0, 0xba, 0xd9, 0x46, 0xe2, + 0x08, 0xe2, 0x4f, 0xa0, 0x74, 0xe5, 0xab, 0x31, 0x43, 0xdb, 0x5b, 0xfc, 0xe0, 0xfd, 0x10, 0x8e, + 0x4b, 0x82, 0xd1, 0x20, 0xa9, 0x3a, 0xd2, 0xca, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, +}; + +static const unsigned char kSRPGenerator5 = 5; +/* }}} */ + +/* {{{ HKDF */ +#if defined(HAVE_OPENSSL) +#define MD_ALGO_SHA512 EVP_sha512() +typedef const EVP_MD* MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE EVP_MD_size +#define MD_MAX_DIGEST_SIZE EVP_MAX_MD_SIZE + +#elif defined(HAVE_GCRYPT) +#define MD_ALGO_SHA512 GCRY_MD_SHA512 +typedef int MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE gcry_md_get_algo_dlen +#define MD_MAX_DIGEST_SIZE 64 + +static void HMAC(MD_ALGO_TYPE_T md, unsigned char* key, unsigned int key_len, unsigned char* data, unsigned int data_len, unsigned char* out, unsigned int* out_len) +{ + gcry_md_hd_t hd; + if (gcry_md_open(&hd, md, GCRY_MD_FLAG_HMAC)) { + debug_info("gcry_md_open() failed"); + return; + } + if (gcry_md_setkey(hd, key, key_len)) { + gcry_md_close (hd); + debug_info("gcry_md_setkey() failed"); + return; + } + gcry_md_write(hd, data, data_len); + + unsigned char* digest = gcry_md_read(hd, md); + if (!digest) { + gcry_md_close(hd); + debug_info("gcry_md_read() failed"); + return; + } + + *out_len = gcry_md_get_algo_dlen(md); + memcpy(out, digest, *out_len); + gcry_md_close(hd); +} +#elif defined(HAVE_MBEDTLS) +#define MD_ALGO_SHA512 MBEDTLS_MD_SHA512 +typedef mbedtls_md_type_t MD_ALGO_TYPE_T; +#define MD_ALGO_DIGEST_SIZE(x) mbedtls_md_get_size(mbedtls_md_info_from_type(x)) +#define MD_MAX_DIGEST_SIZE MBEDTLS_MD_MAX_SIZE + +static void HMAC(MD_ALGO_TYPE_T md, unsigned char* key, unsigned int key_len, unsigned char* data, unsigned int data_len, unsigned char* out, unsigned int* out_len) +{ + mbedtls_md_context_t mdctx; + mbedtls_md_init(&mdctx); + int mr = mbedtls_md_setup(&mdctx, mbedtls_md_info_from_type(md), 1); + if (mr != 0) { + debug_info("mbedtls_md_setup() failed: %d", mr); + return; + } + + mr = mbedtls_md_hmac_starts(&mdctx, key, key_len); + if (mr != 0) { + mbedtls_md_free(&mdctx); + debug_info("mbedtls_md_hmac_starts() failed: %d", mr); + return; + } + + mbedtls_md_hmac_update(&mdctx, data, data_len); + + mr = mbedtls_md_hmac_finish(&mdctx, out); + if (mr == 0) { + *out_len = mbedtls_md_get_size(mbedtls_md_info_from_type(md)); + } else { + debug_info("mbedtls_md_hmac_finish() failed: %d", mr); + } + mbedtls_md_free(&mdctx); +} +#endif + +static void hkdf_md_extract(MD_ALGO_TYPE_T md, unsigned char* salt, unsigned int salt_len, unsigned char* input_key_material, unsigned int input_key_material_len, unsigned char* out, unsigned int* out_len) +{ + unsigned char empty_salt[MD_MAX_DIGEST_SIZE]; + if (!md || !out || !out_len || !*out_len) return; + if (salt_len == 0) { + salt_len = MD_ALGO_DIGEST_SIZE(md); + salt = (unsigned char*)empty_salt; + } + HMAC(md, salt, salt_len, input_key_material, input_key_material_len, out, out_len); +} + +static void hkdf_md_expand(MD_ALGO_TYPE_T md, unsigned char* prk, unsigned int prk_len, unsigned char* info, unsigned int info_len, unsigned char* out, unsigned int* out_len) +{ + if (!md || !out || !out_len || !*out_len) return; + unsigned int md_size = MD_ALGO_DIGEST_SIZE(md); + if (*out_len > 255 * md_size) { + *out_len = 0; + return; + } + int blocks_needed = (*out_len) / md_size; + if (((*out_len) % md_size) != 0) blocks_needed++; + unsigned int okm_len = 0; + unsigned char okm_block[MD_MAX_DIGEST_SIZE]; + unsigned int okm_block_len = 0; + int i; + for (i = 0; i < blocks_needed; i++) { + unsigned int output_block_len = okm_block_len + info_len + 1; + unsigned char* output_block = malloc(output_block_len); + if (okm_block_len > 0) { + memcpy(output_block, okm_block, okm_block_len); + } + memcpy(output_block + okm_block_len, info, info_len); + output_block[okm_block_len + info_len] = (uint8_t)(i+1); + + HMAC(md, prk, prk_len, output_block, output_block_len, okm_block, &okm_block_len); + if (okm_len < *out_len) { + memcpy(out + okm_len, okm_block, (okm_len + okm_block_len > *out_len) ? *out_len - okm_len : okm_block_len); + } + okm_len += okm_block_len; + free(output_block); + } +} + +static void hkdf_md(MD_ALGO_TYPE_T md, unsigned char* salt, unsigned int salt_len, unsigned char* info, unsigned int info_len, unsigned char* initial_key_material, unsigned int initial_key_material_size, unsigned char* out, unsigned int *out_len) +{ + if (!md || !initial_key_material || !out || !out_len || !*out_len) return; + + unsigned char prk[MD_MAX_DIGEST_SIZE]; + unsigned int prk_len = MD_ALGO_DIGEST_SIZE(md); + + hkdf_md_extract(md, salt, salt_len, initial_key_material, initial_key_material_size, prk, &prk_len); + if (prk_len > 0) { + hkdf_md_expand(md, prk, prk_len, info, info_len, out, out_len); + } else { + *out_len = 0; + } +} +/* }}} */ + +/* {{{ chacha20 poly1305 encryption/decryption */ +#if defined(HAVE_OPENSSL) && defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x2030200fL) +/* {{{ From: OpenBSD's e_chacha20poly1305.c */ +/* + * Copyright (c) 2015 Reyk Floter <reyk@openbsd.org> + * Copyright (c) 2014, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +static void +poly1305_update_with_length(poly1305_state *poly1305, + const unsigned char *data, size_t data_len) +{ + size_t j = data_len; + unsigned char length_bytes[8]; + unsigned i; + + for (i = 0; i < sizeof(length_bytes); i++) { + length_bytes[i] = j; + j >>= 8; + } + + if (data != NULL) + CRYPTO_poly1305_update(poly1305, data, data_len); + CRYPTO_poly1305_update(poly1305, length_bytes, sizeof(length_bytes)); +} + +static void +poly1305_update_with_pad16(poly1305_state *poly1305, + const unsigned char *data, size_t data_len) +{ + static const unsigned char zero_pad16[16]; + size_t pad_len; + + CRYPTO_poly1305_update(poly1305, data, data_len); + + /* pad16() is defined in RFC 7539 2.8.1. */ + if ((pad_len = data_len % 16) == 0) + return; + + CRYPTO_poly1305_update(poly1305, zero_pad16, 16 - pad_len); +} +/* }}} */ +#endif + +static void chacha20_poly1305_encrypt_96(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ +#if defined(HAVE_OPENSSL) +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x2040000fL) + const EVP_AEAD *aead = EVP_aead_chacha20_poly1305(); + EVP_AEAD_CTX ctx; + EVP_AEAD_CTX_init(&ctx, aead, key, EVP_AEAD_key_length(aead), EVP_AEAD_DEFAULT_TAG_LENGTH, NULL); + EVP_AEAD_CTX_seal(&ctx, out, out_len, *out_len, nonce, 12, in, in_len, ad, ad_len); +#else + unsigned char poly1305_key[32]; + poly1305_state poly1305; + uint64_t ctr = (uint64_t)(nonce[0] | nonce[1] << 8 | nonce[2] << 16 | nonce[3] << 24) << 32; + const unsigned char* iv = nonce + 4; + + memset(poly1305_key, 0, sizeof(poly1305_key)); + CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, iv, ctr); + + CRYPTO_poly1305_init(&poly1305, poly1305_key); + poly1305_update_with_pad16(&poly1305, ad, ad_len); + CRYPTO_chacha_20(out, in, in_len, key, iv, ctr + 1); + poly1305_update_with_pad16(&poly1305, out, in_len); + poly1305_update_with_length(&poly1305, NULL, ad_len); + poly1305_update_with_length(&poly1305, NULL, in_len); + + CRYPTO_poly1305_finish(&poly1305, out + in_len); + + *out_len = in_len + 16; +#endif +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L) + int outl = 0; + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_EncryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce); + EVP_EncryptUpdate(ctx, out, &outl, in, in_len); + *out_len = outl; + outl = 0; + EVP_EncryptFinal_ex(ctx, out + *out_len, &outl); + *out_len += outl; + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, 16, out + *out_len); + EVP_CIPHER_CTX_free(ctx); + *out_len += 16; +#else +#error Please use a newer version of OpenSSL (>= 1.1.0) +#endif +#elif defined(HAVE_GCRYPT) +#if defined(GCRYPT_VERSION_NUMBER) && (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_cipher_hd_t hd; + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0)) { + debug_info("gcry_cipher_open() failed"); + return; + } + gcry_cipher_setkey(hd, key, 32); + gcry_cipher_setiv(hd, nonce, 12); + gcry_cipher_authenticate(hd, ad, ad_len); + *out_len = in_len + 16; + if (gcry_cipher_encrypt(hd, out, *out_len, in, in_len)) { + *out_len = 0; + } + gcry_cipher_gettag(hd, out+in_len, 16); + gcry_cipher_close(hd); +#else +#error Please use a newer version of libgcrypt (>= 1.7.0) +#endif +#elif defined (HAVE_MBEDTLS) + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + mbedtls_chachapoly_setkey(&ctx, key); + if (mbedtls_chachapoly_encrypt_and_tag(&ctx, in_len, nonce, ad, ad_len, in, out, out+in_len) != 0) { + *out_len = 0; + } + mbedtls_chachapoly_free(&ctx); +#else +#error chacha20_poly1305_encrypt_96 is not implemented +#endif +} + +static void chacha20_poly1305_encrypt_64(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ + unsigned char _nonce[12]; + *(uint32_t*)(&_nonce[0]) = 0; + memcpy(&_nonce[4], nonce, 8); + chacha20_poly1305_encrypt_96(key, _nonce, ad, ad_len, in, in_len, out, out_len); +} + +static void chacha20_poly1305_decrypt_96(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ +#if defined(HAVE_OPENSSL) +#if defined(LIBRESSL_VERSION_NUMBER) && (LIBRESSL_VERSION_NUMBER < 0x3050000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x2040000fL) + const EVP_AEAD *aead = EVP_aead_chacha20_poly1305(); + EVP_AEAD_CTX ctx; + EVP_AEAD_CTX_init(&ctx, aead, key, EVP_AEAD_key_length(aead), EVP_AEAD_DEFAULT_TAG_LENGTH, NULL); + EVP_AEAD_CTX_open(&ctx, out, out_len, *out_len, nonce, 12, in, in_len, ad, ad_len); +#else + unsigned char mac[16]; + unsigned char poly1305_key[32]; + poly1305_state poly1305; + size_t plaintext_len = in_len - 16; + uint64_t ctr = (uint64_t)(nonce[0] | nonce[1] << 8 | nonce[2] << 16 | nonce[3] << 24) << 32; + const unsigned char *iv = nonce + 4; + + memset(poly1305_key, 0, sizeof(poly1305_key)); + CRYPTO_chacha_20(poly1305_key, poly1305_key, sizeof(poly1305_key), key, iv, ctr); + + CRYPTO_poly1305_init(&poly1305, poly1305_key); + poly1305_update_with_pad16(&poly1305, ad, ad_len); + poly1305_update_with_pad16(&poly1305, in, plaintext_len); + poly1305_update_with_length(&poly1305, NULL, ad_len); + poly1305_update_with_length(&poly1305, NULL, plaintext_len); + + CRYPTO_poly1305_finish(&poly1305, mac); + + if (memcmp(mac, in + plaintext_len, 16) != 0) { + *out_len = 0; + return; + } + + CRYPTO_chacha_20(out, in, plaintext_len, key, iv, ctr + 1); + *out_len = plaintext_len; +#endif +#elif defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x10100000L) + int outl = 0; + size_t plaintext_len = in_len - 16; + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); + EVP_DecryptInit_ex(ctx, EVP_chacha20_poly1305(), NULL, key, nonce); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, 16, in + plaintext_len); + EVP_DecryptUpdate(ctx, out, &outl, in, plaintext_len); + *out_len = outl; + outl = 0; + if (EVP_DecryptFinal_ex(ctx, out + *out_len, &outl) == 1) { + *out_len += outl; + } else { + *out_len = 0; + } + EVP_CIPHER_CTX_free(ctx); +#else +#error Please use a newer version of OpenSSL (>= 1.1.0) +#endif +#elif defined(HAVE_GCRYPT) +#if defined(GCRYPT_VERSION_NUMBER) && (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_cipher_hd_t hd; + if (gcry_cipher_open(&hd, GCRY_CIPHER_CHACHA20, GCRY_CIPHER_MODE_POLY1305, 0)) { + debug_info("gcry_cipher_open() failed"); + return; + } + gcry_cipher_setkey(hd, key, 32); + gcry_cipher_setiv(hd, nonce, 12); + gcry_cipher_authenticate(hd, ad, ad_len); + unsigned int plaintext_len = in_len - 16; + gcry_cipher_decrypt(hd, out, *out_len, in, plaintext_len); + if (gcry_cipher_checktag(hd, in + plaintext_len, 16) == 0) { + *out_len = plaintext_len; + } else { + *out_len = 0; + } + gcry_cipher_close(hd); +#else +#error Please use a newer version of libgcrypt (>= 1.7.0) +#endif +#elif defined(HAVE_MBEDTLS) + mbedtls_chachapoly_context ctx; + mbedtls_chachapoly_init(&ctx); + mbedtls_chachapoly_setkey(&ctx, key); + unsigned int plaintext_len = in_len - 16; + if (mbedtls_chachapoly_auth_decrypt(&ctx, plaintext_len, nonce, ad, ad_len, in + plaintext_len, in, out) == 0) { + *out_len = plaintext_len; + } else { + *out_len = 0; + } + mbedtls_chachapoly_free(&ctx); +#else +#error chacha20_poly1305_decrypt_96 is not implemented +#endif +} + +static void chacha20_poly1305_decrypt_64(unsigned char* key, unsigned char* nonce, unsigned char* ad, size_t ad_len, unsigned char* in, size_t in_len, unsigned char* out, size_t* out_len) +{ + unsigned char _nonce[12]; + *(uint32_t*)(&_nonce[0]) = 0; + memcpy(&_nonce[4], nonce, 8); + chacha20_poly1305_decrypt_96(key, _nonce, ad, ad_len, in, in_len, out, out_len); +} +/* }}} */ + +#define PAIRING_ERROR(x) \ + debug_info(x); \ + if (pairing_callback) { \ + pairing_callback(LOCKDOWN_CU_PAIRING_ERROR, cb_user_data, (char*)x, NULL); \ + } + +#define PAIRING_ERROR_FMT(...) \ + sprintf(tmp, __VA_ARGS__); \ + debug_info(tmp); \ + if (pairing_callback) { \ + pairing_callback(LOCKDOWN_CU_PAIRING_ERROR, cb_user_data, tmp, NULL); \ + } + +#endif /* HAVE_WIRELESS_PAIRING */ + +lockdownd_error_t lockdownd_cu_pairing_create(lockdownd_client_t client, lockdownd_cu_pairing_cb_t pairing_callback, void* cb_user_data, plist_t host_info, plist_t acl) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client || !pairing_callback || (host_info && plist_get_node_type(host_info) != PLIST_DICT) || (acl && plist_get_node_type(acl) != PLIST_DICT)) + return LOCKDOWN_E_INVALID_ARG; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + if (client->device && client->device->version == 0) { + plist_t p_version = NULL; + if (lockdownd_get_value(client, NULL, "ProductVersion", &p_version) == LOCKDOWN_E_SUCCESS) { + int vers[3] = {0, 0, 0}; + char *s_version = NULL; + plist_get_string_val(p_version, &s_version); + if (s_version && sscanf(s_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { + client->device->version = DEVICE_VERSION(vers[0], vers[1], vers[2]); + } + free(s_version); + } + plist_free(p_version); + } + + char* pairing_uuid = NULL; + if (host_info) { + plist_t accountid = plist_dict_get_item(host_info, "accountID"); + if (accountid && plist_get_node_type(accountid) == PLIST_STRING) { + plist_get_string_val(accountid, &pairing_uuid); + } + } + if (!pairing_uuid) { + userpref_read_system_buid(&pairing_uuid); + } + if (!pairing_uuid) { + pairing_uuid = generate_uuid(); + } + unsigned int pairing_uuid_len = strlen(pairing_uuid); + + SRP_initialize_library(); + + SRP* srp = SRP_new(SRP6a_sha512_client_method()); + if (!srp) { + PAIRING_ERROR("Failed to initialize SRP") + return LOCKDOWN_E_UNKNOWN_ERROR; + } + + char tmp[256]; + plist_t dict = NULL; + uint8_t current_state = 0; + uint8_t final_state = 6; + + unsigned char* salt = NULL; + unsigned int salt_size = 0; + unsigned char* pubkey = NULL; + unsigned int pubkey_size = 0; + + unsigned char setup_encryption_key[32]; + + cstr *thekey = NULL; + + do { + current_state++; + + dict = plist_new_dict(); + plist_dict_set_item(dict, "Request", plist_new_string("CUPairingCreate")); + if (current_state == 1) { + plist_dict_set_item(dict, "Flags", plist_new_uint(1)); + } else { + plist_dict_set_item(dict, "Flags", plist_new_uint(0)); + } + + tlv_buf_t tlv = tlv_buf_new(); + + if (current_state == 1) { + /* send method */ + tlv_buf_append(tlv, 0x00, 1, (void*)"\x00"); // 0x00 (Method), 1 bytes, 00 + } else if (current_state == 3) { + /* generate public key */ + cstr* own_pub = NULL; + SRP_gen_pub(srp, &own_pub); + + if (!own_pub) { + PAIRING_ERROR("[SRP] Failed to generate public key") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* compute key from remote's public key */ + if (SRP_compute_key(srp, &thekey, pubkey, pubkey_size) != 0) { + cstr_free(own_pub); + PAIRING_ERROR("[SRP] Failed to compute key") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* compute response */ + cstr *response = NULL; + SRP_respond(srp, &response); + + /* send our public key + response */ + tlv_buf_append(tlv, 0x03, own_pub->length, own_pub->data); + tlv_buf_append(tlv, 0x04, response->length, response->data); + cstr_free(response); + cstr_free(own_pub); + } else if (current_state == 5) { + /* send encrypted info */ + + static const char PAIR_SETUP_ENCRYPT_SALT[] = "Pair-Setup-Encrypt-Salt"; + static const char PAIR_SETUP_ENCRYPT_INFO[] = "Pair-Setup-Encrypt-Info"; + static const char PAIR_SETUP_CONTROLLER_SIGN_SALT[] = "Pair-Setup-Controller-Sign-Salt"; + static const char PAIR_SETUP_CONTROLLER_SIGN_INFO[] = "Pair-Setup-Controller-Sign-Info"; + + // HKDF with above computed key (SRP_compute_key) + Pair-Setup-Encrypt-Salt + Pair-Setup-Encrypt-Info + // result used as key for chacha20-poly1305 + unsigned int setup_encryption_key_len = sizeof(setup_encryption_key); + hkdf_md(MD_ALGO_SHA512, (unsigned char*)PAIR_SETUP_ENCRYPT_SALT, sizeof(PAIR_SETUP_ENCRYPT_SALT)-1, (unsigned char*)PAIR_SETUP_ENCRYPT_INFO, sizeof(PAIR_SETUP_ENCRYPT_INFO)-1, (unsigned char*)thekey->data, thekey->length, setup_encryption_key, &setup_encryption_key_len); + + unsigned char ed25519_pubkey[32]; + unsigned char ed25519_privkey[64]; + unsigned char ed25519seed[32]; + ed25519_create_seed(ed25519seed); + + ed25519_create_keypair(ed25519_pubkey, ed25519_privkey, ed25519seed); + + unsigned int signbuf_len = pairing_uuid_len + 64; + unsigned char* signbuf = malloc(signbuf_len); + unsigned int hkdf_len = 32; + // HKDF with above computed key (SRP_compute_key) + Pair-Setup-Controller-Sign-Salt + Pair-Setup-Controller-Sign-Info + hkdf_md(MD_ALGO_SHA512, (unsigned char*)PAIR_SETUP_CONTROLLER_SIGN_SALT, sizeof(PAIR_SETUP_CONTROLLER_SIGN_SALT)-1, (unsigned char*)PAIR_SETUP_CONTROLLER_SIGN_INFO, sizeof(PAIR_SETUP_CONTROLLER_SIGN_INFO)-1, (unsigned char*)thekey->data, thekey->length, signbuf, &hkdf_len); + + memcpy(signbuf + 32, pairing_uuid, pairing_uuid_len); + memcpy(signbuf + 32 + pairing_uuid_len, ed25519_pubkey, 32); + + unsigned char ed_sig[64]; + ed25519_sign(ed_sig, signbuf, 0x64, ed25519_pubkey, ed25519_privkey); + + tlv_buf_t tlvbuf = tlv_buf_new(); + tlv_buf_append(tlvbuf, 0x01, pairing_uuid_len, (void*)pairing_uuid); + tlv_buf_append(tlvbuf, 0x03, sizeof(ed25519_pubkey), ed25519_pubkey); + tlv_buf_append(tlvbuf, 0x0a, sizeof(ed_sig), ed_sig); + + /* ACL */ + unsigned char* odata = NULL; + unsigned int olen = 0; + if (acl) { + opack_encode_from_plist(acl, &odata, &olen); + } else { + /* defaut ACL */ + plist_t acl_plist = plist_new_dict(); + plist_dict_set_item(acl_plist, "com.apple.ScreenCapture", plist_new_bool(1)); + plist_dict_set_item(acl_plist, "com.apple.developer", plist_new_bool(1)); + opack_encode_from_plist(acl_plist, &odata, &olen); + plist_free(acl_plist); + } + tlv_buf_append(tlvbuf, 0x12, olen, odata); + free(odata); + + /* HOST INFORMATION */ + char hostname[256]; +#if defined(__APPLE__) && !defined(TARGET_OS_IPHONE) + CFStringRef cname = SCDynamicStoreCopyComputerName(NULL, NULL); + CFStringGetCString(cname, hostname, sizeof(hostname), kCFStringEncodingUTF8); + CFRelease(cname); +#else +#ifdef WIN32 + DWORD hostname_len = sizeof(hostname); + GetComputerName(hostname, &hostname_len); +#else + gethostname(hostname, sizeof(hostname)); +#endif +#endif + + char modelname[256]; + modelname[0] = '\0'; +#ifdef __APPLE__ + size_t len = sizeof(modelname); + sysctlbyname("hw.model", &modelname, &len, NULL, 0); +#endif + if (strlen(modelname) == 0) { + strcpy(modelname, "HackbookPro13,37"); + } + + unsigned char primary_mac_addr[6] = { 0, 0, 0, 0, 0, 0 }; + if (get_primary_mac_address(primary_mac_addr) != 0) { + debug_info("Failed to get primary mac address"); + } + debug_info("Primary mac address: %02x:%02x:%02x:%02x:%02x:%02x\n", primary_mac_addr[0], primary_mac_addr[1], primary_mac_addr[2], primary_mac_addr[3], primary_mac_addr[4], primary_mac_addr[5]); + + // "OPACK" encoded device info + plist_t info_plist = plist_new_dict(); + //plist_dict_set_item(info_plist, "altIRK", plist_new_data((char*)altIRK, 16)); + plist_dict_set_item(info_plist, "accountID", plist_new_string(pairing_uuid)); + plist_dict_set_item(info_plist, "model", plist_new_string(modelname)); + plist_dict_set_item(info_plist, "name", plist_new_string(hostname)); + plist_dict_set_item(info_plist, "mac", plist_new_data((char*)primary_mac_addr, 6)); + if (host_info) { + plist_dict_merge(&info_plist, host_info); + } + opack_encode_from_plist(info_plist, &odata, &olen); + plist_free(info_plist); + tlv_buf_append(tlvbuf, 0x11, olen, odata); + free(odata); + + size_t encrypted_len = tlvbuf->length + 16; + unsigned char* encrypted_buf = (unsigned char*)malloc(encrypted_len); + + chacha20_poly1305_encrypt_64(setup_encryption_key, (unsigned char*)"PS-Msg05", NULL, 0, tlvbuf->data, tlvbuf->length, encrypted_buf, &encrypted_len); + + tlv_buf_free(tlvbuf); + + tlv_buf_append(tlv, 0x05, encrypted_len, encrypted_buf); + free(encrypted_buf); + } else { + tlv_buf_free(tlv); + PAIRING_ERROR("[SRP] Invalid state"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + tlv_buf_append(tlv, 0x06, 1, ¤t_state); + plist_dict_set_item(dict, "Payload", plist_new_data((char*)tlv->data, tlv->length)); + tlv_buf_free(tlv); + + plist_dict_set_item(dict, "Label", plist_new_string(client->label)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_uint(2)); + + ret = lockdownd_send(client, dict); + plist_free(dict); + dict = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + + current_state++; + + ret = lockdownd_receive(client, &dict); + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + ret = lockdown_check_result(dict, "CUPairingCreate"); + if (ret != LOCKDOWN_E_SUCCESS) { + break; + } + + plist_t extresp = plist_dict_get_item(dict, "ExtendedResponse"); + if (!extresp) { + ret = LOCKDOWN_E_PLIST_ERROR; + break; + } + plist_t blob = plist_dict_get_item(extresp, "Payload"); + if (!blob) { + ret = LOCKDOWN_E_PLIST_ERROR; + break; + } + uint64_t data_len = 0; + const char* data = plist_get_data_ptr(blob, &data_len); + + uint8_t state = 0; + if (!tlv_data_get_uint8(data, data_len, 0x06, &state)) { + PAIRING_ERROR("[SRP] ERROR: Could not find state in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + if (state != current_state) { + PAIRING_ERROR_FMT("[SRP] ERROR: Unexpected state %d, expected %d", state, current_state); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + unsigned int errval = 0; + uint64_t u64val = 0; + tlv_data_get_uint(data, data_len, 0x07, &u64val); +debug_buffer(data, data_len); + errval = (unsigned int)u64val; + if (errval > 0) { + if (errval == 3) { + u64val = 0; + tlv_data_get_uint(data, data_len, 0x08, &u64val); + if (u64val > 0) { + uint32_t retry_delay = (uint32_t)u64val; + PAIRING_ERROR_FMT("[SRP] Pairing is blocked for another %u seconds", retry_delay) + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + } else if (errval == 2 && state == 4) { + PAIRING_ERROR_FMT("[SRP] Invalid PIN") + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } else { + PAIRING_ERROR_FMT("[SRP] Received error %u in state %d.", errval, state); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + } + + if (state == 2) { + /* receive salt and public key */ + if (!tlv_data_copy_data(data, data_len, 0x02, (void**)&salt, &salt_size)) { + PAIRING_ERROR("[SRP] ERROR: Could not find salt in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + if (!tlv_data_copy_data(data, data_len, 0x03, (void**)&pubkey, &pubkey_size)) { + PAIRING_ERROR("[SRP] ERROR: Could not find public key in response"); + + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + const char PAIR_SETUP[] = "Pair-Setup"; + if (SRP_set_user_raw(srp, (const unsigned char*)PAIR_SETUP, sizeof(PAIR_SETUP)-1) != 0) { + PAIRING_ERROR("[SRP] Failed to set SRP user"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* kSRPParameters_3072_SHA512 */ + if (SRP_set_params(srp, kSRPModulus3072, sizeof(kSRPModulus3072), &kSRPGenerator5, 1, salt, salt_size) != 0) { + PAIRING_ERROR("[SRP] Failed to set SRP parameters"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + + } + + if (pairing_callback) { + char pin[64]; + unsigned int pin_len = sizeof(pin); + pairing_callback(LOCKDOWN_CU_PAIRING_PIN_REQUESTED, cb_user_data, pin, &pin_len); + + SRP_set_auth_password_raw(srp, (const unsigned char*)pin, pin_len); + } + } else if (state == 4) { + /* receive proof */ + unsigned char* proof = NULL; + unsigned int proof_len = 0; + + if (!tlv_data_copy_data(data, data_len, 0x04, (void**)&proof, &proof_len)) { + PAIRING_ERROR("[SRP] ERROR: Could not find proof data in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* verify */ + int vrfy_result = SRP_verify(srp, proof, proof_len); + free(proof); + + if (vrfy_result == 0) { + debug_info("[SRP] PIN verified successfully"); + } else { + PAIRING_ERROR("[SRP] PIN verification failure"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + } else if (state == 6) { + int srp_pair_success = 0; + plist_t node = plist_dict_get_item(extresp, "doSRPPair"); + if (node) { + const char* strv = plist_get_string_ptr(node, NULL); + if (strcmp(strv, "succeed") == 0) { + srp_pair_success = 1; + } + } + if (!srp_pair_success) { + PAIRING_ERROR("SRP Pairing failed"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + /* receive encrypted info */ + unsigned char* encrypted_buf = NULL; + unsigned int enc_len = 0; + if (!tlv_data_copy_data(data, data_len, 0x05, (void**)&encrypted_buf, &enc_len)) { + PAIRING_ERROR("[SRP] ERROR: Could not find encrypted data in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + size_t plain_len = enc_len-16; + unsigned char* plain_buf = malloc(plain_len); + chacha20_poly1305_decrypt_64(setup_encryption_key, (unsigned char*)"PS-Msg06", NULL, 0, encrypted_buf, enc_len, plain_buf, &plain_len); + free(encrypted_buf); + + unsigned char* dev_info = NULL; + unsigned int dev_info_len = 0; + int res = tlv_data_copy_data(plain_buf, plain_len, 0x11, (void**)&dev_info, &dev_info_len); + free(plain_buf); + if (!res) { + PAIRING_ERROR("[SRP] ERROR: Failed to locate device info in response"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + plist_t device_info = NULL; + opack_decode_to_plist(dev_info, dev_info_len, &device_info); + free(dev_info); + + if (!device_info) { + PAIRING_ERROR("[SRP] ERROR: Failed to parse device info"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + + if (pairing_callback) { + pairing_callback(LOCKDOWN_CU_PAIRING_DEVICE_INFO, cb_user_data, device_info, NULL); + } + plist_free(device_info); + } else { + PAIRING_ERROR("[SRP] ERROR: Invalid state"); + ret = LOCKDOWN_E_PAIRING_FAILED; + break; + } + plist_free(dict); + dict = NULL; + + } while (current_state != final_state); + + plist_free(dict); + + free(salt); + free(pubkey); + + SRP_free(srp); + srp = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) { + if (thekey) { + cstr_free(thekey); + } + return ret; + } + + free(client->cu_key); + client->cu_key = malloc(thekey->length); + memcpy(client->cu_key, thekey->data, thekey->length); + client->cu_key_len = thekey->length; + cstr_free(thekey); + + return LOCKDOWN_E_SUCCESS; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_cu_send_request_and_get_reply(lockdownd_client_t client, const char* request, plist_t request_payload, plist_t* reply) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client || !request) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + /* derive keys */ + unsigned char cu_write_key[32]; + unsigned int cu_write_key_len = sizeof(cu_write_key); + static const char WRITE_KEY_SALT_MDLD[] = "WriteKeySaltMDLD"; + static const char WRITE_KEY_INFO_MDLD[] = "WriteKeyInfoMDLD"; + hkdf_md(MD_ALGO_SHA512, (unsigned char*)WRITE_KEY_SALT_MDLD, sizeof(WRITE_KEY_SALT_MDLD)-1, (unsigned char*)WRITE_KEY_INFO_MDLD, sizeof(WRITE_KEY_INFO_MDLD)-1, client->cu_key, client->cu_key_len, cu_write_key, &cu_write_key_len); + + unsigned char cu_read_key[32]; + unsigned int cu_read_key_len = sizeof(cu_write_key); + static const char READ_KEY_SALT_MDLD[] = "ReadKeySaltMDLD"; + static const char READ_KEY_INFO_MDLD[] = "ReadKeyInfoMDLD"; + hkdf_md(MD_ALGO_SHA512, (unsigned char*)READ_KEY_SALT_MDLD, sizeof(READ_KEY_SALT_MDLD)-1, (unsigned char*)READ_KEY_INFO_MDLD, sizeof(READ_KEY_INFO_MDLD)-1, client->cu_key, client->cu_key_len, cu_read_key, &cu_read_key_len); + + // Starting with iOS/tvOS 11.2 and WatchOS 4.2, this nonce is random and sent along with the request. Before, the request doesn't have a nonce and it uses hardcoded nonce "sendone01234". + unsigned char cu_nonce[12] = "sendone01234"; // guaranteed to be random by fair dice troll + if (client->device->version >= DEVICE_VERSION(11,2,0)) { +#if defined(HAVE_OPENSSL) + RAND_bytes(cu_nonce, sizeof(cu_nonce)); +#elif defined(HAVE_GCRYPT) + gcry_create_nonce(cu_nonce, sizeof(cu_nonce)); +#endif + } + + debug_plist(request_payload); + + /* convert request payload to binary */ + uint32_t bin_len = 0; + char* bin = NULL; + plist_to_bin(request_payload, &bin, &bin_len); + + /* encrypt request */ + size_t encrypted_len = bin_len + 16; + unsigned char* encrypted_buf = malloc(encrypted_len); + chacha20_poly1305_encrypt_96(cu_write_key, cu_nonce, NULL, 0, (unsigned char*)bin, bin_len, encrypted_buf, &encrypted_len); + free(bin); + bin = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict,"Request", plist_new_string(request)); + plist_dict_set_item(dict, "Payload", plist_new_data((char*)encrypted_buf, encrypted_len)); + free(encrypted_buf); + plist_dict_set_item(dict, "Nonce", plist_new_data((char*)cu_nonce, sizeof(cu_nonce))); + plist_dict_set_item(dict, "Label", plist_new_string(client->label)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_uint(2)); + + /* send to device */ + ret = lockdownd_send(client, dict); + plist_free(dict); + dict = NULL; + + if (ret != LOCKDOWN_E_SUCCESS) + return ret; + + /* Now get device's answer */ + ret = lockdownd_receive(client, &dict); + if (ret != LOCKDOWN_E_SUCCESS) + return ret; + + ret = lockdown_check_result(dict, request); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(dict); + return ret; + } + + /* get payload */ + plist_t blob = plist_dict_get_item(dict, "Payload"); + if (!blob) { + plist_free(dict); + return LOCKDOWN_E_DICT_ERROR; + } + + uint64_t dl = 0; + const char* dt = plist_get_data_ptr(blob, &dl); + + /* see if we have a nonce */ + blob = plist_dict_get_item(dict, "Nonce"); + const unsigned char* rnonce = (unsigned char*)"receiveone01"; + if (blob) { + uint64_t rl = 0; + rnonce = (const unsigned char*)plist_get_data_ptr(blob, &rl); + } + + /* decrypt payload */ + size_t decrypted_len = dl-16; + unsigned char* decrypted = malloc(decrypted_len); + chacha20_poly1305_decrypt_96(cu_read_key, (unsigned char*)rnonce, NULL, 0, (unsigned char*)dt, dl, decrypted, &decrypted_len); + plist_free(dict); + dict = NULL; + + plist_from_memory((const char*)decrypted, decrypted_len, &dict, NULL); + if (!dict) { + ret = LOCKDOWN_E_PLIST_ERROR; + debug_info("Failed to parse PLIST from decrypted payload:"); + debug_buffer((const char*)decrypted, decrypted_len); + free(decrypted); + return ret; + } + free(decrypted); + + debug_plist(dict); + + if (reply) { + *reply = dict; + } else { + plist_free(dict); + } + + return LOCKDOWN_E_SUCCESS; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_get_value_cu(lockdownd_client_t client, const char* domain, const char* key, plist_t* value) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + plist_t request = plist_new_dict(); + if (domain) { + plist_dict_set_item(request, "Domain", plist_new_string(domain)); + } + if (key) { + plist_dict_set_item(request, "Key", plist_new_string(key)); + } + + plist_t reply = NULL; + ret = lockdownd_cu_send_request_and_get_reply(client, "GetValueCU", request, &reply); + plist_free(request); + if (ret != LOCKDOWN_E_SUCCESS) { + return ret; + } + + plist_t value_node = plist_dict_get_item(reply, "Value"); + if (value_node) { + debug_info("has a value"); + *value = plist_copy(value_node); + } + plist_free(reply); + + return ret; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} + +lockdownd_error_t lockdownd_pair_cu(lockdownd_client_t client) +{ +#ifdef HAVE_WIRELESS_PAIRING + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + if (!client->cu_key) + return LOCKDOWN_E_NO_RUNNING_SESSION; + + lockdownd_error_t ret; + + plist_t wifi_mac = NULL; + ret = lockdownd_get_value_cu(client, NULL, "WiFiAddress", &wifi_mac); + if (ret != LOCKDOWN_E_SUCCESS) { + return ret; + } + + plist_t pubkey = NULL; + ret = lockdownd_get_value_cu(client, NULL, "DevicePublicKey", &pubkey); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(wifi_mac); + return ret; + } + + key_data_t public_key = { NULL, 0 }; + uint64_t data_len = 0; + plist_get_data_val(pubkey, (char**)&public_key.data, &data_len); + public_key.size = (unsigned int)data_len; + plist_free(pubkey); + + plist_t pair_record_plist = plist_new_dict(); + pair_record_generate_keys_and_certs(pair_record_plist, public_key); + + char* host_id = NULL; + char* system_buid = NULL; + + /* set SystemBUID */ + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(pair_record_plist, USERPREF_SYSTEM_BUID_KEY, plist_new_string(system_buid)); + free(system_buid); + } + + /* set HostID */ + host_id = generate_uuid(); + pair_record_set_host_id(pair_record_plist, host_id); + free(host_id); + + plist_t request_pair_record = plist_copy(pair_record_plist); + /* remove stuff that is private */ + plist_dict_remove_item(request_pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY); + plist_dict_remove_item(request_pair_record, USERPREF_HOST_PRIVATE_KEY_KEY); + + plist_t request = plist_new_dict(); + plist_dict_set_item(request, "PairRecord", request_pair_record); + plist_t pairing_opts = plist_new_dict(); + plist_dict_set_item(pairing_opts, "ExtendedPairingErrors", plist_new_bool(1)); + plist_dict_set_item(request, "PairingOptions", pairing_opts); + + plist_t reply = NULL; + ret = lockdownd_cu_send_request_and_get_reply(client, "PairCU", request, &reply); + plist_free(request); + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(wifi_mac); + return ret; + } + + char *s_udid = NULL; + plist_t p_udid = plist_dict_get_item(reply, "UDID"); + if (p_udid) { + plist_get_string_val(p_udid, &s_udid); + } + plist_t ebag = plist_dict_get_item(reply, "EscrowBag"); + if (ebag) { + plist_dict_set_item(pair_record_plist, USERPREF_ESCROW_BAG_KEY, plist_copy(ebag)); + } + plist_dict_set_item(pair_record_plist, USERPREF_WIFI_MAC_ADDRESS_KEY, wifi_mac); + plist_free(reply); + + if (userpref_save_pair_record(s_udid, 0, pair_record_plist) != 0) { + printf("Failed to save pair record for UDID %s\n", s_udid); + } + free(s_udid); + s_udid = NULL; + plist_free(pair_record_plist); + + ret = LOCKDOWN_E_SUCCESS; + + return ret; +#else + debug_info("not supported"); + return LOCKDOWN_E_UNKNOWN_ERROR; +#endif +} diff --git a/src/lockdown.c b/src/lockdown.c index 935f24e..256bff0 100644 --- a/src/lockdown.c +++ b/src/lockdown.c @@ -2,6 +2,8 @@ * lockdown.c * com.apple.mobile.lockdownd service implementation. * + * Copyright (c) 2009-2015 Martin Szulecki All Rights Reserved. + * Copyright (c) 2014-2015 Nikias Bassen All Rights Reserved. * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * @@ -20,36 +22,125 @@ * 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> #define _GNU_SOURCE 1 #define __USE_GNU 1 #include <stdio.h> #include <ctype.h> -#include <glib.h> -#include <libtasn1.h> -#include <gnutls/x509.h> +#include <unistd.h> #include <plist/plist.h> +#include <libimobiledevice-glue/utils.h> #include "property_list_service.h" #include "lockdown.h" #include "idevice.h" -#include "debug.h" -#include "userpref.h" - -#define RESULT_SUCCESS 0 -#define RESULT_FAILURE 1 - -const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = { - {"PKCS1", 536872976, 0}, - {0, 1073741836, 0}, - {"RSAPublicKey", 536870917, 0}, - {"modulus", 1073741827, 0}, - {"publicExponent", 3, 0}, - {0, 0, 0} +#include "common/debug.h" +#include "common/userpref.h" +#include "asprintf.h" + +#ifdef WIN32 +#include <windows.h> +#define sleep(x) Sleep(x*1000) +#endif + +struct st_lockdownd_error_str_map { + const char *lockdown_errstr; + const char *errstr; + lockdownd_error_t errcode; +}; + +static struct st_lockdownd_error_str_map lockdownd_error_str_map[] = { + { "InvalidResponse", "Invalid response", LOCKDOWN_E_INVALID_RESPONSE }, + { "MissingKey", "Missing key", LOCKDOWN_E_MISSING_KEY }, + { "MissingValue", "Missing value", LOCKDOWN_E_MISSING_VALUE }, + { "GetProhibited", "Get value prohibited", LOCKDOWN_E_GET_PROHIBITED }, + { "SetProhibited", "Set value prohibited", LOCKDOWN_E_SET_PROHIBITED }, + { "RemoveProhibited", "Remove value prohibited", LOCKDOWN_E_REMOVE_PROHIBITED }, + { "ImmutableValue", "Immutable value", LOCKDOWN_E_IMMUTABLE_VALUE }, + { "PasswordProtected", "Password protected", LOCKDOWN_E_PASSWORD_PROTECTED }, + { "UserDeniedPairing", "User denied pairing", LOCKDOWN_E_USER_DENIED_PAIRING }, + { "PairingDialogResponsePending", "Pairing dialog response pending", LOCKDOWN_E_PAIRING_DIALOG_RESPONSE_PENDING }, + { "MissingHostID", "Missing HostID", LOCKDOWN_E_MISSING_HOST_ID }, + { "InvalidHostID", "Invalid HostID", LOCKDOWN_E_INVALID_HOST_ID }, + { "SessionActive", "Session active", LOCKDOWN_E_SESSION_ACTIVE }, + { "SessionInactive", "Session inactive", LOCKDOWN_E_SESSION_INACTIVE }, + { "MissingSessionID", "Missing session ID", LOCKDOWN_E_MISSING_SESSION_ID }, + { "InvalidSessionID", "Invalid session ID", LOCKDOWN_E_INVALID_SESSION_ID }, + { "MissingService", "Missing service", LOCKDOWN_E_MISSING_SERVICE }, + { "InvalidService", "Invalid service", LOCKDOWN_E_INVALID_SERVICE }, + { "ServiceLimit", "Service limit reached", LOCKDOWN_E_SERVICE_LIMIT }, + { "MissingPairRecord", "Missing pair record", LOCKDOWN_E_MISSING_PAIR_RECORD }, + { "SavePairRecordFailed", "Saving pair record failed", LOCKDOWN_E_SAVE_PAIR_RECORD_FAILED }, + { "InvalidPairRecord", "Invalid pair record", LOCKDOWN_E_INVALID_PAIR_RECORD }, + { "InvalidActivationRecord", "Invalid activation record", LOCKDOWN_E_INVALID_ACTIVATION_RECORD }, + { "MissingActivationRecord", "Missing activation record", LOCKDOWN_E_MISSING_ACTIVATION_RECORD }, + { "ServiceProhibited", "Service prohibited", LOCKDOWN_E_SERVICE_PROHIBITED }, + { "EscrowLocked", "Escrow lockded", LOCKDOWN_E_ESCROW_LOCKED }, + { "PairingProhibitedOverThisConnection", "Pairing prohibited over this connection", LOCKDOWN_E_PAIRING_PROHIBITED_OVER_THIS_CONNECTION }, + { "FMiPProtected", "Find My iPhone/iPod/iPad protected", LOCKDOWN_E_FMIP_PROTECTED }, + { "MCProtected", "MC protected" , LOCKDOWN_E_MC_PROTECTED }, + { "MCChallengeRequired", "MC challenge required", LOCKDOWN_E_MC_CHALLENGE_REQUIRED }, + { NULL, NULL, 0 } }; /** + * Convert an error string identifier to a lockdownd_error_t value. + * Used internally to get correct error codes from a response. + * + * @param name The error name to convert. + * + * @return A matching lockdownd_error_t error code, + * LOCKDOWN_E_UNKNOWN_ERROR otherwise. + */ +static lockdownd_error_t lockdownd_strtoerr(const char* name) +{ + lockdownd_error_t err = LOCKDOWN_E_UNKNOWN_ERROR; + int i = 0; + while (lockdownd_error_str_map[i].lockdown_errstr) { + if (strcmp(lockdownd_error_str_map[i].lockdown_errstr, name) == 0) { + return lockdownd_error_str_map[i].errcode; + } + i++; + } + return err; +} + +/** + * Convert a property_list_service_error_t value to a lockdownd_error_t + * value. Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching lockdownd_error_t error code, + * LOCKDOWND_E_UNKNOWN_ERROR otherwise. + */ +static lockdownd_error_t lockdownd_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return LOCKDOWN_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return LOCKDOWN_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return LOCKDOWN_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return LOCKDOWN_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return LOCKDOWN_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return LOCKDOWN_E_RECEIVE_TIMEOUT; + default: + break; + } + return LOCKDOWN_E_UNKNOWN_ERROR; +} + +/** * Internally used function for checking the result from lockdown's answer * plist to a previously sent request. * @@ -57,58 +148,66 @@ const ASN1_ARRAY_TYPE pkcs1_asn1_tab[] = { * @param query_match Name of the request to match or NULL if no match is * required. * - * @return RESULT_SUCCESS when the result is 'Success', - * RESULT_FAILURE when the result is 'Failure', - * or a negative value if an error occured during evaluation. + * @return LOCKDOWN_E_SUCCESS when the result is 'Success', + * LOCKDOWN_E_UNKNOWN_ERROR when the result is 'Failure', + * or a specific error code if derieved from the result. */ -static int lockdown_check_result(plist_t dict, const char *query_match) +lockdownd_error_t lockdown_check_result(plist_t dict, const char *query_match) { - int ret = -1; + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t query_node = plist_dict_get_item(dict, "Request"); if (!query_node) { return ret; } + if (plist_get_node_type(query_node) != PLIST_STRING) { return ret; - } else { - char *query_value = NULL; - plist_get_string_val(query_node, &query_value); - if (!query_value) { - return ret; - } - if (query_match && (strcmp(query_value, query_match) != 0)) { - free(query_value); - return ret; - } - free(query_value); } - plist_t result_node = plist_dict_get_item(dict, "Result"); - if (!result_node) { + const char *query_value = plist_get_string_ptr(query_node, NULL); + if (!query_value) { return ret; } - plist_type result_type = plist_get_node_type(result_node); - - if (result_type == PLIST_STRING) { - - char *result_value = NULL; + if (query_match && (strcmp(query_value, query_match) != 0)) { + return ret; + } - plist_get_string_val(result_node, &result_value); + /* Check for 'Error' in reply */ + plist_t err_node = plist_dict_get_item(dict, "Error"); + if (err_node) { + if (plist_get_node_type(err_node) == PLIST_STRING) { + const char *err_value = plist_get_string_ptr(err_node, NULL); + if (err_value) { + debug_info("ERROR: %s", err_value); + ret = lockdownd_strtoerr(err_value); + } else { + debug_info("ERROR: unknown error occurred"); + } + } + return ret; + } + plist_t result_node = plist_dict_get_item(dict, "Result"); + if (!result_node) { + /* With iOS 5+ 'Result' is not present anymore. + If there is no 'Error', we can just assume success. */ + return LOCKDOWN_E_SUCCESS; + } + if (plist_get_node_type(result_node) == PLIST_STRING) { + const char *result_value = plist_get_string_ptr(result_node, NULL); if (result_value) { if (!strcmp(result_value, "Success")) { - ret = RESULT_SUCCESS; + ret = LOCKDOWN_E_SUCCESS; } else if (!strcmp(result_value, "Failure")) { - ret = RESULT_FAILURE; + ret = LOCKDOWN_E_UNKNOWN_ERROR; } else { debug_info("ERROR: unknown result value '%s'", result_value); } } - if (result_value) - free(result_value); } + return ret; } @@ -123,20 +222,10 @@ static void plist_dict_add_label(plist_t plist, const char *label) { if (plist && label) { if (plist_get_node_type(plist) == PLIST_DICT) - plist_dict_insert_item(plist, "Label", plist_new_string(label)); + plist_dict_set_item(plist, "Label", plist_new_string(label)); } } -/** - * Closes the lockdownd session by sending the StopSession request. - * - * @see lockdownd_start_session - * - * @param client The lockdown client - * @param session_id The id of a running session - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char *session_id) { if (!client) @@ -151,8 +240,8 @@ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char * plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StopSession")); - plist_dict_insert_item(dict,"SessionID", plist_new_string(session_id)); + plist_dict_set_item(dict,"Request", plist_new_string("StopSession")); + plist_dict_set_item(dict,"SessionID", plist_new_string(session_id)); debug_info("stopping session %s", session_id); @@ -168,64 +257,74 @@ lockdownd_error_t lockdownd_stop_session(lockdownd_client_t client, const char * return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "StopSession") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "StopSession"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; + + if (client->session_id) { + free(client->session_id); + client->session_id = NULL; + } + if (client->ssl_enabled) { property_list_service_disable_ssl(client->parent); + client->ssl_enabled = 0; } + return ret; } -/** - * Closes the lockdownd client session if one is running and frees up the - * lockdownd_client struct. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ -lockdownd_error_t lockdownd_client_free(lockdownd_client_t client) +static lockdownd_error_t lockdownd_client_free_simple(lockdownd_client_t client) { if (!client) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (client->session_id) { - lockdownd_stop_session(client, client->session_id); - free(client->session_id); - } + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; if (client->parent) { - lockdownd_goodbye(client); - if (property_list_service_client_free(client->parent) == PROPERTY_LIST_SERVICE_E_SUCCESS) { ret = LOCKDOWN_E_SUCCESS; } } - if (client->uuid) { - free(client->uuid); + if (client->session_id) { + free(client->session_id); + client->session_id = NULL; } if (client->label) { free(client->label); } + if (client->cu_key) { + free(client->cu_key); + client->cu_key = NULL; + } free(client); + client = NULL; + + return ret; +} + +lockdownd_error_t lockdownd_client_free(lockdownd_client_t client) +{ + if (!client) + return LOCKDOWN_E_INVALID_ARG; + + lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; + + if (client->session_id) { + lockdownd_stop_session(client, client->session_id); + } + + ret = lockdownd_client_free_simple(client); + return ret; } -/** - * Sets the label to send for requests to lockdownd. - * - * @param client The lockdown client - * @param label The label to set or NULL to disable sending a label - * - */ void lockdownd_client_set_label(lockdownd_client_t client, const char *label) { if (client) { @@ -236,69 +335,22 @@ void lockdownd_client_set_label(lockdownd_client_t client, const char *label) } } -/** - * Receives a plist from lockdownd. - * - * @param client The lockdownd client - * @param plist The plist to store the received data - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ lockdownd_error_t lockdownd_receive(lockdownd_client_t client, plist_t *plist) { if (!client || !plist || (plist && *plist)) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; - property_list_service_error_t err; - - err = property_list_service_receive_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = LOCKDOWN_E_UNKNOWN_ERROR; - } - - if (!*plist) - ret = LOCKDOWN_E_PLIST_ERROR; - return ret; + return lockdownd_error(property_list_service_receive_plist(client->parent, plist)); } -/** - * Sends a plist to lockdownd. - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The lockdownd client - * @param plist The plist to send - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ lockdownd_error_t lockdownd_send(lockdownd_client_t client, plist_t plist) { if (!client || !plist) return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; - idevice_error_t err; - - err = property_list_service_send_xml_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = LOCKDOWN_E_UNKNOWN_ERROR; - } - return ret; + return lockdownd_error(property_list_service_send_xml_plist(client->parent, plist)); } -/** - * Query the type of the service daemon. Depending on whether the device is - * queried in normal mode or restore mode, different types will be returned. - * - * @param client The lockdownd client - * @param type The type returned by the service daemon. Pass NULL to ignore. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) { if (!client) @@ -308,7 +360,7 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("QueryType")); + plist_dict_set_item(dict,"Request", plist_new_string("QueryType")); debug_info("called"); ret = lockdownd_send(client, dict); @@ -322,14 +374,21 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) return ret; ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "QueryType") == RESULT_SUCCESS) { + plist_t type_node = plist_dict_get_item(dict, "Type"); + if (type_node && (plist_get_node_type(type_node) == PLIST_STRING)) { + char* typestr = NULL; + plist_get_string_val(type_node, &typestr); + debug_info("success with type %s", typestr); /* return the type if requested */ if (type != NULL) { - plist_t type_node = plist_dict_get_item(dict, "Type"); - plist_get_string_val(type_node, type); + *type = typestr; + } else { + free(typestr); } - debug_info("success with type %s", *type); ret = LOCKDOWN_E_SUCCESS; + } else { + debug_info("hmm. QueryType response does not contain a type?!"); + debug_plist(dict); } plist_free(dict); dict = NULL; @@ -337,16 +396,6 @@ lockdownd_error_t lockdownd_query_type(lockdownd_client_t client, char **type) return ret; } -/** - * Retrieves a preferences plist using an optional domain and/or key name. - * - * @param client An initialized lockdownd client. - * @param domain The domain to query on or NULL for global domain - * @param key The key name to request or NULL to query for all keys - * @param value A plist node representing the result value node - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *domain, const char *key, plist_t *value) { if (!client) @@ -359,12 +408,12 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("GetValue")); + plist_dict_set_item(dict,"Request", plist_new_string("GetValue")); /* send to device */ ret = lockdownd_send(client, dict); @@ -380,10 +429,11 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "GetValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "GetValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + if (ret != LOCKDOWN_E_SUCCESS) { plist_free(dict); return ret; @@ -400,17 +450,6 @@ lockdownd_error_t lockdownd_get_value(lockdownd_client_t client, const char *dom return ret; } -/** - * Sets a preferences value using a plist and optional by domain and/or key name. - * - * @param client an initialized lockdownd client. - * @param domain the domain to query on or NULL for global domain - * @param key the key name to set the value or NULL to set a value dict plist - * @param value a plist node of any node type representing the value to set - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * value is NULL - */ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *domain, const char *key, plist_t value) { if (!client || !value) @@ -423,13 +462,13 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("SetValue")); - plist_dict_insert_item(dict,"Value", value); + plist_dict_set_item(dict,"Request", plist_new_string("SetValue")); + plist_dict_set_item(dict,"Value", value); /* send to device */ ret = lockdownd_send(client, dict); @@ -445,9 +484,9 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "SetValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "SetValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } if (ret != LOCKDOWN_E_SUCCESS) { @@ -459,17 +498,6 @@ lockdownd_error_t lockdownd_set_value(lockdownd_client_t client, const char *dom return ret; } -/** - * Removes a preference node by domain and/or key name. - * - * @note: Use with caution as this could remove vital information on the device - * - * @param client An initialized lockdownd client. - * @param domain The domain to query on or NULL for global domain - * @param key The key name to remove or NULL remove all keys for the current domain - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char *domain, const char *key) { if (!client) @@ -482,12 +510,12 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * dict = plist_new_dict(); plist_dict_add_label(dict, client->label); if (domain) { - plist_dict_insert_item(dict,"Domain", plist_new_string(domain)); + plist_dict_set_item(dict,"Domain", plist_new_string(domain)); } if (key) { - plist_dict_insert_item(dict,"Key", plist_new_string(key)); + plist_dict_set_item(dict,"Key", plist_new_string(key)); } - plist_dict_insert_item(dict,"Request", plist_new_string("RemoveValue")); + plist_dict_set_item(dict,"Request", plist_new_string("RemoveValue")); /* send to device */ ret = lockdownd_send(client, dict); @@ -503,9 +531,9 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * if (ret != LOCKDOWN_E_SUCCESS) return ret; - if (lockdown_check_result(dict, "RemoveValue") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "RemoveValue"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } if (ret != LOCKDOWN_E_SUCCESS) { @@ -517,16 +545,7 @@ lockdownd_error_t lockdownd_remove_value(lockdownd_client_t client, const char * return ret; } -/** - * Returns the unique id of the device from lockdownd. - * - * @param client An initialized lockdownd client. - * @param uuid Holds the unique id of the device. The caller is responsible - * for freeing the memory. - * - * @return LOCKDOWN_E_SUCCESS on success - */ -lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uuid) +lockdownd_error_t lockdownd_get_device_udid(lockdownd_client_t client, char **udid) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t value = NULL; @@ -535,7 +554,7 @@ lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uu if (ret != LOCKDOWN_E_SUCCESS) { return ret; } - plist_get_string_val(value, uuid); + plist_get_string_val(value, udid); plist_free(value); value = NULL; @@ -551,7 +570,7 @@ lockdownd_error_t lockdownd_get_device_uuid(lockdownd_client_t client, char **uu * * @return LOCKDOWN_E_SUCCESS on success */ -lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnutls_datum_t * public_key) +static lockdownd_error_t lockdownd_get_device_public_key_as_key_data(lockdownd_client_t client, key_data_t *public_key) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t value = NULL; @@ -572,15 +591,6 @@ lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnu return ret; } -/** - * Retrieves the name of the device from lockdownd set by the user. - * - * @param client An initialized lockdownd client. - * @param device_name Holds the name of the device. The caller is - * responsible for freeing the memory. - * - * @return LOCKDOWN_E_SUCCESS on success - */ lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **device_name) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; @@ -598,29 +608,19 @@ lockdownd_error_t lockdownd_get_device_name(lockdownd_client_t client, char **de return ret; } -/** - * Creates a new lockdownd client for the device. - * - * @note This function does not pair with the device or start a session. This - * has to be done manually by the caller after the client is created. - * The device disconnects automatically if the lockdown connection idles - * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon - * as the connection is no longer needed. - * - * @param device The device to create a lockdownd client for - * @param client The pointer to the location of the new lockdownd_client - * @param label The label to use for communication. Usually the program name. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *client, const char *label) { - if (!client) + if (!device || !client) return LOCKDOWN_E_INVALID_ARG; + static struct lockdownd_service_descriptor service = { + .port = 0xf27e, + .ssl_enabled = 0 + }; + property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, 0xf27e, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - debug_info("could not connect to lockdownd (device %s)", device->uuid); + if (property_list_service_client_new(device, (lockdownd_service_descriptor_t)&service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + debug_info("could not connect to lockdownd (device %s)", device->udid); return LOCKDOWN_E_MUX_ERROR; } @@ -628,7 +628,14 @@ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *cli client_loc->parent = plistclient; client_loc->ssl_enabled = 0; client_loc->session_id = NULL; - client_loc->uuid = NULL; + client_loc->device = device; + client_loc->cu_key = NULL; + client_loc->cu_key_len = 0; + + if (device->udid) { + debug_info("device udid: %s", device->udid); + } + client_loc->label = label ? strdup(label) : NULL; *client = client_loc; @@ -636,23 +643,6 @@ lockdownd_error_t lockdownd_client_new(idevice_t device, lockdownd_client_t *cli return LOCKDOWN_E_SUCCESS; } -/** - * Creates a new lockdownd client for the device and starts initial handshake. - * The handshake consists out of query_type, validate_pair, pair and - * start_session calls. It uses the internal pairing record management. - * - * @note The device disconnects automatically if the lockdown connection idles - * for more than 10 seconds. Make sure to call lockdownd_client_free() as soon - * as the connection is no longer needed. - * - * @param device The device to create a lockdownd client for - * @param client The pointer to the location of the new lockdownd_client - * @param label The label to use for communication. Usually the program name. - * Pass NULL to disable sending the label in requests to lockdownd. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_INVALID_CONF if configuration data is wrong - */ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdownd_client_t *client, const char *label) { if (!client) @@ -660,6 +650,7 @@ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdown lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; lockdownd_client_t client_loc = NULL; + plist_t pair_record = NULL; char *host_id = NULL; char *type = NULL; @@ -670,60 +661,125 @@ lockdownd_error_t lockdownd_client_new_with_handshake(idevice_t device, lockdown } /* perform handshake */ - if (LOCKDOWN_E_SUCCESS != lockdownd_query_type(client_loc, &type)) { + ret = lockdownd_query_type(client_loc, &type); + if (LOCKDOWN_E_SUCCESS != ret) { debug_info("QueryType failed in the lockdownd client."); - ret = LOCKDOWN_E_NOT_ENOUGH_DATA; - } else { - if (strcmp("com.apple.mobile.lockdown", type)) { - debug_info("Warning QueryType request returned \"%s\".", type); + } else if (strcmp("com.apple.mobile.lockdown", type) != 0) { + debug_info("Warning QueryType request returned \"%s\".", type); + } + free(type); + + if (device->version == 0) { + plist_t p_version = NULL; + if (lockdownd_get_value(client_loc, NULL, "ProductVersion", &p_version) == LOCKDOWN_E_SUCCESS) { + int vers[3] = {0, 0, 0}; + char *s_version = NULL; + plist_get_string_val(p_version, &s_version); + if (s_version && sscanf(s_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { + device->version = DEVICE_VERSION(vers[0], vers[1], vers[2]); + } + free(s_version); } - if (type) - free(type); + plist_free(p_version); } - - ret = idevice_get_uuid(device, &client_loc->uuid); - if (LOCKDOWN_E_SUCCESS != ret) { - debug_info("failed to get device uuid."); + if (device->device_class == 0) { + plist_t p_device_class = NULL; + if (lockdownd_get_value(client_loc, NULL, "DeviceClass", &p_device_class) == LOCKDOWN_E_SUCCESS) { + char* s_device_class = NULL; + plist_get_string_val(p_device_class, &s_device_class); + if (s_device_class != NULL) { + if (!strcmp(s_device_class, "iPhone")) { + device->device_class = DEVICE_CLASS_IPHONE; + } else if (!strcmp(s_device_class, "iPad")) { + device->device_class = DEVICE_CLASS_IPAD; + } else if (!strcmp(s_device_class, "iPod")) { + device->device_class = DEVICE_CLASS_IPOD; + } else if (!strcmp(s_device_class, "Watch")) { + device->device_class = DEVICE_CLASS_WATCH; + } else if (!strcmp(s_device_class, "AppleTV")) { + device->device_class = DEVICE_CLASS_APPLETV; + } else { + device->device_class = DEVICE_CLASS_UNKNOWN; + } + free(s_device_class); + } + } + plist_free(p_device_class); } - debug_info("device uuid: %s", client_loc->uuid); - userpref_get_host_id(&host_id); - if (LOCKDOWN_E_SUCCESS == ret && !host_id) { + userpref_error_t uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } + if (pair_record) { + pair_record_get_host_id(pair_record, &host_id); + } + if (LOCKDOWN_E_SUCCESS == ret && pair_record && !host_id) { ret = LOCKDOWN_E_INVALID_CONF; } - if (LOCKDOWN_E_SUCCESS == ret && !userpref_has_device_public_key(client_loc->uuid)) + if (LOCKDOWN_E_SUCCESS == ret && !pair_record) { + /* attempt pairing */ + free(host_id); + host_id = NULL; ret = lockdownd_pair(client_loc, NULL); + } - /* in any case, we need to validate pairing to receive trusted host status */ - ret = lockdownd_validate_pair(client_loc, NULL); + plist_free(pair_record); + pair_record = NULL; - /* if not paired yet, let's do it now */ - if (LOCKDOWN_E_INVALID_HOST_ID == ret) { - ret = lockdownd_pair(client_loc, NULL); - if (LOCKDOWN_E_SUCCESS == ret) { - ret = lockdownd_validate_pair(client_loc, NULL); + if (device->version < DEVICE_VERSION(7,0,0) && device->device_class != DEVICE_CLASS_WATCH) { + /* for older devices, we need to validate pairing to receive trusted host status */ + ret = lockdownd_validate_pair(client_loc, NULL); + + /* if not paired yet, let's do it now */ + if (LOCKDOWN_E_INVALID_HOST_ID == ret) { + free(host_id); + host_id = NULL; + ret = lockdownd_pair(client_loc, NULL); + if (LOCKDOWN_E_SUCCESS == ret) { + ret = lockdownd_validate_pair(client_loc, NULL); + } } } if (LOCKDOWN_E_SUCCESS == ret) { + if (!host_id) { + uerr = userpref_read_pair_record(client_loc->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client_loc->device->udid); + lockdownd_client_free(client_loc); + return LOCKDOWN_E_INVALID_CONF; + } + if (pair_record) { + pair_record_get_host_id(pair_record, &host_id); + plist_free(pair_record); + } + } + ret = lockdownd_start_session(client_loc, host_id, NULL, NULL); if (LOCKDOWN_E_SUCCESS != ret) { debug_info("Session opening failed."); } - if (host_id) { - free(host_id); - host_id = NULL; - } } - + if (LOCKDOWN_E_SUCCESS == ret) { *client = client_loc; } else { lockdownd_client_free(client_loc); } - + free(host_id); return ret; } @@ -740,68 +796,77 @@ static plist_t lockdownd_pair_record_to_plist(lockdownd_pair_record_t pair_recor if (!pair_record) return NULL; - char *host_id_loc = pair_record->host_id; - /* setup request plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "DeviceCertificate", plist_new_data(pair_record->device_certificate, strlen(pair_record->device_certificate))); - plist_dict_insert_item(dict, "HostCertificate", plist_new_data(pair_record->host_certificate, strlen(pair_record->host_certificate))); - if (!pair_record->host_id) - userpref_get_host_id(&host_id_loc); - plist_dict_insert_item(dict, "HostID", plist_new_string(host_id_loc)); - plist_dict_insert_item(dict, "RootCertificate", plist_new_data(pair_record->root_certificate, strlen(pair_record->root_certificate))); - - if (!pair_record->host_id) - free(host_id_loc); + plist_dict_set_item(dict, "DeviceCertificate", plist_new_data(pair_record->device_certificate, strlen(pair_record->device_certificate))); + plist_dict_set_item(dict, "HostCertificate", plist_new_data(pair_record->host_certificate, strlen(pair_record->host_certificate))); + plist_dict_set_item(dict, "HostID", plist_new_string(pair_record->host_id)); + plist_dict_set_item(dict, "RootCertificate", plist_new_data(pair_record->root_certificate, strlen(pair_record->root_certificate))); + plist_dict_set_item(dict, "SystemBUID", plist_new_string(pair_record->system_buid)); return dict; } /** - * Generates a new pairing record plist and required certificates for the - * supplied public key of the device and the host_id of the caller's host - * computer. + * Generates a pair record plist with required certificates for a specific + * device. If a pairing exists, it is loaded from the computer instead of being + * generated. * - * @param public_key The public key of the device. - * @param host_id The HostID to use for the pair record plist. - * @param pair_record_plist Holds the generated pair record. + * @param pair_record_plist Holds the pair record. * * @return LOCKDOWN_E_SUCCESS on success */ -static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, char *host_id, plist_t *pair_record_plist) +static lockdownd_error_t pair_record_generate(lockdownd_client_t client, plist_t *pair_record) { lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - gnutls_datum_t device_cert = { NULL, 0 }; - gnutls_datum_t host_cert = { NULL, 0 }; - gnutls_datum_t root_cert = { NULL, 0 }; + key_data_t public_key = { NULL, 0 }; + char* host_id = NULL; + char* system_buid = NULL; - ret = lockdownd_gen_pair_cert(public_key, &device_cert, &host_cert, &root_cert); + /* retrieve device public key */ + ret = lockdownd_get_device_public_key_as_key_data(client, &public_key); if (ret != LOCKDOWN_E_SUCCESS) { - return ret; + debug_info("device refused to send public key."); + goto leave; + } + debug_info("device public key follows:\n%.*s", public_key.size, public_key.data); + + *pair_record = plist_new_dict(); + + /* generate keys and certificates into pair record */ + userpref_error_t uret = USERPREF_E_SUCCESS; + uret = pair_record_generate_keys_and_certs(*pair_record, public_key); + switch(uret) { + case USERPREF_E_INVALID_ARG: + ret = LOCKDOWN_E_INVALID_ARG; + break; + case USERPREF_E_INVALID_CONF: + ret = LOCKDOWN_E_INVALID_CONF; + break; + case USERPREF_E_SSL_ERROR: + ret = LOCKDOWN_E_SSL_ERROR; + default: + break; } - char *host_id_loc = host_id; + /* set SystemBUID */ + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(*pair_record, USERPREF_SYSTEM_BUID_KEY, plist_new_string(system_buid)); + } - if (!host_id) - userpref_get_host_id(&host_id_loc); + /* set HostID */ + host_id = generate_uuid(); + pair_record_set_host_id(*pair_record, host_id); - /* setup request plist */ - *pair_record_plist = plist_new_dict(); - plist_dict_insert_item(*pair_record_plist, "DeviceCertificate", plist_new_data((const char*)device_cert.data, device_cert.size)); - plist_dict_insert_item(*pair_record_plist, "HostCertificate", plist_new_data((const char*)host_cert.data, host_cert.size)); - plist_dict_insert_item(*pair_record_plist, "HostID", plist_new_string(host_id_loc)); - plist_dict_insert_item(*pair_record_plist, "RootCertificate", plist_new_data((const char*)root_cert.data, root_cert.size)); - - if (!host_id) - free(host_id_loc); - - if (device_cert.data) - free(device_cert.data); - if (host_cert.data) - free(host_cert.data); - if (root_cert.data) - free(root_cert.data); +leave: + if (host_id) + free(host_id); + if (system_buid) + free(system_buid); + if (public_key.data) + free(public_key.data); return ret; } @@ -809,11 +874,13 @@ static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, c /** * Function used internally by lockdownd_pair() and lockdownd_validate_pair() * - * @param client The lockdown client to pair with. + * @param client The lockdown client * @param pair_record The pair record to use for pairing. If NULL is passed, then * the pair records from the current machine are used. New records will be * generated automatically when pairing is done for the first time. * @param verb This is either "Pair", "ValidatePair" or "Unpair". + * @param options The pairing options to pass. + * @param response If non-NULL a pointer to lockdownd's response dictionary is returned. * * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, @@ -821,73 +888,103 @@ static lockdownd_error_t generate_pair_record_plist(gnutls_datum_t public_key, c * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id */ -static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record, const char *verb) +static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record, const char *verb, plist_t options, plist_t *result) { if (!client) return LOCKDOWN_E_INVALID_ARG; lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; plist_t dict = NULL; - plist_t dict_record = NULL; - gnutls_datum_t public_key = { NULL, 0 }; + plist_t pair_record_plist = NULL; + plist_t wifi_node = NULL; int pairing_mode = 0; /* 0 = libimobiledevice, 1 = external */ - if (pair_record && pair_record->host_id) { + if (pair_record && pair_record->system_buid && pair_record->host_id) { /* valid pair_record passed? */ if (!pair_record->device_certificate || !pair_record->host_certificate || !pair_record->root_certificate) { return LOCKDOWN_E_PLIST_ERROR; } /* use passed pair_record */ - dict_record = lockdownd_pair_record_to_plist(pair_record); + pair_record_plist = lockdownd_pair_record_to_plist(pair_record); pairing_mode = 1; } else { - ret = lockdownd_get_device_public_key(client, &public_key); - if (ret != LOCKDOWN_E_SUCCESS) { - if (public_key.data) - free(public_key.data); - debug_info("device refused to send public key."); - return ret; - } - debug_info("device public key follows:\n%.*s", public_key.size, public_key.data); - /* get libimobiledevice pair_record */ - ret = generate_pair_record_plist(public_key, NULL, &dict_record); - if (ret != LOCKDOWN_E_SUCCESS) { - if (dict_record) - plist_free(dict_record); - return ret; + /* generate a new pair record if pairing */ + if (!strcmp("Pair", verb)) { + ret = pair_record_generate(client, &pair_record_plist); + + if (ret != LOCKDOWN_E_SUCCESS) { + if (pair_record_plist) + plist_free(pair_record_plist); + return ret; + } + + /* get wifi mac now, if we get it later we fail on iOS 7 which causes a reconnect */ + lockdownd_get_value(client, NULL, "WiFiAddress", &wifi_node); + } else { + /* use existing pair record */ + userpref_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record_plist); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client->device->udid); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->udid); + return LOCKDOWN_E_INVALID_CONF; + } } } - /* Setup Pair request plist */ + plist_t request_pair_record = plist_copy(pair_record_plist); + + /* remove stuff that is private */ + plist_dict_remove_item(request_pair_record, USERPREF_ROOT_PRIVATE_KEY_KEY); + plist_dict_remove_item(request_pair_record, USERPREF_HOST_PRIVATE_KEY_KEY); + + /* setup pair request plist */ dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"PairRecord", dict_record); - plist_dict_insert_item(dict, "Request", plist_new_string(verb)); + plist_dict_set_item(dict, "PairRecord", request_pair_record); + plist_dict_set_item(dict, "Request", plist_new_string(verb)); + plist_dict_set_item(dict, "ProtocolVersion", plist_new_string(LOCKDOWN_PROTOCOL_VERSION)); + + if (options) { + plist_dict_set_item(dict, "PairingOptions", plist_copy(options)); + } /* send to device */ ret = lockdownd_send(client, dict); plist_free(dict); dict = NULL; - if (ret != LOCKDOWN_E_SUCCESS) + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(pair_record_plist); + if (wifi_node) + plist_free(wifi_node); return ret; + } /* Now get device's answer */ ret = lockdownd_receive(client, &dict); - if (ret != LOCKDOWN_E_SUCCESS) + if (ret != LOCKDOWN_E_SUCCESS) { + plist_free(pair_record_plist); + if (wifi_node) + plist_free(wifi_node); return ret; + } if (strcmp(verb, "Unpair") == 0) { /* workaround for Unpair giving back ValidatePair, * seems to be a bug in the device's fw */ - if (lockdown_check_result(dict, NULL) != RESULT_SUCCESS) { + if (lockdown_check_result(dict, NULL) != LOCKDOWN_E_SUCCESS) { ret = LOCKDOWN_E_PAIRING_FAILED; } } else { - if (lockdown_check_result(dict, verb) != RESULT_SUCCESS) { + if (lockdown_check_result(dict, verb) != LOCKDOWN_E_SUCCESS) { ret = LOCKDOWN_E_PAIRING_FAILED; } } @@ -896,13 +993,32 @@ static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_ if (ret == LOCKDOWN_E_SUCCESS) { debug_info("%s success", verb); if (!pairing_mode) { + debug_info("internal pairing mode"); if (!strcmp("Unpair", verb)) { /* remove public key from config */ - userpref_remove_device_public_key(client->uuid); + userpref_delete_pair_record(client->device->udid); } else { - /* store public key in config */ - userpref_set_device_public_key(client->uuid, public_key); + if (!strcmp("Pair", verb)) { + /* add returned escrow bag if available */ + plist_t extra_node = plist_dict_get_item(dict, USERPREF_ESCROW_BAG_KEY); + if (extra_node && plist_get_node_type(extra_node) == PLIST_DATA) { + debug_info("Saving EscrowBag from response in pair record"); + plist_dict_set_item(pair_record_plist, USERPREF_ESCROW_BAG_KEY, plist_copy(extra_node)); + } + + /* save previously retrieved wifi mac address in pair record */ + if (wifi_node) { + debug_info("Saving WiFiAddress from device in pair record"); + plist_dict_set_item(pair_record_plist, USERPREF_WIFI_MAC_ADDRESS_KEY, plist_copy(wifi_node)); + plist_free(wifi_node); + wifi_node = NULL; + } + + userpref_save_pair_record(client->device->udid, client->device->mux_id, pair_record_plist); + } } + } else { + debug_info("external pairing mode"); } } else { debug_info("%s failure", verb); @@ -914,92 +1030,60 @@ static lockdownd_error_t lockdownd_do_pair(lockdownd_client_t client, lockdownd_ plist_get_string_val(error_node, &value); if (value) { /* the first pairing fails if the device is password protected */ - if (!strcmp(value, "PasswordProtected")) { - ret = LOCKDOWN_E_PASSWORD_PROTECTED; - } else if (!strcmp(value, "InvalidHostID")) { - ret = LOCKDOWN_E_INVALID_HOST_ID; - } + ret = lockdownd_strtoerr(value); free(value); } - - plist_free(error_node); - error_node = NULL; } } - plist_free(dict); - dict = NULL; - if (public_key.data) - free(public_key.data); + + if (pair_record_plist) { + plist_free(pair_record_plist); + pair_record_plist = NULL; + } + + if (wifi_node) { + plist_free(wifi_node); + wifi_node = NULL; + } + + if (result) { + *result = dict; + } else { + plist_free(dict); + dict = NULL; + } + return ret; } -/** - * Pairs the device using the supplied pair record. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to use for pairing. If NULL is passed, then - * the pair records from the current machine are used. New records will be - * generated automatically when pairing is done for the first time. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "Pair"); + + plist_t options = plist_new_dict(); + plist_dict_set_item(options, "ExtendedPairingErrors", plist_new_bool(1)); + + lockdownd_error_t ret = lockdownd_do_pair(client, pair_record, "Pair", options, NULL); + + plist_free(options); + + return ret; +} + +lockdownd_error_t lockdownd_pair_with_options(lockdownd_client_t client, lockdownd_pair_record_t pair_record, plist_t options, plist_t *response) +{ + return lockdownd_do_pair(client, pair_record, "Pair", options, response); } -/** - * Validates if the device is paired with the given HostID. If succeeded them - * specified host will become trusted host of the device indicated by the - * lockdownd preference named TrustedHostAttached. Otherwise the host must because - * paired using lockdownd_pair() first. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to validate pairing with. If NULL is - * passed, then the pair record is read from the internal pairing record - * management. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_validate_pair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "ValidatePair"); + return lockdownd_do_pair(client, pair_record, "ValidatePair", NULL, NULL); } -/** - * Unpairs the device with the given HostID and removes the pairing records - * from the device and host if the internal pairing record management is used. - * - * @param client The lockdown client to pair with. - * @param pair_record The pair record to use for unpair. If NULL is passed, then - * the pair records from the current machine are used. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the pair_record certificates are wrong, - * LOCKDOWN_E_PAIRING_FAILED if the pairing failed, - * LOCKDOWN_E_PASSWORD_PROTECTED if the device is password protected, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the caller's host id - */ lockdownd_error_t lockdownd_unpair(lockdownd_client_t client, lockdownd_pair_record_t pair_record) { - return lockdownd_do_pair(client, pair_record, "Unpair"); + return lockdownd_do_pair(client, pair_record, "Unpair", NULL, NULL); } -/** - * Tells the device to immediately enter recovery mode. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) { if (!client) @@ -1009,7 +1093,7 @@ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("EnterRecovery")); + plist_dict_set_item(dict,"Request", plist_new_string("EnterRecovery")); debug_info("telling device to enter recovery mode"); @@ -1019,23 +1103,17 @@ lockdownd_error_t lockdownd_enter_recovery(lockdownd_client_t client) ret = lockdownd_receive(client, &dict); - if (lockdown_check_result(dict, "EnterRecovery") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "EnterRecovery"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; + return ret; } -/** - * Sends the Goodbye request to lockdownd signaling the end of communication. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_PLIST_ERROR if the device did not acknowledge the request - */ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) { if (!client) @@ -1045,7 +1123,7 @@ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Goodbye")); + plist_dict_set_item(dict,"Request", plist_new_string("Goodbye")); debug_info("called"); @@ -1059,187 +1137,17 @@ lockdownd_error_t lockdownd_goodbye(lockdownd_client_t client) return LOCKDOWN_E_PLIST_ERROR; } - if (lockdown_check_result(dict, "Goodbye") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Goodbye"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; - return ret; -} - -/** - * Generates the device certificate from the public key as well as the host - * and root certificates. - * - * @param public_key The public key of the device to use for generation. - * @param odevice_cert Holds the generated device certificate. - * @param ohost_cert Holds the generated host certificate. - * @param oroot_cert Holds the generated root certificate. - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when a parameter is NULL, - * LOCKDOWN_E_INVALID_CONF if the internal configuration system failed, - * LOCKDOWN_E_SSL_ERROR if the certificates could not be generated - */ -lockdownd_error_t lockdownd_gen_pair_cert(gnutls_datum_t public_key, gnutls_datum_t * odevice_cert, - gnutls_datum_t * ohost_cert, gnutls_datum_t * oroot_cert) -{ - if (!public_key.data || !odevice_cert || !ohost_cert || !oroot_cert) - return LOCKDOWN_E_INVALID_ARG; - lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - userpref_error_t uret = USERPREF_E_UNKNOWN_ERROR; - - gnutls_datum_t modulus = { NULL, 0 }; - gnutls_datum_t exponent = { NULL, 0 }; - - /* now decode the PEM encoded key */ - gnutls_datum_t der_pub_key; - if (GNUTLS_E_SUCCESS == gnutls_pem_base64_decode_alloc("RSA PUBLIC KEY", &public_key, &der_pub_key)) { - - /* initalize asn.1 parser */ - ASN1_TYPE pkcs1 = ASN1_TYPE_EMPTY; - if (ASN1_SUCCESS == asn1_array2tree(pkcs1_asn1_tab, &pkcs1, NULL)) { - - ASN1_TYPE asn1_pub_key = ASN1_TYPE_EMPTY; - asn1_create_element(pkcs1, "PKCS1.RSAPublicKey", &asn1_pub_key); - - if (ASN1_SUCCESS == asn1_der_decoding(&asn1_pub_key, der_pub_key.data, der_pub_key.size, NULL)) { - - /* get size to read */ - int ret1 = asn1_read_value(asn1_pub_key, "modulus", NULL, (int*)&modulus.size); - int ret2 = asn1_read_value(asn1_pub_key, "publicExponent", NULL, (int*)&exponent.size); - - modulus.data = gnutls_malloc(modulus.size); - exponent.data = gnutls_malloc(exponent.size); - - ret1 = asn1_read_value(asn1_pub_key, "modulus", modulus.data, (int*)&modulus.size); - ret2 = asn1_read_value(asn1_pub_key, "publicExponent", exponent.data, (int*)&exponent.size); - if (ASN1_SUCCESS == ret1 && ASN1_SUCCESS == ret2) - ret = LOCKDOWN_E_SUCCESS; - } - if (asn1_pub_key) - asn1_delete_structure(&asn1_pub_key); - } - if (pkcs1) - asn1_delete_structure(&pkcs1); - } - - /* now generate certificates */ - if (LOCKDOWN_E_SUCCESS == ret && 0 != modulus.size && 0 != exponent.size) { - - gnutls_global_init(); - gnutls_datum_t essentially_null = { (unsigned char*)strdup("abababababababab"), strlen("abababababababab") }; - - gnutls_x509_privkey_t fake_privkey, root_privkey, host_privkey; - gnutls_x509_crt_t dev_cert, root_cert, host_cert; - - gnutls_x509_privkey_init(&fake_privkey); - gnutls_x509_crt_init(&dev_cert); - gnutls_x509_crt_init(&root_cert); - gnutls_x509_crt_init(&host_cert); - - if (GNUTLS_E_SUCCESS == - gnutls_x509_privkey_import_rsa_raw(fake_privkey, &modulus, &exponent, &essentially_null, &essentially_null, - &essentially_null, &essentially_null)) { - - gnutls_x509_privkey_init(&root_privkey); - gnutls_x509_privkey_init(&host_privkey); - - uret = userpref_get_keys_and_certs(root_privkey, root_cert, host_privkey, host_cert); - - if (USERPREF_E_SUCCESS == uret) { - /* generate device certificate */ - gnutls_x509_crt_set_key(dev_cert, fake_privkey); - gnutls_x509_crt_set_serial(dev_cert, "\x00", 1); - gnutls_x509_crt_set_version(dev_cert, 3); - gnutls_x509_crt_set_ca_status(dev_cert, 0); - gnutls_x509_crt_set_activation_time(dev_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(dev_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(dev_cert, root_cert, root_privkey); - - if (LOCKDOWN_E_SUCCESS == ret) { - /* if everything went well, export in PEM format */ - size_t export_size = 0; - gnutls_datum_t dev_pem = { NULL, 0 }; - gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, NULL, &export_size); - dev_pem.data = gnutls_malloc(export_size); - gnutls_x509_crt_export(dev_cert, GNUTLS_X509_FMT_PEM, dev_pem.data, &export_size); - dev_pem.size = export_size; - - gnutls_datum_t pem_root_cert = { NULL, 0 }; - gnutls_datum_t pem_host_cert = { NULL, 0 }; - - uret = userpref_get_certs_as_pem(&pem_root_cert, &pem_host_cert); - - if (USERPREF_E_SUCCESS == uret) { - /* copy buffer for output */ - odevice_cert->data = malloc(dev_pem.size); - memcpy(odevice_cert->data, dev_pem.data, dev_pem.size); - odevice_cert->size = dev_pem.size; - - ohost_cert->data = malloc(pem_host_cert.size); - memcpy(ohost_cert->data, pem_host_cert.data, pem_host_cert.size); - ohost_cert->size = pem_host_cert.size; - - oroot_cert->data = malloc(pem_root_cert.size); - memcpy(oroot_cert->data, pem_root_cert.data, pem_root_cert.size); - oroot_cert->size = pem_root_cert.size; - - g_free(pem_root_cert.data); - g_free(pem_host_cert.data); - - if (dev_pem.data) - gnutls_free(dev_pem.data); - } - } - } - - switch(uret) { - case USERPREF_E_INVALID_ARG: - ret = LOCKDOWN_E_INVALID_ARG; - break; - case USERPREF_E_INVALID_CONF: - ret = LOCKDOWN_E_INVALID_CONF; - break; - case USERPREF_E_SSL_ERROR: - ret = LOCKDOWN_E_SSL_ERROR; - default: - break; - } - } - - if (essentially_null.data) - free(essentially_null.data); - gnutls_x509_crt_deinit(dev_cert); - gnutls_x509_crt_deinit(root_cert); - gnutls_x509_crt_deinit(host_cert); - gnutls_x509_privkey_deinit(fake_privkey); - gnutls_x509_privkey_deinit(root_privkey); - gnutls_x509_privkey_deinit(host_privkey); - - } - - gnutls_free(modulus.data); - gnutls_free(exponent.data); - - gnutls_free(der_pub_key.data); return ret; } -/** - * Opens a session with lockdownd and switches to SSL mode if device wants it. - * - * @param client The lockdownd client - * @param host_id The HostID of the computer - * @param session_id The new session_id of the created session - * @param ssl_enabled Whether SSL communication is used in the session - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when a client or - * host_id is NULL, LOCKDOWN_E_PLIST_ERROR if the response plist had errors, - * LOCKDOWN_E_INVALID_HOST_ID if the device does not know the supplied HostID, - * LOCKDOWN_E_SSL_ERROR if enabling SSL communication failed - */ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char *host_id, char **session_id, int *ssl_enabled) { lockdownd_error_t ret = LOCKDOWN_E_SUCCESS; @@ -1251,14 +1159,28 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char /* if we have a running session, stop current one first */ if (client->session_id) { lockdownd_stop_session(client, client->session_id); - free(client->session_id); } /* setup request plist */ dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"HostID", plist_new_string(host_id)); - plist_dict_insert_item(dict,"Request", plist_new_string("StartSession")); + plist_dict_set_item(dict,"Request", plist_new_string("StartSession")); + + /* add host id */ + if (host_id) { + plist_dict_set_item(dict, "HostID", plist_new_string(host_id)); + } + + /* add system buid */ + char *system_buid = NULL; + userpref_read_system_buid(&system_buid); + if (system_buid) { + plist_dict_set_item(dict, "SystemBUID", plist_new_string(system_buid)); + if (system_buid) { + free(system_buid); + system_buid = NULL; + } + } ret = lockdownd_send(client, dict); plist_free(dict); @@ -1272,17 +1194,8 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char if (!dict) return LOCKDOWN_E_PLIST_ERROR; - if (lockdown_check_result(dict, "StartSession") == RESULT_FAILURE) { - plist_t error_node = plist_dict_get_item(dict, "Error"); - if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { - char *error = NULL; - plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidHostID")) { - ret = LOCKDOWN_E_INVALID_HOST_ID; - } - free(error); - } - } else { + ret = lockdown_check_result(dict, "StartSession"); + if (ret == LOCKDOWN_E_SUCCESS) { uint8_t use_ssl = 0; plist_t enable_ssl = plist_dict_get_item(dict, "EnableSessionSSL"); @@ -1299,6 +1212,7 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char if (session_node && (plist_get_node_type(session_node) == PLIST_STRING)) { plist_get_string_val(session_node, &client->session_id); } + if (client->session_id) { debug_info("SessionID: %s", client->session_id); if (session_id != NULL) @@ -1306,18 +1220,15 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char } else { debug_info("Failed to get SessionID!"); } - debug_info("Enable SSL Session: %s", (use_ssl?"true":"false")); + + debug_info("Enable SSL Session: %s", (use_ssl ? "true" : "false")); + if (use_ssl) { - ret = property_list_service_enable_ssl(client->parent); - if (ret == PROPERTY_LIST_SERVICE_E_SUCCESS) { - client->ssl_enabled = 1; - } else { - ret = LOCKDOWN_E_SSL_ERROR; - client->ssl_enabled = 0; - } + ret = lockdownd_error(property_list_service_enable_ssl(client->parent)); + client->ssl_enabled = (ret == LOCKDOWN_E_SUCCESS ? 1 : 0); } else { - client->ssl_enabled = 0; ret = LOCKDOWN_E_SUCCESS; + client->ssl_enabled = 0; } } @@ -1328,40 +1239,96 @@ lockdownd_error_t lockdownd_start_session(lockdownd_client_t client, const char } /** - * Requests to start a service and retrieve it's port on success. + * Internal function used by lockdownd_do_start_service to create the + * StartService request's plist. * * @param client The lockdownd client - * @param service The name of the service to start - * @param port The port number the service was started on - - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter + * @param identifier The identifier of the service to start + * @param send_escrow_bag Should we send the device's escrow bag with the request + * @param request The request's plist on success, NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_CONF on failure + * to read the escrow bag from the device's record (when used). + */ +static lockdownd_error_t lockdownd_build_start_service_request(lockdownd_client_t client, const char *identifier, int send_escrow_bag, plist_t *request) +{ + plist_t dict = plist_new_dict(); + + /* create the basic request params */ + plist_dict_add_label(dict, client->label); + plist_dict_set_item(dict, "Request", plist_new_string("StartService")); + plist_dict_set_item(dict, "Service", plist_new_string(identifier)); + + /* if needed - get the escrow bag for the device and send it with the request */ + if (send_escrow_bag) { + /* get the pairing record */ + plist_t pair_record = NULL; + userpref_error_t uerr = userpref_read_pair_record(client->device->udid, &pair_record); + if (uerr == USERPREF_E_READ_ERROR) { + debug_info("ERROR: Failed to retrieve pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_RECEIVE_TIMEOUT; + } else if (uerr == USERPREF_E_NOENT) { + debug_info("ERROR: No pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_INVALID_CONF; + } else if (uerr != USERPREF_E_SUCCESS) { + debug_info("ERROR: Failed to retrieve or parse pair record for %s", client->device->udid); + plist_free(dict); + return LOCKDOWN_E_INVALID_CONF; + } + + /* try to read the escrow bag from the record */ + plist_t escrow_bag = plist_dict_get_item(pair_record, USERPREF_ESCROW_BAG_KEY); + if (!escrow_bag || (PLIST_DATA != plist_get_node_type(escrow_bag))) { + debug_info("ERROR: Failed to retrieve the escrow bag from the device's record"); + plist_free(dict); + plist_free(pair_record); + return LOCKDOWN_E_INVALID_CONF; + } + + debug_info("Adding escrow bag to StartService for %s", identifier); + plist_dict_set_item(dict, USERPREF_ESCROW_BAG_KEY, plist_copy(escrow_bag)); + plist_free(pair_record); + } + + *request = dict; + return LOCKDOWN_E_SUCCESS; +} + +/** + * Function used internally by lockdownd_start_service and lockdownd_start_service_with_escrow_bag. + * + * @param client The lockdownd client + * @param identifier The identifier of the service to start + * @param send_escrow_bag Should we send the device's escrow bag with the request + * @param descriptor The service descriptor on success or NULL on failure + * + * @return LOCKDOWN_E_SUCCESS on success, LOCKDOWN_E_INVALID_ARG if a parameter * is NULL, LOCKDOWN_E_INVALID_SERVICE if the requested service is not known * by the device, LOCKDOWN_E_START_SERVICE_FAILED if the service could not because - * started by the device + * started by the device, LOCKDOWN_E_INVALID_CONF if the host id or escrow bag (when + * used) are missing from the device record. */ -lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *service, uint16_t *port) +static lockdownd_error_t lockdownd_do_start_service(lockdownd_client_t client, const char *identifier, int send_escrow_bag, lockdownd_service_descriptor_t *service) { - if (!client || !service || !port) + if (!client || !identifier || !service) return LOCKDOWN_E_INVALID_ARG; - char *host_id = NULL; - userpref_get_host_id(&host_id); - if (!host_id) - return LOCKDOWN_E_INVALID_CONF; - if (!client->session_id) - return LOCKDOWN_E_NO_RUNNING_SESSION; + if (*service) { + // reset fields if service descriptor is reused + (*service)->port = 0; + (*service)->ssl_enabled = 0; + } plist_t dict = NULL; uint16_t port_loc = 0; lockdownd_error_t ret = LOCKDOWN_E_UNKNOWN_ERROR; - free(host_id); - host_id = NULL; - - dict = plist_new_dict(); - plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StartService")); - plist_dict_insert_item(dict,"Service", plist_new_string(service)); + /* create StartService request */ + ret = lockdownd_build_start_service_request(client, identifier, send_escrow_bag, &dict); + if (LOCKDOWN_E_SUCCESS != ret) + return ret; /* send to device */ ret = lockdownd_send(client, dict); @@ -1379,57 +1346,63 @@ lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char if (!dict) return LOCKDOWN_E_PLIST_ERROR; - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "StartService") == RESULT_SUCCESS) { - plist_t port_value_node = plist_dict_get_item(dict, "Port"); - - if (port_value_node && (plist_get_node_type(port_value_node) == PLIST_UINT)) { + ret = lockdown_check_result(dict, "StartService"); + if (ret == LOCKDOWN_E_SUCCESS) { + if (*service == NULL) + *service = (lockdownd_service_descriptor_t)malloc(sizeof(struct lockdownd_service_descriptor)); + (*service)->port = 0; + (*service)->ssl_enabled = 0; + (*service)->identifier = strdup(identifier); + + /* read service port number */ + plist_t node = plist_dict_get_item(dict, "Port"); + if (node && (plist_get_node_type(node) == PLIST_UINT)) { uint64_t port_value = 0; - plist_get_uint_val(port_value_node, &port_value); + plist_get_uint_val(node, &port_value); if (port_value) { port_loc = port_value; ret = LOCKDOWN_E_SUCCESS; } - if (port && ret == LOCKDOWN_E_SUCCESS) - *port = port_loc; + if (port_loc && ret == LOCKDOWN_E_SUCCESS) { + (*service)->port = port_loc; + } + } + + /* check if the service requires SSL */ + node = plist_dict_get_item(dict, "EnableServiceSSL"); + if (node && (plist_get_node_type(node) == PLIST_BOOLEAN)) { + uint8_t b = 0; + plist_get_bool_val(node, &b); + (*service)->ssl_enabled = b; } } else { - ret = LOCKDOWN_E_START_SERVICE_FAILED; plist_t error_node = plist_dict_get_item(dict, "Error"); if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { char *error = NULL; plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidService")) { - ret = LOCKDOWN_E_INVALID_SERVICE; - } + ret = lockdownd_strtoerr(error); free(error); } } plist_free(dict); dict = NULL; + return ret; } -/** - * Activates the device. Only works within an open session. - * The ActivationRecord plist dictionary must be obtained using the - * activation protocol requesting from Apple's https webservice. - * - * @see http://iphone-docs.org/doku.php?id=docs:protocols:activation - * - * @param client The lockdown client - * @param activation_record The activation record plist dictionary - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * activation_record is NULL, LOCKDOWN_E_NO_RUNNING_SESSION if no session is - * open, LOCKDOWN_E_PLIST_ERROR if the received plist is broken, - * LOCKDOWN_E_ACTIVATION_FAILED if the activation failed, - * LOCKDOWN_E_INVALID_ACTIVATION_RECORD if the device reports that the - * activation_record is invalid - */ -lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record) +lockdownd_error_t lockdownd_start_service(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service) +{ + return lockdownd_do_start_service(client, identifier, 0, service); +} + +lockdownd_error_t lockdownd_start_service_with_escrow_bag(lockdownd_client_t client, const char *identifier, lockdownd_service_descriptor_t *service) +{ + return lockdownd_do_start_service(client, identifier, 1, service); +} + +lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activation_record) { if (!client) return LOCKDOWN_E_INVALID_ARG; @@ -1444,8 +1417,8 @@ lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activati plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Activate")); - plist_dict_insert_item(dict,"ActivationRecord", plist_copy(activation_record)); + plist_dict_set_item(dict,"Request", plist_new_string("Activate")); + plist_dict_set_item(dict,"ActivationRecord", plist_copy(activation_record)); ret = lockdownd_send(client, dict); plist_free(dict); @@ -1457,39 +1430,17 @@ lockdownd_error_t lockdownd_activate(lockdownd_client_t client, plist_t activati return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_ACTIVATION_FAILED; - if (lockdown_check_result(dict, "Activate") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Activate"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; - - } else { - plist_t error_node = plist_dict_get_item(dict, "Error"); - if (error_node && PLIST_STRING == plist_get_node_type(error_node)) { - char *error = NULL; - plist_get_string_val(error_node, &error); - if (!strcmp(error, "InvalidActivationRecord")) { - ret = LOCKDOWN_E_INVALID_ACTIVATION_RECORD; - } - free(error); - } } - + plist_free(dict); dict = NULL; return ret; } -/** - * Deactivates the device, returning it to the locked “Activate with iTunes” - * screen. - * - * @param client The lockdown client - * - * @return LOCKDOWN_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, - * LOCKDOWN_E_PLIST_ERROR if the received plist is broken - */ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) { if (!client) @@ -1502,7 +1453,7 @@ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Deactivate")); + plist_dict_set_item(dict,"Request", plist_new_string("Deactivate")); ret = lockdownd_send(client, dict); plist_free(dict); @@ -1514,11 +1465,11 @@ lockdownd_error_t lockdownd_deactivate(lockdownd_client_t client) return LOCKDOWN_E_PLIST_ERROR; } - ret = LOCKDOWN_E_UNKNOWN_ERROR; - if (lockdown_check_result(dict, "Deactivate") == RESULT_SUCCESS) { + ret = lockdown_check_result(dict, "Deactivate"); + if (ret == LOCKDOWN_E_SUCCESS) { debug_info("success"); - ret = LOCKDOWN_E_SUCCESS; } + plist_free(dict); dict = NULL; @@ -1537,19 +1488,6 @@ static void str_remove_spaces(char *source) *dest = 0; } -/** - * Calculates and returns the data classes the device supports from lockdownd. - * - * @param client An initialized lockdownd client. - * @param classes A pointer to store an array of class names. The caller is responsible - * for freeing the memory which can be done using mobilesync_data_classes_free(). - * @param count The number of items in the classes array. - * - * @return LOCKDOWN_E_SUCCESS on success, - * LOCKDOWN_E_INVALID_ARG when client is NULL, - * LOCKDOWN_E_NO_RUNNING_SESSION if no session is open, - * LOCKDOWN_E_PLIST_ERROR if the received plist is broken - */ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, char ***classes, int *count) { if (!client) @@ -1583,14 +1521,16 @@ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, cha } while((value = plist_array_get_item(dict, *count)) != NULL) { - plist_get_string_val(value, &val); - newlist = realloc(*classes, sizeof(char*) * (*count+1)); - str_remove_spaces(val); - asprintf(&newlist[*count], "com.apple.%s", val); - free(val); - val = NULL; - *classes = newlist; - *count = *count+1; + plist_get_string_val(value, &val); + newlist = realloc(*classes, sizeof(char*) * (*count+1)); + str_remove_spaces(val); + if (asprintf(&newlist[*count], "com.apple.%s", val) < 0) { + debug_info("ERROR: asprintf failed"); + } + free(val); + val = NULL; + *classes = newlist; + *count = *count+1; } newlist = realloc(*classes, sizeof(char*) * (*count+1)); @@ -1603,14 +1543,6 @@ lockdownd_error_t lockdownd_get_sync_data_classes(lockdownd_client_t client, cha return LOCKDOWN_E_SUCCESS; } - -/** - * Frees memory of an allocated array of data classes as returned by lockdownd_get_sync_data_classes() - * - * @param classes An array of class names to free. - * - * @return LOCKDOWN_E_SUCCESS on success - */ lockdownd_error_t lockdownd_data_classes_free(char **classes) { if (classes) { @@ -1622,3 +1554,51 @@ lockdownd_error_t lockdownd_data_classes_free(char **classes) } return LOCKDOWN_E_SUCCESS; } + +lockdownd_error_t lockdownd_service_descriptor_free(lockdownd_service_descriptor_t service) +{ + if (service) { + free(service->identifier); + free(service); + } + + return LOCKDOWN_E_SUCCESS; +} + +const char* lockdownd_strerror(lockdownd_error_t err) +{ + switch (err) { + case LOCKDOWN_E_SUCCESS: + return "Success"; + case LOCKDOWN_E_INVALID_ARG: + return "Invalid argument"; + case LOCKDOWN_E_INVALID_CONF: + return "Invalid configuration"; + case LOCKDOWN_E_PLIST_ERROR: + return "PropertyList error"; + case LOCKDOWN_E_PAIRING_FAILED: + return "Pairing failed"; + case LOCKDOWN_E_SSL_ERROR: + return "SSL error"; + case LOCKDOWN_E_DICT_ERROR: + return "Invalid dictionary"; + case LOCKDOWN_E_RECEIVE_TIMEOUT: + return "Receive timeout"; + case LOCKDOWN_E_MUX_ERROR: + return "Mux error"; + case LOCKDOWN_E_NO_RUNNING_SESSION: + return "No running session"; + case LOCKDOWN_E_UNKNOWN_ERROR: + return "Unknown Error"; + default: { + int i = 0; + while (lockdownd_error_str_map[i].lockdown_errstr) { + if (lockdownd_error_str_map[i].errcode == err) { + return lockdownd_error_str_map[i].errstr; + } + i++; + } + } break; + } + return "Unknown Error"; +} diff --git a/src/lockdown.h b/src/lockdown.h index a25e59d..ba291ec 100644 --- a/src/lockdown.h +++ b/src/lockdown.h @@ -1,7 +1,8 @@ /* - * lockdownd.h + * lockdown.h * Defines lockdown stuff, like the client struct. * + * Copyright (c) 2014 Martin Szulecki All Rights Reserved. * Copyright (c) 2008 Zach C. All Rights Reserved. * * This library is free software; you can redistribute it and/or @@ -19,24 +20,25 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef LOCKDOWND_H -#define LOCKDOWND_H - -#include <gnutls/gnutls.h> +#ifndef __LOCKDOWND_H +#define __LOCKDOWND_H +#include "idevice.h" #include "libimobiledevice/lockdown.h" #include "property_list_service.h" +#define LOCKDOWN_PROTOCOL_VERSION "2" + struct lockdownd_client_private { property_list_service_client_t parent; int ssl_enabled; char *session_id; - char *uuid; char *label; + idevice_t device; + unsigned char* cu_key; + unsigned int cu_key_len; }; -lockdownd_error_t lockdownd_get_device_public_key(lockdownd_client_t client, gnutls_datum_t * public_key); -lockdownd_error_t lockdownd_gen_pair_cert(gnutls_datum_t public_key, gnutls_datum_t * device_cert, - gnutls_datum_t * host_cert, gnutls_datum_t * root_cert); +lockdownd_error_t lockdown_check_result(plist_t dict, const char *query_match); #endif diff --git a/src/misagent.c b/src/misagent.c new file mode 100644 index 0000000..e3da997 --- /dev/null +++ b/src/misagent.c @@ -0,0 +1,290 @@ +/* + * misagent.c + * com.apple.misagent service implementation. + * + * Copyright (c) 2012 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <plist/plist.h> +#include <stdio.h> + +#include "misagent.h" +#include "property_list_service.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a misagent_error_t + * value. Used internally to get correct error codes. + * + * @param err A property_list_service_error_t error code + * + * @return A matching misagent_error_t error code, + * MISAGENT_E_UNKNOWN_ERROR otherwise. + */ +static misagent_error_t misagent_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return MISAGENT_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return MISAGENT_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return MISAGENT_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return MISAGENT_E_CONN_FAILED; + default: + break; + } + return MISAGENT_E_UNKNOWN_ERROR; +} + +/** + * Checks the response from misagent to determine if the operation + * was successful or an error occurred. Internally used only. + * + * @param response a PLIST_DICT received from device's misagent + * @param status_code pointer to an int that will be set to the status code + * contained in the response + */ +static misagent_error_t misagent_check_result(plist_t response, int* status_code) +{ + if (plist_get_node_type(response) != PLIST_DICT) { + return MISAGENT_E_PLIST_ERROR; + } + + plist_t node = plist_dict_get_item(response, "Status"); + if (!node || (plist_get_node_type(node) != PLIST_UINT)) { + return MISAGENT_E_PLIST_ERROR; + } + + uint64_t val = -1LL; + plist_get_uint_val(node, &val); + if ((int64_t)val == -1LL) { + return MISAGENT_E_PLIST_ERROR; + } + *status_code = (int)(val & 0xFFFFFFFF); + if (*status_code == 0) { + return MISAGENT_E_SUCCESS; + } + return MISAGENT_E_REQUEST_FAILED; +} + +misagent_error_t misagent_client_new(idevice_t device, lockdownd_service_descriptor_t service, misagent_client_t *client) +{ + property_list_service_client_t plistclient = NULL; + misagent_error_t err = misagent_error(property_list_service_client_new(device, service, &plistclient)); + if (err != MISAGENT_E_SUCCESS) { + return err; + } + + misagent_client_t client_loc = (misagent_client_t) malloc(sizeof(struct misagent_client_private)); + client_loc->parent = plistclient; + client_loc->last_error = 0; + + *client = client_loc; + return MISAGENT_E_SUCCESS; +} + +misagent_error_t misagent_client_start_service(idevice_t device, misagent_client_t * client, const char* label) +{ + misagent_error_t err = MISAGENT_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MISAGENT_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(misagent_client_new), &err); + return err; +} + +misagent_error_t misagent_client_free(misagent_client_t client) +{ + if (!client) + return MISAGENT_E_INVALID_ARG; + + misagent_error_t err = MISAGENT_E_SUCCESS; + if (client->parent && client->parent->parent) { + misagent_error(property_list_service_client_free(client->parent)); + } + client->parent = NULL; + free(client); + + return err; +} + +misagent_error_t misagent_install(misagent_client_t client, plist_t profile) +{ + if (!client || !client->parent || !profile || (plist_get_node_type(profile) != PLIST_DATA)) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Install")); + plist_dict_set_item(dict, "Profile", plist_copy(profile)); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + plist_free(dict); + + return res; +} + +misagent_error_t misagent_copy(misagent_client_t client, plist_t* profiles) +{ + if (!client || !client->parent || !profiles) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Copy")); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + if (res == MISAGENT_E_SUCCESS) { + *profiles = plist_copy(plist_dict_get_item(dict, "Payload")); + } + plist_free(dict); + + return res; + +} + +misagent_error_t misagent_copy_all(misagent_client_t client, plist_t* profiles) +{ + if (!client || !client->parent || !profiles) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("CopyAll")); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + if (res == MISAGENT_E_SUCCESS) { + *profiles = plist_copy(plist_dict_get_item(dict, "Payload")); + } + plist_free(dict); + + return res; + +} + +misagent_error_t misagent_remove(misagent_client_t client, const char* profileID) +{ + if (!client || !client->parent || !profileID) + return MISAGENT_E_INVALID_ARG; + + client->last_error = MISAGENT_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "MessageType", plist_new_string("Remove")); + plist_dict_set_item(dict, "ProfileID", plist_new_string(profileID)); + plist_dict_set_item(dict, "ProfileType", plist_new_string("Provisioning")); + + misagent_error_t res = misagent_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + dict = NULL; + + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + return res; + } + + res = misagent_error(property_list_service_receive_plist(client->parent, &dict)); + if (res != MISAGENT_E_SUCCESS) { + debug_info("could not receive response, error %d", res); + return res; + } + if (!dict) { + debug_info("could not get response plist"); + return MISAGENT_E_UNKNOWN_ERROR; + } + + res = misagent_check_result(dict, &client->last_error); + plist_free(dict); + + return res; +} + +int misagent_get_status_code(misagent_client_t client) +{ + if (!client) { + return -1; + } + return client->last_error; +} diff --git a/src/misagent.h b/src/misagent.h new file mode 100644 index 0000000..e394087 --- /dev/null +++ b/src/misagent.h @@ -0,0 +1,34 @@ +/* + * misagent.h + * com.apple.misagent service header file. + * + * Copyright (c) 2012 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 __MISAGENT_H +#define __MISAGENT_H + +#include "idevice.h" +#include "libimobiledevice/misagent.h" +#include "property_list_service.h" + +struct misagent_client_private { + property_list_service_client_t parent; + int last_error; +}; + +#endif diff --git a/src/mobile_image_mounter.c b/src/mobile_image_mounter.c index 367bee0..6df50c4 100644 --- a/src/mobile_image_mounter.c +++ b/src/mobile_image_mounter.c @@ -2,23 +2,26 @@ * mobile_image_mounter.c * com.apple.mobile.mobile_image_mounter service implementation. * - * Copyright (c) 2010 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2010-2019 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 + * 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 <unistd.h> @@ -26,7 +29,7 @@ #include "mobile_image_mounter.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" /** * Locks a mobile_image_mounter client, used for thread safety. @@ -35,17 +38,17 @@ */ static void mobile_image_mounter_lock(mobile_image_mounter_client_t client) { - g_mutex_lock(client->mutex); + mutex_lock(&client->mutex); } /** * Unlocks a mobile_image_mounter client, used for thread safety. - * + * * @param client mobile_image_mounter client to unlock */ static void mobile_image_mounter_unlock(mobile_image_mounter_client_t client) { - g_mutex_unlock(client->mutex); + mutex_unlock(&client->mutex); } /** @@ -75,51 +78,30 @@ static mobile_image_mounter_error_t mobile_image_mounter_error(property_list_ser return MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; } -/** - * Connects to the mobile_image_mounter service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * mobile_image_mounter_client_t upon successful return. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if device is NULL, - * or MOBILE_IMAGE_MOUNTER_E_CONN_FAILED if the connection to the - * device could not be established. - */ -mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, uint16_t port, mobile_image_mounter_client_t *client) +mobile_image_mounter_error_t mobile_image_mounter_new(idevice_t device, lockdownd_service_descriptor_t service, mobile_image_mounter_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return MOBILE_IMAGE_MOUNTER_E_CONN_FAILED; + mobile_image_mounter_error_t err = mobile_image_mounter_error(property_list_service_client_new(device, service, &plistclient)); + if (err != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + return err; } mobile_image_mounter_client_t client_loc = (mobile_image_mounter_client_t) malloc(sizeof(struct mobile_image_mounter_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return MOBILE_IMAGE_MOUNTER_E_SUCCESS; } -/** - * Disconnects a mobile_image_mounter client from the device and frees up the - * mobile_image_mounter client data. - * - * @param client The mobile_image_mounter client to disconnect and free. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * or MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is NULL. - */ +mobile_image_mounter_error_t mobile_image_mounter_start_service(idevice_t device, mobile_image_mounter_client_t * client, const char* label) +{ + mobile_image_mounter_error_t err = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILE_IMAGE_MOUNTER_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobile_image_mounter_new), &err); + return err; +} + mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_client_t client) { if (!client) @@ -127,27 +109,12 @@ mobile_image_mounter_error_t mobile_image_mounter_free(mobile_image_mounter_clie property_list_service_client_free(client->parent); client->parent = NULL; - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return MOBILE_IMAGE_MOUNTER_E_SUCCESS; } -/** - * Tells if the image of ImageType is already mounted. - * - * @param client The client use - * @param image_type The type of the image to look up - * @param result Pointer to a plist that will receive the result of the - * operation. - * - * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the - * operation has failed. Check the resulting plist for further information. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, or an error code on error - */ mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_mounter_client_t client, const char *image_type, plist_t *result) { if (!client || !image_type || !result) { @@ -156,8 +123,8 @@ mobile_image_mounter_error_t mobile_image_mounter_lookup_image(mobile_image_moun mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("LookupImage")); - plist_dict_insert_item(dict,"ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict,"Command", plist_new_string("LookupImage")); + plist_dict_set_item(dict,"ImageType", plist_new_string(image_type)); mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -177,39 +144,139 @@ leave_unlock: return res; } -/** - * Mounts an image on the device. - * - * @param client The connected mobile_image_mounter client. - * @param image_path The absolute path of the image to mount. The image must - * be present before calling this function. - * @param image_signature Pointer to a buffer holding the images' signature - * @param signature_length Length of the signature image_signature points to - * @param image_type Type of image to mount - * @param result Pointer to a plist that will receive the result of the - * operation. - * - * @note This function may return MOBILE_IMAGE_MOUNTER_E_SUCCESS even if the - * operation has failed. Check the resulting plist for further information. - * Note that there is no unmounting function. The mount persists until the - * device is rebooted. - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if on ore more parameters are - * invalid, or another error code otherwise. - */ -mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const char *image_signature, uint16_t signature_length, const char *image_type, plist_t *result) +static mobile_image_mounter_error_t process_result(plist_t result, const char *expected_status) +{ + mobile_image_mounter_error_t res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + char* strval = NULL; + plist_t node; + + node = plist_dict_get_item(result, "Error"); + if (node && plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &strval); + } + if (strval) { + if (!strcmp(strval, "DeviceLocked")) { + debug_info("Device is locked, can't mount"); + res = MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED; + } else { + debug_info("Unhandled error '%s' received", strval); + } + free(strval); + return res; + } + + node = plist_dict_get_item(result, "Status"); + if (node && plist_get_node_type(node) == PLIST_STRING) { + plist_get_string_val(node, &strval); + } + if (!strval) { + debug_info("Error: Unexpected response received!"); + } else if (strcmp(strval, expected_status) == 0) { + res = MOBILE_IMAGE_MOUNTER_E_SUCCESS; + } else { + debug_info("Error: didn't get %s but %s", expected_status, strval); + } + free(strval); + + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_upload_image(mobile_image_mounter_client_t client, const char *image_type, size_t image_size, const unsigned char *signature, unsigned int signature_size, mobile_image_mounter_upload_cb_t upload_cb, void* userdata) +{ + if (!client || !image_type || (image_size == 0) || !upload_cb) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + plist_t result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("ReceiveBytes")); + if (signature && signature_size != 0) + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + plist_dict_set_item(dict, "ImageSize", plist_new_uint(image_size)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error sending XML plist to device!"); + goto leave_unlock; + } + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error receiving response from device!"); + goto leave_unlock; + } + res = process_result(result, "ReceiveBytesAck"); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + goto leave_unlock; + } + + size_t tx = 0; + size_t bufsize = 65536; + unsigned char *buf = (unsigned char*)malloc(bufsize); + if (!buf) { + debug_info("Out of memory"); + res = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + goto leave_unlock; + } + debug_info("uploading image (%d bytes)", (int)image_size); + while (tx < image_size) { + size_t remaining = image_size - tx; + size_t amount = (remaining < bufsize) ? remaining : bufsize; + ssize_t r = upload_cb(buf, amount, userdata); + if (r < 0) { + debug_info("upload_cb returned %d", (int)r); + break; + } + uint32_t sent = 0; + if (service_send(client->parent->parent, (const char*)buf, (uint32_t)r, &sent) != SERVICE_E_SUCCESS) { + debug_info("service_send failed"); + break; + } + tx += r; + } + free(buf); + if (tx < image_size) { + debug_info("Error: failed to upload image"); + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + goto leave_unlock; + } + debug_info("image uploaded"); + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("Error receiving response from device!"); + goto leave_unlock; + } + res = process_result(result, "Complete"); + +leave_unlock: + mobile_image_mounter_unlock(client); + if (result) + plist_free(result); + return res; + +} + +mobile_image_mounter_error_t mobile_image_mounter_mount_image_with_options(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t options, plist_t *result) { - if (!client || !image_path || !image_signature || (signature_length == 0) || !image_type || !result) { + if (!client || !image_path || !image_type || !result) { return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; } mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string("MountImage")); - plist_dict_insert_item(dict, "ImagePath", plist_new_string(image_path)); - plist_dict_insert_item(dict, "ImageSignature", plist_new_data(image_signature, signature_length)); - plist_dict_insert_item(dict, "ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "Command", plist_new_string("MountImage")); + plist_dict_set_item(dict, "ImagePath", plist_new_string(image_path)); + if (signature && signature_size != 0) + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + if (PLIST_IS_DICT(options)) { + plist_dict_merge(&dict, options); + } mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -229,17 +296,56 @@ leave_unlock: return res; } -/** - * Hangs up the connection to the mobile_image_mounter service. - * This functions has to be called before freeing up a mobile_image_mounter - * instance. If not, errors appear in the device's syslog. - * - * @param client The client to hang up - * - * @return MOBILE_IMAGE_MOUNTER_E_SUCCESS on success, - * MOBILE_IMAGE_MOUNTER_E_INVALID_ARG if client is invalid, - * or another error code otherwise. - */ +mobile_image_mounter_error_t mobile_image_mounter_mount_image(mobile_image_mounter_client_t client, const char *image_path, const unsigned char *signature, unsigned int signature_size, const char *image_type, plist_t *result) +{ + return mobile_image_mounter_mount_image_with_options(client, image_path, signature, signature_size, image_type, NULL, result); +} + +mobile_image_mounter_error_t mobile_image_mounter_unmount_image(mobile_image_mounter_client_t client, const char *mount_path) +{ + if (!client || !mount_path) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("UnmountImage")); + plist_dict_set_item(dict, "MountPath", plist_new_string(mount_path)); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_error = plist_dict_get_item(result, "Error"); + if (p_error) { + plist_t p_detailed = plist_dict_get_item(result, "DetailedError"); + const char* detailederr = (p_detailed) ? plist_get_string_ptr(p_detailed, NULL) : ""; + const char* errstr = plist_get_string_ptr(p_error, NULL); + if (errstr && !strcmp(errstr, "UnknownCommand")) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else if (errstr && !strcmp(errstr, "DeviceLocked")) { + res = MOBILE_IMAGE_MOUNTER_E_DEVICE_LOCKED; + } else if (strstr(detailederr, "no matching entry")) { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } else { + res = MOBILE_IMAGE_MOUNTER_E_UNKNOWN_ERROR; + } + } + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_client_t client) { if (!client) { @@ -248,7 +354,7 @@ mobile_image_mounter_error_t mobile_image_mounter_hangup(mobile_image_mounter_cl mobile_image_mounter_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "Command", plist_new_string("Hangup")); + plist_dict_set_item(dict, "Command", plist_new_string("Hangup")); mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); @@ -272,3 +378,215 @@ leave_unlock: mobile_image_mounter_unlock(client); return res; } + +mobile_image_mounter_error_t mobile_image_mounter_query_developer_mode_status(mobile_image_mounter_client_t client, plist_t *result) +{ + if (!client || !result) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryDeveloperModeStatus")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_nonce(mobile_image_mounter_client_t client, const char* image_type, unsigned char** nonce, unsigned int* nonce_size) +{ + if (!client || !nonce || !nonce_size) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryNonce")); + if (image_type) { + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + } + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_nonce = plist_dict_get_item(result, "PersonalizationNonce"); + if (!p_nonce) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else { + uint64_t nonce_size_ = 0; + plist_get_data_val(p_nonce, (char**)nonce, &nonce_size_); + if (*nonce) { + *nonce_size = (unsigned int)nonce_size_; + } else { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + } + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_personalization_identifiers(mobile_image_mounter_client_t client, const char* image_type, plist_t *result) +{ + if (!client || !result) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryPersonalizationIdentifiers")); + if (image_type) { + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + } + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t _result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &_result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + *result = plist_copy(plist_dict_get_item(_result, "PersonalizationIdentifiers")); + if (!*result) { + debug_info("%s: Response did not contain PersonalizationIdentifiers!", __func__); + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_query_personalization_manifest(mobile_image_mounter_client_t client, const char* image_type, const unsigned char* signature, unsigned int signature_size, unsigned char** manifest, unsigned int* manifest_size) +{ + if (!client || !image_type || !signature || !signature_size || !manifest || !manifest_size) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("QueryPersonalizationManifest")); + plist_dict_set_item(dict, "PersonalizedImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "ImageType", plist_new_string(image_type)); + plist_dict_set_item(dict, "ImageSignature", plist_new_data((char*)signature, signature_size)); + + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } else { + plist_t p_manifest = plist_dict_get_item(result, "ImageSignature"); + if (!p_manifest) { + res = MOBILE_IMAGE_MOUNTER_E_NOT_SUPPORTED; + } else { + uint64_t manifest_size_ = 0; + plist_get_data_val(p_manifest, (char**)manifest, &manifest_size_); + if (*manifest) { + *manifest_size = (unsigned int)manifest_size_; + } else { + res = MOBILE_IMAGE_MOUNTER_E_COMMAND_FAILED; + } + } + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_roll_personalization_nonce(mobile_image_mounter_client_t client) +{ + if (!client) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("RollPersonalizationNonce")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} + +mobile_image_mounter_error_t mobile_image_mounter_roll_cryptex_nonce(mobile_image_mounter_client_t client) +{ + if (!client) { + return MOBILE_IMAGE_MOUNTER_E_INVALID_ARG; + } + mobile_image_mounter_lock(client); + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("RollCryptexNonce")); + mobile_image_mounter_error_t res = mobile_image_mounter_error(property_list_service_send_xml_plist(client->parent, dict)); + plist_free(dict); + + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error sending XML plist to device!", __func__); + goto leave_unlock; + } + + plist_t result = NULL; + res = mobile_image_mounter_error(property_list_service_receive_plist(client->parent, &result)); + if (res != MOBILE_IMAGE_MOUNTER_E_SUCCESS) { + debug_info("%s: Error receiving response from device!", __func__); + } + plist_free(result); + +leave_unlock: + mobile_image_mounter_unlock(client); + return res; +} diff --git a/src/mobile_image_mounter.h b/src/mobile_image_mounter.h index 2615dbc..9a8fcdd 100644 --- a/src/mobile_image_mounter.h +++ b/src/mobile_image_mounter.h @@ -8,27 +8,28 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef IMOBILE_IMAGE_MOUNTER_H -#define IMOBILE_IMAGE_MOUNTER_H -#include <glib.h> +#ifndef __MOBILE_IMAGE_MOUNTER_H +#define __MOBILE_IMAGE_MOUNTER_H +#include "idevice.h" #include "libimobiledevice/mobile_image_mounter.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct mobile_image_mounter_client_private { property_list_service_client_t parent; - GMutex *mutex; + mutex_t mutex; }; #endif diff --git a/src/mobileactivation.c b/src/mobileactivation.c new file mode 100644 index 0000000..fce5f16 --- /dev/null +++ b/src/mobileactivation.c @@ -0,0 +1,314 @@ +/* + * mobileactivation.c + * com.apple.mobileactivationd service implementation. + * + * Copyright (c) 2016-2017 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include "mobileactivation.h" +#include "property_list_service.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a mobileactivation_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching mobileactivation_error_t error code, + * MOBILEACTIVATION_E_UNKNOWN_ERROR otherwise. + */ +static mobileactivation_error_t mobileactivation_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return MOBILEACTIVATION_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return MOBILEACTIVATION_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return MOBILEACTIVATION_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return MOBILEACTIVATION_E_MUX_ERROR; + default: + break; + } + return MOBILEACTIVATION_E_UNKNOWN_ERROR; +} + +mobileactivation_error_t mobileactivation_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobileactivation_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) { + return MOBILEACTIVATION_E_INVALID_ARG; + } + + property_list_service_client_t plistclient = NULL; + if (property_list_service_client_new(device, service, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return MOBILEACTIVATION_E_MUX_ERROR; + } + + /* create client object */ + mobileactivation_client_t client_loc = (mobileactivation_client_t) malloc(sizeof(struct mobileactivation_client_private)); + client_loc->parent = plistclient; + + /* all done, return success */ + *client = client_loc; + return MOBILEACTIVATION_E_SUCCESS; +} + +mobileactivation_error_t mobileactivation_client_start_service(idevice_t device, mobileactivation_client_t * client, const char* label) +{ + mobileactivation_error_t err = MOBILEACTIVATION_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEACTIVATION_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobileactivation_client_new), &err); + return err; +} + +mobileactivation_error_t mobileactivation_client_free(mobileactivation_client_t client) +{ + if (!client) + return MOBILEACTIVATION_E_INVALID_ARG; + + if (property_list_service_client_free(client->parent) != PROPERTY_LIST_SERVICE_E_SUCCESS) { + return MOBILEACTIVATION_E_UNKNOWN_ERROR; + } + free(client); + return MOBILEACTIVATION_E_SUCCESS; +} + +static plist_t plist_data_from_plist(plist_t plist) +{ + if (plist && plist_get_node_type(plist) == PLIST_DATA) { + return plist_copy(plist); + } + plist_t result = NULL; + char *xml = NULL; + uint32_t xml_len = 0; + plist_to_xml(plist, &xml, &xml_len); + result = plist_new_data(xml, xml_len); + free(xml); + return result; +} + +static mobileactivation_error_t mobileactivation_check_result(plist_t dict, const char *command) +{ + if (!dict || plist_get_node_type(dict) != PLIST_DICT) { + return MOBILEACTIVATION_E_PLIST_ERROR; + } + + plist_t err_node = plist_dict_get_item(dict, "Error"); + if (!err_node) { + return MOBILEACTIVATION_E_SUCCESS; + } + + char *errmsg = NULL; + plist_get_string_val(err_node, &errmsg); + debug_info("ERROR: %s: %s", command, errmsg); + free(errmsg); + return MOBILEACTIVATION_E_REQUEST_FAILED; +} + +static mobileactivation_error_t mobileactivation_send_command_plist(mobileactivation_client_t client, plist_t command, plist_t *result) +{ + if (!client || !command) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t cmd = plist_dict_get_item(command, "Command"); + char* command_str = NULL; + if (cmd) { + plist_get_string_val(cmd, &command_str); + } + if (!command_str) + return MOBILEACTIVATION_E_INVALID_ARG; + + mobileactivation_error_t ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + *result = NULL; + + ret = mobileactivation_error(property_list_service_send_binary_plist(client->parent, command)); + + plist_t dict = NULL; + ret = mobileactivation_error(property_list_service_receive_plist(client->parent, &dict)); + if (!dict) { + debug_info("ERROR: Did not get reply for %s command", command_str); + free(command_str); + return MOBILEACTIVATION_E_PLIST_ERROR; + } + + *result = dict; + ret = mobileactivation_check_result(dict, command_str); + free(command_str); + return ret; +} + +static mobileactivation_error_t mobileactivation_send_command(mobileactivation_client_t client, const char* command, plist_t value, plist_t *result) +{ + if (!client || !command || !result) + return MOBILEACTIVATION_E_INVALID_ARG; + + mobileactivation_error_t ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + *result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string(command)); + if (value) { + plist_dict_set_item(dict, "Value", plist_copy(value)); + } + + ret = mobileactivation_send_command_plist(client, dict, result); + plist_free(dict); + return ret; +} + +mobileactivation_error_t mobileactivation_get_activation_state(mobileactivation_client_t client, plist_t *state) +{ + if (!client || !state) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "GetActivationStateRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: GetActivationStateRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *state = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_session_info(mobileactivation_client_t client, plist_t *blob) +{ + if (!client || !blob) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateTunnel1SessionInfoRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateTunnel1SessionInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *blob = plist_copy(node); + } + } + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_info(mobileactivation_client_t client, plist_t *info) +{ + if (!client || !info) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateActivationInfoRequest", NULL, &result); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateActivationInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *info = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_create_activation_info_with_session(mobileactivation_client_t client, plist_t handshake_response, plist_t *info) +{ + if (!client || !info) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + plist_t data = plist_data_from_plist(handshake_response); + mobileactivation_error_t ret = mobileactivation_send_command(client, "CreateTunnel1ActivationInfoRequest", data, &result); + plist_free(data); + if (ret == MOBILEACTIVATION_E_SUCCESS) { + plist_t node = plist_dict_get_item(result, "Value"); + if (!node) { + debug_info("ERROR: CreateTunnel1ActivationInfoRequest command returned success but has no value in reply"); + ret = MOBILEACTIVATION_E_UNKNOWN_ERROR; + } else { + *info = plist_copy(node); + } + } + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_activate(mobileactivation_client_t client, plist_t activation_record) +{ + if (!client || !activation_record) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "HandleActivationInfoRequest", activation_record, &result); + plist_free(result); + result = NULL; + + return ret; +} + +mobileactivation_error_t mobileactivation_activate_with_session(mobileactivation_client_t client, plist_t activation_record, plist_t headers) +{ + if (!client || !activation_record) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("HandleActivationInfoWithSessionRequest")); + plist_dict_set_item(dict, "Value", plist_data_from_plist(activation_record)); + if (headers) { + plist_dict_set_item(dict, "ActivationResponseHeaders", plist_copy(headers)); + } + + mobileactivation_error_t ret = mobileactivation_send_command_plist(client, dict, &result); + plist_free(dict); + plist_free(result); + result = NULL; + + return ret; +} + + +mobileactivation_error_t mobileactivation_deactivate(mobileactivation_client_t client) +{ + if (!client) + return MOBILEACTIVATION_E_INVALID_ARG; + + plist_t result = NULL; + mobileactivation_error_t ret = mobileactivation_send_command(client, "DeactivateRequest", NULL, &result); + plist_free(result); + result = NULL; + + return ret; +} diff --git a/src/mobileactivation.h b/src/mobileactivation.h new file mode 100644 index 0000000..a8dff5d --- /dev/null +++ b/src/mobileactivation.h @@ -0,0 +1,33 @@ +/* + * mobileactivation.h + * com.apple.mobileactivationd service header file. + * + * Copyright (c) 2016 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 __MOBILEACTIVATION_H +#define __MOBILEACTIVATION_H + +#include "idevice.h" +#include "libimobiledevice/mobileactivation.h" +#include "property_list_service.h" + +struct mobileactivation_client_private { + property_list_service_client_t parent; +}; + +#endif diff --git a/src/mobilebackup.c b/src/mobilebackup.c index fcff60d..36986a4 100644 --- a/src/mobilebackup.c +++ b/src/mobilebackup.c @@ -1,36 +1,41 @@ /* - * mobilebackup.c + * mobilebackup.c * Contains functions for the built-in MobileBackup client. - * + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <plist/plist.h> #include <string.h> #include <stdlib.h> +#include <stdio.h> #include "mobilebackup.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" #define MBACKUP_VERSION_INT1 100 #define MBACKUP_VERSION_INT2 0 -#define IS_FLAG_SET(x, y) ((x & y) == y) +#define IS_FLAG_SET(x, y) (((x) & (y)) == (y)) /** * Convert an device_link_service_error_t value to an mobilebackup_error_t value. @@ -52,6 +57,10 @@ static mobilebackup_error_t mobilebackup_error(device_link_service_error_t err) return MOBILEBACKUP_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return MOBILEBACKUP_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILEBACKUP_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILEBACKUP_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return MOBILEBACKUP_E_BAD_VERSION; default: @@ -60,26 +69,13 @@ static mobilebackup_error_t mobilebackup_error(device_link_service_error_t err) return MOBILEBACKUP_E_UNKNOWN_ERROR; } -/** - * Connects to the mobilebackup service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * mobilebackup_client_t upon successful return. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID ARG if one - * or more parameters are invalid, or DEVICE_LINK_SERVICE_E_BAD_VERSION if - * the mobilebackup version on the device is newer. - */ -mobilebackup_error_t mobilebackup_client_new(idevice_t device, uint16_t port, - mobilebackup_client_t * client) +mobilebackup_error_t mobilebackup_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilebackup_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return MOBILEBACKUP_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - mobilebackup_error_t ret = mobilebackup_error(device_link_service_client_new(device, port, &dlclient)); + mobilebackup_error_t ret = mobilebackup_error(device_link_service_client_new(device, service, &dlclient)); if (ret != MOBILEBACKUP_E_SUCCESS) { return ret; } @@ -100,36 +96,26 @@ mobilebackup_error_t mobilebackup_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a mobilebackup client from the device and frees up the - * mobilebackup client data. - * - * @param client The mobilebackup client to disconnect and free. - * - * @return MOBILEBACKUP_E_SUCCESS on success, or MOBILEBACKUP_E_INVALID_ARG - * if client is NULL. - */ +mobilebackup_error_t mobilebackup_client_start_service(idevice_t device, mobilebackup_client_t * client, const char* label) +{ + mobilebackup_error_t err = MOBILEBACKUP_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEBACKUP_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilebackup_client_new), &err); + return err; +} + mobilebackup_error_t mobilebackup_client_free(mobilebackup_client_t client) { if (!client) return MOBILEBACKUP_E_INVALID_ARG; mobilebackup_error_t err = MOBILEBACKUP_E_SUCCESS; if (client->parent) { - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, NULL); err = mobilebackup_error(device_link_service_client_free(client->parent)); } free(client); return err; } -/** - * Polls the device for mobilebackup data. - * - * @param client The mobilebackup client - * @param plist A pointer to the location where the plist should be stored - * - * @return an error code - */ mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t * plist) { if (!client) @@ -138,17 +124,6 @@ mobilebackup_error_t mobilebackup_receive(mobilebackup_client_t client, plist_t return ret; } -/** - * Sends mobilebackup data to the device - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The mobilebackup client - * @param plist The location of the plist to send - * - * @return an error code - */ mobilebackup_error_t mobilebackup_send(mobilebackup_client_t client, plist_t plist) { if (!client || !plist) @@ -186,7 +161,7 @@ static mobilebackup_error_t mobilebackup_send_message(mobilebackup_client_t clie } else { dict = plist_new_dict(); } - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string(message)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string(message)); /* send it as DLMessageProcessMessage */ err = mobilebackup_error(device_link_service_send_process_message(client->parent, dict)); @@ -266,23 +241,6 @@ leave: return err; } -/** - * Request a backup from the connected device. - * - * @param client The connected MobileBackup client to use. - * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT - * containing the backup state of the last backup. For a first-time backup - * set this parameter to NULL. - * @param base_path The base path on the device to use for the backup - * operation, usually "/". - * @param proto_version A string denoting the version of the backup protocol - * to use. Latest known version is "1.6" - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if - * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR - * if a communication error occurs, MOBILEBACKUP_E_REPLY_NOT_OK - */ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, plist_t backup_manifest, const char *base_path, const char *proto_version) { if (!client || !client->parent || !base_path || !proto_version) @@ -296,10 +254,10 @@ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, p /* construct request plist */ plist_t dict = plist_new_dict(); if (backup_manifest) - plist_dict_insert_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); - plist_dict_insert_item(dict, "BackupComputerBasePathKey", plist_new_string(base_path)); - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest")); - plist_dict_insert_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); + plist_dict_set_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); + plist_dict_set_item(dict, "BackupComputerBasePathKey", plist_new_string(base_path)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageBackupRequest")); + plist_dict_set_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); /* send request */ err = mobilebackup_send_message(client, NULL, dict); @@ -322,7 +280,15 @@ mobilebackup_error_t mobilebackup_request_backup(mobilebackup_client_t client, p char *str = NULL; plist_get_string_val(node, &str); if (str) { - if (strcmp(str, proto_version) != 0) { + int maj = 0; + int min = 0; + sscanf(str, "%u.%u", &maj, &min); + uint32_t this_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + maj = 0; + min = 0; + sscanf(proto_version, "%u.%u", &maj, &min); + uint32_t proto_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + if (this_ver > proto_ver) { err = MOBILEBACKUP_E_BAD_VERSION; } free(str); @@ -343,41 +309,11 @@ leave: return err; } -/** - * Sends a confirmation to the device that a backup file has been received. - * - * @param client The connected MobileBackup client to use. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_send_backup_file_received(mobilebackup_client_t client) { return mobilebackup_send_message(client, "kBackupMessageBackupFileReceived", NULL); } -/** - * Request that a backup should be restored to the connected device. - * - * @param client The connected MobileBackup client to use. - * @param backup_manifest The backup manifest, a plist_t of type PLIST_DICT - * containing the backup state to be restored. - * @param flags Flags to send with the request. Currently this is a combination - * of the following mobilebackup_flags_t: - * MB_RESTORE_NOTIFY_SPRINGBOARD - let SpringBoard show a 'Restore' screen - * MB_RESTORE_PRESERVE_SETTINGS - do not overwrite any settings - * MB_RESTORE_PRESERVE_CAMERA_ROLL - preserve the photos of the camera roll - * @param proto_version A string denoting the version of the backup protocol - * to use. Latest known version is "1.6". Ideally this value should be - * extracted from the given manifest plist. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, MOBILEBACKUP_E_PLIST_ERROR if - * backup_manifest is not of type PLIST_DICT, MOBILEBACKUP_E_MUX_ERROR - * if a communication error occurs, or MOBILEBACKUP_E_REPLY_NOT_OK - * if the device did not accept the request. - */ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, plist_t backup_manifest, mobilebackup_flags_t flags, const char *proto_version) { if (!client || !client->parent || !backup_manifest || !proto_version) @@ -390,13 +326,13 @@ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, /* construct request plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); - plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("kBackupMessageRestoreRequest")); - plist_dict_insert_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); + plist_dict_set_item(dict, "BackupManifestKey", plist_copy(backup_manifest)); + plist_dict_set_item(dict, "BackupMessageTypeKey", plist_new_string("kBackupMessageRestoreRequest")); + plist_dict_set_item(dict, "BackupProtocolVersion", plist_new_string(proto_version)); /* add flags */ - plist_dict_insert_item(dict, "BackupNotifySpringBoard", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_NOTIFY_SPRINGBOARD))); - plist_dict_insert_item(dict, "BackupPreserveSettings", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_SETTINGS))); - plist_dict_insert_item(dict, "BackupPreserveCameraRoll", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_CAMERA_ROLL))); + plist_dict_set_item(dict, "BackupNotifySpringBoard", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_NOTIFY_SPRINGBOARD))); + plist_dict_set_item(dict, "BackupPreserveSettings", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_SETTINGS))); + plist_dict_set_item(dict, "BackupPreserveCameraRoll", plist_new_bool(IS_FLAG_SET(flags, MB_RESTORE_PRESERVE_CAMERA_ROLL))); /* send request */ err = mobilebackup_send_message(client, NULL, dict); @@ -419,7 +355,15 @@ mobilebackup_error_t mobilebackup_request_restore(mobilebackup_client_t client, char *str = NULL; plist_get_string_val(node, &str); if (str) { - if (strcmp(str, proto_version) != 0) { + int maj = 0; + int min = 0; + sscanf(str, "%u.%u", &maj, &min); + uint32_t this_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + maj = 0; + min = 0; + sscanf(proto_version, "%u.%u", &maj, &min); + uint32_t proto_ver = ((maj & 0xFF) << 8) | (min & 0xFF); + if (this_ver > proto_ver) { err = MOBILEBACKUP_E_BAD_VERSION; } free(str); @@ -432,63 +376,16 @@ leave: return err; } -/** - * Receive a confirmation from the device that it successfully received - * a restore file. - * - * @param client The connected MobileBackup client to use. - * @param result Pointer to a plist_t that will be set to the received plist - * for further processing. The caller has to free it using plist_free(). - * Note that it will be set to NULL if the operation itself fails due to - * a communication or plist error. - * If this parameter is NULL, it will be ignored. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected - * 'BackupMessageRestoreFileReceived' message could not be received, - * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup - * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_receive_restore_file_received(mobilebackup_client_t client, plist_t *result) { return mobilebackup_receive_message(client, "BackupMessageRestoreFileReceived", result); } -/** - * Receive a confirmation from the device that it successfully received - * application data file. - * - * @param client The connected MobileBackup client to use. - * @param result Pointer to a plist_t that will be set to the received plist - * for further processing. The caller has to free it using plist_free(). - * Note that it will be set to NULL if the operation itself fails due to - * a communication or plist error. - * If this parameter is NULL, it will be ignored. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_REPLY_NOT_OK if the expected - * 'BackupMessageRestoreApplicationReceived' message could not be received, - * MOBILEBACKUP_E_PLIST_ERROR if the received message is not a valid backup - * message plist, or MOBILEBACKUP_E_MUX_ERROR if a communication error - * occurs. - */ mobilebackup_error_t mobilebackup_receive_restore_application_received(mobilebackup_client_t client, plist_t *result) { return mobilebackup_receive_message(client, "BackupMessageRestoreApplicationReceived", result); } -/** - * Tells the device that the restore process is complete and waits for the - * device to close the connection. After that, the device should reboot. - * - * @param client The connected MobileBackup client to use. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * client is invalid, MOBILEBACKUP_E_PLIST_ERROR if the received disconnect - * message plist is invalid, or MOBILEBACKUP_E_MUX_ERROR if a communication - * error occurs. - */ mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t client) { mobilebackup_error_t err = mobilebackup_send_message(client, "BackupMessageRestoreComplete", NULL); @@ -534,16 +431,6 @@ mobilebackup_error_t mobilebackup_send_restore_complete(mobilebackup_client_t cl return err; } -/** - * Sends a backup error message to the device. - * - * @param client The connected MobileBackup client to use. - * @param reason A string describing the reason for the error message. - * - * @return MOBILEBACKUP_E_SUCCESS on success, MOBILEBACKUP_E_INVALID_ARG if - * one of the parameters is invalid, or MOBILEBACKUP_E_MUX_ERROR if a - * communication error occurs. - */ mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const char *reason) { if (!client || !client->parent || !reason) @@ -553,7 +440,7 @@ mobilebackup_error_t mobilebackup_send_error(mobilebackup_client_t client, const /* construct error plist */ plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "BackupErrorReasonKey", plist_new_string(reason)); + plist_dict_set_item(dict, "BackupErrorReasonKey", plist_new_string(reason)); err = mobilebackup_send_message(client, "BackupMessageError", dict); plist_free(dict); diff --git a/src/mobilebackup.h b/src/mobilebackup.h index 2c5be62..04ec479 100644 --- a/src/mobilebackup.h +++ b/src/mobilebackup.h @@ -1,26 +1,29 @@ - /* +/* * mobilebackup.h * Definitions for the mobilebackup service - * + * + * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009 Martin Szulecki 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef MOBILEBACKUP_H -#define MOBILEBACKUP_H +#ifndef __MOBILEBACKUP_H +#define __MOBILEBACKUP_H + +#include "idevice.h" #include "libimobiledevice/mobilebackup.h" #include "device_link_service.h" diff --git a/src/mobilebackup2.c b/src/mobilebackup2.c new file mode 100644 index 0000000..a8d673f --- /dev/null +++ b/src/mobilebackup2.c @@ -0,0 +1,386 @@ +/* + * mobilebackup2.c + * Contains functions for the built-in MobileBackup2 client (iOS4+ only) + * + * Copyright (c) 2010-2019 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <plist/plist.h> +#include <string.h> +#include <stdlib.h> + +#include "mobilebackup2.h" +#include "device_link_service.h" +#include "common/debug.h" + +#define MBACKUP2_VERSION_INT1 400 +#define MBACKUP2_VERSION_INT2 0 + +#define IS_FLAG_SET(x, y) (((x) & (y)) == (y)) + +/** + * Convert an device_link_service_error_t value to an mobilebackup2_error_t value. + * Used internally to get correct error codes from the underlying + * device_link_service. + * + * @param err An device_link_service_error_t error code + * + * @return A matching mobilebackup2_error_t error code, + * MOBILEBACKUP2_E_UNKNOWN_ERROR otherwise. + */ +static mobilebackup2_error_t mobilebackup2_error(device_link_service_error_t err) +{ + switch (err) { + case DEVICE_LINK_SERVICE_E_SUCCESS: + return MOBILEBACKUP2_E_SUCCESS; + case DEVICE_LINK_SERVICE_E_INVALID_ARG: + return MOBILEBACKUP2_E_INVALID_ARG; + case DEVICE_LINK_SERVICE_E_PLIST_ERROR: + return MOBILEBACKUP2_E_PLIST_ERROR; + case DEVICE_LINK_SERVICE_E_MUX_ERROR: + return MOBILEBACKUP2_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILEBACKUP2_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILEBACKUP2_E_RECEIVE_TIMEOUT; + case DEVICE_LINK_SERVICE_E_BAD_VERSION: + return MOBILEBACKUP2_E_BAD_VERSION; + default: + break; + } + return MOBILEBACKUP2_E_UNKNOWN_ERROR; +} + +mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, lockdownd_service_descriptor_t service, + mobilebackup2_client_t * client) +{ + if (!device || !service || service->port == 0 || !client || *client) + return MOBILEBACKUP2_E_INVALID_ARG; + + device_link_service_client_t dlclient = NULL; + mobilebackup2_error_t ret = mobilebackup2_error(device_link_service_client_new(device, service, &dlclient)); + if (ret != MOBILEBACKUP2_E_SUCCESS) { + return ret; + } + + mobilebackup2_client_t client_loc = (mobilebackup2_client_t) malloc(sizeof(struct mobilebackup2_client_private)); + client_loc->parent = dlclient; + + /* perform handshake */ + ret = mobilebackup2_error(device_link_service_version_exchange(dlclient, MBACKUP2_VERSION_INT1, MBACKUP2_VERSION_INT2)); + if (ret != MOBILEBACKUP2_E_SUCCESS) { + debug_info("version exchange failed, error %d", ret); + mobilebackup2_client_free(client_loc); + return ret; + } + + *client = client_loc; + + return ret; +} + +mobilebackup2_error_t mobilebackup2_client_start_service(idevice_t device, mobilebackup2_client_t * client, const char* label) +{ + mobilebackup2_error_t err = MOBILEBACKUP2_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILEBACKUP2_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilebackup2_client_new), &err); + return err; +} + +mobilebackup2_error_t mobilebackup2_client_free(mobilebackup2_client_t client) +{ + if (!client) + return MOBILEBACKUP2_E_INVALID_ARG; + mobilebackup2_error_t err = MOBILEBACKUP2_E_SUCCESS; + if (client->parent) { + device_link_service_disconnect(client->parent, NULL); + err = mobilebackup2_error(device_link_service_client_free(client->parent)); + } + free(client); + return err; +} + +mobilebackup2_error_t mobilebackup2_send_message(mobilebackup2_client_t client, const char *message, plist_t options) +{ + if (!client || !client->parent || (!message && !options)) + return MOBILEBACKUP2_E_INVALID_ARG; + + if (options && (plist_get_node_type(options) != PLIST_DICT)) { + return MOBILEBACKUP2_E_INVALID_ARG; + } + + mobilebackup2_error_t err; + + if (message) { + plist_t dict = NULL; + if (options) { + dict = plist_copy(options); + } else { + dict = plist_new_dict(); + } + plist_dict_set_item(dict, "MessageName", plist_new_string(message)); + + /* send it as DLMessageProcessMessage */ + err = mobilebackup2_error(device_link_service_send_process_message(client->parent, dict)); + plist_free(dict); + } else { + err = mobilebackup2_error(device_link_service_send_process_message(client->parent, options)); + } + if (err != MOBILEBACKUP2_E_SUCCESS) { + debug_info("ERROR: Could not send message '%s' (%d)!", message, err); + } + return err; +} + +/** + * Receives a plist from the device and checks if the value for the + * MessageName key matches the value passed in the message parameter. + * + * @param client The connected MobileBackup client to use. + * @param message The expected message to check. + * @param result Pointer to a plist_t that will be set to the received plist + * for further processing. The caller has to free it using plist_free(). + * Note that it will be set to NULL if the operation itself fails due to + * a communication or plist error. + * If this parameter is NULL, it will be ignored. + * + * @return MOBILEBACKUP2_E_SUCCESS on success, MOBILEBACKUP2_E_INVALID_ARG if + * client or message is invalid, MOBILEBACKUP2_E_REPLY_NOT_OK if the + * expected message could not be received, MOBILEBACKUP2_E_PLIST_ERROR if + * the received message is not a valid backup message plist (i.e. the + * MessageName key is not present), or MOBILEBACKUP2_E_MUX_ERROR + * if a communication error occurs. + */ +static mobilebackup2_error_t internal_mobilebackup2_receive_message(mobilebackup2_client_t client, const char *message, plist_t *result) +{ + if (!client || !client->parent || !message) + return MOBILEBACKUP2_E_INVALID_ARG; + + if (result) + *result = NULL; + mobilebackup2_error_t err; + + plist_t dict = NULL; + + /* receive DLMessageProcessMessage */ + err = mobilebackup2_error(device_link_service_receive_process_message(client->parent, &dict)); + if (err != MOBILEBACKUP2_E_SUCCESS) { + goto leave; + } + + plist_t node = plist_dict_get_item(dict, "MessageName"); + if (!node) { + debug_info("ERROR: MessageName key not found in plist!"); + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + char *str = NULL; + plist_get_string_val(node, &str); + if (str && (strcmp(str, message) == 0)) { + err = MOBILEBACKUP2_E_SUCCESS; + } else { + debug_info("ERROR: MessageName value does not match '%s'!", message); + err = MOBILEBACKUP2_E_REPLY_NOT_OK; + } + if (str) + free(str); + + if (result) { + *result = dict; + dict = NULL; + } +leave: + if (dict) { + plist_free(dict); + } + + return err; +} + +mobilebackup2_error_t mobilebackup2_receive_message(mobilebackup2_client_t client, plist_t *msg_plist, char **dlmessage) +{ + return mobilebackup2_error(device_link_service_receive_message(client->parent, msg_plist, dlmessage)); +} + +mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, const char *data, uint32_t length, uint32_t *bytes) +{ + if (!client || !client->parent || !data || (length == 0) || !bytes) + return MOBILEBACKUP2_E_INVALID_ARG; + + *bytes = 0; + + service_client_t raw = client->parent->parent->parent; + + int bytes_loc = 0; + uint32_t sent = 0; + do { + bytes_loc = 0; + service_send(raw, data+sent, length-sent, (uint32_t*)&bytes_loc); + if (bytes_loc <= 0) + break; + sent += bytes_loc; + } while (sent < length); + if (sent > 0) { + *bytes = sent; + return MOBILEBACKUP2_E_SUCCESS; + } + return MOBILEBACKUP2_E_MUX_ERROR; +} + +mobilebackup2_error_t mobilebackup2_receive_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes) +{ + if (!client || !client->parent || !data || (length == 0) || !bytes) + return MOBILEBACKUP2_E_INVALID_ARG; + + service_client_t raw = client->parent->parent->parent; + + *bytes = 0; + + int bytes_loc = 0; + uint32_t received = 0; + do { + bytes_loc = 0; + service_receive(raw, data+received, length-received, (uint32_t*)&bytes_loc); + if (bytes_loc <= 0) break; + received += bytes_loc; + } while (received < length); + if (received > 0) { + *bytes = received; + return MOBILEBACKUP2_E_SUCCESS; + } + if (received == 0) { + return MOBILEBACKUP2_E_SUCCESS; + } + return MOBILEBACKUP2_E_MUX_ERROR; +} + +mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client, double local_versions[], char count, double *remote_version) +{ + int i; + + if (!client || !client->parent) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t dict = plist_new_dict(); + plist_t array = plist_new_array(); + for (i = 0; i < count; i++) { + plist_array_append_item(array, plist_new_real(local_versions[i])); + } + plist_dict_set_item(dict, "SupportedProtocolVersions", array); + + mobilebackup2_error_t err = mobilebackup2_send_message(client, "Hello", dict); + plist_free(dict); + + if (err != MOBILEBACKUP2_E_SUCCESS) + goto leave; + + dict = NULL; + err = internal_mobilebackup2_receive_message(client, "Response", &dict); + if (err != MOBILEBACKUP2_E_SUCCESS) + goto leave; + + /* check if we received an error */ + plist_t node = plist_dict_get_item(dict, "ErrorCode"); + if (!node || (plist_get_node_type(node) != PLIST_UINT)) { + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + uint64_t val = 0; + plist_get_uint_val(node, &val); + if (val != 0) { + if (val == 1) { + err = MOBILEBACKUP2_E_NO_COMMON_VERSION; + } else { + err = MOBILEBACKUP2_E_REPLY_NOT_OK; + } + goto leave; + } + + /* retrieve the protocol version of the device */ + node = plist_dict_get_item(dict, "ProtocolVersion"); + if (!node || (plist_get_node_type(node) != PLIST_REAL)) { + err = MOBILEBACKUP2_E_PLIST_ERROR; + goto leave; + } + + *remote_version = 0.0; + plist_get_real_val(node, remote_version); +leave: + if (dict) + plist_free(dict); + return err; +} + +mobilebackup2_error_t mobilebackup2_send_request(mobilebackup2_client_t client, const char *request, const char *target_identifier, const char *source_identifier, plist_t options) +{ + if (!client || !client->parent || !request || !target_identifier) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "TargetIdentifier", plist_new_string(target_identifier)); + if (source_identifier) { + plist_dict_set_item(dict, "SourceIdentifier", plist_new_string(source_identifier)); + } + if (options) { + plist_dict_set_item(dict, "Options", plist_copy(options)); + } + if (!strcmp(request, "Unback") && options) { + plist_t node = plist_dict_get_item(options, "Password"); + if (node) { + plist_dict_set_item(dict, "Password", plist_copy(node)); + } + } + if (!strcmp(request, "EnableCloudBackup") && options) { + plist_t node = plist_dict_get_item(options, "CloudBackupState"); + if (node) { + plist_dict_set_item(dict, "CloudBackupState", plist_copy(node)); + } + } + mobilebackup2_error_t err = mobilebackup2_send_message(client, request, dict); + plist_free(dict); + + return err; +} + +mobilebackup2_error_t mobilebackup2_send_status_response(mobilebackup2_client_t client, int status_code, const char *status1, plist_t status2) +{ + if (!client || !client->parent) + return MOBILEBACKUP2_E_INVALID_ARG; + + plist_t array = plist_new_array(); + plist_array_append_item(array, plist_new_string("DLMessageStatusResponse")); + plist_array_append_item(array, plist_new_uint(status_code)); + if (status1) { + plist_array_append_item(array, plist_new_string(status1)); + } else { + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); + } + if (status2) { + plist_array_append_item(array, plist_copy(status2)); + } else { + plist_array_append_item(array, plist_new_string("___EmptyParameterString___")); + } + + mobilebackup2_error_t err = mobilebackup2_error(device_link_service_send(client->parent, array)); + plist_free(array); + + return err; +} diff --git a/src/mobilebackup2.h b/src/mobilebackup2.h new file mode 100644 index 0000000..e232b97 --- /dev/null +++ b/src/mobilebackup2.h @@ -0,0 +1,33 @@ +/* + * mobilebackup2.h + * Definitions for the mobilebackup2 service (iOS4+) + * + * Copyright (c) 2010-2019 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 __MOBILEBACKUP2_H +#define __MOBILEBACKUP2_H + +#include "idevice.h" +#include "libimobiledevice/mobilebackup2.h" +#include "device_link_service.h" + +struct mobilebackup2_client_private { + device_link_service_client_t parent; +}; + +#endif diff --git a/src/mobilesync.c b/src/mobilesync.c index ee9af5f..9b81a49 100644 --- a/src/mobilesync.c +++ b/src/mobilesync.c @@ -1,7 +1,7 @@ /* - * mobilesync.c + * mobilesync.c * Contains functions for the built-in MobileSync client. - * + * * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * @@ -9,31 +9,32 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #define _GNU_SOURCE 1 #define __USE_GNU 1 - #include <plist/plist.h> #include <string.h> #include <stdlib.h> #include <stdio.h> -#include <glib.h> #include "mobilesync.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" -#define MSYNC_VERSION_INT1 100 +#define MSYNC_VERSION_INT1 400 #define MSYNC_VERSION_INT2 100 #define EMPTY_PARAMETER_STRING "___EmptyParameterString___" @@ -58,6 +59,10 @@ static mobilesync_error_t mobilesync_error(device_link_service_error_t err) return MOBILESYNC_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return MOBILESYNC_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return MOBILESYNC_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return MOBILESYNC_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return MOBILESYNC_E_BAD_VERSION; default: @@ -66,27 +71,14 @@ static mobilesync_error_t mobilesync_error(device_link_service_error_t err) return MOBILESYNC_E_UNKNOWN_ERROR; } -/** - * Connects to the mobilesync service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service()). - * @param client Pointer that will be set to a newly allocated - * #mobilesync_client_t upon successful return. - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one or more parameters are invalid - * @retval DEVICE_LINK_SERVICE_E_BAD_VERSION if the mobilesync version on - * the device is newer. - */ -mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, +mobilesync_error_t mobilesync_client_new(idevice_t device, lockdownd_service_descriptor_t service, mobilesync_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return MOBILESYNC_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, port, &dlclient)); + mobilesync_error_t ret = mobilesync_error(device_link_service_client_new(device, service, &dlclient)); if (ret != MOBILESYNC_E_SUCCESS) { return ret; } @@ -109,33 +101,23 @@ mobilesync_error_t mobilesync_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a mobilesync client from the device and frees up the - * mobilesync client data. - * - * @param client The mobilesync client to disconnect and free. - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if \a client is NULL. - */ +mobilesync_error_t mobilesync_client_start_service(idevice_t device, mobilesync_client_t * client, const char* label) +{ + mobilesync_error_t err = MOBILESYNC_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, MOBILESYNC_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(mobilesync_client_new), &err); + return err; +} + mobilesync_error_t mobilesync_client_free(mobilesync_client_t client) { if (!client) return MOBILESYNC_E_INVALID_ARG; - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, "All done, thanks for the memories"); mobilesync_error_t err = mobilesync_error(device_link_service_client_free(client->parent)); free(client); return err; } -/** - * Polls the device for mobilesync data. - * - * @param client The mobilesync client - * @param plist A pointer to the location where the plist should be stored - * - * @return an error code - */ mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t * plist) { if (!client) @@ -144,17 +126,6 @@ mobilesync_error_t mobilesync_receive(mobilesync_client_t client, plist_t * plis return ret; } -/** - * Sends mobilesync data to the device - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The mobilesync client - * @param plist The location of the plist to send - * - * @return an error code - */ mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) { if (!client || !plist) @@ -162,26 +133,7 @@ mobilesync_error_t mobilesync_send(mobilesync_client_t client, plist_t plist) return mobilesync_error(device_link_service_send(client->parent, plist)); } -/** - * Requests starting synchronization of a data class with the device - * - * @param client The mobilesync client - * @param data_class The data class identifier to sync - * @param anchors The anchors required to exchange with the device. The anchors - * allow the device to tell if the synchronization information on the computer - * and device are consistent to determine what sync type is required. - * @param computer_data_class_version The version of the data class storage on the computer - * @param sync_type A pointer to store the sync type reported by the device_anchor - * @param device_data_class_version The version of the data class storage on the device - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - * @retval MOBILESYNC_E_SYNC_REFUSED if the device refused to sync - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * sync request - */ -mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version) +mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data_class, mobilesync_anchors_t anchors, uint64_t computer_data_class_version, mobilesync_sync_type_t *sync_type, uint64_t *device_data_class_version, char** error_description) { if (!client || client->data_class || !data_class || !anchors || !anchors->computer_anchor) { @@ -194,6 +146,8 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data plist_t msg = NULL; plist_t response_type_node = NULL; + *error_description = NULL; + msg = plist_new_array(); plist_array_append_item(msg, plist_new_string("SDMessageSyncDataClassWithDevice")); plist_array_append_item(msg, plist_new_string(data_class)); @@ -233,23 +187,19 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data goto out; } + // did the device refuse to sync with the computer? if (!strcmp(response_type, "SDMessageRefuseToSyncDataClassWithComputer")) { - char *reason = NULL; err = MOBILESYNC_E_SYNC_REFUSED; - plist_get_string_val(plist_array_get_item(msg, 2), &reason); - debug_info("Device refused sync: %s", reason); - free(reason); - reason = NULL; + plist_get_string_val(plist_array_get_item(msg, 2), error_description); + debug_info("Device refused sync: %s", error_description); goto out; } + // did the device cancel the session? if (!strcmp(response_type, "SDMessageCancelSession")) { - char *reason = NULL; err = MOBILESYNC_E_CANCELLED; - plist_get_string_val(plist_array_get_item(msg, 2), &reason); - debug_info("Device cancelled: %s", reason); - free(reason); - reason = NULL; + plist_get_string_val(plist_array_get_item(msg, 2), error_description); + debug_info("Device cancelled: %s", error_description); goto out; } @@ -309,17 +259,6 @@ mobilesync_error_t mobilesync_start(mobilesync_client_t client, const char *data return err; } -/** - * Finish a synchronization session of a data class on the device. - * A session must have previously been started using mobilesync_start(). - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid - * form - */ mobilesync_error_t mobilesync_finish(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -395,7 +334,7 @@ static mobilesync_error_t mobilesync_get_records(mobilesync_client_t client, con msg = plist_new_array(); plist_array_append_item(msg, plist_new_string(operation)); plist_array_append_item(msg, plist_new_string(client->data_class)); - + err = mobilesync_send(client, msg); if (msg) { @@ -405,49 +344,16 @@ static mobilesync_error_t mobilesync_get_records(mobilesync_client_t client, con return err; } -/** - * Requests to receive all records of the currently set data class from the device. - * The actually changes are retrieved using mobilesync_receive_changes() after this - * request has been successful. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_get_all_records_from_device(mobilesync_client_t client) { return mobilesync_get_records(client, "SDMessageGetAllRecordsFromDevice"); } -/** - * Requests to receive only changed records of the currently set data class from the device. - * The actually changes are retrieved using mobilesync_receive_changes() after this - * request has been successful. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_get_changes_from_device(mobilesync_client_t client) { return mobilesync_get_records(client, "SDMessageGetChangesFromDevice"); } -/** - * Receives changed entitites of the currently set data class from the device - * - * @param client The mobilesync client - * @param entities A pointer to store the changed entity records as a PLIST_DICT - * @param is_last_record A pointer to store a flag indicating if this submission is the last one - * @param actions A pointer to additional flags the device is sending or NULL to ignore - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - */ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_t *entities, uint8_t *is_last_record, plist_t *actions) { if (!client || !client->data_class) { @@ -497,7 +403,7 @@ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_ if (actions != NULL) { actions_node = plist_array_get_item(msg, 4); - if (plist_get_node_type(actions) == PLIST_DICT) + if (plist_get_node_type(actions_node) == PLIST_DICT) *actions = plist_copy(actions_node); else *actions = NULL; @@ -515,17 +421,6 @@ mobilesync_error_t mobilesync_receive_changes(mobilesync_client_t client, plist_ return err; } -/** - * Requests the device to delete all records of the current data class - * - * @note The operation must be called after starting synchronization. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - */ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -578,7 +473,7 @@ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t cl goto out; } - if (strcmp(response_type, "SDMessageDeviceWillClearAllRecords")) { + if (strcmp(response_type, "SDMessageDeviceWillClearAllRecords") != 0) { err = MOBILESYNC_E_PLIST_ERROR; } @@ -595,14 +490,6 @@ mobilesync_error_t mobilesync_clear_all_records_on_device(mobilesync_client_t cl return err; } -/** - * Acknowledges to the device that the changes have been merged on the computer - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_acknowledge_changes_from_device(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -637,23 +524,6 @@ static plist_t create_process_changes_message(const char *data_class, plist_t en return msg; } -/** - * Verifies if the device is ready to receive changes from the computer. - * This call changes the synchronization direction so that mobilesync_send_changes() - * can be used to send changes to the device. - * - * @param client The mobilesync client - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid form - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - * @retval MOBILESYNC_E_NOT_READY if the device is not ready to start - * receiving any changes - */ mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_client_t client) { if (!client || !client->data_class) { @@ -721,20 +591,6 @@ mobilesync_error_t mobilesync_ready_to_send_changes_from_computer(mobilesync_cli return err; } -/** - * Sends changed entities of the currently set data class to the device - * - * @param client The mobilesync client - * @param entities The changed entity records as a PLIST_DICT - * @param is_last_record A flag indicating if this submission is the last one - * @param actions Additional actions for the device created with mobilesync_actions_new() - * or NULL if no actions should be passed - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid, - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - */ mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t entities, uint8_t is_last_record, plist_t actions) { if (!client || !client->data_class || !entities) { @@ -763,21 +619,6 @@ mobilesync_error_t mobilesync_send_changes(mobilesync_client_t client, plist_t e return err; } -/** - * Receives any remapped identifiers reported after the device merged submitted changes. - * - * @param client The mobilesync client - * @param mapping A pointer to an array plist containing a dict of identifier remappings - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - * @retval MOBILESYNC_E_PLIST_ERROR if the received plist is not of valid - * form - * @retval MOBILESYNC_E_WRONG_DIRECTION if the current sync direction does - * not permit this call - * @retval MOBILESYNC_E_CANCELLED if the device explicitly cancelled the - * session - */ mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plist_t *mapping) { if (!client || !client->data_class) { @@ -847,15 +688,6 @@ mobilesync_error_t mobilesync_remap_identifiers(mobilesync_client_t client, plis return err; } -/** - * Cancels a running synchronization session with a device at any time. - * - * @param client The mobilesync client - * @param reason The reason to supply to the device for cancelling - * - * @retval MOBILESYNC_E_SUCCESS on success - * @retval MOBILESYNC_E_INVALID_ARG if one of the parameters is invalid - */ mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* reason) { if (!client || !client->data_class || !reason) { @@ -882,18 +714,9 @@ mobilesync_error_t mobilesync_cancel(mobilesync_client_t client, const char* rea return err; } -/** - * Allocates memory for a new anchors struct initialized with the passed anchors. - * - * @param device_anchor An anchor the device reported the last time or NULL - * if none is known yet which for instance is true on first synchronization. - * @param computer_anchor An arbitrary string to use as anchor for the computer. - * - * @return A new #mobilesync_anchors_t struct. Must be freed using mobilesync_anchors_free(). - */ mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const char *computer_anchor) { - mobilesync_anchors_t anchors = (mobilesync_anchors_t) malloc(sizeof(mobilesync_anchors)); + mobilesync_anchors_t anchors = (mobilesync_anchors_t) malloc(sizeof(mobilesync_anchors)); if (device_anchor != NULL) { anchors->device_anchor = strdup(device_anchor); } else { @@ -908,11 +731,6 @@ mobilesync_anchors_t mobilesync_anchors_new(const char *device_anchor, const cha return anchors; } -/** - * Free memory used by anchors. - * - * @param anchors The anchors to free. - */ void mobilesync_anchors_free(mobilesync_anchors_t anchors) { if (anchors->device_anchor != NULL) { @@ -927,28 +745,11 @@ void mobilesync_anchors_free(mobilesync_anchors_t anchors) anchors = NULL; } -/** - * Create a new actions plist to use in mobilesync_send_changes(). - * - * @return A new plist_t of type PLIST_DICT. - */ -plist_t mobilesync_actions_new() +plist_t mobilesync_actions_new(void) { return plist_new_dict(); } -/** - * Add one or more new key:value pairs to the given actions plist. - * - * @param actions The actions to modify. - * @param ... KEY, VALUE, [KEY, VALUE], NULL - * - * @note The known keys so far are "SyncDeviceLinkEntityNamesKey" which expects - * an array of entity names, followed by a count paramter as well as - * "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey" which expects an - * integer to use as a boolean value indicating that the device should - * link submitted changes and report remapped identifiers. - */ void mobilesync_actions_add(plist_t actions, ...) { if (!actions) @@ -969,10 +770,10 @@ void mobilesync_actions_add(plist_t actions, ...) plist_array_append_item(array, plist_new_string(entity_names[i])); } - plist_dict_insert_item(actions, key, array); + plist_dict_set_item(actions, key, array); } else if (!strcmp(key, "SyncDeviceLinkAllRecordsOfPulledEntityTypeSentKey")) { int link_records = va_arg(args, int); - plist_dict_insert_item(actions, key, plist_new_bool(link_records)); + plist_dict_set_item(actions, key, plist_new_bool(link_records)); } free(key); key = NULL; @@ -981,11 +782,6 @@ void mobilesync_actions_add(plist_t actions, ...) va_end(args); } -/** - * Free actions plist. - * - * @param actions The actions plist to free. Does nothing if NULL is passed. - */ void mobilesync_actions_free(plist_t actions) { if (actions) { diff --git a/src/mobilesync.h b/src/mobilesync.h index 24e61af..3b5ece9 100644 --- a/src/mobilesync.h +++ b/src/mobilesync.h @@ -1,7 +1,7 @@ -/* +/* * mobilesync.h * Definitions for the built-in MobileSync client - * + * * Copyright (c) 2010 Bryan Forbes All Rights Reserved. * Copyright (c) 2009 Jonathan Beck All Rights Reserved. * @@ -9,19 +9,21 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef MOBILESYNC_H -#define MOBILESYNC_H +#ifndef __MOBILESYNC_H +#define __MOBILESYNC_H + +#include "idevice.h" #include "libimobiledevice/mobilesync.h" #include "device_link_service.h" diff --git a/src/notification_proxy.c b/src/notification_proxy.c index 80a82c4..60b2e03 100644 --- a/src/notification_proxy.c +++ b/src/notification_proxy.c @@ -8,17 +8,20 @@ * 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 + * 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 <unistd.h> @@ -26,7 +29,11 @@ #include "notification_proxy.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" + +#ifdef WIN32 +#define sleep(x) Sleep(x*1000) +#endif struct np_thread { np_client_t client; @@ -41,19 +48,19 @@ struct np_thread { */ static void np_lock(np_client_t client) { - debug_info("NP: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks a notification_proxy client, used for thread safety. - * + * * @param client notification_proxy client to unlock */ static void np_unlock(np_client_t client) { - debug_info("NP: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** @@ -82,78 +89,85 @@ static np_error_t np_error(property_list_service_error_t err) return NP_E_UNKNOWN_ERROR; } -/** - * Connects to the notification_proxy on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated np_client_t - * upon successful return. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when device is NULL, - * or NP_E_CONN_FAILED when the connection to the device could not be - * established. - */ -np_error_t np_client_new(idevice_t device, uint16_t port, np_client_t *client) +np_error_t np_client_new(idevice_t device, lockdownd_service_descriptor_t service, np_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return NP_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, port, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - return NP_E_CONN_FAILED; + np_error_t err = np_error(property_list_service_client_new(device, service, &plistclient)); + if (err != NP_E_SUCCESS) { + return err; } np_client_t client_loc = (np_client_t) malloc(sizeof(struct np_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); - - client_loc->notifier = NULL; + mutex_init(&client_loc->mutex); + client_loc->notifier = THREAD_T_NULL; *client = client_loc; return NP_E_SUCCESS; } -/** - * Disconnects a notification_proxy client from the device and frees up the - * notification_proxy client data. - * - * @param client The notification_proxy client to disconnect and free. - * - * @return NP_E_SUCCESS on success, or NP_E_INVALID_ARG when client is NULL. - */ +np_error_t np_client_start_service(idevice_t device, np_client_t* client, const char* label) +{ + np_error_t err = NP_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, NP_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(np_client_new), &err); + return err; +} + np_error_t np_client_free(np_client_t client) { + plist_t dict; + property_list_service_client_t parent; + if (!client) return NP_E_INVALID_ARG; - property_list_service_client_free(client->parent); + dict = plist_new_dict(); + plist_dict_set_item(dict,"Command", plist_new_string("Shutdown")); + property_list_service_send_xml_plist(client->parent, dict); + plist_free(dict); + + parent = client->parent; + /* notifies the client->notifier thread that it should terminate */ client->parent = NULL; + if (client->notifier) { debug_info("joining np callback"); - g_thread_join(client->notifier); - } - if (client->mutex) { - g_mutex_free(client->mutex); + thread_join(client->notifier); + thread_free(client->notifier); + client->notifier = THREAD_T_NULL; + } else { + dict = NULL; + property_list_service_receive_plist(parent, &dict); + if (dict) { +#ifndef STRIP_DEBUG_CODE + char *cmd_value = NULL; + plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); + if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { + plist_get_string_val(cmd_value_node, &cmd_value); + } + if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { + // this is the expected answer + } else { + debug_info("Did not get ProxyDeath but:"); + debug_plist(dict); + } + if (cmd_value) { + free(cmd_value); + } +#endif + plist_free(dict); + } } + + property_list_service_client_free(parent); + + mutex_destroy(&client->mutex); free(client); return NP_E_SUCCESS; } -/** - * Sends a notification to the device's notification_proxy. - * - * @param client The client to send to - * @param notification The notification message to send - * - * @return NP_E_SUCCESS on success, or an error returned by np_plist_send - */ np_error_t np_post_notification(np_client_t client, const char *notification) { if (!client || !notification) { @@ -162,66 +176,24 @@ np_error_t np_post_notification(np_client_t client, const char *notification) np_lock(client); plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("PostNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + plist_dict_set_item(dict,"Command", plist_new_string("PostNotification")); + plist_dict_set_item(dict,"Name", plist_new_string(notification)); np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); plist_free(dict); - dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("Shutdown")); - - res = np_error(property_list_service_send_xml_plist(client->parent, dict)); - plist_free(dict); - if (res != NP_E_SUCCESS) { debug_info("Error sending XML plist to device!"); } - - // try to read an answer, we just ignore errors here - dict = NULL; - property_list_service_receive_plist(client->parent, &dict); - if (dict) { -#ifndef STRIP_DEBUG_CODE - char *cmd_value = NULL; - plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); - if (plist_get_node_type(cmd_value_node) == PLIST_STRING) { - plist_get_string_val(cmd_value_node, &cmd_value); - } - - if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { - // this is the expected answer - } else { - debug_plist(dict); - } - g_free(cmd_value); -#endif - plist_free(dict); - } - np_unlock(client); return res; } -/** - * Tells the device to send a notification on the specified event. - * - * @param client The client to send to - * @param notification The notifications that should be observed. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * notification are NULL, or an error returned by np_plist_send. - */ -np_error_t np_observe_notification( np_client_t client, const char *notification ) +static np_error_t internal_np_observe_notification(np_client_t client, const char *notification) { - if (!client || !notification) { - return NP_E_INVALID_ARG; - } - np_lock(client); - plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict,"Command", plist_new_string("ObserveNotification")); - plist_dict_insert_item(dict,"Name", plist_new_string(notification)); + plist_dict_set_item(dict,"Command", plist_new_string("ObserveNotification")); + plist_dict_set_item(dict,"Name", plist_new_string(notification)); np_error_t res = np_error(property_list_service_send_xml_plist(client->parent, dict)); if (res != NP_E_SUCCESS) { @@ -229,21 +201,20 @@ np_error_t np_observe_notification( np_client_t client, const char *notification } plist_free(dict); + return res; +} + +np_error_t np_observe_notification( np_client_t client, const char *notification ) +{ + if (!client || !notification) { + return NP_E_INVALID_ARG; + } + np_lock(client); + np_error_t res = internal_np_observe_notification(client, notification); np_unlock(client); return res; } -/** - * Tells the device to send a notification on specified events. - * - * @param client The client to send to - * @param notification_spec Specification of the notifications that should be - * observed. This is expected to be an array of const char* that MUST have a - * terminating NULL entry. - * - * @return NP_E_SUCCESS on success, NP_E_INVALID_ARG when client is null, - * or an error returned by np_observe_notification. - */ np_error_t np_observe_notifications(np_client_t client, const char **notification_spec) { int i = 0; @@ -258,13 +229,15 @@ np_error_t np_observe_notifications(np_client_t client, const char **notificatio return NP_E_INVALID_ARG; } + np_lock(client); while (notifications[i]) { - res = np_observe_notification(client, notifications[i]); + res = internal_np_observe_notification(client, notifications[i]); if (res != NP_E_SUCCESS) { break; } i++; } + np_unlock(client); return res; } @@ -277,7 +250,7 @@ np_error_t np_observe_notifications(np_client_t client, const char **notificatio * with the notification that has been received. * * @return 0 if a notification has been received or nothing has been received, - * or a negative value if an error occured. + * or a negative value if an error occurred. * * @note You probably want to check out np_set_notify_callback * @see np_set_notify_callback @@ -292,11 +265,15 @@ static int np_get_notification(np_client_t client, char **notification) np_lock(client); - property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); - if (!dict) { + property_list_service_error_t perr = property_list_service_receive_plist_with_timeout(client->parent, &dict, 500); + if (perr == PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT) { debug_info("NotificationProxy: no notification received!"); res = 0; - } else { + } else if (perr != PROPERTY_LIST_SERVICE_E_SUCCESS) { + debug_info("NotificationProxy: error %d occurred!", perr); + res = perr; + } + if (dict) { char *cmd_value = NULL; plist_t cmd_value_node = plist_dict_get_item(dict, "Command"); @@ -315,11 +292,11 @@ static int np_get_notification(np_client_t client, char **notification) res = -2; if (name_value_node && name_value) { *notification = name_value; - debug_info("got notification %s\n", __func__, name_value); + debug_info("got notification %s", __func__, name_value); res = 0; } } else if (cmd_value && !strcmp(cmd_value, "ProxyDeath")) { - debug_info("ERROR: NotificationProxy died!"); + debug_info("NotificationProxy died!"); res = -1; } else if (cmd_value) { debug_info("unknown NotificationProxy command '%s' received!", cmd_value); @@ -342,7 +319,7 @@ static int np_get_notification(np_client_t client, char **notification) /** * Internally used thread function. */ -gpointer np_notifier( gpointer arg ) +void* np_notifier( void* arg ) { char *notification = NULL; struct np_thread *npt = (struct np_thread*)arg; @@ -351,7 +328,10 @@ gpointer np_notifier( gpointer arg ) debug_info("starting callback."); while (npt->client->parent) { - np_get_notification(npt->client, ¬ification); + if (np_get_notification(npt->client, ¬ification) < 0) { + npt->cbfunc("", npt->user_data); + break; + } if (notification) { npt->cbfunc(notification, npt->user_data); free(notification); @@ -366,25 +346,6 @@ gpointer np_notifier( gpointer arg ) return NULL; } -/** - * This function allows an application to define a callback function that will - * be called when a notification has been received. - * It will start a thread that polls for notifications and calls the callback - * function if a notification has been received. - * - * @param client the NP client - * @param notify_cb pointer to a callback function or NULL to de-register a - * previously set callback function. - * @param user_data Pointer that will be passed to the callback function as - * user data. If notify_cb is NULL, this parameter is ignored. - * - * @note Only one callback function can be registered at the same time; - * any previously set callback function will be removed automatically. - * - * @return NP_E_SUCCESS when the callback was successfully registered, - * NP_E_INVALID_ARG when client is NULL, or NP_E_UNKNOWN_ERROR when - * the callback thread could no be created. - */ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, void *user_data ) { if (!client) @@ -394,11 +355,12 @@ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, np_lock(client); if (client->notifier) { - debug_info("callback already set, removing\n"); + debug_info("callback already set, removing"); property_list_service_client_t parent = client->parent; client->parent = NULL; - g_thread_join(client->notifier); - client->notifier = NULL; + thread_join(client->notifier); + thread_free(client->notifier); + client->notifier = THREAD_T_NULL; client->parent = parent; } @@ -409,8 +371,7 @@ np_error_t np_set_notify_callback( np_client_t client, np_notify_cb_t notify_cb, npt->cbfunc = notify_cb; npt->user_data = user_data; - client->notifier = g_thread_create(np_notifier, npt, TRUE, NULL); - if (client->notifier) { + if (thread_new(&client->notifier, np_notifier, npt) == 0) { res = NP_E_SUCCESS; } } diff --git a/src/notification_proxy.h b/src/notification_proxy.h index 8d5cd24..595cb01 100644 --- a/src/notification_proxy.h +++ b/src/notification_proxy.h @@ -8,30 +8,31 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef INOTIFICATION_PROXY_H -#define INOTIFICATION_PROXY_H -#include <glib.h> +#ifndef __NOTIFICATION_PROXY_H +#define __NOTIFICATION_PROXY_H +#include "idevice.h" #include "libimobiledevice/notification_proxy.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct np_client_private { property_list_service_client_t parent; - GMutex *mutex; - GThread *notifier; + mutex_t mutex; + THREAD_T notifier; }; -gpointer np_notifier(gpointer arg); +void* np_notifier(void* arg); #endif diff --git a/src/preboard.c b/src/preboard.c new file mode 100644 index 0000000..c3eff02 --- /dev/null +++ b/src/preboard.c @@ -0,0 +1,256 @@ +/* + * preboard.c + * com.apple.preboardservice_v2 service implementation. + * + * Copyright (c) 2019 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <string.h> +#include <stdlib.h> +#include <plist/plist.h> + +#include "preboard.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a preboard_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching preboard_error_t error code, + * PREBOARD_E_UNKNOWN_ERROR otherwise. + */ +static preboard_error_t preboard_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return PREBOARD_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return PREBOARD_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return PREBOARD_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return PREBOARD_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return PREBOARD_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return PREBOARD_E_NOT_ENOUGH_DATA; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return PREBOARD_E_TIMEOUT; + default: + break; + } + return PREBOARD_E_UNKNOWN_ERROR; +} + +preboard_error_t preboard_client_new(idevice_t device, lockdownd_service_descriptor_t service, preboard_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to preboard_client_new."); + return PREBOARD_E_INVALID_ARG; + } + + debug_info("Creating preboard_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + preboard_error_t ret = preboard_error(property_list_service_client_new(device, service, &plclient)); + if (ret != PREBOARD_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + preboard_client_t client_loc = (preboard_client_t) malloc(sizeof(struct preboard_client_private)); + client_loc->parent = plclient; + client_loc->receive_status_thread = THREAD_T_NULL; + + *client = client_loc; + + debug_info("preboard_client successfully created."); + return 0; +} + +preboard_error_t preboard_client_start_service(idevice_t device, preboard_client_t * client, const char* label) +{ + preboard_error_t err = PREBOARD_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, PREBOARD_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(preboard_client_new), &err); + return err; +} + +preboard_error_t preboard_client_free(preboard_client_t client) +{ + if (!client) + return PREBOARD_E_INVALID_ARG; + + property_list_service_client_t parent = client->parent; + client->parent = NULL; + if (client->receive_status_thread) { + debug_info("joining receive_status_thread"); + thread_join(client->receive_status_thread); + thread_free(client->receive_status_thread); + client->receive_status_thread = THREAD_T_NULL; + } + preboard_error_t err = preboard_error(property_list_service_client_free(parent)); + free(client); + + return err; +} + +preboard_error_t preboard_send(preboard_client_t client, plist_t plist) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + res = preboard_error(property_list_service_send_binary_plist(client->parent, plist)); + if (res != PREBOARD_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + return res; +} + +preboard_error_t preboard_receive_with_timeout(preboard_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + plist_t outplist = NULL; + res = preboard_error(property_list_service_receive_plist_with_timeout(client->parent, &outplist, timeout_ms)); + if (res != PREBOARD_E_SUCCESS && res != PREBOARD_E_TIMEOUT) { + debug_info("Could not receive plist, error %d", res); + plist_free(outplist); + } else if (res == PREBOARD_E_SUCCESS) { + *plist = outplist; + } + return res; +} + +preboard_error_t preboard_receive(preboard_client_t client, plist_t * plist) +{ + return preboard_receive_with_timeout(client, plist, 5000); +} + +struct preboard_status_data { + preboard_client_t client; + preboard_status_cb_t cbfunc; + void *user_data; +}; + +static void* preboard_receive_status_loop_thread(void* arg) +{ + struct preboard_status_data *data = (struct preboard_status_data*)arg; + + /* run until the service disconnects or an error occurs */ + while (data->client && data->client->parent) { + plist_t pl = NULL; + preboard_error_t perr = preboard_receive_with_timeout(data->client, &pl, 1000); + if (perr == PREBOARD_E_TIMEOUT) { + continue; + } + if (perr == PREBOARD_E_SUCCESS) { + data->cbfunc(pl, data->user_data); + } + plist_free(pl); + if (perr != PREBOARD_E_SUCCESS) { + data->cbfunc(NULL, data->user_data); + break; + } + } + + /* cleanup */ + debug_info("done, cleaning up."); + + if (data->client->receive_status_thread) { + thread_free(data->client->receive_status_thread); + data->client->receive_status_thread = THREAD_T_NULL; + } + free(data); + + return NULL; +} + +static preboard_error_t preboard_receive_status_loop_with_callback(preboard_client_t client, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client || !client->parent) { + return PREBOARD_E_INVALID_ARG; + } + + if (client->receive_status_thread) { + return PREBOARD_E_OP_IN_PROGRESS; + } + + preboard_error_t res = PREBOARD_E_UNKNOWN_ERROR; + struct preboard_status_data *data = (struct preboard_status_data*)malloc(sizeof(struct preboard_status_data)); + if (data) { + data->client = client; + data->cbfunc = status_cb; + data->user_data = user_data; + if (thread_new(&client->receive_status_thread, preboard_receive_status_loop_thread, data) == 0) { + res = PREBOARD_E_SUCCESS; + } + } + + return res; +} + +preboard_error_t preboard_create_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CreateStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); +} + +preboard_error_t preboard_commit_stashbag(preboard_client_t client, plist_t manifest, preboard_status_cb_t status_cb, void *user_data) +{ + if (!client) { + return PREBOARD_E_INVALID_ARG; + } + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "Command", plist_new_string("CommitStashbag")); + if (manifest) { + plist_dict_set_item(dict, "Manifest", plist_copy(manifest)); + } + preboard_error_t perr = preboard_send(client, dict); + plist_free(dict); + if (perr != PREBOARD_E_SUCCESS) { + return perr; + } + if (!status_cb) { + return PREBOARD_E_SUCCESS; + } + + return preboard_receive_status_loop_with_callback(client, status_cb, user_data); +} diff --git a/src/preboard.h b/src/preboard.h new file mode 100644 index 0000000..f8164eb --- /dev/null +++ b/src/preboard.h @@ -0,0 +1,35 @@ +/* + * preboard.h + * com.apple.preboard_v2 service header file. + * + * Copyright (c) 2019 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 __PREBOARD_H +#define __PREBOARD_H + +#include "idevice.h" +#include "libimobiledevice/preboard.h" +#include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> + +struct preboard_client_private { + property_list_service_client_t parent; + THREAD_T receive_status_thread; +}; + +#endif diff --git a/src/property_list_service.c b/src/property_list_service.c index 8af958e..2fca4e7 100644 --- a/src/property_list_service.c +++ b/src/property_list_service.c @@ -1,4 +1,4 @@ -/* +/* * property_list_service.c * PropertyList service implementation. * @@ -8,97 +8,86 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <stdlib.h> #include <string.h> -#include <glib.h> #include "property_list_service.h" -#include "idevice.h" -#include "debug.h" +#include "common/debug.h" +#include "endianness.h" /** - * Convert an idevice_error_t value to an property_list_service_error_t value. + * Convert a service_error_t value to a property_list_service_error_t value. * Used internally to get correct error codes. * - * @param err An idevice_error_t error code + * @param err A service_error_t error code * * @return A matching property_list_service_error_t error code, * PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. */ -static property_list_service_error_t idevice_to_property_list_service_error(idevice_error_t err) +static property_list_service_error_t service_to_property_list_service_error(service_error_t err) { switch (err) { - case IDEVICE_E_SUCCESS: + case SERVICE_E_SUCCESS: return PROPERTY_LIST_SERVICE_E_SUCCESS; - case IDEVICE_E_INVALID_ARG: + case SERVICE_E_INVALID_ARG: return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - case IDEVICE_E_SSL_ERROR: + case SERVICE_E_MUX_ERROR: + return PROPERTY_LIST_SERVICE_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: return PROPERTY_LIST_SERVICE_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT; default: break; } return PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; } -/** - * Creates a new property list service for the specified port. - * - * @param device The device to connect to. - * @param port The port on the device to connect to, usually opened by a call to - * lockdownd_start_service. - * @param client Pointer that will be set to a newly allocated - * property_list_service_client_t upon successful return. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when one of the arguments is invalid, - * or PROPERTY_LIST_SERVICE_E_MUX_ERROR when connecting to the device failed. - */ -property_list_service_error_t property_list_service_client_new(idevice_t device, uint16_t port, property_list_service_client_t *client) +property_list_service_error_t property_list_service_client_new(idevice_t device, lockdownd_service_descriptor_t service, property_list_service_client_t *client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - /* Attempt connection */ - idevice_connection_t connection = NULL; - if (idevice_connect(device, port, &connection) != IDEVICE_E_SUCCESS) { - return PROPERTY_LIST_SERVICE_E_MUX_ERROR; + service_client_t parent = NULL; + service_error_t rerr = service_client_new(device, service, &parent); + if (rerr != SERVICE_E_SUCCESS) { + return service_to_property_list_service_error(rerr); } /* create client object */ property_list_service_client_t client_loc = (property_list_service_client_t)malloc(sizeof(struct property_list_service_client_private)); - client_loc->connection = connection; + client_loc->parent = parent; + /* all done, return success */ *client = client_loc; - return PROPERTY_LIST_SERVICE_E_SUCCESS; } -/** - * Frees a PropertyList service. - * - * @param client The property list service to free. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client is invalid, or a - * PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when another error occured. - */ property_list_service_error_t property_list_service_client_free(property_list_service_client_t client) { if (!client) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - property_list_service_error_t err = idevice_to_property_list_service_error(idevice_disconnect(client->connection)); + property_list_service_error_t err = service_to_property_list_service_error(service_client_free(client->parent)); + free(client); + client = NULL; + return err; } @@ -113,7 +102,8 @@ property_list_service_error_t property_list_service_client_free(property_list_se * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, * PROPERTY_LIST_SERVICE_E_INVALID_ARG when one or more parameters are * invalid, PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid - * plist, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified + * plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a communication error + * occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified * error occurs. */ static property_list_service_error_t internal_plist_send(property_list_service_client_t client, plist_t plist, int binary) @@ -122,9 +112,9 @@ static property_list_service_error_t internal_plist_send(property_list_service_c char *content = NULL; uint32_t length = 0; uint32_t nlen = 0; - int bytes = 0; + uint32_t bytes = 0; - if (!client || (client && !client->connection) || !plist) { + if (!client || (client && !client->parent) || !plist) { return PROPERTY_LIST_SERVICE_E_INVALID_ARG; } @@ -138,15 +128,15 @@ static property_list_service_error_t internal_plist_send(property_list_service_c return PROPERTY_LIST_SERVICE_E_PLIST_ERROR; } - nlen = GUINT32_TO_BE(length); + nlen = htobe32(length); debug_info("sending %d bytes", length); - idevice_connection_send(client->connection, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes); + service_send(client->parent, (const char*)&nlen, sizeof(nlen), &bytes); if (bytes == sizeof(nlen)) { - idevice_connection_send(client->connection, content, length, (uint32_t*)&bytes); + service_send(client->parent, content, length, &bytes); if (bytes > 0) { debug_info("sent %d bytes", bytes); debug_plist(plist); - if ((uint32_t)bytes == length) { + if (bytes == length) { res = PROPERTY_LIST_SERVICE_E_SUCCESS; } else { debug_info("ERROR: Could not send all data (%d of %d)!", bytes, length); @@ -155,40 +145,18 @@ static property_list_service_error_t internal_plist_send(property_list_service_c } if (bytes <= 0) { debug_info("ERROR: sending to device failed."); + res = PROPERTY_LIST_SERVICE_E_MUX_ERROR; } free(content); - return res; } -/** - * Sends an XML plist. - * - * @param client The property list service client to use for sending. - * @param plist plist to send - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. - */ property_list_service_error_t property_list_service_send_xml_plist(property_list_service_client_t client, plist_t plist) { return internal_plist_send(client, plist, 0); } -/** - * Sends a binary plist. - * - * @param client The property list service client to use for sending. - * @param plist plist to send - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when dict is not a valid plist, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when an unspecified error occurs. - */ property_list_service_error_t property_list_service_send_binary_plist(property_list_service_client_t client, plist_t plist) { return internal_plist_send(client, plist, 1); @@ -205,6 +173,8 @@ property_list_service_error_t property_list_service_send_binary_plist(property_l * * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL, + * PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA when not enough data + * received, PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT when the connection times out, * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR @@ -216,135 +186,110 @@ static property_list_service_error_t internal_plist_receive_timeout(property_lis uint32_t pktlen = 0; uint32_t bytes = 0; - if (!client || (client && !client->connection) || !plist) { + if (!client || (client && !client->parent) || !plist) { return PROPERTY_LIST_SERVICE_E_INVALID_ARG; } - idevice_connection_receive_timeout(client->connection, (char*)&pktlen, sizeof(pktlen), &bytes, timeout); - debug_info("initial read=%i", bytes); - if (bytes < 4) { + *plist = NULL; + service_error_t serr = service_receive_with_timeout(client->parent, (char*)&pktlen, sizeof(pktlen), &bytes, timeout); + if (serr != SERVICE_E_SUCCESS) { debug_info("initial read failed!"); - return PROPERTY_LIST_SERVICE_E_MUX_ERROR; - } else { - pktlen = GUINT32_FROM_BE(pktlen); - if (pktlen < (1 << 24)) { /* prevent huge buffers */ - uint32_t curlen = 0; - char *content = NULL; - debug_info("%d bytes following", pktlen); - content = (char*)malloc(pktlen); - - while (curlen < pktlen) { - idevice_connection_receive(client->connection, content+curlen, pktlen-curlen, &bytes); - if (bytes <= 0) { - res = PROPERTY_LIST_SERVICE_E_MUX_ERROR; - break; - } - debug_info("received %d bytes", bytes); - curlen += bytes; - } - if (!memcmp(content, "bplist00", 8)) { - plist_from_bin(content, pktlen, plist); - } else { - /* iOS 4.3 hack: plist data might contain invalid null characters, thus we convert those to spaces */ - for (bytes = 0; bytes < pktlen-1; bytes++) { - if (content[bytes] == 0x0) - content[bytes] = 0x20; - } - plist_from_xml(content, pktlen, plist); - } - if (*plist) { - debug_plist(*plist); - res = PROPERTY_LIST_SERVICE_E_SUCCESS; - } else { - res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR; - } - free(content); - content = NULL; - } else { - res = PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; + return service_to_property_list_service_error(serr); + } + + if (bytes == 0) { + /* success but 0 bytes length, assume timeout */ + return PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT; + } + + debug_info("initial read=%i", bytes); + + uint32_t curlen = 0; + char *content = NULL; + + pktlen = be32toh(pktlen); + debug_info("%d bytes following", pktlen); + content = (char*)malloc(pktlen); + if (!content) { + debug_info("out of memory when allocating %d bytes", pktlen); + return PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR; + } + + while (curlen < pktlen) { + serr = service_receive(client->parent, content+curlen, pktlen-curlen, &bytes); + if (serr != SERVICE_E_SUCCESS) { + res = service_to_property_list_service_error(serr); + break; } + debug_info("received %d bytes", bytes); + curlen += bytes; } + + if (curlen < pktlen) { + debug_info("received incomplete packet (%d of %d bytes)", curlen, pktlen); + if (curlen > 0) { + debug_info("incomplete packet following:"); + debug_buffer(content, curlen); + } + free(content); + return res; + } + + if ((pktlen > 8) && !memcmp(content, "bplist00", 8)) { + plist_from_bin(content, pktlen, plist); + } else if ((pktlen > 5) && !memcmp(content, "<?xml", 5)) { + /* iOS 4.3+ hack: plist data might contain invalid characters, thus we convert those to spaces */ + for (bytes = 0; bytes < pktlen-1; bytes++) { + if ((content[bytes] >= 0) && (content[bytes] < 0x20) && (content[bytes] != 0x09) && (content[bytes] != 0x0a) && (content[bytes] != 0x0d)) + content[bytes] = 0x20; + } + plist_from_xml(content, pktlen, plist); + } else { + debug_info("WARNING: received unexpected non-plist content"); + debug_buffer(content, pktlen); + } + + if (*plist) { + debug_plist(*plist); + res = PROPERTY_LIST_SERVICE_E_SUCCESS; + } else { + res = PROPERTY_LIST_SERVICE_E_PLIST_ERROR; + } + + free(content); + content = NULL; + return res; } -/** - * Receives a plist using the given property list service client with specified - * timeout. - * Binary or XML plists are automatically handled. - * - * @param client The property list service client to use for receiving - * @param plist pointer to a plist_t that will point to the received plist - * upon successful return - * @param timeout Maximum time in milliseconds to wait for data. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when connection or *plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be - * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a - * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when - * an unspecified error occurs. - */ property_list_service_error_t property_list_service_receive_plist_with_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout) { return internal_plist_receive_timeout(client, plist, timeout); } -/** - * Receives a plist using the given property list service client. - * Binary or XML plists are automatically handled. - * - * This function is like property_list_service_receive_plist_with_timeout - * using a timeout of 10 seconds. - * @see property_list_service_receive_plist_with_timeout - * - * @param client The property list service client to use for receiving - * @param plist pointer to a plist_t that will point to the received plist - * upon successful return - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG when client or *plist is NULL, - * PROPERTY_LIST_SERVICE_E_PLIST_ERROR when the received data cannot be - * converted to a plist, PROPERTY_LIST_SERVICE_E_MUX_ERROR when a - * communication error occurs, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR when - * an unspecified error occurs. - */ property_list_service_error_t property_list_service_receive_plist(property_list_service_client_t client, plist_t *plist) { - return internal_plist_receive_timeout(client, plist, 10000); + return internal_plist_receive_timeout(client, plist, 30000); } -/** - * Enable SSL for the given property list service client. - * - * @param client The connected property list service client for which SSL - * should be enabled. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG if client or client->connection is - * NULL, PROPERTY_LIST_SERVICE_E_SSL_ERROR when SSL could not be enabled, - * or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. - */ property_list_service_error_t property_list_service_enable_ssl(property_list_service_client_t client) { - if (!client || !client->connection) + if (!client || !client->parent) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - return idevice_to_property_list_service_error(idevice_connection_enable_ssl(client->connection)); + return service_to_property_list_service_error(service_enable_ssl(client->parent)); } -/** - * Disable SSL for the given property list service client. - * - * @param client The connected property list service client for which SSL - * should be disabled. - * - * @return PROPERTY_LIST_SERVICE_E_SUCCESS on success, - * PROPERTY_LIST_SERVICE_E_INVALID_ARG if client or client->connection is - * NULL, or PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR otherwise. - */ property_list_service_error_t property_list_service_disable_ssl(property_list_service_client_t client) { - if (!client || !client->connection) + if (!client || !client->parent) return PROPERTY_LIST_SERVICE_E_INVALID_ARG; - return idevice_to_property_list_service_error(idevice_connection_disable_ssl(client->connection)); + return service_to_property_list_service_error(service_disable_ssl(client->parent)); } +property_list_service_error_t property_list_service_get_service_client(property_list_service_client_t client, service_client_t *service_client) +{ + if (!client || !client->parent || !service_client) + return PROPERTY_LIST_SERVICE_E_INVALID_ARG; + *service_client = client->parent; + return PROPERTY_LIST_SERVICE_E_SUCCESS; +} diff --git a/src/property_list_service.h b/src/property_list_service.h index 037f9aa..0e9e948 100644 --- a/src/property_list_service.h +++ b/src/property_list_service.h @@ -1,59 +1,33 @@ - /* +/* * property_list_service.h * Definitions for the PropertyList service - * + * * Copyright (c) 2010 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef PROPERTY_LIST_SERVICE_H -#define PROPERTY_LIST_SERVICE_H -#include "idevice.h" - -/* Error Codes */ -#define PROPERTY_LIST_SERVICE_E_SUCCESS 0 -#define PROPERTY_LIST_SERVICE_E_INVALID_ARG -1 -#define PROPERTY_LIST_SERVICE_E_PLIST_ERROR -2 -#define PROPERTY_LIST_SERVICE_E_MUX_ERROR -3 -#define PROPERTY_LIST_SERVICE_E_SSL_ERROR -4 +#ifndef __PROPERTY_LIST_SERVICE_H +#define __PROPERTY_LIST_SERVICE_H -#define PROPERTY_LIST_SERVICE_E_UNKNOWN_ERROR -256 +#include "idevice.h" +#include "libimobiledevice/property_list_service.h" +#include "service.h" struct property_list_service_client_private { - idevice_connection_t connection; + service_client_t parent; }; -typedef struct property_list_service_client_private *property_list_service_client_t; - -typedef int16_t property_list_service_error_t; - -/* creation and destruction */ -property_list_service_error_t property_list_service_client_new(idevice_t device, uint16_t port, property_list_service_client_t *client); -property_list_service_error_t property_list_service_client_free(property_list_service_client_t client); - -/* sending */ -property_list_service_error_t property_list_service_send_xml_plist(property_list_service_client_t client, plist_t plist); -property_list_service_error_t property_list_service_send_binary_plist(property_list_service_client_t client, plist_t plist); - -/* receiving */ -property_list_service_error_t property_list_service_receive_plist_with_timeout(property_list_service_client_t client, plist_t *plist, unsigned int timeout); -property_list_service_error_t property_list_service_receive_plist(property_list_service_client_t client, plist_t *plist); - -/* misc */ -property_list_service_error_t property_list_service_enable_ssl(property_list_service_client_t client); -property_list_service_error_t property_list_service_disable_ssl(property_list_service_client_t client); - #endif diff --git a/src/restore.c b/src/restore.c index fd23d85..d13a28a 100644 --- a/src/restore.c +++ b/src/restore.c @@ -19,17 +19,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <arpa/inet.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <errno.h> #include <string.h> #include <stdlib.h> -#include <glib.h> #include <plist/plist.h> #include "property_list_service.h" #include "restore.h" #include "idevice.h" -#include "debug.h" +#include "common/debug.h" #define RESULT_SUCCESS 0 #define RESULT_FAILURE 1 @@ -43,7 +44,7 @@ * * @return RESULT_SUCCESS when the result is 'Success', * RESULT_FAILURE when the result is 'Failure', - * or a negative value if an error occured during evaluation. + * or a negative value if an error occurred during evaluation. */ static int restored_check_result(plist_t dict) { @@ -87,40 +88,49 @@ static void plist_dict_add_label(plist_t plist, const char *label) { if (plist && label) { if (plist_get_node_type(plist) == PLIST_DICT) - plist_dict_insert_item(plist, "Label", plist_new_string(label)); + plist_dict_set_item(plist, "Label", plist_new_string(label)); } } -/** - * Closes the restored client session if one is running and frees up the - * restored_client struct. - * - * @param client The restore client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ +static restored_error_t restored_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return RESTORE_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return RESTORE_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return RESTORE_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return RESTORE_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return RESTORE_E_RECEIVE_TIMEOUT; + default: + break; + } + return RESTORE_E_UNKNOWN_ERROR; +} + restored_error_t restored_client_free(restored_client_t client) { if (!client) return RESTORE_E_INVALID_ARG; - + restored_error_t ret = RESTORE_E_UNKNOWN_ERROR; if (client->parent) { restored_goodbye(client); - if (property_list_service_client_free(client->parent) == PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_SUCCESS; - } + ret = restored_error(property_list_service_client_free(client->parent)); } - if (client->uuid) { - free(client->uuid); + if (client->udid) { + free(client->udid); } if (client->label) { free(client->label); } - + if (client->info) { plist_free(client->info); } @@ -129,13 +139,6 @@ restored_error_t restored_client_free(restored_client_t client) return ret; } -/** - * Sets the label to send for requests to restored. - * - * @param client The restore client - * @param label The label to set or NULL to disable sending a label - * - */ void restored_client_set_label(restored_client_t client, const char *label) { if (client) { @@ -146,71 +149,22 @@ void restored_client_set_label(restored_client_t client, const char *label) } } -/** - * Receives a plist from restored. - * - * @param client The restored client - * @param plist The plist to store the received data - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ restored_error_t restored_receive(restored_client_t client, plist_t *plist) { if (!client || !plist || (plist && *plist)) return RESTORE_E_INVALID_ARG; - - restored_error_t ret = RESTORE_E_SUCCESS; - property_list_service_error_t err; - - err = property_list_service_receive_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_UNKNOWN_ERROR; - } - if (!*plist) - ret = RESTORE_E_PLIST_ERROR; - - return ret; + return restored_error(property_list_service_receive_plist(client->parent, plist)); } -/** - * Sends a plist to restored. - * - * @note This function is low-level and should only be used if you need to send - * a new type of message. - * - * @param client The restored client - * @param plist The plist to send - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client or - * plist is NULL - */ restored_error_t restored_send(restored_client_t client, plist_t plist) { if (!client || !plist) return RESTORE_E_INVALID_ARG; - restored_error_t ret = RESTORE_E_SUCCESS; - idevice_error_t err; - - err = property_list_service_send_xml_plist(client->parent, plist); - if (err != PROPERTY_LIST_SERVICE_E_SUCCESS) { - ret = RESTORE_E_UNKNOWN_ERROR; - } - return ret; + return restored_error(property_list_service_send_xml_plist(client->parent, plist)); } -/** - * Query the type of the service daemon. Depending on whether the device is - * queried in normal mode or restore mode, different types will be returned. - * - * @param client The restored client - * @param type The type returned by the service daemon. Pass NULL to ignore. - * @param version The restore protocol version. Pass NULL to ignore. - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ restored_error_t restored_query_type(restored_client_t client, char **type, uint64_t *version) { if (!client) @@ -220,7 +174,7 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("QueryType")); + plist_dict_set_item(dict,"Request", plist_new_string("QueryType")); debug_info("called"); ret = restored_send(client, dict); @@ -229,25 +183,25 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint dict = NULL; ret = restored_receive(client, &dict); - + if (RESTORE_E_SUCCESS != ret) return ret; ret = RESTORE_E_UNKNOWN_ERROR; - if (restored_check_result(dict) == RESULT_SUCCESS) { + plist_t type_node = plist_dict_get_item(dict, "Type"); + if (type_node && (plist_get_node_type(type_node) == PLIST_STRING)) { + char* typestr = NULL; + /* save our device information info */ client->info = dict; - + + plist_get_string_val(type_node, &typestr); + debug_info("success with type %s", typestr); /* return the type if requested */ if (type) { - plist_t type_node = plist_dict_get_item(dict, "Type"); - if (type_node && PLIST_STRING == plist_get_node_type(type_node)) { - plist_get_string_val(type_node, type); - debug_info("success with type %s", *type); - ret = RESTORE_E_SUCCESS; - } else { - return RESTORE_E_UNKNOWN_ERROR; - } + *type = typestr; + } else { + free(typestr); } /* fetch the restore protocol version */ @@ -256,87 +210,121 @@ restored_error_t restored_query_type(restored_client_t client, char **type, uint if (version_node && PLIST_UINT == plist_get_node_type(version_node)) { plist_get_uint_val(version_node, version); debug_info("restored protocol version %llu", *version); - ret = RESTORE_E_SUCCESS; } else { return RESTORE_E_UNKNOWN_ERROR; } } ret = RESTORE_E_SUCCESS; + } else { + debug_info("hmm. QueryType response does not contain a type?!"); + debug_plist(dict); + plist_free(dict); } return ret; } -/** - * Retrieves a value from information plist specified by a key. - * - * @param client An initialized restored client. - * @param key The key name to request or NULL to query for all keys - * @param value A plist node representing the result value node - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, RESTORE_E_PLIST_ERROR if value for key can't be found - */ -restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) +restored_error_t restored_query_value(restored_client_t client, const char *key, plist_t *value) { + if (!client || !key) + return RESTORE_E_INVALID_ARG; + + plist_t dict = NULL; + restored_error_t ret = RESTORE_E_UNKNOWN_ERROR; + + /* setup request plist */ + dict = plist_new_dict(); + plist_dict_add_label(dict, client->label); + if (key) { + plist_dict_set_item(dict,"QueryKey", plist_new_string(key)); + } + plist_dict_set_item(dict,"Request", plist_new_string("QueryValue")); + + /* send to device */ + ret = restored_send(client, dict); + + plist_free(dict); + dict = NULL; + + if (ret != RESTORE_E_SUCCESS) + return ret; + + /* Now get device's answer */ + ret = restored_receive(client, &dict); + if (ret != RESTORE_E_SUCCESS) + return ret; + + plist_t value_node = plist_dict_get_item(dict, key); + if (value_node) { + debug_info("has a value"); + *value = plist_copy(value_node); + } else { + ret = RESTORE_E_PLIST_ERROR; + } + + plist_free(dict); + return ret; +} + +restored_error_t restored_get_value(restored_client_t client, const char *key, plist_t *value) +{ + plist_t item; + if (!client || !value || (value && *value)) return RESTORE_E_INVALID_ARG; - + if (!client->info) return RESTORE_E_NOT_ENOUGH_DATA; - - restored_error_t ret = RESTORE_E_SUCCESS; - plist_t item = NULL; - + if (!key) { *value = plist_copy(client->info); return RESTORE_E_SUCCESS; - } else { - item = plist_dict_get_item(client->info, key); } - - if (item) { - *value = plist_copy(item); - } else { - ret = RESTORE_E_PLIST_ERROR; + + item = plist_dict_get_item(client->info, key); + if (!item) { + return RESTORE_E_PLIST_ERROR; } - - return ret; + + *value = plist_copy(item); + + return RESTORE_E_SUCCESS; } -/** - * Creates a new restored client for the device. - * - * @param device The device to create a restored client for - * @param client The pointer to the location of the new restored_client - * @param label The label to use for communication. Usually the program name. - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL - */ restored_error_t restored_client_new(idevice_t device, restored_client_t *client, const char *label) { if (!client) return RESTORE_E_INVALID_ARG; restored_error_t ret = RESTORE_E_SUCCESS; + idevice_error_t idev_ret; + + static struct lockdownd_service_descriptor service = { + .port = 0xf27e, + .ssl_enabled = 0 + }; property_list_service_client_t plistclient = NULL; - if (property_list_service_client_new(device, 0xf27e, &plistclient) != PROPERTY_LIST_SERVICE_E_SUCCESS) { - debug_info("could not connect to restored (device %s)", device->uuid); - return RESTORE_E_MUX_ERROR; + ret = restored_error(property_list_service_client_new(device, (lockdownd_service_descriptor_t)&service, &plistclient)); + if (ret != RESTORE_E_SUCCESS) { + debug_info("could not connect to restored (device %s)", device->udid); + return ret; } restored_client_t client_loc = (restored_client_t) malloc(sizeof(struct restored_client_private)); client_loc->parent = plistclient; - client_loc->uuid = NULL; + client_loc->udid = NULL; client_loc->label = NULL; + client_loc->info = NULL; if (label != NULL) client_loc->label = strdup(label); - ret = idevice_get_uuid(device, &client_loc->uuid); - if (RESTORE_E_SUCCESS != ret) { - debug_info("failed to get device uuid."); + idev_ret = idevice_get_udid(device, &client_loc->udid); + if (IDEVICE_E_SUCCESS != idev_ret) { + debug_info("failed to get device udid."); + ret = RESTORE_E_UNKNOWN_ERROR; } - debug_info("device uuid: %s", client_loc->uuid); + debug_info("device udid: %s", client_loc->udid); if (RESTORE_E_SUCCESS == ret) { *client = client_loc; @@ -347,14 +335,6 @@ restored_error_t restored_client_new(idevice_t device, restored_client_t *client return ret; } -/** - * Sends the Goodbye request to restored signaling the end of communication. - * - * @param client The restore client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG when client is NULL, - * RESTORE_E_PLIST_ERROR if the device did not acknowledge the request - */ restored_error_t restored_goodbye(restored_client_t client) { if (!client) @@ -364,7 +344,7 @@ restored_error_t restored_goodbye(restored_client_t client) plist_t dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Goodbye")); + plist_dict_set_item(dict,"Request", plist_new_string("Goodbye")); debug_info("called"); @@ -387,15 +367,7 @@ restored_error_t restored_goodbye(restored_client_t client) return ret; } -/** - * Requests to start a restore and retrieve it's port on success. - * - * @param client The restored client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter - * is NULL, RESTORE_E_START_RESTORE_FAILED if the request fails - */ -restored_error_t restored_start_restore(restored_client_t client) +restored_error_t restored_start_restore(restored_client_t client, plist_t options, uint64_t version) { if (!client) return RESTORE_E_INVALID_ARG; @@ -405,8 +377,11 @@ restored_error_t restored_start_restore(restored_client_t client) dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("StartRestore")); - plist_dict_insert_item(dict,"RestoreProtocolVersion", plist_new_uint(2)); + plist_dict_set_item(dict,"Request", plist_new_string("StartRestore")); + if (options) { + plist_dict_set_item(dict, "RestoreOptions", plist_copy(options)); + } + plist_dict_set_item(dict,"RestoreProtocolVersion", plist_new_uint(version)); /* send to device */ ret = restored_send(client, dict); @@ -416,14 +391,6 @@ restored_error_t restored_start_restore(restored_client_t client) return ret; } -/** - * Requests device to reboot. - * - * @param client The restored client - * - * @return RESTORE_E_SUCCESS on success, NP_E_INVALID_ARG if a parameter - * is NULL - */ restored_error_t restored_reboot(restored_client_t client) { if (!client) @@ -434,7 +401,7 @@ restored_error_t restored_reboot(restored_client_t client) dict = plist_new_dict(); plist_dict_add_label(dict, client->label); - plist_dict_insert_item(dict,"Request", plist_new_string("Reboot")); + plist_dict_set_item(dict,"Request", plist_new_string("Reboot")); /* send to device */ ret = restored_send(client, dict); @@ -455,4 +422,3 @@ restored_error_t restored_reboot(restored_client_t client) dict = NULL; return ret; } - diff --git a/src/restore.h b/src/restore.h index d790d01..ec6fa04 100644 --- a/src/restore.h +++ b/src/restore.h @@ -19,17 +19,18 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef RESTORED_H -#define RESTORED_H +#ifndef __RESTORED_H +#define __RESTORED_H #include <string.h> +#include "idevice.h" #include "libimobiledevice/restore.h" #include "property_list_service.h" struct restored_client_private { property_list_service_client_t parent; - char *uuid; + char *udid; char *label; plist_t info; }; diff --git a/src/reverse_proxy.c b/src/reverse_proxy.c new file mode 100644 index 0000000..2fcfdd1 --- /dev/null +++ b/src/reverse_proxy.c @@ -0,0 +1,810 @@ +/* + * 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> +#define _GNU_SOURCE 1 +#define __USE_GNU 1 +#include <stdio.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" +#include "asprintf.h" + +#ifndef ECONNRESET +#define ECONNRESET 108 +#endif +#ifndef ETIMEDOUT +#define ETIMEDOUT 138 +#endif + +#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; + if(vasprintf(&buffer, format, args)<0){} + 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; + if(vasprintf(&buffer, format, args)<0){} + 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); + if (sockfd < 0) { + free(buf); + _reverse_proxy_log(client, "ERROR: Connection to %s:%u failed: %s", host, port, strerror(errno)); + free(host); + 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(host); + 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; +} + +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; +} + +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; +} + +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; +} + +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; +} + +reverse_proxy_client_type_t reverse_proxy_get_type(reverse_proxy_client_t client) +{ + if (!client) + return 0; + return client->type; +} + +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; +} + +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; +} + +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..7f441bd --- /dev/null +++ b/src/reverse_proxy.h @@ -0,0 +1,51 @@ +/* + * 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 "idevice.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 diff --git a/src/sbservices.c b/src/sbservices.c index 3596cbd..365e130 100644 --- a/src/sbservices.c +++ b/src/sbservices.c @@ -8,17 +8,20 @@ * 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 + * 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 <unistd.h> @@ -26,28 +29,28 @@ #include "sbservices.h" #include "property_list_service.h" -#include "debug.h" +#include "common/debug.h" /** * Locks an sbservices client, used for thread safety. * * @param client sbservices client to lock. */ -static void sbs_lock(sbservices_client_t client) +static void sbservices_lock(sbservices_client_t client) { - debug_info("SBServices: Locked"); - g_mutex_lock(client->mutex); + debug_info("Locked"); + mutex_lock(&client->mutex); } /** * Unlocks an sbservices client, used for thread safety. - * + * * @param client sbservices client to unlock */ -static void sbs_unlock(sbservices_client_t client) +static void sbservices_unlock(sbservices_client_t client) { - debug_info("SBServices: Unlocked"); - g_mutex_unlock(client->mutex); + debug_info("Unlocked"); + mutex_unlock(&client->mutex); } /** @@ -61,64 +64,44 @@ static void sbs_unlock(sbservices_client_t client) */ static sbservices_error_t sbservices_error(property_list_service_error_t err) { - switch (err) { - case PROPERTY_LIST_SERVICE_E_SUCCESS: - return SBSERVICES_E_SUCCESS; - case PROPERTY_LIST_SERVICE_E_INVALID_ARG: - return SBSERVICES_E_INVALID_ARG; - case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: - return SBSERVICES_E_PLIST_ERROR; - case PROPERTY_LIST_SERVICE_E_MUX_ERROR: - return SBSERVICES_E_CONN_FAILED; - default: - break; - } - return SBSERVICES_E_UNKNOWN_ERROR; + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return SBSERVICES_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return SBSERVICES_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return SBSERVICES_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return SBSERVICES_E_CONN_FAILED; + default: + break; + } + return SBSERVICES_E_UNKNOWN_ERROR; } -/** - * Connects to the springboardservices service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will point to a newly allocated - * sbservices_client_t upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ -sbservices_error_t sbservices_client_new(idevice_t device, uint16_t port, sbservices_client_t *client) +sbservices_error_t sbservices_client_new(idevice_t device, lockdownd_service_descriptor_t service, sbservices_client_t *client) { - /* makes sure thread environment is available */ - if (!g_thread_supported()) - g_thread_init(NULL); - - if (!device) - return SBSERVICES_E_INVALID_ARG; - property_list_service_client_t plistclient = NULL; - sbservices_error_t err = sbservices_error(property_list_service_client_new(device, port, &plistclient)); + sbservices_error_t err = sbservices_error(property_list_service_client_new(device, service, &plistclient)); if (err != SBSERVICES_E_SUCCESS) { return err; } sbservices_client_t client_loc = (sbservices_client_t) malloc(sizeof(struct sbservices_client_private)); client_loc->parent = plistclient; - client_loc->mutex = g_mutex_new(); + mutex_init(&client_loc->mutex); *client = client_loc; return SBSERVICES_E_SUCCESS; } -/** - * Disconnects an sbservices client from the device and frees up the - * sbservices client data. - * - * @param client The sbservices client to disconnect and free. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client is NULL, or an SBSERVICES_E_* error code otherwise. - */ +sbservices_error_t sbservices_client_start_service(idevice_t device, sbservices_client_t * client, const char* label) +{ + sbservices_error_t err = SBSERVICES_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SBSERVICES_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(sbservices_client_new), &err); + return err; +} + sbservices_error_t sbservices_client_free(sbservices_client_t client) { if (!client) @@ -126,28 +109,12 @@ sbservices_error_t sbservices_client_free(sbservices_client_t client) sbservices_error_t err = sbservices_error(property_list_service_client_free(client->parent)); client->parent = NULL; - if (client->mutex) { - g_mutex_free(client->mutex); - } + mutex_destroy(&client->mutex); free(client); return err; } -/** - * Gets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param state Pointer that will point to a newly allocated plist containing - * the current icon state. It is up to the caller to free the memory. - * @param format_version A string to be passed as formatVersion along with - * the request, or NULL if no formatVersion should be passed. This is only - * supported since iOS 4.0 so for older firmware versions this must be set - * to NULL. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or state is invalid, or an SBSERVICES_E_* error code otherwise. - */ sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t *state, const char *format_version) { if (!client || !client->parent || !state) @@ -156,12 +123,12 @@ sbservices_error_t sbservices_get_icon_state(sbservices_client_t client, plist_t sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getIconState")); + plist_dict_set_item(dict, "command", plist_new_string("getIconState")); if (format_version) { - plist_dict_insert_item(dict, "formatVersion", plist_new_string(format_version)); + plist_dict_set_item(dict, "formatVersion", plist_new_string(format_version)); } - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -184,19 +151,10 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } -/** - * Sets the icon state of the connected device. - * - * @param client The connected sbservices client to use. - * @param newstate A plist containing the new iconstate. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or newstate is NULL, or an SBSERVICES_E_* error code otherwise. - */ sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t newstate) { if (!client || !client->parent || !newstate) @@ -205,39 +163,27 @@ sbservices_error_t sbservices_set_icon_state(sbservices_client_t client, plist_t sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("setIconState")); - plist_dict_insert_item(dict, "iconState", plist_copy(newstate)); + plist_dict_set_item(dict, "command", plist_new_string("setIconState")); + plist_dict_set_item(dict, "iconState", plist_copy(newstate)); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { debug_info("could not send plist, error %d", res); } - /* NO RESPONSE */ + + uint32_t bytes = 0; + service_receive_with_timeout(client->parent->parent, malloc(4), 4, &bytes, 2000); + debug_info("setIconState response: %u", bytes); if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } -/** - * Get the icon of the specified app as PNG data. - * - * @param client The connected sbservices client to use. - * @param bundleId The bundle identifier of the app to retrieve the icon for. - * @param pngdata Pointer that will point to a newly allocated buffer - * containing the PNG data upon successful return. It is up to the caller - * to free the memory. - * @param pngsize Pointer to a uint64_t that will be set to the size of the - * buffer pngdata points to upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client, bundleId, or pngdata are invalid, or an SBSERVICES_E_* error - * code otherwise. - */ sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const char *bundleId, char **pngdata, uint64_t *pngsize) { if (!client || !client->parent || !bundleId || !pngdata) @@ -246,10 +192,10 @@ sbservices_error_t sbservices_get_icon_pngdata(sbservices_client_t client, const sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getIconPNGData")); - plist_dict_insert_item(dict, "bundleId", plist_new_string(bundleId)); + plist_dict_set_item(dict, "command", plist_new_string("getIconPNGData")); + plist_dict_set_item(dict, "bundleId", plist_new_string(bundleId)); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -271,25 +217,48 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; +} + +sbservices_error_t sbservices_get_interface_orientation(sbservices_client_t client, sbservices_interface_orientation_t* interface_orientation) +{ + if (!client || !client->parent || !interface_orientation) + return SBSERVICES_E_INVALID_ARG; + + sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; + + plist_t dict = plist_new_dict(); + plist_dict_set_item(dict, "command", plist_new_string("getInterfaceOrientation")); + + sbservices_lock(client); + + res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); + if (res != SBSERVICES_E_SUCCESS) { + debug_info("could not send plist, error %d", res); + goto leave_unlock; + } + plist_free(dict); + dict = NULL; + + res = sbservices_error(property_list_service_receive_plist(client->parent, &dict)); + if (res == SBSERVICES_E_SUCCESS) { + plist_t node = plist_dict_get_item(dict, "interfaceOrientation"); + if (node) { + uint64_t value = SBSERVICES_INTERFACE_ORIENTATION_UNKNOWN; + plist_get_uint_val(node, &value); + *interface_orientation = (sbservices_interface_orientation_t)value; + } + } +leave_unlock: + if (dict) { + plist_free(dict); + } + sbservices_unlock(client); + return res; } -/** - * Get the home screen wallpaper as PNG data. - * - * @param client The connected sbservices client to use. - * @param pngdata Pointer that will point to a newly allocated buffer - * containing the PNG data upon successful return. It is up to the caller - * to free the memory. - * @param pngsize Pointer to a uint64_t that will be set to the size of the - * buffer pngdata points to upon successful return. - * - * @return SBSERVICES_E_SUCCESS on success, SBSERVICES_E_INVALID_ARG when - * client or pngdata are invalid, or an SBSERVICES_E_* error - * code otherwise. - */ sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_client_t client, char **pngdata, uint64_t *pngsize) { if (!client || !client->parent || !pngdata) @@ -298,9 +267,9 @@ sbservices_error_t sbservices_get_home_screen_wallpaper_pngdata(sbservices_clien sbservices_error_t res = SBSERVICES_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "command", plist_new_string("getHomeScreenWallpaperPNGData")); + plist_dict_set_item(dict, "command", plist_new_string("getHomeScreenWallpaperPNGData")); - sbs_lock(client); + sbservices_lock(client); res = sbservices_error(property_list_service_send_binary_plist(client->parent, dict)); if (res != SBSERVICES_E_SUCCESS) { @@ -322,6 +291,6 @@ leave_unlock: if (dict) { plist_free(dict); } - sbs_unlock(client); + sbservices_unlock(client); return res; } diff --git a/src/sbservices.h b/src/sbservices.h index 3a4120f..b67281e 100644 --- a/src/sbservices.h +++ b/src/sbservices.h @@ -8,27 +8,28 @@ * 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef ISBSERVICES_H -#define ISBSERVICES_H -#include <glib.h> +#ifndef __SBSERVICES_H +#define __SBSERVICES_H +#include "idevice.h" #include "libimobiledevice/sbservices.h" #include "property_list_service.h" +#include <libimobiledevice-glue/thread.h> struct sbservices_client_private { property_list_service_client_t parent; - GMutex *mutex; + mutex_t mutex; }; #endif diff --git a/src/screenshotr.c b/src/screenshotr.c index 0c4c9b2..c3cc9ba 100644 --- a/src/screenshotr.c +++ b/src/screenshotr.c @@ -1,33 +1,36 @@ /* - * screenshotr.c + * screenshotr.c * com.apple.mobile.screenshotr service implementation. - * - * Copyright (c) 2010 Nikias Bassen All Rights Reserved. + * + * Copyright (c) 2010-2019 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif #include <plist/plist.h> #include <string.h> #include <stdlib.h> #include "screenshotr.h" #include "device_link_service.h" -#include "debug.h" +#include "common/debug.h" -#define SCREENSHOTR_VERSION_INT1 100 +#define SCREENSHOTR_VERSION_INT1 400 #define SCREENSHOTR_VERSION_INT2 0 /** @@ -50,6 +53,10 @@ static screenshotr_error_t screenshotr_error(device_link_service_error_t err) return SCREENSHOTR_E_PLIST_ERROR; case DEVICE_LINK_SERVICE_E_MUX_ERROR: return SCREENSHOTR_E_MUX_ERROR; + case DEVICE_LINK_SERVICE_E_SSL_ERROR: + return SCREENSHOTR_E_SSL_ERROR; + case DEVICE_LINK_SERVICE_E_RECEIVE_TIMEOUT: + return SCREENSHOTR_E_RECEIVE_TIMEOUT; case DEVICE_LINK_SERVICE_E_BAD_VERSION: return SCREENSHOTR_E_BAD_VERSION; default: @@ -58,29 +65,14 @@ static screenshotr_error_t screenshotr_error(device_link_service_error_t err) return SCREENSHOTR_E_UNKNOWN_ERROR; } -/** - * Connects to the screenshotr service on the specified device. - * - * @param device The device to connect to. - * @param port Destination port (usually given by lockdownd_start_service). - * @param client Pointer that will be set to a newly allocated - * screenshotr_client_t upon successful return. - * - * @note This service is only available if a developer disk image has been - * mounted. - * - * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID ARG if one - * or more parameters are invalid, or SCREENSHOTR_E_CONN_FAILED if the - * connection to the device could not be established. - */ -screenshotr_error_t screenshotr_client_new(idevice_t device, uint16_t port, +screenshotr_error_t screenshotr_client_new(idevice_t device, lockdownd_service_descriptor_t service, screenshotr_client_t * client) { - if (!device || port == 0 || !client || *client) + if (!device || !service || service->port == 0 || !client || *client) return SCREENSHOTR_E_INVALID_ARG; device_link_service_client_t dlclient = NULL; - screenshotr_error_t ret = screenshotr_error(device_link_service_client_new(device, port, &dlclient)); + screenshotr_error_t ret = screenshotr_error(device_link_service_client_new(device, service, &dlclient)); if (ret != SCREENSHOTR_E_SUCCESS) { return ret; } @@ -101,39 +93,23 @@ screenshotr_error_t screenshotr_client_new(idevice_t device, uint16_t port, return ret; } -/** - * Disconnects a screenshotr client from the device and frees up the - * screenshotr client data. - * - * @param client The screenshotr client to disconnect and free. - * - * @return SCREENSHOTR_E_SUCCESS on success, or SCREENSHOTR_E_INVALID_ARG - * if client is NULL. - */ +screenshotr_error_t screenshotr_client_start_service(idevice_t device, screenshotr_client_t * client, const char* label) +{ + screenshotr_error_t err = SCREENSHOTR_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SCREENSHOTR_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(screenshotr_client_new), &err); + return err; +} + screenshotr_error_t screenshotr_client_free(screenshotr_client_t client) { if (!client) return SCREENSHOTR_E_INVALID_ARG; - device_link_service_disconnect(client->parent); + device_link_service_disconnect(client->parent, NULL); screenshotr_error_t err = screenshotr_error(device_link_service_client_free(client->parent)); free(client); return err; } -/** - * Get a screen shot from the connected device. - * - * @param client The connection screenshotr service client. - * @param imgdata Pointer that will point to a newly allocated buffer - * containing TIFF image data upon successful return. It is up to the - * caller to free the memory. - * @param imgsize Pointer to a uint64_t that will be set to the size of the - * buffer imgdata points to upon successful return. - * - * @return SCREENSHOTR_E_SUCCESS on success, SCREENSHOTR_E_INVALID_ARG if - * one or more parameters are invalid, or another error code if an - * error occured. - */ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, char **imgdata, uint64_t *imgsize) { if (!client || !client->parent || !imgdata) @@ -142,7 +118,7 @@ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, cha screenshotr_error_t res = SCREENSHOTR_E_UNKNOWN_ERROR; plist_t dict = plist_new_dict(); - plist_dict_insert_item(dict, "MessageType", plist_new_string("ScreenShotRequest")); + plist_dict_set_item(dict, "MessageType", plist_new_string("ScreenShotRequest")); res = screenshotr_error(device_link_service_send_process_message(client->parent, dict)); plist_free(dict); @@ -166,7 +142,7 @@ screenshotr_error_t screenshotr_take_screenshot(screenshotr_client_t client, cha plist_t node = plist_dict_get_item(dict, "MessageType"); char *strval = NULL; plist_get_string_val(node, &strval); - if (!strval || strcmp(strval, "ScreenShotReply")) { + if (!strval || strcmp(strval, "ScreenShotReply") != 0) { debug_info("invalid screenshot data received!"); res = SCREENSHOTR_E_PLIST_ERROR; goto leave; diff --git a/src/screenshotr.h b/src/screenshotr.h index df6774a..1319ec0 100644 --- a/src/screenshotr.h +++ b/src/screenshotr.h @@ -1,26 +1,28 @@ /* * screenshotr.h * com.apple.mobile.screenshotr service header file. - * + * * Copyright (c) 2010 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 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef SCREENSHOTR_H -#define SCREENSHOTR_H +#ifndef __SCREENSHOTR_H +#define __SCREENSHOTR_H + +#include "idevice.h" #include "libimobiledevice/screenshotr.h" #include "device_link_service.h" diff --git a/src/service.c b/src/service.c new file mode 100644 index 0000000..9474021 --- /dev/null +++ b/src/service.c @@ -0,0 +1,207 @@ +/* + * service.c + * generic service implementation. + * + * Copyright (c) 2013 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 + */ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <stdlib.h> +#include <string.h> + +#include "service.h" +#include "idevice.h" +#include "common/debug.h" + +/** + * Convert an idevice_error_t value to an service_error_t value. + * Used internally to get correct error codes. + * + * @param err An idevice_error_t error code + * + * @return A matching service_error_t error code, + * SERVICE_E_UNKNOWN_ERROR otherwise. + */ +static service_error_t idevice_to_service_error(idevice_error_t err) +{ + switch (err) { + case IDEVICE_E_SUCCESS: + return SERVICE_E_SUCCESS; + case IDEVICE_E_INVALID_ARG: + return SERVICE_E_INVALID_ARG; + case IDEVICE_E_SSL_ERROR: + return SERVICE_E_SSL_ERROR; + case IDEVICE_E_NOT_ENOUGH_DATA: + return SERVICE_E_NOT_ENOUGH_DATA; + case IDEVICE_E_TIMEOUT: + return SERVICE_E_TIMEOUT; + default: + break; + } + return SERVICE_E_UNKNOWN_ERROR; +} + +service_error_t service_client_new(idevice_t device, lockdownd_service_descriptor_t service, service_client_t *client) +{ + if (!device || !service || service->port == 0 || !client || *client) + return SERVICE_E_INVALID_ARG; + + /* Attempt connection */ + idevice_connection_t connection = NULL; + if (idevice_connect(device, service->port, &connection) != IDEVICE_E_SUCCESS) { + return SERVICE_E_MUX_ERROR; + } + + /* create client object */ + service_client_t client_loc = (service_client_t)malloc(sizeof(struct service_client_private)); + client_loc->connection = connection; + + /* enable SSL if requested */ + if (service->ssl_enabled == 1) + service_enable_ssl(client_loc); + + /* all done, return success */ + *client = client_loc; + return SERVICE_E_SUCCESS; +} + +service_error_t service_client_factory_start_service(idevice_t device, const char* service_name, void **client, const char* label, int32_t (*constructor_func)(idevice_t, lockdownd_service_descriptor_t, void**), int32_t *error_code) +{ + *client = NULL; + + lockdownd_client_t lckd = NULL; + if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(device, &lckd, label)) { + debug_info("Could not create a lockdown client."); + return SERVICE_E_START_SERVICE_ERROR; + } + + lockdownd_service_descriptor_t service = NULL; + lockdownd_error_t lerr = lockdownd_start_service(lckd, service_name, &service); + lockdownd_client_free(lckd); + + if (lerr != LOCKDOWN_E_SUCCESS) { + debug_info("Could not start service %s: %s", service_name, lockdownd_strerror(lerr)); + return SERVICE_E_START_SERVICE_ERROR; + } + + int32_t ec; + if (constructor_func) { + ec = (int32_t)constructor_func(device, service, client); + } else { + ec = service_client_new(device, service, (service_client_t*)client); + } + if (error_code) { + *error_code = ec; + } + + if (ec != SERVICE_E_SUCCESS) { + debug_info("Could not connect to service %s! Port: %i, error: %i", service_name, service->port, ec); + } + + lockdownd_service_descriptor_free(service); + service = NULL; + + return (ec == SERVICE_E_SUCCESS) ? SERVICE_E_SUCCESS : SERVICE_E_START_SERVICE_ERROR; +} + +service_error_t service_client_free(service_client_t client) +{ + if (!client) + return SERVICE_E_INVALID_ARG; + + service_error_t err = idevice_to_service_error(idevice_disconnect(client->connection)); + + free(client); + client = NULL; + + return err; +} + +service_error_t service_send(service_client_t client, const char* data, uint32_t size, uint32_t *sent) +{ + service_error_t res = SERVICE_E_UNKNOWN_ERROR; + uint32_t bytes = 0; + + if (!client || (client && !client->connection) || !data || (size == 0)) { + return SERVICE_E_INVALID_ARG; + } + + debug_info("sending %d bytes", size); + res = idevice_to_service_error(idevice_connection_send(client->connection, data, size, &bytes)); + if (res != SERVICE_E_SUCCESS) { + debug_info("ERROR: sending to device failed."); + } + if (sent) { + *sent = bytes; + } + + return res; +} + +service_error_t service_receive_with_timeout(service_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + service_error_t res = SERVICE_E_UNKNOWN_ERROR; + uint32_t bytes = 0; + + if (!client || (client && !client->connection) || !data || (size == 0)) { + return SERVICE_E_INVALID_ARG; + } + + res = idevice_to_service_error(idevice_connection_receive_timeout(client->connection, data, size, &bytes, timeout)); + if (res != SERVICE_E_SUCCESS && res != SERVICE_E_TIMEOUT) { + debug_info("could not read data"); + return res; + } + if (received) { + *received = bytes; + } + + return res; +} + +service_error_t service_receive(service_client_t client, char* data, uint32_t size, uint32_t *received) +{ + return service_receive_with_timeout(client, data, size, received, 30000); +} + +service_error_t service_enable_ssl(service_client_t client) +{ + if (!client || !client->connection) + return SERVICE_E_INVALID_ARG; + return idevice_to_service_error(idevice_connection_enable_ssl(client->connection)); +} + +service_error_t service_disable_ssl(service_client_t client) +{ + return service_disable_bypass_ssl(client, 0); +} + +service_error_t service_disable_bypass_ssl(service_client_t client, uint8_t sslBypass) +{ + if (!client || !client->connection) + return SERVICE_E_INVALID_ARG; + return idevice_to_service_error(idevice_connection_disable_bypass_ssl(client->connection, sslBypass)); +} + +service_error_t service_get_connection(service_client_t client, idevice_connection_t *connection) +{ + if (!client || !client->connection || !connection) + return SERVICE_E_INVALID_ARG; + *connection = client->connection; + return SERVICE_E_SUCCESS; +} diff --git a/src/service.h b/src/service.h new file mode 100644 index 0000000..071fe3f --- /dev/null +++ b/src/service.h @@ -0,0 +1,32 @@ +/* + * service.h + * Definitions for the generic service implementation + * + * Copyright (c) 2013 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 SERVICE_H +#define SERVICE_H + +#include "idevice.h" +#include "libimobiledevice/service.h" +#include "libimobiledevice/lockdown.h" + +struct service_client_private { + idevice_connection_t connection; +}; + +#endif diff --git a/src/syslog_relay.c b/src/syslog_relay.c new file mode 100644 index 0000000..9f4296e --- /dev/null +++ b/src/syslog_relay.c @@ -0,0 +1,248 @@ +/* + * syslog_relay.c + * com.apple.syslog_relay service implementation. + * + * Copyright (c) 2019-2020 Nikias Bassen, All Rights Reserved. + * Copyright (c) 2013-2015 Martin Szulecki, 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 "syslog_relay.h" +#include "lockdown.h" +#include "common/debug.h" + +struct syslog_relay_worker_thread { + syslog_relay_client_t client; + syslog_relay_receive_cb_t cbfunc; + void *user_data; + int is_raw; +}; + +/** + * Convert a service_error_t value to a syslog_relay_error_t value. + * Used internally to get correct error codes. + * + * @param err An service_error_t error code + * + * @return A matching syslog_relay_error_t error code, + * SYSLOG_RELAY_E_UNKNOWN_ERROR otherwise. + */ +static syslog_relay_error_t syslog_relay_error(service_error_t err) +{ + switch (err) { + case SERVICE_E_SUCCESS: + return SYSLOG_RELAY_E_SUCCESS; + case SERVICE_E_INVALID_ARG: + return SYSLOG_RELAY_E_INVALID_ARG; + case SERVICE_E_MUX_ERROR: + return SYSLOG_RELAY_E_MUX_ERROR; + case SERVICE_E_SSL_ERROR: + return SYSLOG_RELAY_E_SSL_ERROR; + case SERVICE_E_NOT_ENOUGH_DATA: + return SYSLOG_RELAY_E_NOT_ENOUGH_DATA; + case SERVICE_E_TIMEOUT: + return SYSLOG_RELAY_E_TIMEOUT; + default: + break; + } + return SYSLOG_RELAY_E_UNKNOWN_ERROR; +} + +syslog_relay_error_t syslog_relay_client_new(idevice_t device, lockdownd_service_descriptor_t service, syslog_relay_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to syslog_relay_client_new."); + return SYSLOG_RELAY_E_INVALID_ARG; + } + + debug_info("Creating syslog_relay_client, port = %d.", service->port); + + service_client_t parent = NULL; + syslog_relay_error_t ret = syslog_relay_error(service_client_new(device, service, &parent)); + if (ret != SYSLOG_RELAY_E_SUCCESS) { + debug_info("Creating base service client failed. Error: %i", ret); + return ret; + } + + syslog_relay_client_t client_loc = (syslog_relay_client_t) malloc(sizeof(struct syslog_relay_client_private)); + client_loc->parent = parent; + client_loc->worker = THREAD_T_NULL; + + *client = client_loc; + + debug_info("syslog_relay_client successfully created."); + return 0; +} + +syslog_relay_error_t syslog_relay_client_start_service(idevice_t device, syslog_relay_client_t * client, const char* label) +{ + syslog_relay_error_t err = SYSLOG_RELAY_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, SYSLOG_RELAY_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(syslog_relay_client_new), &err); + return err; +} + +syslog_relay_error_t syslog_relay_client_free(syslog_relay_client_t client) +{ + if (!client) + return SYSLOG_RELAY_E_INVALID_ARG; + syslog_relay_stop_capture(client); + syslog_relay_error_t err = syslog_relay_error(service_client_free(client->parent)); + free(client); + + return err; +} + +syslog_relay_error_t syslog_relay_receive(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received) +{ + return syslog_relay_receive_with_timeout(client, data, size, received, 1000); +} + +syslog_relay_error_t syslog_relay_receive_with_timeout(syslog_relay_client_t client, char* data, uint32_t size, uint32_t *received, unsigned int timeout) +{ + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + int bytes = 0; + + if (!client || !data || (size == 0)) { + return SYSLOG_RELAY_E_INVALID_ARG; + } + + res = syslog_relay_error(service_receive_with_timeout(client->parent, data, size, (uint32_t*)&bytes, timeout)); + if (res != SYSLOG_RELAY_E_SUCCESS && res != SYSLOG_RELAY_E_TIMEOUT && res != SYSLOG_RELAY_E_NOT_ENOUGH_DATA) { + debug_info("Could not read data, error %d", res); + } + if (received) { + *received = (uint32_t)bytes; + } + + return res; +} + +void *syslog_relay_worker(void *arg) +{ + syslog_relay_error_t ret = SYSLOG_RELAY_E_UNKNOWN_ERROR; + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)arg; + + if (!srwt) + return NULL; + + debug_info("Running"); + + while (srwt->client->parent) { + char c; + uint32_t bytes = 0; + ret = syslog_relay_receive_with_timeout(srwt->client, &c, 1, &bytes, 100); + if (ret == SYSLOG_RELAY_E_TIMEOUT || ret == SYSLOG_RELAY_E_NOT_ENOUGH_DATA || ((bytes == 0) && (ret == SYSLOG_RELAY_E_SUCCESS))) { + continue; + } + if (ret < 0) { + debug_info("Connection to syslog relay interrupted"); + break; + } + if (srwt->is_raw) { + srwt->cbfunc(c, srwt->user_data); + } else if (c != 0) { + srwt->cbfunc(c, srwt->user_data); + } + } + + if (srwt) { + free(srwt); + } + + debug_info("Exiting"); + + return NULL; +} + +syslog_relay_error_t syslog_relay_start_capture(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return SYSLOG_RELAY_E_INVALID_ARG; + + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); + if (srwt) { + srwt->client = client; + srwt->cbfunc = callback; + srwt->user_data = user_data; + srwt->is_raw = 0; + + if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { + res = SYSLOG_RELAY_E_SUCCESS; + } + } + + return res; +} + +syslog_relay_error_t syslog_relay_start_capture_raw(syslog_relay_client_t client, syslog_relay_receive_cb_t callback, void* user_data) +{ + if (!client || !callback) + return SYSLOG_RELAY_E_INVALID_ARG; + + syslog_relay_error_t res = SYSLOG_RELAY_E_UNKNOWN_ERROR; + + if (client->worker) { + debug_info("Another syslog capture thread appears to be running already."); + return res; + } + + /* start worker thread */ + struct syslog_relay_worker_thread *srwt = (struct syslog_relay_worker_thread*)malloc(sizeof(struct syslog_relay_worker_thread)); + if (srwt) { + srwt->client = client; + srwt->cbfunc = callback; + srwt->user_data = user_data; + srwt->is_raw = 1; + + if (thread_new(&client->worker, syslog_relay_worker, srwt) == 0) { + res = SYSLOG_RELAY_E_SUCCESS; + } + } + + return res; +} + +syslog_relay_error_t syslog_relay_stop_capture(syslog_relay_client_t client) +{ + if (client->worker) { + /* notify thread to finish */ + service_client_t parent = client->parent; + client->parent = NULL; + /* join thread to make it exit */ + thread_join(client->worker); + thread_free(client->worker); + client->worker = THREAD_T_NULL; + client->parent = parent; + } + + return SYSLOG_RELAY_E_SUCCESS; +} diff --git a/src/syslog_relay.h b/src/syslog_relay.h new file mode 100644 index 0000000..d5263e2 --- /dev/null +++ b/src/syslog_relay.h @@ -0,0 +1,37 @@ +/* + * syslog_relay.h + * com.apple.syslog_relay service header file. + * + * Copyright (c) 2013 Martin Szulecki 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 _SYSLOG_RELAY_H +#define _SYSLOG_RELAY_H + +#include "idevice.h" +#include "libimobiledevice/syslog_relay.h" +#include "service.h" +#include <libimobiledevice-glue/thread.h> + +struct syslog_relay_client_private { + service_client_t parent; + THREAD_T worker; +}; + +void *syslog_relay_worker(void *arg); + +#endif diff --git a/src/userpref.c b/src/userpref.c deleted file mode 100644 index 6e62000..0000000 --- a/src/userpref.c +++ /dev/null @@ -1,605 +0,0 @@ -/* - * userpref.c - * contains methods to access user specific certificates IDs and more. - * - * Copyright (c) 2008 Jonathan Beck 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 - */ - -#include <glib.h> -#include <glib/gstdio.h> -#include <glib/gprintf.h> -#include <stdio.h> -#include <stdint.h> -#include <stdlib.h> -#include <string.h> -#include <gnutls/gnutls.h> -#include <gnutls/x509.h> -#include <gcrypt.h> - -#include "userpref.h" -#include "debug.h" - -#define LIBIMOBILEDEVICE_CONF_DIR "libimobiledevice" -#define LIBIMOBILEDEVICE_CONF_FILE "libimobiledevicerc" - -#define LIBIMOBILEDEVICE_ROOT_PRIVKEY "RootPrivateKey.pem" -#define LIBIMOBILEDEVICE_HOST_PRIVKEY "HostPrivateKey.pem" -#define LIBIMOBILEDEVICE_ROOT_CERTIF "RootCertificate.pem" -#define LIBIMOBILEDEVICE_HOST_CERTIF "HostCertificate.pem" - - -/** - * Creates a freedesktop compatible configuration directory. - */ -static void userpref_create_config_dir(void) -{ - gchar *config_dir = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, NULL); - - if (!g_file_test(config_dir, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))) - g_mkdir_with_parents(config_dir, 0755); - - g_free(config_dir); -} - -static int get_rand(int min, int max) -{ - int retval = (rand() % (max - min)) + min; - return retval; -} - -/** - * Generates a valid HostID (which is actually a UUID). - * - * @return A null terminated string containing a valid HostID. - */ -static char *userpref_generate_host_id() -{ - /* HostID's are just UUID's, and UUID's are 36 characters long */ - char *hostid = (char *) malloc(sizeof(char) * 37); - const char *chars = "ABCDEF0123456789"; - srand(time(NULL)); - int i = 0; - - for (i = 0; i < 36; i++) { - if (i == 8 || i == 13 || i == 18 || i == 23) { - hostid[i] = '-'; - continue; - } else { - hostid[i] = chars[get_rand(0, 16)]; - } - } - /* make it a real string */ - hostid[36] = '\0'; - return hostid; -} - -/** - * Store HostID in config file. - * - * @param host_id A null terminated string containing a valid HostID. - */ -static int userpref_set_host_id(const char *host_id) -{ - GKeyFile *key_file; - gsize length; - gchar *buf, *config_file; - GIOChannel *file; - - if (!host_id) - return 0; - - /* Make sure config directory exists */ - userpref_create_config_dir(); - - /* Now parse file to get the HostID */ - key_file = g_key_file_new(); - - /* Store in config file */ - debug_info("setting hostID to %s", host_id); - g_key_file_set_value(key_file, "Global", "HostID", host_id); - - /* Write config file on disk */ - buf = g_key_file_to_data(key_file, &length, NULL); - config_file = - g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_CONF_FILE, NULL); - file = g_io_channel_new_file(config_file, "w", NULL); - g_free(config_file); - g_io_channel_write_chars(file, buf, length, NULL, NULL); - g_io_channel_shutdown(file, TRUE, NULL); - g_io_channel_unref(file); - - g_key_file_free(key_file); - return 1; -} - -/** - * Reads the HostID from a previously generated configuration file. - * - * @note It is the responsibility of the calling function to free the returned host_id - * - * @return The string containing the HostID or NULL - */ -void userpref_get_host_id(char **host_id) -{ - gchar *config_file; - GKeyFile *key_file; - gchar *loc_host_id; - - config_file = - g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_CONF_FILE, NULL); - - /* now parse file to get the HostID */ - key_file = g_key_file_new(); - if (g_key_file_load_from_file(key_file, config_file, G_KEY_FILE_KEEP_COMMENTS, NULL)) { - loc_host_id = g_key_file_get_value(key_file, "Global", "HostID", NULL); - if (loc_host_id) - *host_id = strdup((char *) loc_host_id); - g_free(loc_host_id); - } - g_key_file_free(key_file); - g_free(config_file); - - if (!*host_id) { - /* no config, generate host_id */ - *host_id = userpref_generate_host_id(); - userpref_set_host_id(*host_id); - } - - debug_info("Using %s as HostID", *host_id); -} - -/** - * Determines whether this device has been connected to this system before. - * - * @param uid The device uid as given by the device. - * - * @return 1 if the device has been connected previously to this configuration - * or 0 otherwise. - */ -int userpref_has_device_public_key(const char *uuid) -{ - int ret = 0; - gchar *config_file; - - /* first get config file */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - config_file = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - if (g_file_test(config_file, (G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))) - ret = 1; - g_free(config_file); - g_free(device_file); - return ret; -} - -/** - * Fills a list with UUIDs of devices that have been connected to this - * system before, i.e. for which a public key file exists. - * - * @param list A pointer to a char** initially pointing to NULL that will - * hold a newly allocated list of UUIDs upon successful return. - * The caller is responsible for freeing the memory. Note that if - * no public key file was found the list has to be freed too as it - * points to a terminating NULL element. - * @param count The number of UUIDs found. This parameter can be NULL if it - * is not required. - * - * @return USERPREF_E_SUCCESS on success, or USERPREF_E_INVALID_ARG if the - * list parameter is not pointing to NULL. - */ -userpref_error_t userpref_get_paired_uuids(char ***list, unsigned int *count) -{ - GDir *config_dir; - gchar *config_path; - const gchar *dir_file; - GList *uuids = NULL; - unsigned int i; - unsigned int found = 0; - - if (!list || (list && *list)) { - debug_info("ERROR: The list parameter needs to point to NULL!"); - return USERPREF_E_INVALID_ARG; - } - - if (count) { - *count = 0; - } - - config_path = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, NULL); - - config_dir = g_dir_open(config_path,0,NULL); - if (config_dir) { - while ((dir_file = g_dir_read_name(config_dir))) { - if (g_str_has_suffix(dir_file, ".pem") && (strlen(dir_file) == 44)) { - uuids = g_list_append(uuids, g_strndup(dir_file, strlen(dir_file)-4)); - found++; - } - } - g_dir_close(config_dir); - } - *list = (char**)malloc(sizeof(char*) * (found+1)); - for (i = 0; i < found; i++) { - (*list)[i] = g_list_nth_data(uuids, i); - } - (*list)[i] = NULL; - - if (count) { - *count = found; - } - g_list_free(uuids); - g_free(config_path); - - return USERPREF_E_SUCCESS; -} - -/** - * Mark the device (as represented by the key) as having connected to this - * configuration. - * - * @param public_key The public key given by the device - * - * @return 1 on success and 0 if no public key is given or if it has already - * been marked as connected previously. - */ -userpref_error_t userpref_set_device_public_key(const char *uuid, gnutls_datum_t public_key) -{ - if (NULL == public_key.data) - return USERPREF_E_INVALID_ARG; - - if (userpref_has_device_public_key(uuid)) - return USERPREF_E_SUCCESS; - - /* ensure config directory exists */ - userpref_create_config_dir(); - - /* build file path */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - gchar *pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - - /* store file */ - FILE *pFile = fopen(pem, "wb"); - fwrite(public_key.data, 1, public_key.size, pFile); - fclose(pFile); - g_free(pem); - g_free(device_file); - - return USERPREF_E_SUCCESS; -} - -/** - * Remove the public key stored for the device with uuid from this host. - * - * @param uuid The uuid of the device - * - * @return USERPREF_E_SUCCESS on success. - */ -userpref_error_t userpref_remove_device_public_key(const char *uuid) -{ - if (!userpref_has_device_public_key(uuid)) - return USERPREF_E_SUCCESS; - - /* build file path */ - gchar *device_file = g_strconcat(uuid, ".pem", NULL); - gchar *pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, device_file, NULL); - - /* remove file */ - g_remove(pem); - - g_free(pem); - g_free(device_file); - - return USERPREF_E_SUCCESS; -} - -/** - * Private function which reads the given file into a gnutls structure. - * - * @param file The filename of the file to read - * @param data The pointer at which to store the data. - * - * @return 1 if the file contents where read successfully and 0 otherwise. - */ -static int userpref_get_file_contents(const char *file, gnutls_datum_t * data) -{ - gboolean success; - gsize size; - char *content; - gchar *filepath; - - if (NULL == file || NULL == data) - return 0; - - /* Read file */ - filepath = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, file, NULL); - success = g_file_get_contents(filepath, &content, &size, NULL); - g_free(filepath); - - /* Add it to the gnutls_datnum_t structure */ - data->data = (uint8_t*) content; - data->size = size; - - return success; -} - -/** - * Private function which generate private keys and certificates. - * - * @return 1 if keys were successfully generated, 0 otherwise - */ -static userpref_error_t userpref_gen_keys_and_cert(void) -{ - userpref_error_t ret = USERPREF_E_SSL_ERROR; - - gnutls_x509_privkey_t root_privkey; - gnutls_x509_crt_t root_cert; - gnutls_x509_privkey_t host_privkey; - gnutls_x509_crt_t host_cert; - - gnutls_global_deinit(); - gnutls_global_init(); - - //use less secure random to speed up key generation - gcry_control(GCRYCTL_ENABLE_QUICK_RANDOM); - - gnutls_x509_privkey_init(&root_privkey); - gnutls_x509_privkey_init(&host_privkey); - - gnutls_x509_crt_init(&root_cert); - gnutls_x509_crt_init(&host_cert); - - /* generate root key */ - gnutls_x509_privkey_generate(root_privkey, GNUTLS_PK_RSA, 2048, 0); - gnutls_x509_privkey_generate(host_privkey, GNUTLS_PK_RSA, 2048, 0); - - /* generate certificates */ - gnutls_x509_crt_set_key(root_cert, root_privkey); - gnutls_x509_crt_set_serial(root_cert, "\x00", 1); - gnutls_x509_crt_set_version(root_cert, 3); - gnutls_x509_crt_set_ca_status(root_cert, 1); - gnutls_x509_crt_set_activation_time(root_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(root_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(root_cert, root_cert, root_privkey); - - gnutls_x509_crt_set_key(host_cert, host_privkey); - gnutls_x509_crt_set_serial(host_cert, "\x00", 1); - gnutls_x509_crt_set_version(host_cert, 3); - gnutls_x509_crt_set_ca_status(host_cert, 0); - gnutls_x509_crt_set_key_usage(host_cert, GNUTLS_KEY_KEY_ENCIPHERMENT | GNUTLS_KEY_DIGITAL_SIGNATURE); - gnutls_x509_crt_set_activation_time(host_cert, time(NULL)); - gnutls_x509_crt_set_expiration_time(host_cert, time(NULL) + (60 * 60 * 24 * 365 * 10)); - gnutls_x509_crt_sign(host_cert, root_cert, root_privkey); - - /* export to PEM format */ - size_t root_key_export_size = 0; - size_t host_key_export_size = 0; - gnutls_datum_t root_key_pem = { NULL, 0 }; - gnutls_datum_t host_key_pem = { NULL, 0 }; - - gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, NULL, &root_key_export_size); - gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, NULL, &host_key_export_size); - - root_key_pem.data = gnutls_malloc(root_key_export_size); - host_key_pem.data = gnutls_malloc(host_key_export_size); - - gnutls_x509_privkey_export(root_privkey, GNUTLS_X509_FMT_PEM, root_key_pem.data, &root_key_export_size); - root_key_pem.size = root_key_export_size; - gnutls_x509_privkey_export(host_privkey, GNUTLS_X509_FMT_PEM, host_key_pem.data, &host_key_export_size); - host_key_pem.size = host_key_export_size; - - size_t root_cert_export_size = 0; - size_t host_cert_export_size = 0; - gnutls_datum_t root_cert_pem = { NULL, 0 }; - gnutls_datum_t host_cert_pem = { NULL, 0 }; - - gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, NULL, &root_cert_export_size); - gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, NULL, &host_cert_export_size); - - root_cert_pem.data = gnutls_malloc(root_cert_export_size); - host_cert_pem.data = gnutls_malloc(host_cert_export_size); - - gnutls_x509_crt_export(root_cert, GNUTLS_X509_FMT_PEM, root_cert_pem.data, &root_cert_export_size); - root_cert_pem.size = root_cert_export_size; - gnutls_x509_crt_export(host_cert, GNUTLS_X509_FMT_PEM, host_cert_pem.data, &host_cert_export_size); - host_cert_pem.size = host_cert_export_size; - - if (NULL != root_cert_pem.data && 0 != root_cert_pem.size && - NULL != host_cert_pem.data && 0 != host_cert_pem.size) - ret = USERPREF_E_SUCCESS; - - /* store values in config file */ - userpref_set_keys_and_certs( &root_key_pem, &root_cert_pem, &host_key_pem, &host_cert_pem); - - gnutls_free(root_key_pem.data); - gnutls_free(root_cert_pem.data); - gnutls_free(host_key_pem.data); - gnutls_free(host_cert_pem.data); - - //restore gnutls env - gnutls_global_deinit(); - gnutls_global_init(); - - return ret; -} - -/** - * Private function which import the given key into a gnutls structure. - * - * @param key_name The filename of the private key to import. - * @param key the gnutls key structure. - * - * @return 1 if the key was successfully imported. - */ -static userpref_error_t userpref_import_key(const char* key_name, gnutls_x509_privkey_t key) -{ - userpref_error_t ret = USERPREF_E_INVALID_CONF; - gnutls_datum_t pem_key = { NULL, 0 }; - - if (userpref_get_file_contents(key_name, &pem_key)) { - if (GNUTLS_E_SUCCESS == gnutls_x509_privkey_import(key, &pem_key, GNUTLS_X509_FMT_PEM)) - ret = USERPREF_E_SUCCESS; - else - ret = USERPREF_E_SSL_ERROR; - } - gnutls_free(pem_key.data); - return ret; -} - -/** - * Private function which import the given certificate into a gnutls structure. - * - * @param crt_name The filename of the certificate to import. - * @param cert the gnutls certificate structure. - * - * @return IDEVICE_E_SUCCESS if the certificate was successfully imported. - */ -static userpref_error_t userpref_import_crt(const char* crt_name, gnutls_x509_crt_t cert) -{ - userpref_error_t ret = USERPREF_E_INVALID_CONF; - gnutls_datum_t pem_cert = { NULL, 0 }; - - if (userpref_get_file_contents(crt_name, &pem_cert)) { - if (GNUTLS_E_SUCCESS == gnutls_x509_crt_import(cert, &pem_cert, GNUTLS_X509_FMT_PEM)) - ret = USERPREF_E_SUCCESS; - else - ret = USERPREF_E_SSL_ERROR; - } - gnutls_free(pem_cert.data); - return ret; -} - -/** - * Function to retrieve host keys and certificates. - * This function trigger key generation if they do not exists yet or are invalid. - * - * @note This function can take few seconds to complete (typically 5 seconds) - * - * @param root_privkey The root private key. - * @param root_crt The root certificate. - * @param host_privkey The host private key. - * @param host_crt The host certificate. - * - * @return 1 if the keys and certificates were successfully retrieved, 0 otherwise - */ -userpref_error_t userpref_get_keys_and_certs(gnutls_x509_privkey_t root_privkey, gnutls_x509_crt_t root_crt, gnutls_x509_privkey_t host_privkey, gnutls_x509_crt_t host_crt) -{ - userpref_error_t ret = USERPREF_E_SUCCESS; - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_ROOT_PRIVKEY, root_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_HOST_PRIVKEY, host_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_ROOT_CERTIF, root_crt); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_HOST_CERTIF, host_crt); - - - if (USERPREF_E_SUCCESS != ret) { - //we had problem reading or importing root cert - //try with a new ones. - ret = userpref_gen_keys_and_cert(); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_ROOT_PRIVKEY, root_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_key(LIBIMOBILEDEVICE_HOST_PRIVKEY, host_privkey); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_ROOT_CERTIF, root_crt); - - if (ret == USERPREF_E_SUCCESS) - ret = userpref_import_crt(LIBIMOBILEDEVICE_HOST_CERTIF, host_crt); - } - - return ret; -} - -/** - * Function to retrieve certificates encoded in PEM format. - * - * @param pem_root_cert The root certificate. - * @param pem_host_cert The host certificate. - * - * @return 1 if the certificates were successfully retrieved, 0 otherwise - */ -userpref_error_t userpref_get_certs_as_pem(gnutls_datum_t *pem_root_cert, gnutls_datum_t *pem_host_cert) -{ - if (!pem_root_cert || !pem_host_cert) - return USERPREF_E_INVALID_ARG; - - if (userpref_get_file_contents(LIBIMOBILEDEVICE_ROOT_CERTIF, pem_root_cert) && userpref_get_file_contents(LIBIMOBILEDEVICE_HOST_CERTIF, pem_host_cert)) - return USERPREF_E_SUCCESS; - else { - g_free(pem_root_cert->data); - g_free(pem_host_cert->data); - } - return USERPREF_E_INVALID_CONF; -} - -/** - * Create and save a configuration file containing the given data. - * - * @note: All fields must specified and be non-null - * - * @param root_key The root key - * @param root_cert The root certificate - * @param host_key The host key - * @param host_cert The host certificate - * - * @return 1 on success and 0 otherwise. - */ -userpref_error_t userpref_set_keys_and_certs(gnutls_datum_t * root_key, gnutls_datum_t * root_cert, gnutls_datum_t * host_key, gnutls_datum_t * host_cert) -{ - FILE *pFile; - gchar *pem; - - if (!root_key || !host_key || !root_cert || !host_cert) - return USERPREF_E_INVALID_ARG; - - /* Make sure config directory exists */ - userpref_create_config_dir(); - - /* Now write keys and certificates to disk */ - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_ROOT_PRIVKEY, NULL); - pFile = fopen(pem, "wb"); - fwrite(root_key->data, 1, root_key->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_HOST_PRIVKEY, NULL); - pFile = fopen(pem, "wb"); - fwrite(host_key->data, 1, host_key->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_ROOT_CERTIF, NULL); - pFile = fopen(pem, "wb"); - fwrite(root_cert->data, 1, root_cert->size, pFile); - fclose(pFile); - g_free(pem); - - pem = g_build_path(G_DIR_SEPARATOR_S, g_get_user_config_dir(), LIBIMOBILEDEVICE_CONF_DIR, LIBIMOBILEDEVICE_HOST_CERTIF, NULL); - pFile = fopen(pem, "wb"); - fwrite(host_cert->data, 1, host_cert->size, pFile); - fclose(pFile); - g_free(pem); - - return USERPREF_E_SUCCESS; -} diff --git a/src/userpref.h b/src/userpref.h deleted file mode 100644 index f9d3913..0000000 --- a/src/userpref.h +++ /dev/null @@ -1,46 +0,0 @@ -/* - * userpref.h - * contains methods to access user specific certificates IDs and more. - * - * Copyright (c) 2008 Jonathan Beck 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 USERPREF_H -#define USERPREF_H - -#include <gnutls/gnutls.h> -#include <glib.h> - -#define USERPREF_E_SUCCESS 0 -#define USERPREF_E_INVALID_ARG -1 -#define USERPREF_E_INVALID_CONF -2 -#define USERPREF_E_SSL_ERROR -3 - -#define USERPREF_E_UNKNOWN_ERROR -256 - -typedef int16_t userpref_error_t; - -G_GNUC_INTERNAL userpref_error_t userpref_get_keys_and_certs(gnutls_x509_privkey_t root_privkey, gnutls_x509_crt_t root_crt, gnutls_x509_privkey_t host_privkey, gnutls_x509_crt_t host_crt); -G_GNUC_INTERNAL userpref_error_t userpref_set_keys_and_certs(gnutls_datum_t * root_key, gnutls_datum_t * root_cert, gnutls_datum_t * host_key, gnutls_datum_t * host_cert); -G_GNUC_INTERNAL userpref_error_t userpref_get_certs_as_pem(gnutls_datum_t *pem_root_cert, gnutls_datum_t *pem_host_cert); -G_GNUC_INTERNAL userpref_error_t userpref_set_device_public_key(const char *uuid, gnutls_datum_t public_key); -userpref_error_t userpref_remove_device_public_key(const char *uuid); -G_GNUC_INTERNAL int userpref_has_device_public_key(const char *uuid); -userpref_error_t userpref_get_paired_uuids(char ***list, unsigned int *count); -void userpref_get_host_id(char **host_id); - -#endif diff --git a/src/webinspector.c b/src/webinspector.c new file mode 100644 index 0000000..f960fcc --- /dev/null +++ b/src/webinspector.c @@ -0,0 +1,263 @@ +/* + * webinspector.c + * com.apple.webinspector service implementation. + * + * Copyright (c) 2013 Yury Melnichek 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 <plist/plist.h> + +#include "webinspector.h" +#include "lockdown.h" +#include "common/debug.h" + +/** + * Convert a property_list_service_error_t value to a webinspector_error_t value. + * Used internally to get correct error codes. + * + * @param err An property_list_service_error_t error code + * + * @return A matching webinspector_error_t error code, + * WEBINSPECTOR_E_UNKNOWN_ERROR otherwise. + */ +static webinspector_error_t webinspector_error(property_list_service_error_t err) +{ + switch (err) { + case PROPERTY_LIST_SERVICE_E_SUCCESS: + return WEBINSPECTOR_E_SUCCESS; + case PROPERTY_LIST_SERVICE_E_INVALID_ARG: + return WEBINSPECTOR_E_INVALID_ARG; + case PROPERTY_LIST_SERVICE_E_PLIST_ERROR: + return WEBINSPECTOR_E_PLIST_ERROR; + case PROPERTY_LIST_SERVICE_E_MUX_ERROR: + return WEBINSPECTOR_E_MUX_ERROR; + case PROPERTY_LIST_SERVICE_E_SSL_ERROR: + return WEBINSPECTOR_E_SSL_ERROR; + case PROPERTY_LIST_SERVICE_E_RECEIVE_TIMEOUT: + return WEBINSPECTOR_E_RECEIVE_TIMEOUT; + case PROPERTY_LIST_SERVICE_E_NOT_ENOUGH_DATA: + return WEBINSPECTOR_E_NOT_ENOUGH_DATA; + default: + break; + } + return WEBINSPECTOR_E_UNKNOWN_ERROR; +} + +webinspector_error_t webinspector_client_new(idevice_t device, lockdownd_service_descriptor_t service, webinspector_client_t * client) +{ + *client = NULL; + + if (!device || !service || service->port == 0 || !client || *client) { + debug_info("Incorrect parameter passed to webinspector_client_new."); + return WEBINSPECTOR_E_INVALID_ARG; + } + + debug_info("Creating webinspector_client, port = %d.", service->port); + + property_list_service_client_t plclient = NULL; + webinspector_error_t ret = webinspector_error(property_list_service_client_new(device, service, &plclient)); + if (ret != WEBINSPECTOR_E_SUCCESS) { + debug_info("Creating a property list client failed. Error: %i", ret); + return ret; + } + + webinspector_client_t client_loc = (webinspector_client_t) malloc(sizeof(struct webinspector_client_private)); + client_loc->parent = plclient; + + *client = client_loc; + + debug_info("webinspector_client successfully created."); + return 0; +} + +webinspector_error_t webinspector_client_start_service(idevice_t device, webinspector_client_t * client, const char* label) +{ + webinspector_error_t err = WEBINSPECTOR_E_UNKNOWN_ERROR; + service_client_factory_start_service(device, WEBINSPECTOR_SERVICE_NAME, (void**)client, label, SERVICE_CONSTRUCTOR(webinspector_client_new), &err); + return err; +} + +webinspector_error_t webinspector_client_free(webinspector_client_t client) +{ + if (!client) + return WEBINSPECTOR_E_INVALID_ARG; + + webinspector_error_t err = webinspector_error(property_list_service_client_free(client->parent)); + free(client); + + return err; +} + +webinspector_error_t webinspector_send(webinspector_client_t client, plist_t plist) +{ + webinspector_error_t res = WEBINSPECTOR_E_UNKNOWN_ERROR; + + uint32_t offset = 0; + int is_final_message = 0; + + char *packet = NULL; + uint32_t packet_length = 0; + + debug_info("Sending webinspector message..."); + debug_plist(plist); + + /* convert plist to packet */ + plist_to_bin(plist, &packet, &packet_length); + if (!packet || packet_length == 0) { + debug_info("Error converting plist to binary."); + return res; + } + + do { + /* determine if we need to send partial messages */ + if (packet_length < WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE) { + is_final_message = 1; + } else { + /* send partial packet */ + is_final_message = 0; + } + + plist_t outplist = plist_new_dict(); + if (!is_final_message) { + /* split packet into partial chunks */ + plist_dict_set_item(outplist, "WIRPartialMessageKey", plist_new_data(packet + offset, WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE)); + offset += WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE; + packet_length -= WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE; + } else { + /* send final chunk */ + plist_dict_set_item(outplist, "WIRFinalMessageKey", plist_new_data(packet + offset, packet_length)); + offset += packet_length; + packet_length -= packet_length; + } + + res = webinspector_error(property_list_service_send_binary_plist(client->parent, outplist)); + plist_free(outplist); + outplist = NULL; + if (res != WEBINSPECTOR_E_SUCCESS) { + debug_info("Sending plist failed with error %d", res); + return res; + } + } while(packet_length > 0); + + free(packet); + packet = NULL; + + return res; +} + +webinspector_error_t webinspector_receive(webinspector_client_t client, plist_t * plist) +{ + return webinspector_receive_with_timeout(client, plist, 5000); +} + +webinspector_error_t webinspector_receive_with_timeout(webinspector_client_t client, plist_t * plist, uint32_t timeout_ms) +{ + webinspector_error_t res = WEBINSPECTOR_E_UNKNOWN_ERROR; + plist_t message = NULL; + plist_t key = NULL; + + int is_final_message = 1; + + char* buffer = NULL; + uint64_t length = 0; + + char* packet = NULL; + char* newpacket = NULL; + uint64_t packet_length = 0; + + debug_info("Receiving webinspector message..."); + + do { + /* receive message */ + res = webinspector_error(property_list_service_receive_plist_with_timeout(client->parent, &message, timeout_ms)); + if (res != WEBINSPECTOR_E_SUCCESS || !message) { + debug_info("Could not receive message, error %d", res); + plist_free(message); + return WEBINSPECTOR_E_MUX_ERROR; + } + + /* get message key */ + key = plist_dict_get_item(message, "WIRFinalMessageKey"); + if (!key) { + key = plist_dict_get_item(message, "WIRPartialMessageKey"); + if (!key) { + debug_info("ERROR: Unable to read message key."); + plist_free(message); + return WEBINSPECTOR_E_PLIST_ERROR; + } + is_final_message = 0; + } else { + is_final_message = 1; + } + + /* read partial data */ + plist_get_data_val(key, &buffer, &length); + if (!buffer || length == 0 || length > 0xFFFFFFFF) { + debug_info("ERROR: Unable to get the inner plist binary data."); + free(packet); + free(buffer); + return WEBINSPECTOR_E_PLIST_ERROR; + } + + /* (re)allocate packet data */ + if (!packet) { + packet = (char*)malloc(length * sizeof(char)); + } else { + newpacket = (char*)realloc(packet, (packet_length + length) * sizeof(char)); + packet = newpacket; + } + + /* copy partial data into final packet data */ + memcpy(packet + packet_length, buffer, length); + + /* cleanup buffer */ + free(buffer); + buffer = NULL; + + if (message) { + plist_free(message); + message = NULL; + } + + /* adjust packet length */ + packet_length += length; + length = 0; + } while(!is_final_message); + + /* read final message */ + if (packet_length) { + plist_from_bin(packet, (uint32_t)packet_length, plist); + if (!*plist) { + debug_info("Error restoring the final plist."); + free(packet); + return WEBINSPECTOR_E_PLIST_ERROR; + } + + debug_plist(*plist); + } + + if (packet) { + free(packet); + } + + return res; +} diff --git a/src/webinspector.h b/src/webinspector.h new file mode 100644 index 0000000..d249c58 --- /dev/null +++ b/src/webinspector.h @@ -0,0 +1,35 @@ +/* + * webinspector.h + * com.apple.webinspector service header file. + * + * Copyright (c) 2013 Yury Melnichek 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 __WEBINSPECTOR_H +#define __WEBINSPECTOR_H + +#include "idevice.h" +#include "libimobiledevice/webinspector.h" +#include "property_list_service.h" + +#define WEBINSPECTOR_PARTIAL_PACKET_CHUNK_SIZE 8096 + +struct webinspector_client_private { + property_list_service_client_t parent; +}; + +#endif |