summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am89
-rw-r--r--src/afc.c1161
-rw-r--r--src/afc.h122
-rw-r--r--src/bt_packet_logger.c231
-rw-r--r--src/bt_packet_logger.h37
-rw-r--r--src/companion_proxy.c380
-rw-r--r--src/companion_proxy.h35
-rw-r--r--src/debug.c164
-rw-r--r--src/debug.h52
-rw-r--r--src/debugserver.c657
-rw-r--r--src/debugserver.h44
-rw-r--r--src/device_link_service.c129
-rw-r--r--src/device_link_service.h42
-rw-r--r--src/diagnostics_relay.c467
-rw-r--r--src/diagnostics_relay.h33
-rw-r--r--src/file_relay.c103
-rw-r--r--src/file_relay.h25
-rw-r--r--src/heartbeat.c147
-rw-r--r--src/heartbeat.h33
-rw-r--r--src/house_arrest.c179
-rw-r--r--src/house_arrest.h12
-rw-r--r--src/idevice.c1434
-rw-r--r--src/idevice.h74
-rw-r--r--src/installation_proxy.c1232
-rw-r--r--src/installation_proxy.h20
-rw-r--r--src/libimobiledevice-1.0.pc.in12
-rw-r--r--src/lockdown-cu.c1193
-rw-r--r--src/lockdown.c1440
-rw-r--r--src/lockdown.h20
-rw-r--r--src/misagent.c290
-rw-r--r--src/misagent.h34
-rw-r--r--src/mobile_image_mounter.c514
-rw-r--r--src/mobile_image_mounter.h15
-rw-r--r--src/mobileactivation.c314
-rw-r--r--src/mobileactivation.h33
-rw-r--r--src/mobilebackup.c227
-rw-r--r--src/mobilebackup.h17
-rw-r--r--src/mobilebackup2.c386
-rw-r--r--src/mobilebackup2.h33
-rw-r--r--src/mobilesync.c286
-rw-r--r--src/mobilesync.h16
-rw-r--r--src/notification_proxy.c253
-rw-r--r--src/notification_proxy.h19
-rw-r--r--src/preboard.c256
-rw-r--r--src/preboard.h35
-rw-r--r--src/property_list_service.c301
-rw-r--r--src/property_list_service.h48
-rw-r--r--src/restore.c290
-rw-r--r--src/restore.h7
-rw-r--r--src/reverse_proxy.c810
-rw-r--r--src/reverse_proxy.h51
-rw-r--r--src/sbservices.c219
-rw-r--r--src/sbservices.h15
-rw-r--r--src/screenshotr.c80
-rw-r--r--src/screenshotr.h14
-rw-r--r--src/service.c207
-rw-r--r--src/service.h32
-rw-r--r--src/syslog_relay.c248
-rw-r--r--src/syslog_relay.h37
-rw-r--r--src/userpref.c605
-rw-r--r--src/userpref.h46
-rw-r--r--src/webinspector.c263
-rw-r--r--src/webinspector.h35
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
diff --git a/src/afc.c b/src/afc.c
index 989d0ec..1b4070b 100644
--- a/src/afc.c
+++ b/src/afc.c
@@ -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";
+}
diff --git a/src/afc.h b/src/afc.h
index 9c9f12d..6bfdf56 100644
--- a/src/afc.h
+++ b/src/afc.h
@@ -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, &current_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, &current_amount, &current_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, &current_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, &notification);
+ if (np_get_notification(npt->client, &notification) < 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