summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Nikias Bassen2010-09-05 20:15:43 +0200
committerGravatar Martin Szulecki2011-04-11 17:05:38 +0200
commitca7dba1c618ed499d0693427e89701cda1731ca9 (patch)
treeeb5f1d6de2779f43940c8cbd578b1b9de7c1745a
parentc5fe346717a449a6bfcdbd7477724d95cdeb85d5 (diff)
downloadlibimobiledevice-ca7dba1c618ed499d0693427e89701cda1731ca9.tar.gz
libimobiledevice-ca7dba1c618ed499d0693427e89701cda1731ca9.tar.bz2
Add initial mobilebackup2 support and idevicebackup4 tool
-rw-r--r--include/libimobiledevice/mobilebackup2.h63
-rw-r--r--src/Makefile.am1
-rw-r--r--src/mobilebackup2.c396
-rw-r--r--src/mobilebackup2.h31
-rw-r--r--tools/Makefile.am7
-rw-r--r--tools/idevicebackup4.c2142
6 files changed, 2639 insertions, 1 deletions
diff --git a/include/libimobiledevice/mobilebackup2.h b/include/libimobiledevice/mobilebackup2.h
new file mode 100644
index 0000000..ea49602
--- /dev/null
+++ b/include/libimobiledevice/mobilebackup2.h
@@ -0,0 +1,63 @@
+/**
+ * @file libimobiledevice/mobilebackup2.h
+ * @brief Backup and restore of all device data (mobilebackup2, iOS4+ only)
+ * \internal
+ *
+ * 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
+ */
+
+#ifndef IMOBILEBACKUP2_H
+#define IMOBILEBACKUP2_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <libimobiledevice/libimobiledevice.h>
+
+/** @name Error Codes */
+/*@{*/
+#define MOBILEBACKUP2_E_SUCCESS 0
+#define MOBILEBACKUP2_E_INVALID_ARG -1
+#define MOBILEBACKUP2_E_PLIST_ERROR -2
+#define MOBILEBACKUP2_E_MUX_ERROR -3
+#define MOBILEBACKUP2_E_BAD_VERSION -4
+#define MOBILEBACKUP2_E_REPLY_NOT_OK -5
+
+#define MOBILEBACKUP2_E_UNKNOWN_ERROR -256
+/*@}*/
+
+/** Represents an error code. */
+typedef int16_t mobilebackup2_error_t;
+
+typedef struct mobilebackup2_client_private mobilebackup2_client_private;
+typedef mobilebackup2_client_private *mobilebackup2_client_t; /**< The client handle. */
+
+mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, uint16_t port, mobilebackup2_client_t * client);
+mobilebackup2_error_t mobilebackup2_client_free(mobilebackup2_client_t client);
+mobilebackup2_error_t mobilebackup2_receive_message(mobilebackup2_client_t client, plist_t *msg_plist, char **dlmessage);
+mobilebackup2_error_t mobilebackup2_send_raw(mobilebackup2_client_t client, const char *data, uint32_t length, uint32_t *bytes);
+mobilebackup2_error_t mobilebackup2_receive_raw(mobilebackup2_client_t client, char *data, uint32_t length, uint32_t *bytes);
+mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client);
+mobilebackup2_error_t mobilebackup2_request_backup(mobilebackup2_client_t client, const char *uuid);
+mobilebackup2_error_t mobilebackup2_send_status_response(mobilebackup2_client_t client, int status_code, const char *status1, plist_t status2);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 70dc895..f42ac08 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -21,4 +21,5 @@ libimobiledevice_la_SOURCES = idevice.c idevice.h \
mobilesync.c mobilesync.h\
mobilebackup.c mobilebackup.h\
house_arrest.c house_arrest.h\
+ mobilebackup2.c mobilebackup2.h\
restore.c restore.h
diff --git a/src/mobilebackup2.c b/src/mobilebackup2.c
new file mode 100644
index 0000000..7b6ea33
--- /dev/null
+++ b/src/mobilebackup2.c
@@ -0,0 +1,396 @@
+/*
+ * mobilebackup2.c
+ * Contains functions for the built-in MobileBackup2 client (iOS4+ only)
+ *
+ * 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
+ */
+
+#include <plist/plist.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "mobilebackup2.h"
+#include "device_link_service.h"
+#include "debug.h"
+
+#define MBACKUP2_VERSION_INT1 100
+#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_BAD_VERSION:
+ return MOBILEBACKUP2_E_BAD_VERSION;
+ default:
+ break;
+ }
+ return MOBILEBACKUP2_E_UNKNOWN_ERROR;
+}
+
+/**
+ * Connects to the mobilebackup2 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
+ * mobilebackup2_client_t upon successful return.
+ *
+ * @return MOBILEBACKUP2_E_SUCCESS on success, MOBILEBACKUP2_E_INVALID ARG
+ * if one or more parameter is invalid, or MOBILEBACKUP2_E_BAD_VERSION
+ * if the mobilebackup2 version on the device is newer.
+ */
+mobilebackup2_error_t mobilebackup2_client_new(idevice_t device, uint16_t port,
+ mobilebackup2_client_t * client)
+{
+ if (!device || 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, port, &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;
+}
+
+/**
+ * Disconnects a mobilebackup2 client from the device and frees up the
+ * mobilebackup2 client data.
+ *
+ * @param client The mobilebackup2 client to disconnect and free.
+ *
+ * @return MOBILEBACKUP2_E_SUCCESS on success, or MOBILEBACKUP2_E_INVALID_ARG
+ * if client is NULL.
+ */
+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);
+ err = mobilebackup2_error(device_link_service_client_free(client->parent));
+ }
+ free(client);
+ return err;
+}
+
+/**
+ * Sends a backup message plist.
+ *
+ * @param client The connected MobileBackup client to use.
+ * @param message The message to send. This will be inserted into the request
+ * plist as value for MessageName. If this parameter is NULL,
+ * the plist passed in the options parameter will be sent directly.
+ * @param options Additional options as PLIST_DICT to add to the request.
+ * The MessageName key with the value passed in the message parameter
+ * will be inserted into this plist before sending it. This parameter
+ * can be NULL if message is not NULL.
+ */
+static mobilebackup2_error_t internal_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_insert_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;
+}
+
+/**
+ * TODO
+ */
+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)
+ return MOBILEBACKUP2_E_INVALID_ARG;
+
+ *bytes = 0;
+
+ idevice_connection_t conn = client->parent->parent->connection;
+
+ int bytes_loc = 0;
+ uint32_t sent = 0;
+ do {
+ bytes_loc = 0;
+ idevice_connection_send(conn, 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;
+ } else {
+ 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)
+ return MOBILEBACKUP2_E_INVALID_ARG;
+
+ idevice_connection_t conn = client->parent->parent->connection;
+
+ *bytes = 0;
+
+ int bytes_loc = 0;
+ uint32_t received = 0;
+ do {
+ bytes_loc = 0;
+ idevice_connection_receive(conn, 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;
+ } else if (received == 0) {
+ return MOBILEBACKUP2_E_SUCCESS;
+ } else {
+ return MOBILEBACKUP2_E_MUX_ERROR;
+ }
+}
+
+/**
+ * TODO
+ */
+mobilebackup2_error_t mobilebackup2_version_exchange(mobilebackup2_client_t client)
+{
+ if (!client || !client->parent)
+ return MOBILEBACKUP2_E_INVALID_ARG;
+
+ plist_t dict = plist_new_dict();
+ plist_t array = plist_new_array();
+ plist_array_append_item(array, plist_new_real(2.0));
+ plist_array_append_item(array, plist_new_real(2.1));
+ plist_dict_insert_item(dict, "SupportedProtocolVersions", array);
+
+ mobilebackup2_error_t err = internal_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;
+
+ 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) {
+ err = MOBILEBACKUP2_E_REPLY_NOT_OK;
+ goto leave;
+ }
+
+ node = plist_dict_get_item(dict, "ProtocolVersion");
+ if (!node || (plist_get_node_type(node) != PLIST_REAL)) {
+ err = MOBILEBACKUP2_E_PLIST_ERROR;
+ goto leave;
+ }
+
+ double rval = 0.0;
+ plist_get_real_val(node, &rval);
+
+ debug_info("using protocol version %f\n", rval);
+
+ // TODO version check ??
+ // if version does not match
+ // err = MOBILEBACKUP2_E_BAD_VERSION
+
+leave:
+ if (dict)
+ plist_free(dict);
+ return err;
+}
+
+/**
+ * TODO
+ */
+mobilebackup2_error_t mobilebackup2_request_backup(mobilebackup2_client_t client, const char *uuid)
+{
+ if (!client || !client->parent)
+ return MOBILEBACKUP2_E_INVALID_ARG;
+
+ plist_t dict = plist_new_dict();
+ plist_dict_insert_item(dict, "TargetIdentifier", plist_new_string(uuid));
+ mobilebackup2_error_t err = internal_mobilebackup2_send_message(client, "Backup", 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..f96e400
--- /dev/null
+++ b/src/mobilebackup2.h
@@ -0,0 +1,31 @@
+ /*
+ * mobilebackup2.h
+ * Definitions for the mobilebackup2 service (iOS4+)
+ *
+ * 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
+ */
+#ifndef MOBILEBACKUP2_H
+#define MOBILEBACKUP2_H
+
+#include "libimobiledevice/mobilebackup2.h"
+#include "device_link_service.h"
+
+struct mobilebackup2_client_private {
+ device_link_service_client_t parent;
+};
+
+#endif
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 0a47fdc..5115811 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -3,7 +3,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/include
AM_CFLAGS = $(GLOBAL_CFLAGS) $(libglib2_CFLAGS) $(libgnutls_CFLAGS) $(libtasn1_CFLAGS) $(libgthread2_CFLAGS) $(LFS_CFLAGS)
AM_LDFLAGS = $(libglib2_LIBS) $(libgnutls_LIBS) $(libtasn1_LIBS) $(libgthread2_LIBS)
-bin_PROGRAMS = idevice_id ideviceinfo idevicepair idevicesyslog idevicebackup ideviceimagemounter idevicescreenshot ideviceenterrecovery idevicedate
+bin_PROGRAMS = idevice_id ideviceinfo idevicepair idevicesyslog idevicebackup idevicebackup4 ideviceimagemounter idevicescreenshot ideviceenterrecovery idevicedate
ideviceinfo_SOURCES = ideviceinfo.c
ideviceinfo_CFLAGS = $(AM_CFLAGS)
@@ -30,6 +30,11 @@ idevicebackup_CFLAGS = $(AM_CFLAGS)
idevicebackup_LDFLAGS = $(AM_LDFLAGS)
idevicebackup_LDADD = ../src/libimobiledevice.la
+idevicebackup4_SOURCES = idevicebackup4.c
+idevicebackup4_CFLAGS = $(AM_CFLAGS)
+idevicebackup4_LDFLAGS = $(AM_LDFLAGS)
+idevicebackup4_LDADD = ../src/libimobiledevice.la
+
ideviceimagemounter_SOURCES = ideviceimagemounter.c
ideviceimagemounter_CFLAGS = $(AM_CFLAGS)
ideviceimagemounter_LDFLAGS = $(AM_LDFLAGS)
diff --git a/tools/idevicebackup4.c b/tools/idevicebackup4.c
new file mode 100644
index 0000000..4b39510
--- /dev/null
+++ b/tools/idevicebackup4.c
@@ -0,0 +1,2142 @@
+/*
+ * idevicebackup4.c
+ * Command line interface to use the device's backup and restore service
+ *
+ * Copyright (c) 2009-2010 Martin Szulecki All Rights Reserved.
+ * 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
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <glib.h>
+#include <gcrypt.h>
+#include <unistd.h>
+
+#include <libimobiledevice/libimobiledevice.h>
+#include <libimobiledevice/lockdown.h>
+#include <libimobiledevice/mobilebackup2.h>
+#include <libimobiledevice/notification_proxy.h>
+#include <libimobiledevice/afc.h>
+
+#define MOBILEBACKUP2_SERVICE_NAME "com.apple.mobilebackup2"
+#define NP_SERVICE_NAME "com.apple.mobile.notification_proxy"
+
+#define LOCK_ATTEMPTS 50
+#define LOCK_WAIT 200000
+
+#define CODE_NULL 0x00
+#define CODE_ERROR_MSG 0x0b
+#define CODE_FILE_DATA 0x0c
+
+static mobilebackup2_client_t mobilebackup2 = NULL;
+static lockdownd_client_t client = NULL;
+static idevice_t phone = NULL;
+
+static int quit_flag = 0;
+
+enum cmd_mode {
+ CMD_BACKUP,
+ CMD_RESTORE,
+ CMD_LEAVE
+};
+
+enum plist_format_t {
+ PLIST_FORMAT_XML,
+ PLIST_FORMAT_BINARY
+};
+
+enum device_link_file_status_t {
+ DEVICE_LINK_FILE_STATUS_NONE = 0,
+ DEVICE_LINK_FILE_STATUS_HUNK,
+ DEVICE_LINK_FILE_STATUS_LAST_HUNK
+};
+
+static void sha1_of_data(const char *input, uint32_t size, unsigned char *hash_out)
+{
+ gcry_md_hash_buffer(GCRY_MD_SHA1, hash_out, input, size);
+}
+
+static int compare_hash(const unsigned char *hash1, const unsigned char *hash2, int hash_len)
+{
+ int i;
+ for (i = 0; i < hash_len; i++) {
+ if (hash1[i] != hash2[i]) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static void compute_datahash(const char *path, const char *destpath, uint8_t greylist, const char *domain, const char *appid, const char *version, unsigned char *hash_out)
+{
+ gcry_md_hd_t hd = NULL;
+ gcry_md_open(&hd, GCRY_MD_SHA1, 0);
+ if (!hd) {
+ printf("ERROR: Could not initialize libgcrypt/SHA1\n");
+ return;
+ }
+ gcry_md_reset(hd);
+
+ FILE *f = fopen(path, "rb");
+ if (f) {
+ unsigned char buf[16384];
+ size_t len;
+ while ((len = fread(buf, 1, 16384, f)) > 0) {
+ gcry_md_write(hd, buf, len);
+ }
+ fclose(f);
+ gcry_md_write(hd, destpath, strlen(destpath));
+ gcry_md_write(hd, ";", 1);
+ if (greylist == 1) {
+ gcry_md_write(hd, "true", 4);
+ } else {
+ gcry_md_write(hd, "false", 5);
+ }
+ gcry_md_write(hd, ";", 1);
+ if (domain) {
+ gcry_md_write(hd, domain, strlen(domain));
+ } else {
+ gcry_md_write(hd, "(null)", 6);
+ }
+ gcry_md_write(hd, ";", 1);
+ if (appid) {
+ gcry_md_write(hd, appid, strlen(appid));
+ } else {
+ gcry_md_write(hd, "(null)", 6);
+ }
+ gcry_md_write(hd, ";", 1);
+ if (version) {
+ gcry_md_write(hd, version, strlen(version));
+ } else {
+ gcry_md_write(hd, "(null)", 6);
+ }
+ unsigned char *newhash = gcry_md_read(hd, GCRY_MD_SHA1);
+ memcpy(hash_out, newhash, 20);
+ }
+ gcry_md_close(hd);
+}
+
+static void print_hash(const unsigned char *hash, int len)
+{
+ int i;
+ for (i = 0; i < len; i++) {
+ printf("%02x", hash[i]);
+ }
+}
+
+static void notify_cb(const char *notification, void *userdata)
+{
+ if (!strcmp(notification, NP_SYNC_CANCEL_REQUEST)) {
+ printf("User has aborted on-device\n");
+ quit_flag++;
+ } else {
+ printf("unhandled notification '%s' (TODO: implement)\n", notification);
+ }
+}
+
+static void debug_buf(const char *data, const int length)
+{
+ int i;
+ int j;
+ unsigned char c;
+
+ for (i = 0; i < length; i += 16) {
+ fprintf(stdout, "%04x: ", i);
+ for (j = 0; j < 16; j++) {
+ if (i + j >= length) {
+ fprintf(stdout, " ");
+ continue;
+ }
+ fprintf(stdout, "%02hhx ", *(data + i + j));
+ }
+ fprintf(stdout, " | ");
+ for (j = 0; j < 16; j++) {
+ if (i + j >= length)
+ break;
+ c = *(data + i + j);
+ if ((c < 32) || (c > 127)) {
+ fprintf(stdout, ".");
+ continue;
+ }
+ fprintf(stdout, "%c", c);
+ }
+ fprintf(stdout, "\n");
+ }
+ fprintf(stdout, "\n");
+}
+
+static plist_t mobilebackup_factory_info_plist_new()
+{
+ /* gather data from lockdown */
+ GTimeVal tv = {0, 0};
+ plist_t value_node = NULL;
+ plist_t root_node = NULL;
+ char *uuid = NULL;
+ char *uuid_uppercase = NULL;
+
+ plist_t ret = plist_new_dict();
+
+ /* get basic device information in one go */
+ lockdownd_get_value(client, NULL, NULL, &root_node);
+
+ /* set fields we understand */
+ value_node = plist_dict_get_item(root_node, "BuildVersion");
+ plist_dict_insert_item(ret, "Build Version", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "DeviceName");
+ plist_dict_insert_item(ret, "Device Name", plist_copy(value_node));
+ plist_dict_insert_item(ret, "Display Name", plist_copy(value_node));
+
+ /* FIXME: How is the GUID generated? */
+ plist_dict_insert_item(ret, "GUID", plist_new_string("---"));
+
+ value_node = plist_dict_get_item(root_node, "InternationalMobileEquipmentIdentity");
+ if (value_node)
+ plist_dict_insert_item(ret, "IMEI", plist_copy(value_node));
+
+ g_get_current_time(&tv);
+ plist_dict_insert_item(ret, "Last Backup Date", plist_new_date(tv.tv_sec, tv.tv_usec));
+
+ value_node = plist_dict_get_item(root_node, "ProductType");
+ plist_dict_insert_item(ret, "Product Type", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "ProductVersion");
+ plist_dict_insert_item(ret, "Product Version", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "SerialNumber");
+ plist_dict_insert_item(ret, "Serial Number", plist_copy(value_node));
+
+ value_node = plist_dict_get_item(root_node, "UniqueDeviceID");
+ idevice_get_uuid(phone, &uuid);
+ plist_dict_insert_item(ret, "Target Identifier", plist_new_string(uuid));
+
+ /* uppercase */
+ uuid_uppercase = g_ascii_strup(uuid, -1);
+ plist_dict_insert_item(ret, "Unique Identifier", plist_new_string(uuid_uppercase));
+ free(uuid_uppercase);
+ free(uuid);
+
+ /* FIXME: Embed files as <data> nodes */
+ plist_t files = plist_new_dict();
+ plist_dict_insert_item(ret, "iTunes Files", files);
+ plist_dict_insert_item(ret, "iTunes Version", plist_new_string("9.0.2"));
+
+ plist_free(root_node);
+
+ return ret;
+}
+
+static void mobilebackup_info_update_last_backup_date(plist_t info_plist)
+{
+ GTimeVal tv = {0, 0};
+ plist_t node = NULL;
+
+ if (!info_plist)
+ return;
+
+ g_get_current_time(&tv);
+ node = plist_dict_get_item(info_plist, "Last Backup Date");
+ plist_set_date_val(node, tv.tv_sec, tv.tv_usec);
+
+ node = NULL;
+}
+
+static void buffer_read_from_filename(const char *filename, char **buffer, uint64_t *length)
+{
+ FILE *f;
+ uint64_t size;
+
+ *length = 0;
+
+ f = fopen(filename, "rb");
+ if (!f) {
+ return;
+ }
+
+ fseek(f, 0, SEEK_END);
+ size = ftell(f);
+ rewind(f);
+
+ if (size == 0) {
+ return;
+ }
+
+ *buffer = (char*)malloc(sizeof(char)*size);
+ fread(*buffer, sizeof(char), size, f);
+ fclose(f);
+
+ *length = size;
+}
+
+static void buffer_write_to_filename(const char *filename, const char *buffer, uint64_t length)
+{
+ FILE *f;
+
+ f = fopen(filename, "ab");
+ if (!f)
+ f = fopen(filename, "wb");
+ if (f) {
+ fwrite(buffer, sizeof(char), length, f);
+ fclose(f);
+ }
+}
+
+static int plist_read_from_filename(plist_t *plist, const char *filename)
+{
+ char *buffer = NULL;
+ uint64_t length;
+
+ if (!filename)
+ return 0;
+
+ buffer_read_from_filename(filename, &buffer, &length);
+
+ if (!buffer) {
+ return 0;
+ }
+
+ if ((length > 8) && (memcmp(buffer, "bplist00", 8) == 0)) {
+ plist_from_bin(buffer, length, plist);
+ } else {
+ plist_from_xml(buffer, length, plist);
+ }
+
+ free(buffer);
+
+ return 1;
+}
+
+static int plist_write_to_filename(plist_t plist, const char *filename, enum plist_format_t format)
+{
+ char *buffer = NULL;
+ uint32_t length;
+
+ if (!plist || !filename)
+ return 0;
+
+ if (format == PLIST_FORMAT_XML)
+ plist_to_xml(plist, &buffer, &length);
+ else if (format == PLIST_FORMAT_BINARY)
+ plist_to_bin(plist, &buffer, &length);
+ else
+ return 0;
+
+ buffer_write_to_filename(filename, buffer, length);
+
+ free(buffer);
+
+ return 1;
+}
+
+static int plist_strcmp(plist_t node, const char *str)
+{
+ char *buffer = NULL;
+ int ret = 0;
+
+ if (plist_get_node_type(node) != PLIST_STRING)
+ return ret;
+
+ plist_get_string_val(node, &buffer);
+ ret = strcmp(buffer, str);
+ free(buffer);
+
+ return ret;
+}
+
+static gchar *mobilebackup_build_path(const char *backup_directory, const char *name, const char *extension)
+{
+ gchar *filename = g_strconcat(name, extension, NULL);
+ gchar *path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, filename, NULL);
+ g_free(filename);
+ return path;
+}
+
+/*static void mobilebackup_write_status(const char *path, int status)
+{
+ struct stat st;
+ plist_t status_plist = plist_new_dict();
+ plist_dict_insert_item(status_plist, "Backup Success", plist_new_bool(status));
+ gchar *file_path = mobilebackup_build_path(path, "Status", ".plist");
+
+ if (stat(file_path, &st) == 0)
+ remove(file_path);
+
+ plist_write_to_filename(status_plist, file_path, PLIST_FORMAT_XML);
+
+ plist_free(status_plist);
+ status_plist = NULL;
+
+ g_free(file_path);
+}*/
+
+static int mobilebackup_read_status(const char *path)
+{
+ int ret = -1;
+ plist_t status_plist = NULL;
+ gchar *file_path = mobilebackup_build_path(path, "Status", ".plist");
+
+ plist_read_from_filename(&status_plist, file_path);
+ g_free(file_path);
+ if (!status_plist) {
+ printf("Could not read Status.plist!\n");
+ return ret;
+ }
+ plist_t node = plist_dict_get_item(status_plist, "SnapshotState");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ char *str = NULL;
+ plist_get_string_val(node, &str);
+ if (!strcmp(str, "finished")) {
+ ret = 1;
+ } else {
+ ret = 0;
+ }
+ g_free(str);
+ } else {
+ printf("%s: ERROR could not get SnapshotState key from Status.plist!\n", __func__);
+ }
+ plist_free(status_plist);
+ return ret;
+}
+
+static int mobilebackup_info_is_current_device(plist_t info)
+{
+ plist_t value_node = NULL;
+ plist_t node = NULL;
+ plist_t root_node = NULL;
+ int ret = 0;
+
+ if (!info)
+ return ret;
+
+ if (plist_get_node_type(info) != PLIST_DICT)
+ return ret;
+
+ /* get basic device information in one go */
+ lockdownd_get_value(client, NULL, NULL, &root_node);
+
+ /* verify UUID */
+ value_node = plist_dict_get_item(root_node, "UniqueDeviceID");
+ node = plist_dict_get_item(info, "Target Identifier");
+
+ if(plist_compare_node_value(value_node, node))
+ ret = 1;
+ else {
+ printf("Info.plist: UniqueDeviceID does not match.\n");
+ }
+
+ /* verify SerialNumber */
+ if (ret == 1) {
+ value_node = plist_dict_get_item(root_node, "SerialNumber");
+ node = plist_dict_get_item(info, "Serial Number");
+
+ if(plist_compare_node_value(value_node, node))
+ ret = 1;
+ else {
+ printf("Info.plist: SerialNumber does not match.\n");
+ ret = 0;
+ }
+ }
+
+ /* verify ProductVersion to prevent using backup with different OS version */
+ if (ret == 1) {
+ value_node = plist_dict_get_item(root_node, "ProductVersion");
+ node = plist_dict_get_item(info, "Product Version");
+
+ if(plist_compare_node_value(value_node, node))
+ ret = 1;
+ else {
+ printf("Info.plist: ProductVersion does not match.\n");
+ ret = 0;
+ }
+ }
+
+ plist_free(root_node);
+ root_node = NULL;
+
+ value_node = NULL;
+ node = NULL;
+
+ return ret;
+}
+
+/*static int mobilebackup_delete_backup_file_by_hash(const char *backup_directory, const char *hash)
+{
+ int ret = 0;
+ gchar *path = mobilebackup_build_path(backup_directory, hash, ".mddata");
+ printf("Removing \"%s\" ", path);
+ if (!remove( path ))
+ ret = 1;
+ else
+ ret = 0;
+
+ g_free(path);
+
+ if (!ret)
+ return ret;
+
+ path = mobilebackup_build_path(backup_directory, hash, ".mdinfo");
+ printf("and \"%s\"... ", path);
+ if (!remove( path ))
+ ret = 1;
+ else
+ ret = 0;
+
+ g_free(path);
+
+ return ret;
+}*/
+
+static int mobilebackup_check_file_integrity(const char *backup_directory, const char *hash, plist_t filedata)
+{
+ char *datapath;
+ char *infopath;
+ plist_t mdinfo = NULL;
+ struct stat st;
+ unsigned char file_hash[20];
+
+ datapath = mobilebackup_build_path(backup_directory, hash, ".mddata");
+ if (stat(datapath, &st) != 0) {
+ printf("\r\n");
+ printf("ERROR: '%s.mddata' is missing!\n", hash);
+ free(datapath);
+ return 0;
+ }
+
+ infopath = mobilebackup_build_path(backup_directory, hash, ".mdinfo");
+ plist_read_from_filename(&mdinfo, infopath);
+ free(infopath);
+ if (!mdinfo) {
+ printf("\r\n");
+ printf("ERROR: '%s.mdinfo' is missing or corrupted!\n", hash);
+ free(datapath);
+ return 0;
+ }
+
+ /* sha1 hash verification */
+ plist_t node = plist_dict_get_item(filedata, "DataHash");
+ if (!node || (plist_get_node_type(node) != PLIST_DATA)) {
+ printf("\r\n");
+ printf("ERROR: Could not get DataHash for file entry '%s'\n", hash);
+ plist_free(mdinfo);
+ free(datapath);
+ return 0;
+ }
+
+ node = plist_dict_get_item(mdinfo, "Metadata");
+ if (!node && (plist_get_node_type(node) != PLIST_DATA)) {
+ printf("\r\n");
+ printf("ERROR: Could not find Metadata in plist '%s.mdinfo'\n", hash);
+ plist_free(mdinfo);
+ free(datapath);
+ return 0;
+ }
+
+ char *meta_bin = NULL;
+ uint64_t meta_bin_size = 0;
+ plist_get_data_val(node, &meta_bin, &meta_bin_size);
+ plist_t metadata = NULL;
+ if (meta_bin) {
+ plist_from_bin(meta_bin, (uint32_t)meta_bin_size, &metadata);
+ }
+ if (!metadata) {
+ printf("\r\n");
+ printf("ERROR: Could not get Metadata from plist '%s.mdinfo'\n", hash);
+ plist_free(mdinfo);
+ free(datapath);
+ return 0;
+ }
+
+ char *version = NULL;
+ node = plist_dict_get_item(metadata, "Version");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &version);
+ }
+
+ char *destpath = NULL;
+ node = plist_dict_get_item(metadata, "Path");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &destpath);
+ }
+
+ uint8_t greylist = 0;
+ node = plist_dict_get_item(metadata, "Greylist");
+ if (node && (plist_get_node_type(node) == PLIST_BOOLEAN)) {
+ plist_get_bool_val(node, &greylist);
+ }
+
+ char *domain = NULL;
+ node = plist_dict_get_item(metadata, "Domain");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &domain);
+ }
+
+ char *fnstr = malloc(strlen(domain) + 1 + strlen(destpath) + 1);
+ strcpy(fnstr, domain);
+ strcat(fnstr, "-");
+ strcat(fnstr, destpath);
+ unsigned char fnhash[20];
+ char fnamehash[41];
+ char *p = fnamehash;
+ sha1_of_data(fnstr, strlen(fnstr), fnhash);
+ free(fnstr);
+ int i;
+ for ( i = 0; i < 20; i++, p += 2 ) {
+ snprintf (p, 3, "%02x", (unsigned char)fnhash[i] );
+ }
+ if (strcmp(fnamehash, hash)) {
+ printf("\r\n");
+ printf("WARNING: filename hash does not match for entry '%s'\n", hash);
+ }
+
+ char *auth_version = NULL;
+ node = plist_dict_get_item(mdinfo, "AuthVersion");
+ if (node && (plist_get_node_type(node) == PLIST_STRING)) {
+ plist_get_string_val(node, &auth_version);
+ }
+
+ if (strcmp(auth_version, "1.0")) {
+ printf("\r\n");
+ printf("WARNING: Unknown AuthVersion '%s', DataHash cannot be verified!\n", auth_version);
+ }
+
+ node = plist_dict_get_item(filedata, "DataHash");
+ if (!node || (plist_get_node_type(node) != PLIST_DATA)) {
+ printf("\r\n");
+ printf("WARNING: Could not get DataHash key from file info data for entry '%s'\n", hash);
+ }
+
+ int res = 1;
+ unsigned char *data_hash = NULL;
+ uint64_t data_hash_len = 0;
+ plist_get_data_val(node, (char**)&data_hash, &data_hash_len);
+ int hash_ok = 0;
+ if (data_hash && (data_hash_len == 20)) {
+ compute_datahash(datapath, destpath, greylist, domain, NULL, version, file_hash);
+ hash_ok = compare_hash(data_hash, file_hash, 20);
+ } else if (data_hash_len == 0) {
+ /* no datahash present */
+ hash_ok = 1;
+ }
+
+ g_free(domain);
+ g_free(version);
+ g_free(destpath);
+
+ if (!hash_ok) {
+ printf("\r\n");
+ printf("ERROR: The hash for '%s.mddata' does not match DataHash entry in Manifest\n", hash);
+ printf("datahash: ");
+ print_hash(data_hash, 20);
+ printf("\nfilehash: ");
+ print_hash(file_hash, 20);
+ printf("\n");
+ res = 0;
+ }
+ g_free(data_hash);
+ plist_free(mdinfo);
+ return res;
+}
+
+static void do_post_notification(const char *notification)
+{
+ uint16_t nport = 0;
+ np_client_t np;
+
+ if (!client) {
+ if (lockdownd_client_new_with_handshake(phone, &client, "idevicebackup") != LOCKDOWN_E_SUCCESS) {
+ return;
+ }
+ }
+
+ lockdownd_start_service(client, NP_SERVICE_NAME, &nport);
+ if (nport) {
+ np_client_new(phone, nport, &np);
+ if (np) {
+ np_post_notification(np, notification);
+ np_client_free(np);
+ }
+ } else {
+ printf("Could not start %s\n", NP_SERVICE_NAME);
+ }
+}
+
+static void print_progress(double progress)
+{
+ int i = 0;
+ if (progress < 0)
+ return;
+
+ if (progress > 100)
+ progress = 100;
+
+ printf("\r[");
+ for(i = 0; i < 50; i++) {
+ if(i < progress / 2) {
+ printf("=");
+ } else {
+ printf(" ");
+ }
+ }
+ printf("] %3.0f%%", progress);
+ fflush(stdout);
+ if (progress == 100)
+ printf("\n");
+}
+
+static void multi_status_add_file_error(plist_t status_dict, const char *path, int error_code, const char *error_message)
+{
+ if (!status_dict) return;
+ plist_t filedict = plist_new_dict();
+ plist_dict_insert_item(filedict, "DLFileErrorString", plist_new_string(error_message));
+ plist_dict_insert_item(filedict, "DLFileErrorCode", plist_new_uint(error_code));
+ plist_dict_insert_item(status_dict, path, filedict);
+}
+
+static int errno_to_device_error(int errno_value)
+{
+ switch (errno_value) {
+ case ENOENT:
+ return -6;
+ case EEXIST:
+ return -7;
+ default:
+ return -errno_value;
+ }
+}
+
+static int handle_send_file(const char *backup_dir, const char *path, plist_t *errplist)
+{
+ uint32_t nlen = 0;
+ uint32_t pathlen = strlen(path);
+ int bytes = 0;
+ gchar *localfile = g_build_path(G_DIR_SEPARATOR_S, backup_dir, path, NULL);
+ char buf[16384];
+ struct stat fst;
+
+ FILE *f = NULL;
+ uint32_t slen = 0;
+ int errcode = -1;
+ int e = -1;
+
+ mobilebackup2_error_t err;
+
+ /* send path length */
+ nlen = GUINT32_TO_BE(pathlen);
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)&nlen, sizeof(nlen), (uint32_t*)&bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != sizeof(nlen)) {
+ err = MOBILEBACKUP2_E_MUX_ERROR;
+ goto leave_proto_err;
+ }
+
+ /* send path */
+ err = mobilebackup2_send_raw(mobilebackup2, path, pathlen, (uint32_t*)&bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if ((uint32_t)bytes != pathlen) {
+ err = MOBILEBACKUP2_E_MUX_ERROR;
+ goto leave_proto_err;
+ }
+
+ if (stat(localfile, &fst) < 0) {
+ printf("%s: stat failed on '%s': %d\n", __func__, localfile, errno);
+ errcode = errno;
+ goto leave;
+ }
+
+ f = fopen(localfile, "rb");
+ if (!f) {
+ printf("%s: Error opening local file '%s': %d\n", __func__, localfile, errno);
+ errcode = errno;
+ goto leave;
+ }
+
+ printf("Sending file '%s'\n", path);
+
+ /* send data size (file size + 1) */
+ uint32_t length = (uint32_t)fst.st_size;
+ nlen = GUINT32_TO_BE(length+1);
+ memcpy(buf, &nlen, sizeof(nlen));
+ /* unknown special byte */
+ buf[4] = 0x0C;
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, 5, (uint32_t*)&bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if (bytes != 5) {
+ goto leave_proto_err;
+ }
+
+ /* send file contents */
+ uint32_t sent = 0;
+ while (sent < length) {
+ size_t r = fread(buf, 1, sizeof(buf), f);
+ if (r <= 0) {
+ printf("%s: read error\n", __func__);
+ errcode = errno;
+ goto leave;
+ }
+ err = mobilebackup2_send_raw(mobilebackup2, buf, r, (uint32_t*)&bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ goto leave_proto_err;
+ }
+ if ((uint32_t)bytes != r) {
+ printf("sent only %d from %d\n", bytes, r);
+ goto leave_proto_err;
+ }
+ sent += r;
+ }
+ fclose(f);
+ f = NULL;
+ errcode = 0;
+
+leave:
+ if (errcode == 0) {
+ e = 0;
+ nlen = 1;
+ nlen = GUINT32_TO_BE(nlen);
+ memcpy(buf, &nlen, 4);
+ buf[4] = 0;
+ mobilebackup2_send_raw(mobilebackup2, buf, 5, &sent);
+ } else {
+ if (!*errplist) {
+ *errplist = plist_new_dict();
+ }
+ char *errdesc = strerror(errcode);
+ multi_status_add_file_error(*errplist, path, errno_to_device_error(errcode), errdesc);
+
+ length = strlen(errdesc);
+ nlen = GUINT32_TO_BE(length+1);
+ memcpy(buf, &nlen, 4);
+ buf[4] = 0x06;
+ slen = 5;
+ memcpy(buf+slen, errdesc, length);
+ slen += length;
+ err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, slen, (uint32_t*)&bytes);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("could not send message\n");
+ }
+ if ((uint32_t)bytes != slen) {
+ printf("could only send %d from %d\n", bytes, slen);
+ }
+ }
+
+leave_proto_err:
+ if (f)
+ fclose(f);
+ g_free(localfile);
+ return e;
+}
+
+static void handle_send_files(plist_t message, const char *backup_dir)
+{
+ uint32_t cnt;
+ uint32_t i = 0;
+ uint32_t sent;
+ plist_t errplist = NULL;
+
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || (plist_array_get_size(message) < 2) || !backup_dir) return;
+
+ plist_t files = plist_array_get_item(message, 1);
+ cnt = plist_array_get_size(files);
+ if (cnt == 0) return;
+
+ for (i = 0; i < cnt; i++) {
+ plist_t val = plist_array_get_item(files, i);
+ if (plist_get_node_type(val) != PLIST_STRING) {
+ continue;
+ }
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (!str)
+ continue;
+
+ if (handle_send_file(backup_dir, str, &errplist) < 0) {
+ //printf("Error when sending file '%s' to device\n", str);
+ // TODO: perhaps we can continue, we've got a multi status response?!
+ break;
+ }
+ }
+
+ /* send terminating 0 dword */
+ uint32_t zero = 0;
+ mobilebackup2_send_raw(mobilebackup2, (char*)&zero, 4, &sent);
+
+ if (!errplist) {
+ mobilebackup2_send_status_response(mobilebackup2, 0, NULL, plist_new_dict());
+ } else {
+ mobilebackup2_send_status_response(mobilebackup2, -13, "Multi status", errplist);
+ plist_free(errplist);
+ }
+}
+
+static int handle_receive_files(plist_t message, const char *backup_dir)
+{
+ uint64_t backup_real_size = 0;
+ uint64_t backup_total_size = 0;
+ uint32_t blocksize;
+ uint32_t bdone;
+ uint32_t rlen;
+ uint32_t nlen = 0;
+ uint32_t r;
+ char buf[32768];
+ char *fname = NULL;
+ gchar *bname = NULL;
+ char code = 0;
+ plist_t node = NULL;
+ FILE *f = NULL;
+ unsigned int file_count = 0;
+
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 4 || !backup_dir) return 0;
+
+ node = plist_array_get_item(message, 3);
+ if (plist_get_node_type(node) == PLIST_UINT) {
+ plist_get_uint_val(node, &backup_total_size);
+ }
+ if (backup_total_size > 0) {
+ gchar *format_size = g_format_size_for_display(backup_total_size);
+ printf("Receiving backup data (%s)\n", format_size);
+ g_free(format_size);
+ }
+
+ do {
+ if (quit_flag)
+ break;
+ r = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ nlen = GUINT32_FROM_BE(nlen);
+ if (nlen == 0) {
+ // we're done here
+ break;
+ }
+ fname = (char*)malloc(nlen+1);
+ r = 0;
+ mobilebackup2_receive_raw(mobilebackup2, fname, nlen, &r);
+ fname[r] = 0;
+ // we don't need this name
+ //printf("\n%s\n", fname);
+ free(fname);
+ nlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ nlen = GUINT32_FROM_BE(nlen);
+ fname = (char*)malloc(nlen+1);
+ mobilebackup2_receive_raw(mobilebackup2, fname, nlen, &r);
+ if (r != nlen) {
+ fprintf(stderr, "hmmm.... received %d from %d\n", r, nlen);
+ }
+ fname[r] = 0;
+ bname = g_build_path(G_DIR_SEPARATOR_S, backup_dir, fname, NULL);
+ free(fname);
+ nlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ nlen = GUINT32_FROM_BE(nlen);
+ code = 0;
+ mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r);
+
+ /* TODO remove this */
+ if ((code != 0) && (code != CODE_FILE_DATA) && (code != CODE_ERROR_MSG)) {
+ printf("Found new flag %02x\n", code);
+ }
+
+ remove(bname);
+ f = fopen(bname, "wb");
+ while (code == CODE_FILE_DATA) {
+ blocksize = nlen-1;
+ bdone = 0;
+ rlen = 0;
+ while (bdone < blocksize) {
+ if ((blocksize - bdone) < sizeof(buf)) {
+ rlen = blocksize - bdone;
+ } else {
+ rlen = sizeof(buf);
+ }
+ mobilebackup2_receive_raw(mobilebackup2, buf, rlen, &r);
+ if ((int)r <= 0) {
+ break;
+ }
+ fwrite(buf, 1, r, f);
+ bdone += r;
+ }
+ if (bdone == blocksize) {
+ backup_real_size += blocksize;
+ }
+ if (quit_flag)
+ break;
+ nlen = 0;
+ mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r);
+ nlen = GUINT32_FROM_BE(nlen);
+ if (nlen > 0) {
+ mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r);
+ } else {
+ break;
+ }
+ }
+ fclose(f);
+
+ file_count++;
+ if (backup_total_size > 0)
+ print_progress(((double)backup_real_size/(double)backup_total_size)*100);
+ if (nlen == 0) {
+ break;
+ }
+
+ /* check if an error message was received */
+ if (code == CODE_ERROR_MSG) {
+ /* error message */
+ char *msg = (char*)malloc(nlen);
+ mobilebackup2_receive_raw(mobilebackup2, msg, nlen-1, &r);
+ msg[r] = 0;
+ fprintf(stdout, "\nReceived an error message from device: %s\n", msg);
+ free(msg);
+ }
+ } while (1);
+ if ((int)nlen-1 > 0) {
+ printf("trashing padding\n");
+ fname = (char*)malloc(nlen-1);
+ mobilebackup2_receive_raw(mobilebackup2, fname, nlen-1, &r);
+ debug_buf(fname, r);
+ free(fname);
+ }
+ // TODO error handling?!
+ mobilebackup2_send_status_response(mobilebackup2, 0, NULL, plist_new_dict());
+ return file_count;
+}
+
+static void handle_list_directory(plist_t message, const char *backup_dir)
+{
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return;
+
+ /* TODO implement, for now we just return an empty listing */
+ mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, 0, NULL, plist_new_dict());
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+}
+
+static void handle_make_directory(plist_t message, const char *backup_dir)
+{
+ if (!message || (plist_get_node_type(message) != PLIST_ARRAY) || plist_array_get_size(message) < 2 || !backup_dir) return;
+
+ plist_t dir = plist_array_get_item(message, 1);
+ char *str = NULL;
+ int errcode = 0;
+ char *errdesc = NULL;
+ plist_get_string_val(dir, &str);
+
+ gchar *newpath = g_build_path(G_DIR_SEPARATOR_S, backup_dir, str, NULL);
+ g_free(str);
+
+ if (mkdir(newpath, 0755) < 0) {
+ errdesc = strerror(errno);
+ if (errno != EEXIST) {
+ printf("mkdir: %s (%d)\n", errdesc, errno);
+ }
+ errcode = errno_to_device_error(errno);
+ }
+ g_free(newpath);
+ mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, NULL);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+}
+
+/**
+ * signal handler function for cleaning up properly
+ */
+static void clean_exit(int sig)
+{
+ fprintf(stderr, "Exiting...\n");
+ quit_flag++;
+}
+
+static void print_usage(int argc, char **argv)
+{
+ char *name = NULL;
+ name = strrchr(argv[0], '/');
+ printf("Usage: %s [OPTIONS] CMD [DIRECTORY]\n", (name ? name + 1: argv[0]));
+ printf("Create or restore backup from the current or specified directory.\n\n");
+ printf("commands:\n");
+ printf(" backup\tSaves a device backup into DIRECTORY\n");
+ printf(" restore\tRestores a device backup from DIRECTORY.\n\n");
+ printf("options:\n");
+ printf(" -d, --debug\t\tenable communication debugging\n");
+ printf(" -u, --uuid UUID\ttarget specific device by its 40-digit device UUID\n");
+ printf(" -h, --help\t\tprints usage information\n");
+ printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+ idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR;
+ int i;
+ char uuid[41];
+ uint16_t port = 0;
+ uuid[0] = 0;
+ int cmd = -1;
+ int is_full_backup = 0;
+ char *backup_directory = NULL;
+ struct stat st;
+ //plist_t node = NULL;
+ plist_t node_tmp = NULL;
+ //plist_t manifest_plist = NULL;
+ plist_t info_plist = NULL;
+ //char *buffer = NULL;
+ //char *file_path = NULL;
+ //uint64_t length = 0;
+ //uint64_t backup_total_size = 0;
+ //enum device_link_file_status_t file_status = DEVICE_LINK_FILE_STATUS_NONE;
+// uint64_t c = 0;
+ mobilebackup2_error_t err;
+ char *manifest_path = NULL;
+
+ /* we need to exit cleanly on running backups and restores or we cause havok */
+ signal(SIGINT, clean_exit);
+ signal(SIGQUIT, clean_exit);
+ signal(SIGTERM, clean_exit);
+ signal(SIGPIPE, SIG_IGN);
+
+ /* parse cmdline args */
+ for (i = 1; i < argc; i++) {
+ if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) {
+ idevice_set_debug_level(1);
+ continue;
+ }
+ else if (!strcmp(argv[i], "-u") || !strcmp(argv[i], "--uuid")) {
+ i++;
+ if (!argv[i] || (strlen(argv[i]) != 40)) {
+ print_usage(argc, argv);
+ return 0;
+ }
+ strcpy(uuid, argv[i]);
+ continue;
+ }
+ else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) {
+ print_usage(argc, argv);
+ return 0;
+ }
+ else if (!strcmp(argv[i], "backup")) {
+ cmd = CMD_BACKUP;
+ }
+ else if (!strcmp(argv[i], "restore")) {
+ cmd = CMD_RESTORE;
+ }
+ else if (backup_directory == NULL) {
+ backup_directory = argv[i];
+ }
+ else {
+ print_usage(argc, argv);
+ return 0;
+ }
+ }
+
+ /* verify options */
+ if (cmd == -1) {
+ printf("No command specified.\n");
+ print_usage(argc, argv);
+ return -1;
+ }
+
+ if (backup_directory == NULL) {
+ printf("No target backup directory specified.\n");
+ print_usage(argc, argv);
+ return -1;
+ }
+
+ /* verify if passed backup directory exists */
+ if (stat(backup_directory, &st) != 0) {
+ printf("ERROR: Backup directory \"%s\" does not exist!\n", backup_directory);
+ return -1;
+ }
+
+ if (uuid[0] != 0) {
+ ret = idevice_new(&phone, uuid);
+ if (ret != IDEVICE_E_SUCCESS) {
+ printf("No device found with uuid %s, is it plugged in?\n", uuid);
+ return -1;
+ }
+ }
+ else
+ {
+ ret = idevice_new(&phone, NULL);
+ if (ret != IDEVICE_E_SUCCESS) {
+ printf("No device found, is it plugged in?\n");
+ return -1;
+ }
+ char *newuuid = NULL;
+ idevice_get_uuid(phone, &newuuid);
+ strcpy(uuid, newuuid);
+ free(newuuid);
+ }
+
+ /* backup directory must contain an Info.plist */
+ gchar *info_path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, uuid, "Info.plist", NULL);
+ if (cmd == CMD_RESTORE) {
+ if (stat(info_path, &st) != 0) {
+ g_free(info_path);
+ printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found.\n", backup_directory);
+ return -1;
+ }
+ } else if (cmd == CMD_BACKUP) {
+ is_full_backup = 1;
+ }
+
+ printf("Backup directory is \"%s\"\n", backup_directory);
+
+ if (LOCKDOWN_E_SUCCESS != lockdownd_client_new_with_handshake(phone, &client, "idevicebackup")) {
+ idevice_free(phone);
+ return -1;
+ }
+
+ /* start notification_proxy */
+ np_client_t np = NULL;
+ ret = lockdownd_start_service(client, NP_SERVICE_NAME, &port);
+ if ((ret == LOCKDOWN_E_SUCCESS) && port) {
+ np_client_new(phone, port, &np);
+ np_set_notify_callback(np, notify_cb, NULL);
+ const char *noties[5] = {
+ NP_SYNC_CANCEL_REQUEST,
+ NP_SYNC_SUSPEND_REQUEST,
+ NP_SYNC_RESUME_REQUEST,
+ NP_BACKUP_DOMAIN_CHANGED,
+ NULL
+ };
+ np_observe_notifications(np, noties);
+ } else {
+ printf("ERROR: Could not start service %s.\n", NP_SERVICE_NAME);
+ }
+
+ afc_client_t afc = NULL;
+ if (cmd == CMD_BACKUP) {
+ /* start AFC, we need this for the lock file */
+ port = 0;
+ ret = lockdownd_start_service(client, "com.apple.afc", &port);
+ if ((ret == LOCKDOWN_E_SUCCESS) && port) {
+ afc_client_new(phone, port, &afc);
+ }
+ }
+
+ /* start mobilebackup service and retrieve port */
+ port = 0;
+ ret = lockdownd_start_service(client, MOBILEBACKUP2_SERVICE_NAME, &port);
+ if ((ret == LOCKDOWN_E_SUCCESS) && port) {
+ printf("Started \"%s\" service on port %d.\n", MOBILEBACKUP2_SERVICE_NAME, port);
+ mobilebackup2_client_new(phone, port, &mobilebackup2);
+
+ /* send Hello message */
+ err = mobilebackup2_version_exchange(mobilebackup2);
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not perform backup protocol version exchange, error code %d\n", err);
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+
+ /* check abort conditions */
+ if (quit_flag > 0) {
+ printf("Aborting backup. Cancelled by user.\n");
+ cmd = CMD_LEAVE;
+ goto checkpoint;
+ }
+
+ /* verify existing Info.plist */
+ if (stat(info_path, &st) == 0) {
+ printf("Reading Info.plist from backup.\n");
+ plist_read_from_filename(&info_plist, info_path);
+
+ if (!info_plist) {
+ printf("Could not read Info.plist\n");
+ is_full_backup = 1;
+ }
+ if (info_plist && (cmd == CMD_BACKUP)) {
+ if (mobilebackup_info_is_current_device(info_plist)) {
+ /* update the last backup time within Info.plist */
+ mobilebackup_info_update_last_backup_date(info_plist);
+ remove(info_path);
+ plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML);
+ } else {
+ printf("Aborting backup. Backup is not compatible with the current device.\n");
+ cmd = CMD_LEAVE;
+ }
+ } else if (info_plist && (cmd == CMD_RESTORE)) {
+ if (!mobilebackup_info_is_current_device(info_plist)) {
+ printf("Aborting restore. Backup data is not compatible with the current device.\n");
+ cmd = CMD_LEAVE;
+ }
+ }
+ } else {
+ if (cmd == CMD_RESTORE) {
+ printf("Aborting restore. Info.plist is missing.\n");
+ cmd = CMD_LEAVE;
+ } else {
+ is_full_backup = 1;
+ }
+ }
+
+ uint64_t lockfile = 0;
+ if (cmd == CMD_BACKUP) {
+ do_post_notification(NP_SYNC_WILL_START);
+ afc_file_open(afc, "/com.apple.itunes.lock_sync", AFC_FOPEN_RW, &lockfile);
+ }
+ if (lockfile) {
+ afc_error_t aerr;
+ do_post_notification(NP_SYNC_LOCK_REQUEST);
+ for (i = 0; i < LOCK_ATTEMPTS; i++) {
+ aerr = afc_file_lock(afc, lockfile, AFC_LOCK_EX);
+ if (aerr == AFC_E_SUCCESS) {
+ do_post_notification(NP_SYNC_DID_START);
+ break;
+ } else if (aerr == AFC_E_OP_WOULD_BLOCK) {
+ usleep(LOCK_WAIT);
+ continue;
+ } else {
+ fprintf(stderr, "ERROR: could not lock file! error code: %d\n", aerr);
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ cmd = CMD_LEAVE;
+ }
+ }
+ if (i == LOCK_ATTEMPTS) {
+ fprintf(stderr, "ERROR: timeout while locking for sync\n");
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ cmd = CMD_LEAVE;
+ }
+ }
+
+ /* Manifest.plist (backup manifest (backup state)) */
+ manifest_path = g_build_path(G_DIR_SEPARATOR_S, backup_directory, uuid, "Manifest.plist", NULL);
+
+checkpoint:
+
+ switch(cmd) {
+ case CMD_BACKUP:
+ printf("Starting backup...\n");
+ /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */
+ /* TODO: verify battery on AC enough battery remaining */
+ gchar *device_backup_dir = g_build_path(G_DIR_SEPARATOR_S, backup_directory, uuid, NULL);
+ if (mobilebackup_read_status(device_backup_dir) <= 0) {
+ gchar *status_plist = g_build_path(G_DIR_SEPARATOR_S, backup_directory, uuid, "Status.plist", NULL);
+ remove(status_plist);
+ g_free(status_plist);
+ }
+ g_free(device_backup_dir);
+
+ /* read the last Manifest.plist */
+ /*
+ if (!is_full_backup) {
+ printf("Reading existing Manifest.\n");
+ plist_read_from_filename(&manifest_plist, manifest_path);
+ if (!manifest_plist) {
+ printf("Could not read Manifest.plist, switching to full backup mode.\n");
+ is_full_backup = 1;
+ }
+ }*/
+
+ /* Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */
+
+ /* create new Info.plist on new backups */
+ if (is_full_backup) {
+ if (info_plist) {
+ plist_free(info_plist);
+ info_plist = NULL;
+ }
+ remove(info_path);
+ printf("Creating Info.plist for new backup.\n");
+ info_plist = mobilebackup_factory_info_plist_new();
+ plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML);
+ }
+ g_free(info_path);
+
+ plist_free(info_plist);
+ info_plist = NULL;
+
+ /* close down the lockdown connection as it is no longer needed */
+ if (client) {
+ lockdownd_client_free(client);
+ client = NULL;
+ }
+
+ /* create Status.plist with failed status for now */
+ //mobilebackup_write_status(backup_directory, 0);
+
+ /* request backup from device with manifest from last backup */
+ printf("Requesting backup from device...\n");
+
+ err = mobilebackup2_request_backup(mobilebackup2, uuid);
+ if (err == MOBILEBACKUP2_E_SUCCESS) {
+ /*if (is_full_backup)
+ printf("Full backup mode.\n");
+ else
+ printf("Incremental backup mode.\n");*/
+ //printf("Please wait. Device is preparing backup data...\n");
+ } else {
+ if (err == MOBILEBACKUP2_E_BAD_VERSION) {
+ printf("ERROR: Could not start backup process: backup protocol version mismatch!\n");
+ } else if (err == MOBILEBACKUP2_E_REPLY_NOT_OK) {
+ printf("ERROR: Could not start backup process: device refused to start the backup process.\n");
+ } else {
+ printf("ERROR: Could not start backup process: unspecified error occured\n");
+ }
+ break;
+ }
+
+ /* reset backup status */
+ int backup_ok = 0;
+ plist_t message = NULL;
+
+ /* TODO receive and save DLSendFile files and metadata, ACK each */
+ char *dlmsg = NULL;
+ /*uint64_t file_size = 0;
+ uint64_t file_size_current = 0;*/
+ int file_count = 0;
+ /*int hunk_index = 0;
+ uint64_t backup_real_size = 0;
+ char *file_ext = NULL;
+ char *filename_mdinfo = NULL;
+ char *filename_mddata = NULL;
+ char *filename_source = NULL;
+ char *format_size = NULL;
+ gboolean is_manifest = FALSE;
+ uint8_t b = 0;*/
+ int errcode = 0;
+ const char *errdesc = NULL;
+
+ /* process series of DLMessage* operations */
+ do {
+ if (dlmsg) {
+ free(dlmsg);
+ dlmsg = NULL;
+ }
+ mobilebackup2_receive_message(mobilebackup2, &message, &dlmsg);
+ if (!message || !dlmsg) {
+ printf("Device is not ready yet. Going to try again in 2 seconds...\n");
+ sleep(2);
+ goto files_out;
+ }
+
+ //node = plist_array_get_item(message, 0);
+
+ if (!strcmp(dlmsg, "DLMessageDownloadFiles")) {
+ /* device wants to download files from the computer */
+ handle_send_files(message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageUploadFiles")) {
+ /* device wants to send files to the computer */
+ file_count += handle_receive_files(message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLContentsOfDirectory")) {
+ /* list directory contents */
+ handle_list_directory(message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageCreateDirectory")) {
+ /* make a directory */
+ handle_make_directory(message, backup_directory);
+ } else if (!strcmp(dlmsg, "DLMessageMoveFiles")) {
+ /* perform a series of rename operations */
+ plist_t moves = plist_array_get_item(message, 1);
+ uint32_t cnt = plist_dict_get_size(moves);
+ printf("Moving %d file%s\n", cnt, (cnt == 1) ? "" : "s");
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(moves, &iter);
+ errcode = 0;
+ errdesc = NULL;
+ if (iter) {
+ char *key = NULL;
+ plist_t val = NULL;
+ do {
+ plist_dict_next_item(moves, iter, &key, &val);
+ if (key && (plist_get_node_type(val) == PLIST_STRING)) {
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (str) {
+ gchar *newpath = g_build_path(G_DIR_SEPARATOR_S, backup_directory, str, NULL);
+ g_free(str);
+ gchar *oldpath = g_build_path(G_DIR_SEPARATOR_S, backup_directory, key, NULL);
+
+ remove(newpath);
+ //fprintf(stderr, "Moving '%s' to '%s'\n", oldpath, newpath);
+ if (rename(oldpath, newpath) < 0) {
+ printf("Renameing '%s' to '%s' failed: %s (%d)\n", oldpath, newpath, strerror(errno), errno);
+ errcode = -errno;
+ errdesc = strerror(errno);
+ break;
+ }
+ g_free(oldpath);
+ g_free(newpath);
+ }
+ free(key);
+ key = NULL;
+ }
+ } while (val);
+ free(iter);
+ } else {
+ errcode = -1;
+ errdesc = "Could not create dict iterator";
+ printf("Could not create dict iterator\n");
+ }
+ err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, plist_new_dict());
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+ } else if (!strcmp(dlmsg, "DLMessageRemoveFiles")) {
+ plist_t removes = plist_array_get_item(message, 1);
+ uint32_t cnt = plist_array_get_size(removes);
+ printf("Removing %d file%s\n", cnt, (cnt == 1) ? "" : "s");
+ uint32_t ii = 0;
+ errcode = 0;
+ errdesc = NULL;
+ for (ii = 0; ii < cnt; ii++) {
+ plist_t val = plist_array_get_item(removes, ii);
+ if (plist_get_node_type(val) == PLIST_STRING) {
+ char *str = NULL;
+ plist_get_string_val(val, &str);
+ if (str) {
+ gchar *newpath = g_build_path(G_DIR_SEPARATOR_S, backup_directory, str, NULL);
+ g_free(str);
+ if (remove(newpath) < 0) {
+ printf("Could not remove '%s': %s (%d)\n", newpath, strerror(errno), errno);
+ //errcode = -errno;
+ //errdesc = strerror(errno);
+ }
+ g_free(newpath);
+ }
+ }
+ }
+ err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, plist_new_dict());
+ if (err != MOBILEBACKUP2_E_SUCCESS) {
+ printf("Could not send status response, error %d\n", err);
+ }
+ } else if (!strcmp(dlmsg, "DLMessageProcessMessage")) {
+ node_tmp = plist_array_get_item(message, 1);
+ if (plist_get_node_type(node_tmp) != PLIST_DICT) {
+ printf("Unknown message received!\n");
+ }
+ plist_t nn;
+ int error_code = -1;
+ nn = plist_dict_get_item(node_tmp, "ErrorCode");
+ if (nn && (plist_get_node_type(nn) == PLIST_UINT)) {
+ uint64_t ec = 0;
+ plist_get_uint_val(nn, &ec);
+ error_code = (uint32_t)ec;
+ if (error_code == 0) {
+ backup_ok = 1;
+ }
+ }
+ nn = plist_dict_get_item(node_tmp, "ErrorDescription");
+ char *str = NULL;
+ if (nn && (plist_get_node_type(nn) == PLIST_STRING)) {
+ plist_get_string_val(nn, &str);
+ }
+ if (error_code != 0) {
+ if (str) {
+ printf("ErrorCode %d: Description: %s\n", error_code, str);
+ } else {
+ printf("ErrorCode %d: (Unknown)\n", error_code);
+ }
+ }
+ if (str) {
+ free(str);
+ }
+ break;
+ }
+
+ /* print status */
+ /*if (plist_array_get_size(message) >= 4) {
+ plist_t pnode = plist_array_get_item(message, 4);
+ if (pnode && (plist_get_node_type(pnode) == PLIST_REAL)) {
+ double progress = 0.0;
+ plist_get_real_val(pnode, &progress);
+ printf("Progress: %f\n", progress);
+ }
+ }*/
+
+#if 0
+ /* get out if we don't get a DLSendFile */
+ if (plist_strcmp(node, "DLSendFile"))
+ break;
+
+ node_tmp = plist_array_get_item(message, 2);
+
+ /* first message hunk contains total backup size */
+ if ((hunk_index == 0) && (file_index == 0)) {
+ node = plist_dict_get_item(node_tmp, "BackupTotalSizeKey");
+ if (node) {
+ plist_get_uint_val(node, &backup_total_size);
+ format_size = g_format_size_for_display(backup_total_size);
+ printf("Backup data requires %s on the disk.\n", format_size);
+ g_free(format_size);
+ }
+ }
+
+ /* check DLFileStatusKey (codes: 1 = Hunk, 2 = Last Hunk) */
+ node = plist_dict_get_item(node_tmp, "DLFileStatusKey");
+ plist_get_uint_val(node, &c);
+ file_status = c;
+
+ /* get source filename */
+ node = plist_dict_get_item(node_tmp, "BackupManifestKey");
+ b = 0;
+ if (node) {
+ plist_get_bool_val(node, &b);
+ }
+ is_manifest = (b == 1) ? TRUE: FALSE;
+
+ if ((hunk_index == 0) && (!is_manifest)) {
+ /* get source filename */
+ node = plist_dict_get_item(node_tmp, "DLFileSource");
+ plist_get_string_val(node, &filename_source);
+
+ /* increase received size */
+ node = plist_dict_get_item(node_tmp, "DLFileAttributesKey");
+ node = plist_dict_get_item(node, "FileSize");
+ plist_get_uint_val(node, &file_size);
+ backup_real_size += file_size;
+
+ format_size = g_format_size_for_display(backup_real_size);
+ printf("(%s", format_size);
+ g_free(format_size);
+
+ format_size = g_format_size_for_display(backup_total_size);
+ printf("/%s): ", format_size);
+ g_free(format_size);
+
+ format_size = g_format_size_for_display(file_size);
+ printf("Receiving file %s (%s)... \n", filename_source, format_size);
+ g_free(format_size);
+
+ if (filename_source)
+ free(filename_source);
+ }
+
+ /* check if we completed a file */
+ if ((file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) && (!is_manifest)) {
+ /* save <hash>.mdinfo */
+ node = plist_dict_get_item(node_tmp, "BackupFileInfo");
+ if (node) {
+ node = plist_dict_get_item(node_tmp, "DLFileDest");
+ plist_get_string_val(node, &file_path);
+
+ filename_mdinfo = mobilebackup_build_path(backup_directory, file_path, ".mdinfo");
+
+ /* remove any existing file */
+ if (stat(filename_mdinfo, &st) == 0)
+ remove(filename_mdinfo);
+
+ node = plist_dict_get_item(node_tmp, "BackupFileInfo");
+ plist_write_to_filename(node, filename_mdinfo, PLIST_FORMAT_BINARY);
+
+ g_free(filename_mdinfo);
+ }
+
+ file_index++;
+ }
+
+ /* save <hash>.mddata */
+ node = plist_dict_get_item(node_tmp, "BackupFileInfo");
+ if (node_tmp) {
+ node = plist_dict_get_item(node_tmp, "DLFileDest");
+ plist_get_string_val(node, &file_path);
+
+ filename_mddata = mobilebackup_build_path(backup_directory, file_path, is_manifest ? NULL: ".mddata");
+
+ /* if this is the first hunk, remove any existing file */
+ if ((hunk_index == 0) && (stat(filename_mddata, &st) == 0))
+ remove(filename_mddata);
+
+ /* get file data hunk */
+ node_tmp = plist_array_get_item(message, 1);
+ plist_get_data_val(node_tmp, &buffer, &length);
+
+ buffer_write_to_filename(filename_mddata, buffer, length);
+ if (!is_manifest)
+ file_size_current += length;
+
+ /* activate currently sent manifest */
+ if ((file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) && (is_manifest)) {
+ rename(filename_mddata, manifest_path);
+ }
+
+ free(buffer);
+ buffer = NULL;
+
+ g_free(filename_mddata);
+ }
+
+ if ((!is_manifest)) {
+ if (hunk_index == 0 && file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) {
+ print_progress(100);
+ } else {
+ if (file_size > 0)
+ print_progress((double)((file_size_current*100)/file_size));
+ }
+ }
+
+ hunk_index++;
+
+ if (file_ext)
+ free(file_ext);
+#endif
+ if (message)
+ plist_free(message);
+ message = NULL;
+
+#if 0
+ if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) {
+ /* acknowlegdge that we received the file */
+ mobilebackup_send_backup_file_received(mobilebackup);
+ /* reset hunk_index */
+ hunk_index = 0;
+ if (!is_manifest) {
+ file_size_current = 0;
+ file_size = 0;
+ }
+ }
+#endif
+
+files_out:
+ if (quit_flag > 0) {
+ /* need to cancel the backup here */
+ //mobilebackup_send_error(mobilebackup, "Cancelling DLSendFile");
+
+ /* remove any atomic Manifest.plist.tmp */
+ if (manifest_path)
+ g_free(manifest_path);
+
+ /*manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp");
+ if (stat(manifest_path, &st) == 0)
+ remove(manifest_path);*/
+ break;
+ }
+ } while (1);
+
+ printf("Received %d files from device.\n", file_count);
+#if 0
+ if (!quit_flag && !plist_strcmp(node, "DLMessageProcessMessage")) {
+ node_tmp = plist_array_get_item(message, 1);
+ node = plist_dict_get_item(node_tmp, "BackupMessageTypeKey");
+ /* check if we received the final "backup finished" message */
+ if (node && !plist_strcmp(node, "BackupMessageBackupFinished")) {
+ /* backup finished */
+
+ /* process BackupFilesToDeleteKey */
+ node = plist_dict_get_item(node_tmp, "BackupFilesToDeleteKey");
+ if (node) {
+ length = plist_array_get_size(node);
+ i = 0;
+ while ((node_tmp = plist_array_get_item(node, i++)) != NULL) {
+ plist_get_string_val(node_tmp, &file_path);
+
+ if (mobilebackup_delete_backup_file_by_hash(backup_directory, file_path)) {
+ printf("DONE\n");
+ } else
+ printf("FAILED\n");
+ }
+ }
+
+ /* save last valid Manifest.plist */
+ node_tmp = plist_array_get_item(message, 1);
+ manifest_plist = plist_dict_get_item(node_tmp, "BackupManifestKey");
+ if (manifest_plist) {
+ remove(manifest_path);
+ printf("Storing Manifest.plist...\n");
+ plist_write_to_filename(manifest_plist, manifest_path, PLIST_FORMAT_XML);
+ }
+
+ backup_ok = 1;
+ }
+ }
+#endif
+ if (backup_ok) {
+ // /* Status.plist (Info on how the backup process turned out) */
+ printf("Backup Successful.\n");
+ //mobilebackup_write_status(backup_directory, 1);
+ } else {
+ printf("Backup Failed.\n");
+ }
+
+ break;
+ case CMD_RESTORE:
+ /* close down the lockdown connection as it is no longer needed */
+ if (client) {
+ lockdownd_client_free(client);
+ client = NULL;
+ }
+
+ /* TODO: verify battery on AC enough battery remaining */
+
+ /* verify if Status.plist says we read from an successful backup */
+#if 0
+ if (mobilebackup_read_status(backup_directory) <= 0) {
+ printf("ERROR: Cannot ensure we restore from a successful backup. Aborting.\n");
+ break;
+ }
+ /* now make sure backup integrity is ok! verify all files */
+ printf("Reading existing Manifest.\n");
+ plist_read_from_filename(&manifest_plist, manifest_path);
+ if (!manifest_plist) {
+ printf("Could not read Manifest.plist. Aborting.\n");
+ break;
+ }
+
+ printf("Verifying backup integrity, please wait.\n");
+ char *bin = NULL;
+ uint64_t binsize = 0;
+ node = plist_dict_get_item(manifest_plist, "Data");
+ if (!node || (plist_get_node_type(node) != PLIST_DATA)) {
+ printf("Could not read Data key from Manifest.plist!\n");
+ break;
+ }
+ plist_get_data_val(node, &bin, &binsize);
+ plist_t backup_data = NULL;
+ if (bin) {
+ char *auth_ver = NULL;
+ unsigned char *auth_sig = NULL;
+ uint64_t auth_sig_len = 0;
+ /* verify AuthSignature */
+ node = plist_dict_get_item(manifest_plist, "AuthVersion");
+ plist_get_string_val(node, &auth_ver);
+ if (auth_ver && (strcmp(auth_ver, "2.0") == 0)) {
+ node = plist_dict_get_item(manifest_plist, "AuthSignature");
+ if (node && (plist_get_node_type(node) == PLIST_DATA)) {
+ plist_get_data_val(node, (char**)&auth_sig, &auth_sig_len);
+ }
+ if (auth_sig && (auth_sig_len == 20)) {
+ /* calculate the sha1, then compare */
+ unsigned char data_sha1[20];
+ sha1_of_data(bin, binsize, data_sha1);
+ if (compare_hash(auth_sig, data_sha1, 20)) {
+ printf("AuthSignature is valid\n");
+ } else {
+ printf("ERROR: AuthSignature is NOT VALID\n");
+ }
+ } else {
+ printf("Could not get AuthSignature from manifest!\n");
+ }
+ g_free(auth_sig);
+ } else if (auth_ver) {
+ printf("Unknown AuthVersion '%s', cannot verify AuthSignature\n", auth_ver);
+ }
+ plist_from_bin(bin, (uint32_t)binsize, &backup_data);
+ g_free(bin);
+ }
+ if (!backup_data) {
+ printf("Could not read plist from Manifest.plist Data key!\n");
+ break;
+ }
+ plist_t files = plist_dict_get_item(backup_data, "Files");
+ if (files && (plist_get_node_type(files) == PLIST_DICT)) {
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(files, &iter);
+ if (iter) {
+ /* loop over Files entries in Manifest data plist */
+ char *hash = NULL;
+ int file_ok = 0;
+ int total_files = plist_dict_get_size(files);
+ int cur_file = 1;
+ node = NULL;
+ plist_dict_next_item(files, iter, &hash, &node);
+ while (node) {
+ printf("Verifying file %d/%d (%d%%) \r", cur_file, total_files, (cur_file*100/total_files));
+ cur_file++;
+ /* make sure both .mddata/.mdinfo files are available for each entry */
+ file_ok = mobilebackup_check_file_integrity(backup_directory, hash, node);
+ node = NULL;
+ free(hash);
+ hash = NULL;
+ if (!file_ok) {
+ break;
+ }
+ plist_dict_next_item(files, iter, &hash, &node);
+ }
+ printf("\n");
+ free(iter);
+ if (!file_ok) {
+ plist_free(backup_data);
+ break;
+ }
+ printf("All backup files appear to be valid\n");
+ }
+ }
+
+ printf("Requesting restore from device...\n");
+
+ /* request restore from device with manifest (BackupMessageRestoreMigrate) */
+ int restore_flags = MB_RESTORE_NOTIFY_SPRINGBOARD | MB_RESTORE_PRESERVE_SETTINGS | MB_RESTORE_PRESERVE_CAMERA_ROLL;
+ err = mobilebackup_request_restore(mobilebackup, manifest_plist, restore_flags, "1.6");
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ if (err == MOBILEBACKUP_E_BAD_VERSION) {
+ printf("ERROR: Could not start restore process: backup protocol version mismatch!\n");
+ } else if (err == MOBILEBACKUP_E_REPLY_NOT_OK) {
+ printf("ERROR: Could not start restore process: device refused to start the restore process.\n");
+ } else {
+ printf("ERROR: Could not start restore process: unspecified error occured (%d)\n", err);
+ }
+ plist_free(backup_data);
+ break;
+ }
+
+ printf("Entered restore mode.\n");
+
+ int restore_ok = 0;
+
+ if (files && (plist_get_node_type(files) == PLIST_DICT)) {
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(files, &iter);
+ if (iter) {
+ /* loop over Files entries in Manifest data plist */
+ char *hash = NULL;
+ plist_t file_info = NULL;
+ char *file_info_path = NULL;
+ int total_files = plist_dict_get_size(files);
+ int cur_file = 0;
+ uint64_t file_offset = 0;
+ uint8_t is_encrypted = 0;
+ plist_t tmp_node = NULL;
+ plist_t file_path_node = NULL;
+ plist_t send_file_node = NULL;
+ node = NULL;
+ plist_dict_next_item(files, iter, &hash, &node);
+ while (node) {
+ /* TODO: read mddata/mdinfo files and send to device using DLSendFile */
+ file_info_path = mobilebackup_build_path(backup_directory, hash, ".mdinfo");
+ plist_read_from_filename(&file_info, file_info_path);
+
+ /* get encryption state */
+ tmp_node = plist_dict_get_item(file_info, "IsEncrypted");
+ plist_get_bool_val(tmp_node, &is_encrypted);
+ tmp_node = NULL;
+
+ /* get real file path from metadata */
+ tmp_node = plist_dict_get_item(file_info, "Metadata");
+ plist_get_data_val(tmp_node, &buffer, &length);
+ tmp_node = NULL;
+ plist_from_bin(buffer, length, &tmp_node);
+ file_path_node = plist_dict_get_item(tmp_node, "Path");
+ plist_get_string_val(file_path_node, &file_path);
+
+ printf("Restoring file %s %d/%d (%d%%)... ", file_path, cur_file, total_files, (cur_file*100/total_files));
+
+ /* add additional device link file information keys */
+ plist_dict_insert_item(file_info, "DLFileAttributesKey", plist_copy(node));
+ plist_dict_insert_item(file_info, "DLFileSource", plist_new_string(file_info_path));
+ plist_dict_insert_item(file_info, "DLFileDest", plist_new_string("/tmp/RestoreFile.plist"));
+ plist_dict_insert_item(file_info, "DLFileIsEncrypted", plist_new_bool(is_encrypted));
+ plist_dict_insert_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset));
+ plist_dict_insert_item(file_info, "DLFileStatusKey", plist_new_uint(file_status));
+
+ /* read data from file */
+ free(file_info_path);
+ file_info_path = mobilebackup_build_path(backup_directory, hash, ".mddata");
+ buffer_read_from_filename(file_info_path, &buffer, &length);
+ free(file_info_path);
+
+ /* send DLSendFile messages */
+ file_offset = 0;
+ do {
+ if ((length-file_offset) <= 8192)
+ file_status = DEVICE_LINK_FILE_STATUS_LAST_HUNK;
+ else
+ file_status = DEVICE_LINK_FILE_STATUS_HUNK;
+
+ plist_dict_remove_item(file_info, "DLFileOffsetKey");
+ plist_dict_insert_item(file_info, "DLFileOffsetKey", plist_new_uint(file_offset));
+
+ plist_dict_remove_item(file_info, "DLFileStatusKey");
+ plist_dict_insert_item(file_info, "DLFileStatusKey", plist_new_uint(file_status));
+
+ send_file_node = plist_new_array();
+
+ plist_array_append_item(send_file_node, plist_new_string("DLSendFile"));
+
+ if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK)
+ plist_array_append_item(send_file_node, plist_new_data(buffer+file_offset, length-file_offset));
+ else
+ plist_array_append_item(send_file_node, plist_new_data(buffer+file_offset, 8192));
+
+ plist_array_append_item(send_file_node, plist_copy(file_info));
+
+ err = mobilebackup_send(mobilebackup, send_file_node);
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ printf("ERROR: Unable to send file hunk due to error %d. Aborting...\n", err);
+ file_status = DEVICE_LINK_FILE_STATUS_NONE;
+ }
+
+ if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK) {
+ /* TODO: if all hunks of a file are sent, device must send ack */
+ err = mobilebackup_receive_restore_file_received(mobilebackup, NULL);
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ printf("ERROR: Did not receive an ack for the sent file due to error %d. Aborting...\n", err);
+ file_status = DEVICE_LINK_FILE_STATUS_NONE;
+ }
+ }
+
+ file_offset += 8192;
+
+ if (file_status == DEVICE_LINK_FILE_STATUS_LAST_HUNK)
+ printf("DONE\n");
+
+ plist_free(send_file_node);
+
+ if (file_status == DEVICE_LINK_FILE_STATUS_NONE)
+ break;
+
+ } while((file_offset < length));
+
+ free(hash);
+ node = NULL;
+ hash = NULL;
+
+ restore_ok = 1;
+ if (file_status == DEVICE_LINK_FILE_STATUS_NONE) {
+ restore_ok = 0;
+ break;
+ }
+
+ cur_file++;
+ plist_dict_next_item(files, iter, &hash, &node);
+ }
+ free(iter);
+
+ printf("Restored %d files on device.\n", cur_file);
+ }
+ }
+ /* TODO: observe notification_proxy id com.apple.mobile.application_installed */
+ /* TODO: loop over Applications entries in Manifest data plist */
+ plist_t applications = plist_dict_get_item(backup_data, "Applications");
+ if (applications && (plist_get_node_type(applications) == PLIST_DICT) && restore_ok) {
+ plist_dict_iter iter = NULL;
+ plist_dict_new_iter(applications, &iter);
+ if (iter) {
+ /* loop over Application entries in Manifest data plist */
+ char *hash = NULL;
+ int total_files = plist_dict_get_size(applications);
+ int cur_file = 1;
+ plist_t tmp_node = NULL;
+ plist_t dict = NULL;
+ plist_t array = NULL;
+ node = NULL;
+ plist_dict_next_item(applications, iter, &hash, &node);
+ while (node) {
+ printf("Restoring Application %s %d/%d (%d%%)...", hash, cur_file, total_files, (cur_file*100/total_files));
+ /* FIXME: receive com.apple.mobile.application_installed notification */
+ /* send AppInfo entry */
+ tmp_node = plist_dict_get_item(node, "AppInfo");
+
+ dict = plist_new_dict();
+ plist_dict_insert_item(dict, "AppInfo", plist_copy(tmp_node));
+ plist_dict_insert_item(dict, "BackupMessageTypeKey", plist_new_string("BackupMessageRestoreApplicationSent"));
+
+ array = plist_new_array();
+ plist_array_append_item(array, plist_new_string("DLMessageProcessMessage"));
+ plist_array_append_item(array, dict);
+
+ err = mobilebackup_send(mobilebackup, array);
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ printf("ERROR: Unable to restore application %s due to error %d. Aborting...\n", hash, err);
+ restore_ok = 0;
+ }
+
+ plist_free(array);
+ array = NULL;
+ dict = NULL;
+
+ /* receive BackupMessageRestoreApplicationReceived from device */
+ if (restore_ok) {
+ err = mobilebackup_receive_restore_application_received(mobilebackup, NULL);
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ printf("ERROR: Failed to receive an ack from the device for this application due to error %d. Aborting...\n", err);
+ restore_ok = 0;
+ }
+ }
+
+ tmp_node = NULL;
+ node = NULL;
+ free(hash);
+ hash = NULL;
+
+ if (restore_ok) {
+ printf("DONE\n");
+ cur_file++;
+ plist_dict_next_item(applications, iter, &hash, &node);
+ } else
+ break;
+ }
+ free(iter);
+
+ if (restore_ok)
+ printf("All applications restored.\n");
+ else
+ printf("Failed to restore applications.\n");
+ }
+ }
+
+ plist_free(backup_data);
+
+ /* signal restore finished message to device; BackupMessageRestoreComplete */
+ if (restore_ok) {
+ err = mobilebackup_send_restore_complete(mobilebackup);
+ if (err != MOBILEBACKUP_E_SUCCESS) {
+ printf("ERROR: Could not send BackupMessageRestoreComplete, error code %d\n", err);
+ }
+ }
+
+ if (restore_ok) {
+ printf("Restore Successful.\n");
+ } else {
+ printf("Restore Failed.\n");
+ }
+#endif
+ break;
+ case CMD_LEAVE:
+ default:
+ break;
+ }
+ if (lockfile) {
+ afc_file_lock(afc, lockfile, AFC_LOCK_UN);
+ afc_file_close(afc, lockfile);
+ lockfile = 0;
+ do_post_notification(NP_SYNC_DID_FINISH);
+ }
+ if (manifest_path)
+ g_free(manifest_path);
+ } else {
+ printf("ERROR: Could not start service %s.\n", MOBILEBACKUP2_SERVICE_NAME);
+ lockdownd_client_free(client);
+ client = NULL;
+ }
+
+ if (client) {
+ lockdownd_client_free(client);
+ client = NULL;
+ }
+
+ if (afc)
+ afc_client_free(afc);
+
+ if (np)
+ np_client_free(np);
+
+ if (mobilebackup2)
+ mobilebackup2_client_free(mobilebackup2);
+
+ idevice_free(phone);
+
+ return 0;
+}
+