From ca7dba1c618ed499d0693427e89701cda1731ca9 Mon Sep 17 00:00:00 2001 From: Nikias Bassen Date: Sun, 5 Sep 2010 20:15:43 +0200 Subject: Add initial mobilebackup2 support and idevicebackup4 tool --- include/libimobiledevice/mobilebackup2.h | 63 + src/Makefile.am | 1 + src/mobilebackup2.c | 396 ++++++ src/mobilebackup2.h | 31 + tools/Makefile.am | 7 +- tools/idevicebackup4.c | 2142 ++++++++++++++++++++++++++++++ 6 files changed, 2639 insertions(+), 1 deletion(-) create mode 100644 include/libimobiledevice/mobilebackup2.h create mode 100644 src/mobilebackup2.c create mode 100644 src/mobilebackup2.h create mode 100644 tools/idevicebackup4.c 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 + +/** @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 +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 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 .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 .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; +} + -- cgit v1.1-32-gdbae