/* * idevicebackup2.c * Command line interface to use the device's backup and restore service * * Copyright (c) 2010-2019 Nikias Bassen, All Rights Reserved. * Copyright (c) 2009-2010 Martin Szulecki, All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #define TOOL_NAME "idevicebackup2" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common/utils.h" #include #define LOCK_ATTEMPTS 50 #define LOCK_WAIT 200000 #ifdef WIN32 #include #include #define sleep(x) Sleep(x*1000) #ifndef ELOOP #define ELOOP 114 #endif #else #include #include #endif #include #define CODE_SUCCESS 0x00 #define CODE_ERROR_LOCAL 0x06 #define CODE_ERROR_REMOTE 0x0b #define CODE_FILE_DATA 0x0c static int verbose = 1; static int quit_flag = 0; #define PRINT_VERBOSE(min_level, ...) if (verbose >= min_level) { printf(__VA_ARGS__); }; enum cmd_mode { CMD_BACKUP, CMD_RESTORE, CMD_INFO, CMD_LIST, CMD_UNBACK, CMD_CHANGEPW, CMD_LEAVE, CMD_CLOUD }; enum cmd_flags { CMD_FLAG_RESTORE_SYSTEM_FILES = (1 << 1), CMD_FLAG_RESTORE_NO_REBOOT = (1 << 2), CMD_FLAG_RESTORE_COPY_BACKUP = (1 << 3), CMD_FLAG_RESTORE_SETTINGS = (1 << 4), CMD_FLAG_RESTORE_REMOVE_ITEMS = (1 << 5), CMD_FLAG_ENCRYPTION_ENABLE = (1 << 6), CMD_FLAG_ENCRYPTION_DISABLE = (1 << 7), CMD_FLAG_ENCRYPTION_CHANGEPW = (1 << 8), CMD_FLAG_FORCE_FULL_BACKUP = (1 << 9), CMD_FLAG_CLOUD_ENABLE = (1 << 10), CMD_FLAG_CLOUD_DISABLE = (1 << 11), CMD_FLAG_RESTORE_SKIP_APPS = (1 << 12) }; static int backup_domain_changed = 0; static void notify_cb(const char *notification, void *userdata) { if (strlen(notification) == 0) { return; } if (!strcmp(notification, NP_SYNC_CANCEL_REQUEST)) { PRINT_VERBOSE(1, "User has cancelled the backup process on the device.\n"); quit_flag++; } else if (!strcmp(notification, NP_BACKUP_DOMAIN_CHANGED)) { backup_domain_changed = 1; } else { PRINT_VERBOSE(1, "Unhandled notification '%s' (TODO: implement)\n", notification); } } static void mobilebackup_afc_get_file_contents(afc_client_t afc, const char *filename, char **data, uint64_t *size) { if (!afc || !data || !size) { return; } char **fileinfo = NULL; uint32_t fsize = 0; afc_get_file_info(afc, filename, &fileinfo); if (!fileinfo) { return; } int i; for (i = 0; fileinfo[i]; i+=2) { if (!strcmp(fileinfo[i], "st_size")) { fsize = atol(fileinfo[i+1]); break; } } afc_dictionary_free(fileinfo); if (fsize == 0) { return; } uint64_t f = 0; afc_file_open(afc, filename, AFC_FOPEN_RDONLY, &f); if (!f) { return; } char *buf = (char*)malloc((uint32_t)fsize); uint32_t done = 0; while (done < fsize) { uint32_t bread = 0; afc_file_read(afc, f, buf+done, 65536, &bread); if (bread > 0) { done += bread; } else { break; } } if (done == fsize) { *size = fsize; *data = buf; } else { free(buf); } afc_file_close(afc, f); } static int __mkdir(const char* path, int mode) { #ifdef WIN32 return mkdir(path); #else return mkdir(path, mode); #endif } static int mkdir_with_parents(const char *dir, int mode) { if (!dir) return -1; if (__mkdir(dir, mode) == 0) { return 0; } else { if (errno == EEXIST) return 0; } int res; char *parent = strdup(dir); char *parentdir = dirname(parent); if (parentdir) { res = mkdir_with_parents(parentdir, mode); } else { res = -1; } free(parent); if (res == 0) { mkdir_with_parents(dir, mode); } return res; } #ifdef WIN32 static int win32err_to_errno(int err_value) { switch (err_value) { case ERROR_FILE_NOT_FOUND: return ENOENT; case ERROR_ALREADY_EXISTS: return EEXIST; default: return EFAULT; } } #endif static int remove_file(const char* path) { int e = 0; #ifdef WIN32 if (!DeleteFile(path)) { e = win32err_to_errno(GetLastError()); } #else if (remove(path) < 0) { e = errno; } #endif return e; } static int remove_directory(const char* path) { int e = 0; #ifdef WIN32 if (!RemoveDirectory(path)) { e = win32err_to_errno(GetLastError()); } #else if (remove(path) < 0) { e = errno; } #endif return e; } struct entry { char *name; struct entry *next; }; static void scan_directory(const char *path, struct entry **files, struct entry **directories) { DIR* cur_dir = opendir(path); if (cur_dir) { struct dirent* ep; while ((ep = readdir(cur_dir))) { if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { continue; } char *fpath = string_build_path(path, ep->d_name, NULL); if (fpath) { #ifdef HAVE_DIRENT_D_TYPE if (ep->d_type & DT_DIR) { #else struct stat st; if (stat(fpath, &st) != 0) return; if (S_ISDIR(st.st_mode)) { #endif struct entry *ent = malloc(sizeof(struct entry)); if (!ent) return; ent->name = fpath; ent->next = *directories; *directories = ent; scan_directory(fpath, files, directories); fpath = NULL; } else { struct entry *ent = malloc(sizeof(struct entry)); if (!ent) return; ent->name = fpath; ent->next = *files; *files = ent; fpath = NULL; } } } closedir(cur_dir); } } static int rmdir_recursive(const char* path) { int res = 0; struct entry *files = NULL; struct entry *directories = NULL; struct entry *ent; ent = malloc(sizeof(struct entry)); if (!ent) return ENOMEM; ent->name = strdup(path); ent->next = NULL; directories = ent; scan_directory(path, &files, &directories); ent = files; while (ent) { struct entry *del = ent; res = remove_file(ent->name); free(ent->name); ent = ent->next; free(del); } ent = directories; while (ent) { struct entry *del = ent; res = remove_directory(ent->name); free(ent->name); ent = ent->next; free(del); } return res; } static char* get_uuid() { const char *chars = "ABCDEF0123456789"; int i = 0; char *uuid = (char*)malloc(sizeof(char) * 33); srand(time(NULL)); for (i = 0; i < 32; i++) { uuid[i] = chars[rand() % 16]; } uuid[32] = '\0'; return uuid; } static plist_t mobilebackup_factory_info_plist_new(const char* udid, idevice_t device, afc_client_t afc) { /* gather data from lockdown */ plist_t value_node = NULL; plist_t root_node = NULL; plist_t itunes_settings = NULL; plist_t min_itunes_version = NULL; char *udid_uppercase = NULL; lockdownd_client_t lockdown = NULL; if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) { return NULL; } plist_t ret = plist_new_dict(); /* get basic device information in one go */ lockdownd_get_value(lockdown, NULL, NULL, &root_node); /* get iTunes settings */ lockdownd_get_value(lockdown, "com.apple.iTunes", NULL, &itunes_settings); /* get minimum iTunes version */ lockdownd_get_value(lockdown, "com.apple.mobile.iTunes", "MinITunesVersion", &min_itunes_version); lockdownd_client_free(lockdown); /* get a list of installed user applications */ plist_t app_dict = plist_new_dict(); plist_t installed_apps = plist_new_array(); instproxy_client_t ip = NULL; if (instproxy_client_start_service(device, &ip, TOOL_NAME) == INSTPROXY_E_SUCCESS) { plist_t client_opts = instproxy_client_options_new(); instproxy_client_options_add(client_opts, "ApplicationType", "User", NULL); instproxy_client_options_set_return_attributes(client_opts, "CFBundleIdentifier", "ApplicationSINF", "iTunesMetadata", NULL); plist_t apps = NULL; instproxy_browse(ip, client_opts, &apps); sbservices_client_t sbs = NULL; if (sbservices_client_start_service(device, &sbs, TOOL_NAME) != SBSERVICES_E_SUCCESS) { printf("Couldn't establish sbservices connection. Continuing anyway.\n"); } if (apps && (plist_get_node_type(apps) == PLIST_ARRAY)) { uint32_t app_count = plist_array_get_size(apps); uint32_t i; for (i = 0; i < app_count; i++) { plist_t app_entry = plist_array_get_item(apps, i); plist_t bundle_id = plist_dict_get_item(app_entry, "CFBundleIdentifier"); if (bundle_id) { char *bundle_id_str = NULL; plist_array_append_item(installed_apps, plist_copy(bundle_id)); plist_get_string_val(bundle_id, &bundle_id_str); plist_t sinf = plist_dict_get_item(app_entry, "ApplicationSINF"); plist_t meta = plist_dict_get_item(app_entry, "iTunesMetadata"); if (sinf && meta) { plist_t adict = plist_new_dict(); plist_dict_set_item(adict, "ApplicationSINF", plist_copy(sinf)); if (sbs) { char *pngdata = NULL; uint64_t pngsize = 0; sbservices_get_icon_pngdata(sbs, bundle_id_str, &pngdata, &pngsize); if (pngdata) { plist_dict_set_item(adict, "PlaceholderIcon", plist_new_data(pngdata, pngsize)); free(pngdata); } } plist_dict_set_item(adict, "iTunesMetadata", plist_copy(meta)); plist_dict_set_item(app_dict, bundle_id_str, adict); } free(bundle_id_str); } } } plist_free(apps); if (sbs) { sbservices_client_free(sbs); } instproxy_client_options_free(client_opts); instproxy_client_free(ip); } /* Applications */ plist_dict_set_item(ret, "Applications", app_dict); /* set fields we understand */ value_node = plist_dict_get_item(root_node, "BuildVersion"); plist_dict_set_item(ret, "Build Version", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "DeviceName"); plist_dict_set_item(ret, "Device Name", plist_copy(value_node)); plist_dict_set_item(ret, "Display Name", plist_copy(value_node)); char *uuid = get_uuid(); plist_dict_set_item(ret, "GUID", plist_new_string(uuid)); free(uuid); value_node = plist_dict_get_item(root_node, "IntegratedCircuitCardIdentity"); if (value_node) plist_dict_set_item(ret, "ICCID", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "InternationalMobileEquipmentIdentity"); if (value_node) plist_dict_set_item(ret, "IMEI", plist_copy(value_node)); /* Installed Applications */ plist_dict_set_item(ret, "Installed Applications", installed_apps); plist_dict_set_item(ret, "Last Backup Date", plist_new_date(time(NULL) - MAC_EPOCH, 0)); value_node = plist_dict_get_item(root_node, "MobileEquipmentIdentifier"); if (value_node) plist_dict_set_item(ret, "MEID", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "PhoneNumber"); if (value_node && (plist_get_node_type(value_node) == PLIST_STRING)) { plist_dict_set_item(ret, "Phone Number", plist_copy(value_node)); } /* FIXME Product Name */ value_node = plist_dict_get_item(root_node, "ProductType"); plist_dict_set_item(ret, "Product Type", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "ProductVersion"); plist_dict_set_item(ret, "Product Version", plist_copy(value_node)); value_node = plist_dict_get_item(root_node, "SerialNumber"); plist_dict_set_item(ret, "Serial Number", plist_copy(value_node)); /* FIXME Sync Settings? */ value_node = plist_dict_get_item(root_node, "UniqueDeviceID"); plist_dict_set_item(ret, "Target Identifier", plist_new_string(udid)); plist_dict_set_item(ret, "Target Type", plist_new_string("Device")); /* uppercase */ udid_uppercase = string_toupper((char*)udid); plist_dict_set_item(ret, "Unique Identifier", plist_new_string(udid_uppercase)); free(udid_uppercase); char *data_buf = NULL; uint64_t data_size = 0; mobilebackup_afc_get_file_contents(afc, "/Books/iBooksData2.plist", &data_buf, &data_size); if (data_buf) { plist_dict_set_item(ret, "iBooks Data 2", plist_new_data(data_buf, data_size)); free(data_buf); } plist_t files = plist_new_dict(); const char *itunesfiles[] = { "ApertureAlbumPrefs", "IC-Info.sidb", "IC-Info.sidv", "PhotosFolderAlbums", "PhotosFolderName", "PhotosFolderPrefs", "VoiceMemos.plist", "iPhotoAlbumPrefs", "iTunesApplicationIDs", "iTunesPrefs", "iTunesPrefs.plist", NULL }; int i = 0; for (i = 0; itunesfiles[i]; i++) { data_buf = NULL; data_size = 0; char *fname = (char*)malloc(strlen("/iTunes_Control/iTunes/") + strlen(itunesfiles[i]) + 1); strcpy(fname, "/iTunes_Control/iTunes/"); strcat(fname, itunesfiles[i]); mobilebackup_afc_get_file_contents(afc, fname, &data_buf, &data_size); free(fname); if (data_buf) { plist_dict_set_item(files, itunesfiles[i], plist_new_data(data_buf, data_size)); free(data_buf); } } plist_dict_set_item(ret, "iTunes Files", files); plist_dict_set_item(ret, "iTunes Settings", itunes_settings ? plist_copy(itunes_settings) : plist_new_dict()); /* since we usually don't have iTunes, let's get the minimum required iTunes version from the device */ if (min_itunes_version) { plist_dict_set_item(ret, "iTunes Version", plist_copy(min_itunes_version)); } else { plist_dict_set_item(ret, "iTunes Version", plist_new_string("10.0.1")); } plist_free(itunes_settings); plist_free(min_itunes_version); plist_free(root_node); return ret; } static int write_restore_applications(plist_t info_plist, afc_client_t afc) { int res = -1; uint64_t restore_applications_file = 0; char * applications_plist_xml = NULL; uint32_t applications_plist_xml_length = 0; plist_t applications_plist = plist_dict_get_item(info_plist, "Applications"); if (!applications_plist) { printf("No Applications in Info.plist, skipping creation of RestoreApplications.plist\n"); return 0; } plist_to_xml(applications_plist, &applications_plist_xml, &applications_plist_xml_length); if (!applications_plist_xml) { printf("Error preparing RestoreApplications.plist\n"); goto leave; } afc_error_t afc_err = 0; afc_err = afc_make_directory(afc, "/iTunesRestore"); if (afc_err != AFC_E_SUCCESS) { printf("Error creating directory /iTunesRestore, error code %d\n", afc_err); goto leave; } afc_err = afc_file_open(afc, "/iTunesRestore/RestoreApplications.plist", AFC_FOPEN_WR, &restore_applications_file); if (afc_err != AFC_E_SUCCESS || !restore_applications_file) { printf("Error creating /iTunesRestore/RestoreApplications.plist, error code %d\n", afc_err); goto leave; } uint32_t bytes_written = 0; afc_err = afc_file_write(afc, restore_applications_file, applications_plist_xml, applications_plist_xml_length, &bytes_written); if (afc_err != AFC_E_SUCCESS || bytes_written != applications_plist_xml_length) { printf("Error writing /iTunesRestore/RestoreApplications.plist, error code %d, wrote %u of %u bytes\n", afc_err, bytes_written, applications_plist_xml_length); goto leave; } afc_err = afc_file_close(afc, restore_applications_file); restore_applications_file = 0; if (afc_err != AFC_E_SUCCESS) { goto leave; } /* success */ res = 0; leave: free(applications_plist_xml); if (restore_applications_file) { afc_file_close(afc, restore_applications_file); restore_applications_file = 0; } return res; } static int mb2_status_check_snapshot_state(const char *path, const char *udid, const char *matches) { int ret = 0; plist_t status_plist = NULL; char *file_path = string_build_path(path, udid, "Status.plist", NULL); plist_read_from_filename(&status_plist, file_path); 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* sval = NULL; plist_get_string_val(node, &sval); if (sval) { ret = (strcmp(sval, matches) == 0) ? 1 : 0; free(sval); } } else { printf("%s: ERROR could not get SnapshotState key from Status.plist!\n", __func__); } plist_free(status_plist); return ret; } static void do_post_notification(idevice_t device, const char *notification) { lockdownd_service_descriptor_t service = NULL; np_client_t np; lockdownd_client_t lockdown = NULL; if (lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME) != LOCKDOWN_E_SUCCESS) { return; } lockdownd_error_t ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service); if (service && service->port) { np_client_new(device, service, &np); if (np) { np_post_notification(np, notification); np_client_free(np); } } else { printf("ERROR: Could not start service %s: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); } if (service) { lockdownd_service_descriptor_free(service); service = NULL; } lockdownd_client_free(lockdown); } static void print_progress_real(double progress, int flush) { int i = 0; PRINT_VERBOSE(1, "\r["); for(i = 0; i < 50; i++) { if(i < progress / 2) { PRINT_VERBOSE(1, "="); } else { PRINT_VERBOSE(1, " "); } } PRINT_VERBOSE(1, "] %3.0f%%", progress); if (flush > 0) { fflush(stdout); if (progress == 100) PRINT_VERBOSE(1, "\n"); } } static void print_progress(uint64_t current, uint64_t total) { char *format_size = NULL; double progress = ((double)current/(double)total)*100; if (progress < 0) return; if (progress > 100) progress = 100; print_progress_real((double)progress, 0); format_size = string_format_size(current); PRINT_VERBOSE(1, " (%s", format_size); free(format_size); format_size = string_format_size(total); PRINT_VERBOSE(1, "/%s) ", format_size); free(format_size); fflush(stdout); if (progress == 100) PRINT_VERBOSE(1, "\n"); } static double overall_progress = 0; static void mb2_set_overall_progress(double progress) { if (progress > 0.0) overall_progress = progress; } static void mb2_set_overall_progress_from_message(plist_t message, char* identifier) { plist_t node = NULL; double progress = 0.0; if (!strcmp(identifier, "DLMessageDownloadFiles")) { node = plist_array_get_item(message, 3); } else if (!strcmp(identifier, "DLMessageUploadFiles")) { node = plist_array_get_item(message, 2); } else if (!strcmp(identifier, "DLMessageMoveFiles") || !strcmp(identifier, "DLMessageMoveItems")) { node = plist_array_get_item(message, 3); } else if (!strcmp(identifier, "DLMessageRemoveFiles") || !strcmp(identifier, "DLMessageRemoveItems")) { node = plist_array_get_item(message, 3); } if (node != NULL) { plist_get_real_val(node, &progress); mb2_set_overall_progress(progress); } } static void mb2_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_set_item(filedict, "DLFileErrorString", plist_new_string(error_message)); plist_dict_set_item(filedict, "DLFileErrorCode", plist_new_uint(error_code)); plist_dict_set_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; case ENOTDIR: return -8; case EISDIR: return -9; case ELOOP: return -10; case EIO: return -11; case ENOSPC: return -15; default: return -1; } } static int mb2_handle_send_file(mobilebackup2_client_t mobilebackup2, const char *backup_dir, const char *path, plist_t *errplist) { uint32_t nlen = 0; uint32_t pathlen = strlen(path); uint32_t bytes = 0; char *localfile = string_build_path(backup_dir, path, NULL); char buf[32768]; #ifdef WIN32 struct _stati64 fst; #else struct stat fst; #endif FILE *f = NULL; uint32_t slen = 0; int errcode = -1; int result = -1; uint32_t length; #ifdef WIN32 uint64_t total; uint64_t sent; #else off_t total; off_t sent; #endif mobilebackup2_error_t err; /* send path length */ nlen = htobe32(pathlen); err = mobilebackup2_send_raw(mobilebackup2, (const char*)&nlen, sizeof(nlen), &bytes); if (err != MOBILEBACKUP2_E_SUCCESS) { goto leave_proto_err; } if (bytes != (uint32_t)sizeof(nlen)) { err = MOBILEBACKUP2_E_MUX_ERROR; goto leave_proto_err; } /* send path */ err = mobilebackup2_send_raw(mobilebackup2, path, pathlen, &bytes); if (err != MOBILEBACKUP2_E_SUCCESS) { goto leave_proto_err; } if (bytes != pathlen) { err = MOBILEBACKUP2_E_MUX_ERROR; goto leave_proto_err; } #ifdef WIN32 if (_stati64(localfile, &fst) < 0) #else if (stat(localfile, &fst) < 0) #endif { if (errno != ENOENT) printf("%s: stat failed on '%s': %d\n", __func__, localfile, errno); errcode = errno; goto leave; } total = fst.st_size; char *format_size = string_format_size(total); PRINT_VERBOSE(1, "Sending '%s' (%s)\n", path, format_size); free(format_size); if (total == 0) { errcode = 0; goto leave; } f = fopen(localfile, "rb"); if (!f) { printf("%s: Error opening local file '%s': %d\n", __func__, localfile, errno); errcode = errno; goto leave; } sent = 0; do { length = ((total-sent) < (long long)sizeof(buf)) ? (uint32_t)total-sent : (uint32_t)sizeof(buf); /* send data size (file size + 1) */ nlen = htobe32(length+1); memcpy(buf, &nlen, sizeof(nlen)); buf[4] = CODE_FILE_DATA; err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, 5, &bytes); if (err != MOBILEBACKUP2_E_SUCCESS) { goto leave_proto_err; } if (bytes != 5) { goto leave_proto_err; } /* send file contents */ 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, &bytes); if (err != MOBILEBACKUP2_E_SUCCESS) { goto leave_proto_err; } if (bytes != (uint32_t)r) { printf("Error: sent only %d of %d bytes\n", bytes, (int)r); goto leave_proto_err; } sent += r; } while (sent < total); fclose(f); f = NULL; errcode = 0; leave: if (errcode == 0) { result = 0; nlen = 1; nlen = htobe32(nlen); memcpy(buf, &nlen, 4); buf[4] = CODE_SUCCESS; mobilebackup2_send_raw(mobilebackup2, buf, 5, &bytes); } else { if (!*errplist) { *errplist = plist_new_dict(); } char *errdesc = strerror(errcode); mb2_multi_status_add_file_error(*errplist, path, errno_to_device_error(errcode), errdesc); length = strlen(errdesc); nlen = htobe32(length+1); memcpy(buf, &nlen, 4); buf[4] = CODE_ERROR_LOCAL; slen = 5; memcpy(buf+slen, errdesc, length); slen += length; err = mobilebackup2_send_raw(mobilebackup2, (const char*)buf, slen, &bytes); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("could not send message\n"); } if (bytes != slen) { printf("could only send %d from %d\n", bytes, slen); } } leave_proto_err: if (f) fclose(f); free(localfile); return result; } static void mb2_handle_send_files(mobilebackup2_client_t mobilebackup2, 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); 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 (mb2_handle_send_file(mobilebackup2, backup_dir, str, &errplist) < 0) { free(str); //printf("Error when sending file '%s' to device\n", str); // TODO: perhaps we can continue, we've got a multi status response?! break; } free(str); } /* send terminating 0 dword */ uint32_t zero = 0; mobilebackup2_send_raw(mobilebackup2, (char*)&zero, 4, &sent); if (!errplist) { plist_t emptydict = plist_new_dict(); mobilebackup2_send_status_response(mobilebackup2, 0, NULL, emptydict); plist_free(emptydict); } else { mobilebackup2_send_status_response(mobilebackup2, -13, "Multi status", errplist); plist_free(errplist); } } static int mb2_receive_filename(mobilebackup2_client_t mobilebackup2, char** filename) { uint32_t nlen = 0; uint32_t rlen = 0; do { nlen = 0; rlen = 0; mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &rlen); nlen = be32toh(nlen); if ((nlen == 0) && (rlen == 4)) { // a zero length means no more files to receive return 0; } else if(rlen == 0) { // device needs more time, waiting... continue; } else if (nlen > 4096) { // filename length is too large printf("ERROR: %s: too large filename length (%d)!\n", __func__, nlen); return 0; } if (*filename != NULL) { free(*filename); *filename = NULL; } *filename = (char*)malloc(nlen+1); rlen = 0; mobilebackup2_receive_raw(mobilebackup2, *filename, nlen, &rlen); if (rlen != nlen) { printf("ERROR: %s: could not read filename\n", __func__); return 0; } char* p = *filename; p[rlen] = 0; break; } while(1 && !quit_flag); return nlen; } static int mb2_handle_receive_files(mobilebackup2_client_t mobilebackup2, 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; char *dname = NULL; char *bname = NULL; char code = 0; char last_code = 0; plist_t node = NULL; FILE *f = NULL; unsigned int file_count = 0; int errcode = 0; char *errdesc = NULL; 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) { PRINT_VERBOSE(1, "Receiving files\n"); } do { if (quit_flag) break; nlen = mb2_receive_filename(mobilebackup2, &dname); if (nlen == 0) { break; } nlen = mb2_receive_filename(mobilebackup2, &fname); if (!nlen) { break; } if (bname != NULL) { free(bname); bname = NULL; } bname = string_build_path(backup_dir, fname, NULL); if (fname != NULL) { free(fname); fname = NULL; } r = 0; nlen = 0; mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r); if (r != 4) { printf("ERROR: %s: could not receive code length!\n", __func__); break; } nlen = be32toh(nlen); last_code = code; code = 0; mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r); if (r != 1) { printf("ERROR: %s: could not receive code!\n", __func__); break; } /* TODO remove this */ if ((code != CODE_SUCCESS) && (code != CODE_FILE_DATA) && (code != CODE_ERROR_REMOTE)) { PRINT_VERBOSE(1, "Found new flag %02x\n", code); } remove_file(bname); f = fopen(bname, "wb"); while (f && (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 (backup_total_size > 0) { print_progress(backup_real_size, backup_total_size); } if (quit_flag) break; nlen = 0; mobilebackup2_receive_raw(mobilebackup2, (char*)&nlen, 4, &r); nlen = be32toh(nlen); if (nlen > 0) { last_code = code; mobilebackup2_receive_raw(mobilebackup2, &code, 1, &r); } else { break; } } if (f) { fclose(f); file_count++; } else { errcode = errno_to_device_error(errno); errdesc = strerror(errno); printf("Error opening '%s' for writing: %s\n", bname, errdesc); break; } if (nlen == 0) { break; } /* check if an error message was received */ if (code == CODE_ERROR_REMOTE) { /* error message */ char *msg = (char*)malloc(nlen); mobilebackup2_receive_raw(mobilebackup2, msg, nlen-1, &r); msg[r] = 0; /* If sent using CODE_FILE_DATA, end marker will be CODE_ERROR_REMOTE which is not an error! */ if (last_code != CODE_FILE_DATA) { fprintf(stdout, "\nReceived an error message from device: %s\n", msg); } free(msg); } } while (1); if (fname != NULL) free(fname); /* if there are leftovers to read, finish up cleanly */ if ((int)nlen-1 > 0) { PRINT_VERBOSE(1, "\nDiscarding current data hunk.\n"); fname = (char*)malloc(nlen-1); mobilebackup2_receive_raw(mobilebackup2, fname, nlen-1, &r); free(fname); remove_file(bname); } /* clean up */ if (bname != NULL) free(bname); if (dname != NULL) free(dname); plist_t empty_plist = plist_new_dict(); mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_plist); plist_free(empty_plist); return file_count; } static void mb2_handle_list_directory(mobilebackup2_client_t mobilebackup2, 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 node = plist_array_get_item(message, 1); char *str = NULL; if (plist_get_node_type(node) == PLIST_STRING) { plist_get_string_val(node, &str); } if (!str) { printf("ERROR: Malformed DLContentsOfDirectory message\n"); // TODO error handling return; } char *path = string_build_path(backup_dir, str, NULL); free(str); plist_t dirlist = plist_new_dict(); DIR* cur_dir = opendir(path); if (cur_dir) { struct dirent* ep; while ((ep = readdir(cur_dir))) { if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { continue; } char *fpath = string_build_path(path, ep->d_name, NULL); if (fpath) { plist_t fdict = plist_new_dict(); struct stat st; stat(fpath, &st); const char *ftype = "DLFileTypeUnknown"; if (S_ISDIR(st.st_mode)) { ftype = "DLFileTypeDirectory"; } else if (S_ISREG(st.st_mode)) { ftype = "DLFileTypeRegular"; } plist_dict_set_item(fdict, "DLFileType", plist_new_string(ftype)); plist_dict_set_item(fdict, "DLFileSize", plist_new_uint(st.st_size)); plist_dict_set_item(fdict, "DLFileModificationDate", plist_new_date(st.st_mtime - MAC_EPOCH, 0)); plist_dict_set_item(dirlist, ep->d_name, fdict); free(fpath); } } closedir(cur_dir); } free(path); /* TODO error handling */ mobilebackup2_error_t err = mobilebackup2_send_status_response(mobilebackup2, 0, NULL, dirlist); plist_free(dirlist); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Could not send status response, error %d\n", err); } } static void mb2_handle_make_directory(mobilebackup2_client_t mobilebackup2, 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); char *newpath = string_build_path(backup_dir, str, NULL); free(str); if (mkdir_with_parents(newpath, 0755) < 0) { errdesc = strerror(errno); if (errno != EEXIST) { printf("mkdir: %s (%d)\n", errdesc, errno); } errcode = errno_to_device_error(errno); } 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); } } static void mb2_copy_file_by_path(const char *src, const char *dst) { FILE *from, *to; char buf[BUFSIZ]; size_t length; /* open source file */ if ((from = fopen(src, "rb")) == NULL) { printf("Cannot open source path '%s'.\n", src); return; } /* open destination file */ if ((to = fopen(dst, "wb")) == NULL) { printf("Cannot open destination file '%s'.\n", dst); fclose(from); return; } /* copy the file */ while ((length = fread(buf, 1, BUFSIZ, from)) != 0) { fwrite(buf, 1, length, to); } if(fclose(from) == EOF) { printf("Error closing source file.\n"); } if(fclose(to) == EOF) { printf("Error closing destination file.\n"); } } static void mb2_copy_directory_by_path(const char *src, const char *dst) { if (!src || !dst) { return; } struct stat st; /* if src does not exist */ if ((stat(src, &st) < 0) || !S_ISDIR(st.st_mode)) { printf("ERROR: Source directory does not exist '%s': %s (%d)\n", src, strerror(errno), errno); return; } /* if dst directory does not exist */ if ((stat(dst, &st) < 0) || !S_ISDIR(st.st_mode)) { /* create it */ if (mkdir_with_parents(dst, 0755) < 0) { printf("ERROR: Unable to create destination directory '%s': %s (%d)\n", dst, strerror(errno), errno); return; } } /* loop over src directory contents */ DIR *cur_dir = opendir(src); if (cur_dir) { struct dirent* ep; while ((ep = readdir(cur_dir))) { if ((strcmp(ep->d_name, ".") == 0) || (strcmp(ep->d_name, "..") == 0)) { continue; } char *srcpath = string_build_path(src, ep->d_name, NULL); char *dstpath = string_build_path(dst, ep->d_name, NULL); if (srcpath && dstpath) { /* copy file */ mb2_copy_file_by_path(srcpath, dstpath); } if (srcpath) free(srcpath); if (dstpath) free(dstpath); } closedir(cur_dir); } } #ifdef WIN32 #define BS_CC '\b' #define my_getch getch #else #define BS_CC 0x7f static int my_getch(void) { struct termios oldt, newt; int ch; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.c_lflag &= ~(ICANON | ECHO); tcsetattr(STDIN_FILENO, TCSANOW, &newt); ch = getchar(); tcsetattr(STDIN_FILENO, TCSANOW, &oldt); return ch; } #endif static void get_hidden_input(char *buf, int maxlen) { int pwlen = 0; int c; while ((c = my_getch())) { if ((c == '\r') || (c == '\n')) { break; } else if (isprint(c)) { if (pwlen < maxlen-1) buf[pwlen++] = c; fputc('*', stderr); } else if (c == BS_CC) { if (pwlen > 0) { fputs("\b \b", stderr); pwlen--; } } } buf[pwlen] = 0; } static char* ask_for_password(const char* msg, int type_again) { char pwbuf[256]; fprintf(stderr, "%s: ", msg); fflush(stderr); get_hidden_input(pwbuf, 256); fputc('\n', stderr); if (type_again) { char pwrep[256]; fprintf(stderr, "%s (repeat): ", msg); fflush(stderr); get_hidden_input(pwrep, 256); fputc('\n', stderr); if (strcmp(pwbuf, pwrep) != 0) { printf("ERROR: passwords don't match\n"); return NULL; } } return strdup(pwbuf); } /** * 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 [CMDOPTIONS] DIRECTORY\n", (name ? name + 1: argv[0])); printf("\n"); printf("Create or restore backup from the current or specified directory.\n"); printf("\n"); printf("CMD:\n"); printf(" backup\tcreate backup for the device\n"); printf(" --full\t\tforce full backup from device.\n"); printf(" restore\trestore last backup to the device\n"); printf(" --system\t\trestore system files, too.\n"); printf(" --no-reboot\t\tdo NOT reboot the device when done (default: yes).\n"); printf(" --copy\t\tcreate a copy of backup folder before restoring.\n"); printf(" --settings\t\trestore device settings from the backup.\n"); printf(" --remove\t\tremove items which are not being restored\n"); printf(" --skip-apps\t\tdo not trigger re-installation of apps after restore\n"); printf(" --password PWD\tsupply the password of the source backup\n"); printf(" info\t\tshow details about last completed backup of device\n"); printf(" list\t\tlist files of last completed backup in CSV format\n"); printf(" unback\tunpack a completed backup in DIRECTORY/_unback_/\n"); printf(" encryption on|off [PWD]\tenable or disable backup encryption\n"); printf(" NOTE: password will be requested in interactive mode if omitted\n"); printf(" changepw [OLD NEW] change backup password on target device\n"); printf(" NOTE: passwords will be requested in interactive mode if omitted\n"); printf(" cloud on|off\tenable or disable cloud use (requires iCloud account)\n"); printf("\n"); printf("OPTIONS:\n"); printf(" -u, --udid UDID\ttarget specific device by UDID\n"); printf(" -s, --source UDID\tuse backup data from device specified by UDID\n"); printf(" -n, --network\t\tconnect to network device\n"); printf(" -i, --interactive\trequest passwords interactively\n"); printf(" -d, --debug\t\tenable communication debugging\n"); printf(" -h, --help\t\tprints usage information\n"); printf(" -v, --version\t\tprints version information\n"); printf("\n"); printf("Homepage: <" PACKAGE_URL ">\n"); printf("Bug Reports: <" PACKAGE_BUGREPORT ">\n"); } #define DEVICE_VERSION(maj, min, patch) (((maj & 0xFF) << 16) | ((min & 0xFF) << 8) | (patch & 0xFF)) int main(int argc, char *argv[]) { idevice_error_t ret = IDEVICE_E_UNKNOWN_ERROR; lockdownd_error_t ldret = LOCKDOWN_E_UNKNOWN_ERROR; int i; char* udid = NULL; char* source_udid = NULL; int use_network = 0; lockdownd_service_descriptor_t service = NULL; int cmd = -1; int cmd_flags = 0; int is_full_backup = 0; int result_code = -1; char* backup_directory = NULL; int interactive_mode = 0; char* backup_password = NULL; char* newpw = NULL; struct stat st; plist_t node_tmp = NULL; plist_t info_plist = NULL; plist_t opts = NULL; idevice_t device = NULL; afc_client_t afc = NULL; np_client_t np = NULL; lockdownd_client_t lockdown = NULL; mobilebackup2_client_t mobilebackup2 = NULL; mobilebackup2_error_t err; uint64_t lockfile = 0; /* we need to exit cleanly on running backups and restores or we cause havok */ signal(SIGINT, clean_exit); signal(SIGTERM, clean_exit); #ifndef WIN32 signal(SIGQUIT, clean_exit); signal(SIGPIPE, SIG_IGN); #endif /* 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], "--udid")) { i++; if (!argv[i] || !*argv[i]) { print_usage(argc, argv); return -1; } udid = strdup(argv[i]); continue; } else if (!strcmp(argv[i], "-s") || !strcmp(argv[i], "--source")) { i++; if (!argv[i] || !*argv[i]) { print_usage(argc, argv); return -1; } source_udid = strdup(argv[i]); continue; } else if (!strcmp(argv[i], "-n") || !strcmp(argv[i], "--network")) { use_network = 1; continue; } else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--interactive")) { interactive_mode = 1; continue; } else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { print_usage(argc, argv); return 0; } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { printf("%s %s\n", TOOL_NAME, PACKAGE_VERSION); return 0; } else if (!strcmp(argv[i], "backup")) { cmd = CMD_BACKUP; } else if (!strcmp(argv[i], "restore")) { cmd = CMD_RESTORE; } else if (!strcmp(argv[i], "--system")) { cmd_flags |= CMD_FLAG_RESTORE_SYSTEM_FILES; } else if (!strcmp(argv[i], "--reboot")) { cmd_flags &= ~CMD_FLAG_RESTORE_NO_REBOOT; } else if (!strcmp(argv[i], "--no-reboot")) { cmd_flags |= CMD_FLAG_RESTORE_NO_REBOOT; } else if (!strcmp(argv[i], "--copy")) { cmd_flags |= CMD_FLAG_RESTORE_COPY_BACKUP; } else if (!strcmp(argv[i], "--settings")) { cmd_flags |= CMD_FLAG_RESTORE_SETTINGS; } else if (!strcmp(argv[i], "--remove")) { cmd_flags |= CMD_FLAG_RESTORE_REMOVE_ITEMS; } else if (!strcmp(argv[i], "--skip-apps")) { cmd_flags |= CMD_FLAG_RESTORE_SKIP_APPS; } else if (!strcmp(argv[i], "--password")) { i++; if (!argv[i]) { print_usage(argc, argv); return -1; } if (backup_password) free(backup_password); backup_password = strdup(argv[i]); continue; } else if (!strcmp(argv[i], "cloud")) { cmd = CMD_CLOUD; i++; if (!argv[i]) { printf("No argument given for cloud command; requires either 'on' or 'off'.\n"); print_usage(argc, argv); return -1; } if (!strcmp(argv[i], "on")) { cmd_flags |= CMD_FLAG_CLOUD_ENABLE; } else if (!strcmp(argv[i], "off")) { cmd_flags |= CMD_FLAG_CLOUD_DISABLE; } else { printf("Invalid argument '%s' for cloud command; must be either 'on' or 'off'.\n", argv[i]); } continue; } else if (!strcmp(argv[i], "--full")) { cmd_flags |= CMD_FLAG_FORCE_FULL_BACKUP; } else if (!strcmp(argv[i], "info")) { cmd = CMD_INFO; verbose = 0; } else if (!strcmp(argv[i], "list")) { cmd = CMD_LIST; verbose = 0; } else if (!strcmp(argv[i], "unback")) { cmd = CMD_UNBACK; } else if (!strcmp(argv[i], "encryption")) { cmd = CMD_CHANGEPW; i++; if (!argv[i]) { printf("No argument given for encryption command; requires either 'on' or 'off'.\n"); print_usage(argc, argv); return -1; } if (!strcmp(argv[i], "on")) { cmd_flags |= CMD_FLAG_ENCRYPTION_ENABLE; } else if (!strcmp(argv[i], "off")) { cmd_flags |= CMD_FLAG_ENCRYPTION_DISABLE; } else { printf("Invalid argument '%s' for encryption command; must be either 'on' or 'off'.\n", argv[i]); } // check if a password was given on the command line if (newpw) { free(newpw); newpw = NULL; } if (backup_password) { free(backup_password); backup_password = NULL; } i++; if (argv[i]) { if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { newpw = strdup(argv[i]); } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { backup_password = strdup(argv[i]); } } continue; } else if (!strcmp(argv[i], "changepw")) { cmd = CMD_CHANGEPW; cmd_flags |= CMD_FLAG_ENCRYPTION_CHANGEPW; // check if passwords were given on command line if (newpw) { free(newpw); newpw = NULL; } if (backup_password) { free(backup_password); backup_password = NULL; } i++; if (argv[i]) { backup_password = strdup(argv[i]); i++; if (!argv[i]) { printf("Old and new passwords have to be passed as arguments for the changepw command\n"); print_usage(argc, argv); return -1; } newpw = strdup(argv[i]); } continue; } else if (backup_directory == NULL) { backup_directory = argv[i]; } else { print_usage(argc, argv); return -1; } } /* verify options */ if (cmd == -1) { printf("No command specified.\n"); print_usage(argc, argv); return -1; } if (cmd == CMD_CHANGEPW || cmd == CMD_CLOUD) { backup_directory = (char*)".this_folder_is_not_present_on_purpose"; } else { 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; } } ret = idevice_new_with_options(&device, udid, (use_network) ? IDEVICE_LOOKUP_NETWORK : IDEVICE_LOOKUP_USBMUX); if (ret != IDEVICE_E_SUCCESS) { if (udid) { printf("No device found with udid %s.\n", udid); } else { printf("No device found.\n"); } return -1; } if (!udid) { idevice_get_udid(device, &udid); } if (!source_udid) { source_udid = strdup(udid); } uint8_t is_encrypted = 0; char *info_path = NULL; if (cmd == CMD_CHANGEPW) { if (!interactive_mode && !backup_password && !newpw) { idevice_free(device); printf("ERROR: Can't get password input in non-interactive mode. Either pass password(s) on the command line, or enable interactive mode with -i or --interactive.\n"); return -1; } } else if (cmd != CMD_CLOUD) { /* backup directory must contain an Info.plist */ info_path = string_build_path(backup_directory, source_udid, "Info.plist", NULL); if (cmd == CMD_RESTORE || cmd == CMD_UNBACK) { if (stat(info_path, &st) != 0) { idevice_free(device); free(info_path); printf("ERROR: Backup directory \"%s\" is invalid. No Info.plist found for UDID %s.\n", backup_directory, source_udid); return -1; } char* manifest_path = string_build_path(backup_directory, source_udid, "Manifest.plist", NULL); if (stat(manifest_path, &st) != 0) { free(info_path); } plist_t manifest_plist = NULL; plist_read_from_filename(&manifest_plist, manifest_path); if (!manifest_plist) { idevice_free(device); free(info_path); free(manifest_path); printf("ERROR: Backup directory \"%s\" is invalid. No Manifest.plist found for UDID %s.\n", backup_directory, source_udid); return -1; } node_tmp = plist_dict_get_item(manifest_plist, "IsEncrypted"); if (node_tmp && (plist_get_node_type(node_tmp) == PLIST_BOOLEAN)) { plist_get_bool_val(node_tmp, &is_encrypted); } plist_free(manifest_plist); free(manifest_path); } PRINT_VERBOSE(1, "Backup directory is \"%s\"\n", backup_directory); } if (cmd != CMD_CLOUD && is_encrypted) { PRINT_VERBOSE(1, "This is an encrypted backup.\n"); if (backup_password == NULL) { if (interactive_mode) { backup_password = ask_for_password("Enter backup password", 0); } if (!backup_password || (strlen(backup_password) == 0)) { if (backup_password) { free(backup_password); } idevice_free(device); if (cmd == CMD_RESTORE) { printf("ERROR: a backup password is required to restore an encrypted backup. Cannot continue.\n"); } else if (cmd == CMD_UNBACK) { printf("ERROR: a backup password is required to unback an encrypted backup. Cannot continue.\n"); } return -1; } } } if (LOCKDOWN_E_SUCCESS != (ldret = lockdownd_client_new_with_handshake(device, &lockdown, TOOL_NAME))) { printf("ERROR: Could not connect to lockdownd, error code %d\n", ldret); idevice_free(device); return -1; } uint8_t willEncrypt = 0; node_tmp = NULL; lockdownd_get_value(lockdown, "com.apple.mobile.backup", "WillEncrypt", &node_tmp); if (node_tmp) { if (plist_get_node_type(node_tmp) == PLIST_BOOLEAN) { plist_get_bool_val(node_tmp, &willEncrypt); } plist_free(node_tmp); node_tmp = NULL; } /* get ProductVersion */ char *product_version = NULL; int device_version = 0; node_tmp = NULL; lockdownd_get_value(lockdown, NULL, "ProductVersion", &node_tmp); if (node_tmp) { if (plist_get_node_type(node_tmp) == PLIST_STRING) { plist_get_string_val(node_tmp, &product_version); } plist_free(node_tmp); node_tmp = NULL; } if (product_version) { int vers[3] = { 0, 0, 0 }; if (sscanf(product_version, "%d.%d.%d", &vers[0], &vers[1], &vers[2]) >= 2) { device_version = DEVICE_VERSION(vers[0], vers[1], vers[2]); } } /* start notification_proxy */ ldret = lockdownd_start_service(lockdown, NP_SERVICE_NAME, &service); if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { np_client_new(device, service, &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: %s\n", NP_SERVICE_NAME, lockdownd_strerror(ldret)); cmd = CMD_LEAVE; goto checkpoint; } if (service) { lockdownd_service_descriptor_free(service); service = NULL; } if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) { /* start AFC, we need this for the lock file */ ldret = lockdownd_start_service(lockdown, AFC_SERVICE_NAME, &service); if ((ldret == LOCKDOWN_E_SUCCESS) && service->port) { afc_client_new(device, service, &afc); } else { printf("ERROR: Could not start service %s: %s\n", AFC_SERVICE_NAME, lockdownd_strerror(ldret)); cmd = CMD_LEAVE; goto checkpoint; } } if (service) { lockdownd_service_descriptor_free(service); service = NULL; } /* start mobilebackup service and retrieve port */ ldret = lockdownd_start_service_with_escrow_bag(lockdown, MOBILEBACKUP2_SERVICE_NAME, &service); lockdownd_client_free(lockdown); lockdown = NULL; if ((ldret == LOCKDOWN_E_SUCCESS) && service && service->port) { PRINT_VERBOSE(1, "Started \"%s\" service on port %d.\n", MOBILEBACKUP2_SERVICE_NAME, service->port); mobilebackup2_client_new(device, service, &mobilebackup2); if (service) { lockdownd_service_descriptor_free(service); service = NULL; } /* send Hello message */ double local_versions[2] = {2.0, 2.1}; double remote_version = 0.0; err = mobilebackup2_version_exchange(mobilebackup2, local_versions, 2, &remote_version); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Could not perform backup protocol version exchange, error code %d\n", err); cmd = CMD_LEAVE; goto checkpoint; } PRINT_VERBOSE(1, "Negotiated Protocol Version %.1f\n", remote_version); /* check abort conditions */ if (quit_flag > 0) { PRINT_VERBOSE(1, "Aborting as requested by user...\n"); cmd = CMD_LEAVE; goto checkpoint; } /* verify existing Info.plist */ if (info_path && (stat(info_path, &st) == 0) && cmd != CMD_CLOUD) { PRINT_VERBOSE(1, "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; } } else { if (cmd == CMD_RESTORE) { printf("Aborting restore. Info.plist is missing.\n"); cmd = CMD_LEAVE; } else { is_full_backup = 1; } } if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) { do_post_notification(device, 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(device, 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(device, 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; } } checkpoint: switch(cmd) { case CMD_CLOUD: opts = plist_new_dict(); plist_dict_set_item(opts, "CloudBackupState", plist_new_bool(cmd_flags & CMD_FLAG_CLOUD_ENABLE ? 1: 0)); err = mobilebackup2_send_request(mobilebackup2, "EnableCloudBackup", udid, source_udid, opts); plist_free(opts); opts = NULL; if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Error setting cloud backup state on device, error code %d\n", err); cmd = CMD_LEAVE; } break; case CMD_BACKUP: PRINT_VERBOSE(1, "Starting backup...\n"); /* make sure backup device sub-directory exists */ char* devbackupdir = string_build_path(backup_directory, source_udid, NULL); __mkdir(devbackupdir, 0755); free(devbackupdir); if (strcmp(source_udid, udid) != 0) { /* handle different source backup directory */ // make sure target backup device sub-directory exists devbackupdir = string_build_path(backup_directory, udid, NULL); __mkdir(devbackupdir, 0755); free(devbackupdir); // use Info.plist path in target backup folder */ free(info_path); info_path = string_build_path(backup_directory, udid, "Info.plist", NULL); } /* TODO: check domain com.apple.mobile.backup key RequiresEncrypt and WillEncrypt with lockdown */ /* TODO: verify battery on AC enough battery remaining */ /* re-create Info.plist (Device infos, IC-Info.sidb, photos, app_ids, iTunesPrefs) */ if (info_plist) { plist_free(info_plist); info_plist = NULL; } info_plist = mobilebackup_factory_info_plist_new(udid, device, afc); if (!info_plist) { fprintf(stderr, "Failed to generate Info.plist - aborting\n"); cmd = CMD_LEAVE; } remove_file(info_path); plist_write_to_filename(info_plist, info_path, PLIST_FORMAT_XML); free(info_path); plist_free(info_plist); info_plist = NULL; if (cmd_flags & CMD_FLAG_FORCE_FULL_BACKUP) { PRINT_VERBOSE(1, "Enforcing full backup from device.\n"); opts = plist_new_dict(); plist_dict_set_item(opts, "ForceFullBackup", plist_new_bool(1)); } /* request backup from device with manifest from last backup */ if (willEncrypt) { PRINT_VERBOSE(1, "Backup will be encrypted.\n"); } else { PRINT_VERBOSE(1, "Backup will be unencrypted.\n"); } PRINT_VERBOSE(1, "Requesting backup from device...\n"); err = mobilebackup2_send_request(mobilebackup2, "Backup", udid, source_udid, opts); if (opts) plist_free(opts); if (err == MOBILEBACKUP2_E_SUCCESS) { if (is_full_backup) { PRINT_VERBOSE(1, "Full backup mode.\n"); } else { PRINT_VERBOSE(1, "Incremental backup mode.\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 occurred\n"); } cmd = CMD_LEAVE; } break; case CMD_RESTORE: /* TODO: verify battery on AC enough battery remaining */ /* verify if Status.plist says we read from an successful backup */ if (!mb2_status_check_snapshot_state(backup_directory, source_udid, "finished")) { printf("ERROR: Cannot ensure we restore from a successful backup. Aborting.\n"); cmd = CMD_LEAVE; break; } PRINT_VERBOSE(1, "Starting Restore...\n"); opts = plist_new_dict(); plist_dict_set_item(opts, "RestoreSystemFiles", plist_new_bool(cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES)); PRINT_VERBOSE(1, "Restoring system files: %s\n", (cmd_flags & CMD_FLAG_RESTORE_SYSTEM_FILES ? "Yes":"No")); if (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT) plist_dict_set_item(opts, "RestoreShouldReboot", plist_new_bool(0)); PRINT_VERBOSE(1, "Rebooting after restore: %s\n", (cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT ? "No":"Yes")); if ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0) plist_dict_set_item(opts, "RestoreDontCopyBackup", plist_new_bool(1)); PRINT_VERBOSE(1, "Don't copy backup: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_COPY_BACKUP) == 0 ? "Yes":"No")); plist_dict_set_item(opts, "RestorePreserveSettings", plist_new_bool((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0)); PRINT_VERBOSE(1, "Preserve settings of device: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_SETTINGS) == 0 ? "Yes":"No")); if (cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS) plist_dict_set_item(opts, "RemoveItemsNotRestored", plist_new_bool(1)); PRINT_VERBOSE(1, "Remove items that are not restored: %s\n", ((cmd_flags & CMD_FLAG_RESTORE_REMOVE_ITEMS) ? "Yes":"No")); if (backup_password != NULL) { plist_dict_set_item(opts, "Password", plist_new_string(backup_password)); } PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes")); if (cmd_flags & CMD_FLAG_RESTORE_SKIP_APPS) { PRINT_VERBOSE(1, "Not writing RestoreApplications.plist - apps will not be re-installed after restore\n"); } else { /* Write /iTunesRestore/RestoreApplications.plist so that the device will start * restoring applications once the rest of the restore process is finished */ if (write_restore_applications(info_plist, afc) < 0) { cmd = CMD_LEAVE; break; } else { PRINT_VERBOSE(1, "Wrote RestoreApplications.plist\n"); } } /* Start restore */ err = mobilebackup2_send_request(mobilebackup2, "Restore", udid, source_udid, opts); plist_free(opts); if (err != MOBILEBACKUP2_E_SUCCESS) { if (err == MOBILEBACKUP2_E_BAD_VERSION) { printf("ERROR: Could not start restore process: backup protocol version mismatch!\n"); } else if (err == MOBILEBACKUP2_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 occurred\n"); } cmd = CMD_LEAVE; } break; case CMD_INFO: PRINT_VERBOSE(1, "Requesting backup info from device...\n"); err = mobilebackup2_send_request(mobilebackup2, "Info", udid, source_udid, NULL); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Error requesting backup info from device, error code %d\n", err); cmd = CMD_LEAVE; } break; case CMD_LIST: PRINT_VERBOSE(1, "Requesting backup list from device...\n"); err = mobilebackup2_send_request(mobilebackup2, "List", udid, source_udid, NULL); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Error requesting backup list from device, error code %d\n", err); cmd = CMD_LEAVE; } break; case CMD_UNBACK: PRINT_VERBOSE(1, "Starting to unpack backup...\n"); if (backup_password != NULL) { opts = plist_new_dict(); plist_dict_set_item(opts, "Password", plist_new_string(backup_password)); } PRINT_VERBOSE(1, "Backup password: %s\n", (backup_password == NULL ? "No":"Yes")); err = mobilebackup2_send_request(mobilebackup2, "Unback", udid, source_udid, opts); if (backup_password !=NULL) { plist_free(opts); } if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Error requesting unback operation from device, error code %d\n", err); cmd = CMD_LEAVE; } break; case CMD_CHANGEPW: opts = plist_new_dict(); plist_dict_set_item(opts, "TargetIdentifier", plist_new_string(udid)); if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { if (!willEncrypt) { if (!newpw) { newpw = ask_for_password("Enter new backup password", 1); } if (!newpw) { printf("No backup password given. Aborting.\n"); } } else { printf("ERROR: Backup encryption is already enabled. Aborting.\n"); cmd = CMD_LEAVE; if (newpw) { free(newpw); newpw = NULL; } } } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { if (willEncrypt) { if (!backup_password) { backup_password = ask_for_password("Enter current backup password", 0); } } else { printf("ERROR: Backup encryption is not enabled. Aborting.\n"); cmd = CMD_LEAVE; if (backup_password) { free(backup_password); backup_password = NULL; } } } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { if (willEncrypt) { if (!backup_password) { backup_password = ask_for_password("Enter old backup password", 0); newpw = ask_for_password("Enter new backup password", 1); } } else { printf("ERROR: Backup encryption is not enabled so can't change password. Aborting.\n"); cmd = CMD_LEAVE; if (newpw) { free(newpw); newpw = NULL; } if (backup_password) { free(backup_password); backup_password = NULL; } } } if (newpw) { plist_dict_set_item(opts, "NewPassword", plist_new_string(newpw)); } if (backup_password) { plist_dict_set_item(opts, "OldPassword", plist_new_string(backup_password)); } if (newpw || backup_password) { mobilebackup2_send_message(mobilebackup2, "ChangePassword", opts); uint8_t passcode_hint = 0; if (device_version >= DEVICE_VERSION(13,0,0)) { diagnostics_relay_client_t diag = NULL; if (diagnostics_relay_client_start_service(device, &diag, TOOL_NAME) == DIAGNOSTICS_RELAY_E_SUCCESS) { plist_t dict = NULL; plist_t keys = plist_new_array(); plist_array_append_item(keys, plist_new_string("PasswordConfigured")); if (diagnostics_relay_query_mobilegestalt(diag, keys, &dict) == DIAGNOSTICS_RELAY_E_SUCCESS) { plist_t node = plist_access_path(dict, 2, "MobileGestalt", "PasswordConfigured"); plist_get_bool_val(node, &passcode_hint); } plist_free(keys); plist_free(dict); diagnostics_relay_goodbye(diag); diagnostics_relay_client_free(diag); } } if (passcode_hint) { if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { PRINT_VERBOSE(1, "Please confirm changing the backup password by entering the passcode on the device.\n"); } else if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { PRINT_VERBOSE(1, "Please confirm enabling the backup encryption by entering the passcode on the device.\n"); } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { PRINT_VERBOSE(1, "Please confirm disabling the backup encryption by entering the passcode on the device.\n"); } } /*if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { int retr = 10; while ((retr-- >= 0) && !backup_domain_changed) { sleep(1); } }*/ } else { cmd = CMD_LEAVE; } plist_free(opts); break; default: break; } if (cmd != CMD_LEAVE) { /* reset operation success status */ int operation_ok = 0; plist_t message = NULL; mobilebackup2_error_t mberr; char *dlmsg = NULL; int file_count = 0; int errcode = 0; const char *errdesc = NULL; int progress_finished = 0; /* process series of DLMessage* operations */ do { free(dlmsg); dlmsg = NULL; mberr = mobilebackup2_receive_message(mobilebackup2, &message, &dlmsg); if (mberr == MOBILEBACKUP2_E_RECEIVE_TIMEOUT) { PRINT_VERBOSE(2, "Device is not ready yet, retrying...\n"); goto files_out; } else if (mberr != MOBILEBACKUP2_E_SUCCESS) { PRINT_VERBOSE(0, "ERROR: Could not receive from mobilebackup2 (%d)\n", mberr); quit_flag++; goto files_out; } if (!strcmp(dlmsg, "DLMessageDownloadFiles")) { /* device wants to download files from the computer */ mb2_set_overall_progress_from_message(message, dlmsg); mb2_handle_send_files(mobilebackup2, message, backup_directory); } else if (!strcmp(dlmsg, "DLMessageUploadFiles")) { /* device wants to send files to the computer */ mb2_set_overall_progress_from_message(message, dlmsg); file_count += mb2_handle_receive_files(mobilebackup2, message, backup_directory); } else if (!strcmp(dlmsg, "DLMessageGetFreeDiskSpace")) { /* device wants to know how much disk space is available on the computer */ uint64_t freespace = 0; int res = -1; #ifdef WIN32 if (GetDiskFreeSpaceEx(backup_directory, (PULARGE_INTEGER)&freespace, NULL, NULL)) { res = 0; } #else struct statvfs fs; memset(&fs, '\0', sizeof(fs)); res = statvfs(backup_directory, &fs); if (res == 0) { freespace = (uint64_t)fs.f_bavail * (uint64_t)fs.f_bsize; } #endif plist_t freespace_item = plist_new_uint(freespace); mobilebackup2_send_status_response(mobilebackup2, res, NULL, freespace_item); plist_free(freespace_item); } else if (!strcmp(dlmsg, "DLMessagePurgeDiskSpace")) { /* device wants to purge disk space on the host - not supported */ plist_t empty_dict = plist_new_dict(); err = mobilebackup2_send_status_response(mobilebackup2, -1, "Operation not supported", empty_dict); plist_free(empty_dict); } else if (!strcmp(dlmsg, "DLContentsOfDirectory")) { /* list directory contents */ mb2_handle_list_directory(mobilebackup2, message, backup_directory); } else if (!strcmp(dlmsg, "DLMessageCreateDirectory")) { /* make a directory */ mb2_handle_make_directory(mobilebackup2, message, backup_directory); } else if (!strcmp(dlmsg, "DLMessageMoveFiles") || !strcmp(dlmsg, "DLMessageMoveItems")) { /* perform a series of rename operations */ mb2_set_overall_progress_from_message(message, dlmsg); plist_t moves = plist_array_get_item(message, 1); uint32_t cnt = plist_dict_get_size(moves); PRINT_VERBOSE(1, "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) { char *newpath = string_build_path(backup_directory, str, NULL); free(str); char *oldpath = string_build_path(backup_directory, key, NULL); if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode)) rmdir_recursive(newpath); else remove_file(newpath); if (rename(oldpath, newpath) < 0) { printf("Renameing '%s' to '%s' failed: %s (%d)\n", oldpath, newpath, strerror(errno), errno); errcode = errno_to_device_error(errno); errdesc = strerror(errno); break; } free(oldpath); 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"); } plist_t empty_dict = plist_new_dict(); err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); plist_free(empty_dict); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Could not send status response, error %d\n", err); } } else if (!strcmp(dlmsg, "DLMessageRemoveFiles") || !strcmp(dlmsg, "DLMessageRemoveItems")) { mb2_set_overall_progress_from_message(message, dlmsg); plist_t removes = plist_array_get_item(message, 1); uint32_t cnt = plist_array_get_size(removes); PRINT_VERBOSE(1, "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) { const char *checkfile = strchr(str, '/'); int suppress_warning = 0; if (checkfile) { if (strcmp(checkfile+1, "Manifest.mbdx") == 0) { suppress_warning = 1; } } char *newpath = string_build_path(backup_directory, str, NULL); free(str); int res = 0; if ((stat(newpath, &st) == 0) && S_ISDIR(st.st_mode)) { res = rmdir_recursive(newpath); } else { res = remove_file(newpath); } if (res != 0 && res != ENOENT) { if (!suppress_warning) printf("Could not remove '%s': %s (%d)\n", newpath, strerror(res), res); errcode = errno_to_device_error(res); errdesc = strerror(res); } free(newpath); } } } plist_t empty_dict = plist_new_dict(); err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); plist_free(empty_dict); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Could not send status response, error %d\n", err); } } else if (!strcmp(dlmsg, "DLMessageCopyItem")) { plist_t srcpath = plist_array_get_item(message, 1); plist_t dstpath = plist_array_get_item(message, 2); errcode = 0; errdesc = NULL; if ((plist_get_node_type(srcpath) == PLIST_STRING) && (plist_get_node_type(dstpath) == PLIST_STRING)) { char *src = NULL; char *dst = NULL; plist_get_string_val(srcpath, &src); plist_get_string_val(dstpath, &dst); if (src && dst) { char *oldpath = string_build_path(backup_directory, src, NULL); char *newpath = string_build_path(backup_directory, dst, NULL); PRINT_VERBOSE(1, "Copying '%s' to '%s'\n", src, dst); /* check that src exists */ if ((stat(oldpath, &st) == 0) && S_ISDIR(st.st_mode)) { mb2_copy_directory_by_path(oldpath, newpath); } else if ((stat(oldpath, &st) == 0) && S_ISREG(st.st_mode)) { mb2_copy_file_by_path(oldpath, newpath); } free(newpath); free(oldpath); } free(src); free(dst); } plist_t empty_dict = plist_new_dict(); err = mobilebackup2_send_status_response(mobilebackup2, errcode, errdesc, empty_dict); plist_free(empty_dict); if (err != MOBILEBACKUP2_E_SUCCESS) { printf("Could not send status response, error %d\n", err); } } else if (!strcmp(dlmsg, "DLMessageDisconnect")) { break; } 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) { operation_ok = 1; result_code = 0; } else { result_code = -error_code; } } 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: %s\n", error_code, str); } else { printf("ErrorCode %d: (Unknown)\n", error_code); } } if (str) { free(str); } nn = plist_dict_get_item(node_tmp, "Content"); if (nn && (plist_get_node_type(nn) == PLIST_STRING)) { str = NULL; plist_get_string_val(nn, &str); PRINT_VERBOSE(1, "Content:\n"); printf("%s", str); free(str); } break; } /* print status */ if ((overall_progress > 0) && !progress_finished) { if (overall_progress >= 100.0f) { progress_finished = 1; } print_progress_real(overall_progress, 0); PRINT_VERBOSE(1, " Finished\n"); } files_out: plist_free(message); message = NULL; free(dlmsg); dlmsg = NULL; if (quit_flag > 0) { /* need to cancel the backup here */ //mobilebackup_send_error(mobilebackup, "Cancelling DLSendFile"); /* remove any atomic Manifest.plist.tmp */ /*manifest_path = mobilebackup_build_path(backup_directory, "Manifest", ".plist.tmp"); if (stat(manifest_path, &st) == 0) remove(manifest_path);*/ break; } } while (1); plist_free(message); free(dlmsg); /* report operation status to user */ switch (cmd) { case CMD_CLOUD: if (cmd_flags & CMD_FLAG_CLOUD_ENABLE) { if (operation_ok) { PRINT_VERBOSE(1, "Cloud backup has been enabled successfully.\n"); } else { PRINT_VERBOSE(1, "Could not enable cloud backup.\n"); } } else if (cmd_flags & CMD_FLAG_CLOUD_DISABLE) { if (operation_ok) { PRINT_VERBOSE(1, "Cloud backup has been disabled successfully.\n"); } else { PRINT_VERBOSE(1, "Could not disable cloud backup.\n"); } } break; case CMD_BACKUP: PRINT_VERBOSE(1, "Received %d files from device.\n", file_count); if (operation_ok && mb2_status_check_snapshot_state(backup_directory, udid, "finished")) { PRINT_VERBOSE(1, "Backup Successful.\n"); } else { if (quit_flag) { PRINT_VERBOSE(1, "Backup Aborted.\n"); } else { PRINT_VERBOSE(1, "Backup Failed (Error Code %d).\n", -result_code); } } break; case CMD_UNBACK: if (quit_flag) { PRINT_VERBOSE(1, "Unback Aborted.\n"); } else { PRINT_VERBOSE(1, "The files can now be found in the \"_unback_\" directory.\n"); PRINT_VERBOSE(1, "Unback Successful.\n"); } break; case CMD_CHANGEPW: if (cmd_flags & CMD_FLAG_ENCRYPTION_ENABLE) { if (operation_ok) { PRINT_VERBOSE(1, "Backup encryption has been enabled successfully.\n"); } else { PRINT_VERBOSE(1, "Could not enable backup encryption.\n"); } } else if (cmd_flags & CMD_FLAG_ENCRYPTION_DISABLE) { if (operation_ok) { PRINT_VERBOSE(1, "Backup encryption has been disabled successfully.\n"); } else { PRINT_VERBOSE(1, "Could not disable backup encryption.\n"); } } else if (cmd_flags & CMD_FLAG_ENCRYPTION_CHANGEPW) { if (operation_ok) { PRINT_VERBOSE(1, "Backup encryption password has been changed successfully.\n"); } else { PRINT_VERBOSE(1, "Could not change backup encryption password.\n"); } } break; case CMD_RESTORE: if (operation_ok) { if ((cmd_flags & CMD_FLAG_RESTORE_NO_REBOOT) == 0) PRINT_VERBOSE(1, "The device should reboot now.\n"); PRINT_VERBOSE(1, "Restore Successful.\n"); } else { afc_remove_path(afc, "/iTunesRestore/RestoreApplications.plist"); afc_remove_path(afc, "/iTunesRestore"); if (quit_flag) { PRINT_VERBOSE(1, "Restore Aborted.\n"); } else { PRINT_VERBOSE(1, "Restore Failed (Error Code %d).\n", -result_code); } } break; case CMD_INFO: case CMD_LIST: case CMD_LEAVE: default: if (quit_flag) { PRINT_VERBOSE(1, "Operation Aborted.\n"); } else if (cmd == CMD_LEAVE) { PRINT_VERBOSE(1, "Operation Failed.\n"); } else { PRINT_VERBOSE(1, "Operation Successful.\n"); } break; } } if (lockfile) { afc_file_lock(afc, lockfile, AFC_LOCK_UN); afc_file_close(afc, lockfile); lockfile = 0; if (cmd == CMD_BACKUP || cmd == CMD_RESTORE) do_post_notification(device, NP_SYNC_DID_FINISH); } } else { printf("ERROR: Could not start service %s: %s\n", MOBILEBACKUP2_SERVICE_NAME, lockdownd_strerror(ldret)); lockdownd_client_free(lockdown); lockdown = NULL; } if (lockdown) { lockdownd_client_free(lockdown); lockdown = NULL; } if (mobilebackup2) { mobilebackup2_client_free(mobilebackup2); mobilebackup2 = NULL; } if (afc) { afc_client_free(afc); afc = NULL; } if (np) { np_client_free(np); np = NULL; } idevice_free(device); device = NULL; if (backup_password) { free(backup_password); } if (udid) { free(udid); udid = NULL; } if (source_udid) { free(source_udid); source_udid = NULL; } return result_code; }